Restaurant Reservations - Version 2.4.5

Version Description

(2021-11-29) = - Added a search field to the Bookings admin page. - Added an option to have the Bookings admin page refresh automatically at a chosen interval. - Unavailable time slots are now hidden by default from the booking form time picker (instead of being greyed out). We added an option to disable this (and have them show, but greyed out, like before). - Localized the "Please find a summary of today's reservations in the table below" string. - Fixed issue with MailChimp lists not populating on the settings page. - Fixed pagination issue on Bookings admin page when using date filter. - Fixed issue causing incorrect fetched record totals when using filters on the Bookings admin page. - Fixed an issue with non-numeric values throwing a fatal error with PHP 8.

Download this release

Release Info

Developer Rustaurius
Plugin Icon 128x128 Restaurant Reservations
Version 2.4.5
Comparing to
See all releases

Code changes from version 2.4.4 to 2.4.5

Files changed (52) hide show
  1. GPL.txt +279 -279
  2. Gruntfile.js +71 -71
  3. assets/css/admin-rtb-welcome-screen.css +367 -367
  4. assets/css/admin.css +28 -5
  5. assets/css/booking-form.css +308 -308
  6. assets/css/dashboard-review-ask.css +60 -60
  7. assets/css/editor.css +581 -581
  8. assets/css/plugin-deactivation.css +67 -67
  9. assets/js/admin-rtb-welcome-screen.js +70 -70
  10. assets/js/admin-settings.js +73 -73
  11. assets/js/admin.js +66 -31
  12. assets/js/block-booking-form.js +48 -48
  13. assets/js/booking-form.js +796 -796
  14. assets/js/columns.js +10 -10
  15. assets/js/dashboard-review-ask.js +61 -61
  16. assets/js/editor.js +1236 -1236
  17. assets/js/mailchimp-admin.js +1 -1
  18. assets/js/plugin-deactivation.js +52 -52
  19. assets/js/rtb-recaptcha.js +4 -4
  20. assets/js/stripe-payment.js +190 -190
  21. includes/Addons.class.php +362 -362
  22. includes/AdminBookings.class.php +922 -922
  23. includes/AdminPageSettingLicenseKey.class.php +422 -422
  24. includes/Ajax.class.php +877 -877
  25. includes/Blocks.class.php +99 -99
  26. includes/Booking.class.php +1365 -1365
  27. includes/Compatibility.class.php +191 -191
  28. includes/Cron.class.php +252 -252
  29. includes/CustomFields.class.php +193 -193
  30. includes/CustomPostTypes.class.php +445 -444
  31. includes/DeactivationSurvey.class.php +83 -83
  32. includes/Export.CSV.class.php +376 -376
  33. includes/Export.PDF.class.php +330 -330
  34. includes/ExportHandler.class.php +302 -302
  35. includes/Field.class.php +608 -608
  36. includes/Import.class.php +279 -279
  37. includes/InstallationWalkthrough.class.php +393 -393
  38. includes/Licenses.class.php +270 -270
  39. includes/MailChimp.class.php +539 -539
  40. includes/Migration.class.php +140 -140
  41. includes/MultipleLocations.class.php +1009 -1009
  42. includes/Notification.Email.class.php +304 -304
  43. includes/Notification.SMS.class.php +151 -151
  44. includes/Notification.class.php +126 -126
  45. includes/Notifications.class.php +322 -322
  46. includes/PaymentGateway.interface.php +40 -40
  47. includes/PaymentGatewayPayPal.class.php +215 -215
  48. includes/PaymentGatewayStripe.class.php +461 -461
  49. includes/PaymentManager.class.php +387 -382
  50. includes/Query.class.php +273 -253
  51. includes/ReviewAsk.class.php +97 -97
  52. includes/Settings.class.php +0 -319
GPL.txt CHANGED
@@ -1,280 +1,280 @@
1
- GNU GENERAL PUBLIC LICENSE
2
- Version 2, June 1991
3
-
4
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5
- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
6
-
7
- Everyone is permitted to copy and distribute verbatim copies
8
- of this license document, but changing it is not allowed.
9
-
10
- Preamble
11
-
12
- The licenses for most software are designed to take away your
13
- freedom to share and change it. By contrast, the GNU General Public
14
- License is intended to guarantee your freedom to share and change free
15
- software--to make sure the software is free for all its users. This
16
- General Public License applies to most of the Free Software
17
- Foundation's software and to any other program whose authors commit to
18
- using it. (Some other Free Software Foundation software is covered by
19
- the GNU Library General Public License instead.) You can apply it to
20
- your programs, too.
21
-
22
- When we speak of free software, we are referring to freedom, not
23
- price. Our General Public Licenses are designed to make sure that you
24
- have the freedom to distribute copies of free software (and charge for
25
- this service if you wish), that you receive source code or can get it
26
- if you want it, that you can change the software or use pieces of it
27
- in new free programs; and that you know you can do these things.
28
-
29
- To protect your rights, we need to make restrictions that forbid
30
- anyone to deny you these rights or to ask you to surrender the rights.
31
- These restrictions translate to certain responsibilities for you if you
32
- distribute copies of the software, or if you modify it.
33
-
34
- For example, if you distribute copies of such a program, whether
35
- gratis or for a fee, you must give the recipients all the rights that
36
- you have. You must make sure that they, too, receive or can get the
37
- source code. And you must show them these terms so they know their
38
- rights.
39
-
40
- We protect your rights with two steps: (1) copyright the software, and
41
- (2) offer you this license which gives you legal permission to copy,
42
- distribute and/or modify the software.
43
-
44
- Also, for each author's protection and ours, we want to make certain
45
- that everyone understands that there is no warranty for this free
46
- software. If the software is modified by someone else and passed on, we
47
- want its recipients to know that what they have is not the original, so
48
- that any problems introduced by others will not reflect on the original
49
- authors' reputations.
50
-
51
- Finally, any free program is threatened constantly by software
52
- patents. We wish to avoid the danger that redistributors of a free
53
- program will individually obtain patent licenses, in effect making the
54
- program proprietary. To prevent this, we have made it clear that any
55
- patent must be licensed for everyone's free use or not licensed at all.
56
-
57
- The precise terms and conditions for copying, distribution and
58
- modification follow.
59
-
60
- GNU GENERAL PUBLIC LICENSE
61
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
62
-
63
- 0. This License applies to any program or other work which contains
64
- a notice placed by the copyright holder saying it may be distributed
65
- under the terms of this General Public License. The "Program", below,
66
- refers to any such program or work, and a "work based on the Program"
67
- means either the Program or any derivative work under copyright law:
68
- that is to say, a work containing the Program or a portion of it,
69
- either verbatim or with modifications and/or translated into another
70
- language. (Hereinafter, translation is included without limitation in
71
- the term "modification".) Each licensee is addressed as "you".
72
-
73
- Activities other than copying, distribution and modification are not
74
- covered by this License; they are outside its scope. The act of
75
- running the Program is not restricted, and the output from the Program
76
- is covered only if its contents constitute a work based on the
77
- Program (independent of having been made by running the Program).
78
- Whether that is true depends on what the Program does.
79
-
80
- 1. You may copy and distribute verbatim copies of the Program's
81
- source code as you receive it, in any medium, provided that you
82
- conspicuously and appropriately publish on each copy an appropriate
83
- copyright notice and disclaimer of warranty; keep intact all the
84
- notices that refer to this License and to the absence of any warranty;
85
- and give any other recipients of the Program a copy of this License
86
- along with the Program.
87
-
88
- You may charge a fee for the physical act of transferring a copy, and
89
- you may at your option offer warranty protection in exchange for a fee.
90
-
91
- 2. You may modify your copy or copies of the Program or any portion
92
- of it, thus forming a work based on the Program, and copy and
93
- distribute such modifications or work under the terms of Section 1
94
- above, provided that you also meet all of these conditions:
95
-
96
- a) You must cause the modified files to carry prominent notices
97
- stating that you changed the files and the date of any change.
98
-
99
- b) You must cause any work that you distribute or publish, that in
100
- whole or in part contains or is derived from the Program or any
101
- part thereof, to be licensed as a whole at no charge to all third
102
- parties under the terms of this License.
103
-
104
- c) If the modified program normally reads commands interactively
105
- when run, you must cause it, when started running for such
106
- interactive use in the most ordinary way, to print or display an
107
- announcement including an appropriate copyright notice and a
108
- notice that there is no warranty (or else, saying that you provide
109
- a warranty) and that users may redistribute the program under
110
- these conditions, and telling the user how to view a copy of this
111
- License. (Exception: if the Program itself is interactive but
112
- does not normally print such an announcement, your work based on
113
- the Program is not required to print an announcement.)
114
-
115
- These requirements apply to the modified work as a whole. If
116
- identifiable sections of that work are not derived from the Program,
117
- and can be reasonably considered independent and separate works in
118
- themselves, then this License, and its terms, do not apply to those
119
- sections when you distribute them as separate works. But when you
120
- distribute the same sections as part of a whole which is a work based
121
- on the Program, the distribution of the whole must be on the terms of
122
- this License, whose permissions for other licensees extend to the
123
- entire whole, and thus to each and every part regardless of who wrote it.
124
- Thus, it is not the intent of this section to claim rights or contest
125
- your rights to work written entirely by you; rather, the intent is to
126
- exercise the right to control the distribution of derivative or
127
- collective works based on the Program.
128
-
129
- In addition, mere aggregation of another work not based on the Program
130
- with the Program (or with a work based on the Program) on a volume of
131
- a storage or distribution medium does not bring the other work under
132
- the scope of this License.
133
-
134
- 3. You may copy and distribute the Program (or a work based on it,
135
- under Section 2) in object code or executable form under the terms of
136
- Sections 1 and 2 above provided that you also do one of the following:
137
-
138
- a) Accompany it with the complete corresponding machine-readable
139
- source code, which must be distributed under the terms of Sections
140
- 1 and 2 above on a medium customarily used for software interchange; or,
141
-
142
- b) Accompany it with a written offer, valid for at least three
143
- years, to give any third party, for a charge no more than your
144
- cost of physically performing source distribution, a complete
145
- machine-readable copy of the corresponding source code, to be
146
- distributed under the terms of Sections 1 and 2 above on a medium
147
- customarily used for software interchange; or,
148
-
149
- c) Accompany it with the information you received as to the offer
150
- to distribute corresponding source code. (This alternative is
151
- allowed only for noncommercial distribution and only if you
152
- received the program in object code or executable form with such
153
- an offer, in accord with Subsection b above.)
154
-
155
- The source code for a work means the preferred form of the work for
156
- making modifications to it. For an executable work, complete source
157
- code means all the source code for all modules it contains, plus any
158
- associated interface definition files, plus the scripts used to
159
- control compilation and installation of the executable. However, as a
160
- special exception, the source code distributed need not include
161
- anything that is normally distributed (in either source or binary
162
- form) with the major components (compiler, kernel, and so on) of the
163
- operating system on which the executable runs, unless that component
164
- itself accompanies the executable.
165
-
166
- If distribution of executable or object code is made by offering
167
- access to copy from a designated place, then offering equivalent
168
- access to copy the source code from the same place counts as
169
- distribution of the source code, even though third parties are not
170
- compelled to copy the source along with the object code.
171
-
172
- 4. You may not copy, modify, sublicense, or distribute the Program
173
- except as expressly provided under this License. Any attempt
174
- otherwise to copy, modify, sublicense or distribute the Program is
175
- void, and will automatically terminate your rights under this License.
176
- However, parties who have received copies, or rights, from you under
177
- this License will not have their licenses terminated so long as such
178
- parties remain in full compliance.
179
-
180
- 5. You are not required to accept this License, since you have not
181
- signed it. However, nothing else grants you permission to modify or
182
- distribute the Program or its derivative works. These actions are
183
- prohibited by law if you do not accept this License. Therefore, by
184
- modifying or distributing the Program (or any work based on the
185
- Program), you indicate your acceptance of this License to do so, and
186
- all its terms and conditions for copying, distributing or modifying
187
- the Program or works based on it.
188
-
189
- 6. Each time you redistribute the Program (or any work based on the
190
- Program), the recipient automatically receives a license from the
191
- original licensor to copy, distribute or modify the Program subject to
192
- these terms and conditions. You may not impose any further
193
- restrictions on the recipients' exercise of the rights granted herein.
194
- You are not responsible for enforcing compliance by third parties to
195
- this License.
196
-
197
- 7. If, as a consequence of a court judgment or allegation of patent
198
- infringement or for any other reason (not limited to patent issues),
199
- conditions are imposed on you (whether by court order, agreement or
200
- otherwise) that contradict the conditions of this License, they do not
201
- excuse you from the conditions of this License. If you cannot
202
- distribute so as to satisfy simultaneously your obligations under this
203
- License and any other pertinent obligations, then as a consequence you
204
- may not distribute the Program at all. For example, if a patent
205
- license would not permit royalty-free redistribution of the Program by
206
- all those who receive copies directly or indirectly through you, then
207
- the only way you could satisfy both it and this License would be to
208
- refrain entirely from distribution of the Program.
209
-
210
- If any portion of this section is held invalid or unenforceable under
211
- any particular circumstance, the balance of the section is intended to
212
- apply and the section as a whole is intended to apply in other
213
- circumstances.
214
-
215
- It is not the purpose of this section to induce you to infringe any
216
- patents or other property right claims or to contest validity of any
217
- such claims; this section has the sole purpose of protecting the
218
- integrity of the free software distribution system, which is
219
- implemented by public license practices. Many people have made
220
- generous contributions to the wide range of software distributed
221
- through that system in reliance on consistent application of that
222
- system; it is up to the author/donor to decide if he or she is willing
223
- to distribute software through any other system and a licensee cannot
224
- impose that choice.
225
-
226
- This section is intended to make thoroughly clear what is believed to
227
- be a consequence of the rest of this License.
228
-
229
- 8. If the distribution and/or use of the Program is restricted in
230
- certain countries either by patents or by copyrighted interfaces, the
231
- original copyright holder who places the Program under this License
232
- may add an explicit geographical distribution limitation excluding
233
- those countries, so that distribution is permitted only in or among
234
- countries not thus excluded. In such case, this License incorporates
235
- the limitation as if written in the body of this License.
236
-
237
- 9. The Free Software Foundation may publish revised and/or new versions
238
- of the General Public License from time to time. Such new versions will
239
- be similar in spirit to the present version, but may differ in detail to
240
- address new problems or concerns.
241
-
242
- Each version is given a distinguishing version number. If the Program
243
- specifies a version number of this License which applies to it and "any
244
- later version", you have the option of following the terms and conditions
245
- either of that version or of any later version published by the Free
246
- Software Foundation. If the Program does not specify a version number of
247
- this License, you may choose any version ever published by the Free Software
248
- Foundation.
249
-
250
- 10. If you wish to incorporate parts of the Program into other free
251
- programs whose distribution conditions are different, write to the author
252
- to ask for permission. For software which is copyrighted by the Free
253
- Software Foundation, write to the Free Software Foundation; we sometimes
254
- make exceptions for this. Our decision will be guided by the two goals
255
- of preserving the free status of all derivatives of our free software and
256
- of promoting the sharing and reuse of software generally.
257
-
258
- NO WARRANTY
259
-
260
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
- FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262
- OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
- PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
- OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266
- TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267
- PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
- REPAIR OR CORRECTION.
269
-
270
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
- WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
- REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
- INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
- OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
- TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
- YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
- PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
- POSSIBILITY OF SUCH DAMAGES.
279
-
280
  END OF TERMS AND CONDITIONS
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 2, June 1991
3
+
4
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
6
+
7
+ Everyone is permitted to copy and distribute verbatim copies
8
+ of this license document, but changing it is not allowed.
9
+
10
+ Preamble
11
+
12
+ The licenses for most software are designed to take away your
13
+ freedom to share and change it. By contrast, the GNU General Public
14
+ License is intended to guarantee your freedom to share and change free
15
+ software--to make sure the software is free for all its users. This
16
+ General Public License applies to most of the Free Software
17
+ Foundation's software and to any other program whose authors commit to
18
+ using it. (Some other Free Software Foundation software is covered by
19
+ the GNU Library General Public License instead.) You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ this service if you wish), that you receive source code or can get it
26
+ if you want it, that you can change the software or use pieces of it
27
+ in new free programs; and that you know you can do these things.
28
+
29
+ To protect your rights, we need to make restrictions that forbid
30
+ anyone to deny you these rights or to ask you to surrender the rights.
31
+ These restrictions translate to certain responsibilities for you if you
32
+ distribute copies of the software, or if you modify it.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must give the recipients all the rights that
36
+ you have. You must make sure that they, too, receive or can get the
37
+ source code. And you must show them these terms so they know their
38
+ rights.
39
+
40
+ We protect your rights with two steps: (1) copyright the software, and
41
+ (2) offer you this license which gives you legal permission to copy,
42
+ distribute and/or modify the software.
43
+
44
+ Also, for each author's protection and ours, we want to make certain
45
+ that everyone understands that there is no warranty for this free
46
+ software. If the software is modified by someone else and passed on, we
47
+ want its recipients to know that what they have is not the original, so
48
+ that any problems introduced by others will not reflect on the original
49
+ authors' reputations.
50
+
51
+ Finally, any free program is threatened constantly by software
52
+ patents. We wish to avoid the danger that redistributors of a free
53
+ program will individually obtain patent licenses, in effect making the
54
+ program proprietary. To prevent this, we have made it clear that any
55
+ patent must be licensed for everyone's free use or not licensed at all.
56
+
57
+ The precise terms and conditions for copying, distribution and
58
+ modification follow.
59
+
60
+ GNU GENERAL PUBLIC LICENSE
61
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
62
+
63
+ 0. This License applies to any program or other work which contains
64
+ a notice placed by the copyright holder saying it may be distributed
65
+ under the terms of this General Public License. The "Program", below,
66
+ refers to any such program or work, and a "work based on the Program"
67
+ means either the Program or any derivative work under copyright law:
68
+ that is to say, a work containing the Program or a portion of it,
69
+ either verbatim or with modifications and/or translated into another
70
+ language. (Hereinafter, translation is included without limitation in
71
+ the term "modification".) Each licensee is addressed as "you".
72
+
73
+ Activities other than copying, distribution and modification are not
74
+ covered by this License; they are outside its scope. The act of
75
+ running the Program is not restricted, and the output from the Program
76
+ is covered only if its contents constitute a work based on the
77
+ Program (independent of having been made by running the Program).
78
+ Whether that is true depends on what the Program does.
79
+
80
+ 1. You may copy and distribute verbatim copies of the Program's
81
+ source code as you receive it, in any medium, provided that you
82
+ conspicuously and appropriately publish on each copy an appropriate
83
+ copyright notice and disclaimer of warranty; keep intact all the
84
+ notices that refer to this License and to the absence of any warranty;
85
+ and give any other recipients of the Program a copy of this License
86
+ along with the Program.
87
+
88
+ You may charge a fee for the physical act of transferring a copy, and
89
+ you may at your option offer warranty protection in exchange for a fee.
90
+
91
+ 2. You may modify your copy or copies of the Program or any portion
92
+ of it, thus forming a work based on the Program, and copy and
93
+ distribute such modifications or work under the terms of Section 1
94
+ above, provided that you also meet all of these conditions:
95
+
96
+ a) You must cause the modified files to carry prominent notices
97
+ stating that you changed the files and the date of any change.
98
+
99
+ b) You must cause any work that you distribute or publish, that in
100
+ whole or in part contains or is derived from the Program or any
101
+ part thereof, to be licensed as a whole at no charge to all third
102
+ parties under the terms of this License.
103
+
104
+ c) If the modified program normally reads commands interactively
105
+ when run, you must cause it, when started running for such
106
+ interactive use in the most ordinary way, to print or display an
107
+ announcement including an appropriate copyright notice and a
108
+ notice that there is no warranty (or else, saying that you provide
109
+ a warranty) and that users may redistribute the program under
110
+ these conditions, and telling the user how to view a copy of this
111
+ License. (Exception: if the Program itself is interactive but
112
+ does not normally print such an announcement, your work based on
113
+ the Program is not required to print an announcement.)
114
+
115
+ These requirements apply to the modified work as a whole. If
116
+ identifiable sections of that work are not derived from the Program,
117
+ and can be reasonably considered independent and separate works in
118
+ themselves, then this License, and its terms, do not apply to those
119
+ sections when you distribute them as separate works. But when you
120
+ distribute the same sections as part of a whole which is a work based
121
+ on the Program, the distribution of the whole must be on the terms of
122
+ this License, whose permissions for other licensees extend to the
123
+ entire whole, and thus to each and every part regardless of who wrote it.
124
+ Thus, it is not the intent of this section to claim rights or contest
125
+ your rights to work written entirely by you; rather, the intent is to
126
+ exercise the right to control the distribution of derivative or
127
+ collective works based on the Program.
128
+
129
+ In addition, mere aggregation of another work not based on the Program
130
+ with the Program (or with a work based on the Program) on a volume of
131
+ a storage or distribution medium does not bring the other work under
132
+ the scope of this License.
133
+
134
+ 3. You may copy and distribute the Program (or a work based on it,
135
+ under Section 2) in object code or executable form under the terms of
136
+ Sections 1 and 2 above provided that you also do one of the following:
137
+
138
+ a) Accompany it with the complete corresponding machine-readable
139
+ source code, which must be distributed under the terms of Sections
140
+ 1 and 2 above on a medium customarily used for software interchange; or,
141
+
142
+ b) Accompany it with a written offer, valid for at least three
143
+ years, to give any third party, for a charge no more than your
144
+ cost of physically performing source distribution, a complete
145
+ machine-readable copy of the corresponding source code, to be
146
+ distributed under the terms of Sections 1 and 2 above on a medium
147
+ customarily used for software interchange; or,
148
+
149
+ c) Accompany it with the information you received as to the offer
150
+ to distribute corresponding source code. (This alternative is
151
+ allowed only for noncommercial distribution and only if you
152
+ received the program in object code or executable form with such
153
+ an offer, in accord with Subsection b above.)
154
+
155
+ The source code for a work means the preferred form of the work for
156
+ making modifications to it. For an executable work, complete source
157
+ code means all the source code for all modules it contains, plus any
158
+ associated interface definition files, plus the scripts used to
159
+ control compilation and installation of the executable. However, as a
160
+ special exception, the source code distributed need not include
161
+ anything that is normally distributed (in either source or binary
162
+ form) with the major components (compiler, kernel, and so on) of the
163
+ operating system on which the executable runs, unless that component
164
+ itself accompanies the executable.
165
+
166
+ If distribution of executable or object code is made by offering
167
+ access to copy from a designated place, then offering equivalent
168
+ access to copy the source code from the same place counts as
169
+ distribution of the source code, even though third parties are not
170
+ compelled to copy the source along with the object code.
171
+
172
+ 4. You may not copy, modify, sublicense, or distribute the Program
173
+ except as expressly provided under this License. Any attempt
174
+ otherwise to copy, modify, sublicense or distribute the Program is
175
+ void, and will automatically terminate your rights under this License.
176
+ However, parties who have received copies, or rights, from you under
177
+ this License will not have their licenses terminated so long as such
178
+ parties remain in full compliance.
179
+
180
+ 5. You are not required to accept this License, since you have not
181
+ signed it. However, nothing else grants you permission to modify or
182
+ distribute the Program or its derivative works. These actions are
183
+ prohibited by law if you do not accept this License. Therefore, by
184
+ modifying or distributing the Program (or any work based on the
185
+ Program), you indicate your acceptance of this License to do so, and
186
+ all its terms and conditions for copying, distributing or modifying
187
+ the Program or works based on it.
188
+
189
+ 6. Each time you redistribute the Program (or any work based on the
190
+ Program), the recipient automatically receives a license from the
191
+ original licensor to copy, distribute or modify the Program subject to
192
+ these terms and conditions. You may not impose any further
193
+ restrictions on the recipients' exercise of the rights granted herein.
194
+ You are not responsible for enforcing compliance by third parties to
195
+ this License.
196
+
197
+ 7. If, as a consequence of a court judgment or allegation of patent
198
+ infringement or for any other reason (not limited to patent issues),
199
+ conditions are imposed on you (whether by court order, agreement or
200
+ otherwise) that contradict the conditions of this License, they do not
201
+ excuse you from the conditions of this License. If you cannot
202
+ distribute so as to satisfy simultaneously your obligations under this
203
+ License and any other pertinent obligations, then as a consequence you
204
+ may not distribute the Program at all. For example, if a patent
205
+ license would not permit royalty-free redistribution of the Program by
206
+ all those who receive copies directly or indirectly through you, then
207
+ the only way you could satisfy both it and this License would be to
208
+ refrain entirely from distribution of the Program.
209
+
210
+ If any portion of this section is held invalid or unenforceable under
211
+ any particular circumstance, the balance of the section is intended to
212
+ apply and the section as a whole is intended to apply in other
213
+ circumstances.
214
+
215
+ It is not the purpose of this section to induce you to infringe any
216
+ patents or other property right claims or to contest validity of any
217
+ such claims; this section has the sole purpose of protecting the
218
+ integrity of the free software distribution system, which is
219
+ implemented by public license practices. Many people have made
220
+ generous contributions to the wide range of software distributed
221
+ through that system in reliance on consistent application of that
222
+ system; it is up to the author/donor to decide if he or she is willing
223
+ to distribute software through any other system and a licensee cannot
224
+ impose that choice.
225
+
226
+ This section is intended to make thoroughly clear what is believed to
227
+ be a consequence of the rest of this License.
228
+
229
+ 8. If the distribution and/or use of the Program is restricted in
230
+ certain countries either by patents or by copyrighted interfaces, the
231
+ original copyright holder who places the Program under this License
232
+ may add an explicit geographical distribution limitation excluding
233
+ those countries, so that distribution is permitted only in or among
234
+ countries not thus excluded. In such case, this License incorporates
235
+ the limitation as if written in the body of this License.
236
+
237
+ 9. The Free Software Foundation may publish revised and/or new versions
238
+ of the General Public License from time to time. Such new versions will
239
+ be similar in spirit to the present version, but may differ in detail to
240
+ address new problems or concerns.
241
+
242
+ Each version is given a distinguishing version number. If the Program
243
+ specifies a version number of this License which applies to it and "any
244
+ later version", you have the option of following the terms and conditions
245
+ either of that version or of any later version published by the Free
246
+ Software Foundation. If the Program does not specify a version number of
247
+ this License, you may choose any version ever published by the Free Software
248
+ Foundation.
249
+
250
+ 10. If you wish to incorporate parts of the Program into other free
251
+ programs whose distribution conditions are different, write to the author
252
+ to ask for permission. For software which is copyrighted by the Free
253
+ Software Foundation, write to the Free Software Foundation; we sometimes
254
+ make exceptions for this. Our decision will be guided by the two goals
255
+ of preserving the free status of all derivatives of our free software and
256
+ of promoting the sharing and reuse of software generally.
257
+
258
+ NO WARRANTY
259
+
260
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
+ REPAIR OR CORRECTION.
269
+
270
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
+ POSSIBILITY OF SUCH DAMAGES.
279
+
280
  END OF TERMS AND CONDITIONS
Gruntfile.js CHANGED
@@ -1,71 +1,71 @@
1
- 'use strict';
2
-
3
- module.exports = function(grunt) {
4
-
5
- // Project configuration.
6
- grunt.initConfig({
7
-
8
- pkg: grunt.file.readJSON('package.json'),
9
-
10
- // Configure JSHint
11
- jshint: {
12
- test: {
13
- src: ['assets/js/*.js', '!assets/js/block-booking-form.js', '!assets/js/blocks.build.js']
14
- }
15
- },
16
-
17
- // Watch for changes on some files and auto-compile them
18
- watch: {
19
- js: {
20
- files: ['assets/js/*.js'],
21
- tasks: ['jshint']
22
- },
23
- },
24
-
25
- // Create a .pot file
26
- makepot: {
27
- target: {
28
- options: {
29
- processPot: function( pot, options ) {
30
- pot.headers['report-msgid-bugs-to'] = 'https://themeofthecrop.com';
31
- return pot;
32
- },
33
- type: 'wp-plugin',
34
- }
35
- }
36
- },
37
-
38
- // Build a package for distribution
39
- compress: {
40
- main: {
41
- options: {
42
- archive: 'restaurant-reservations-<%= pkg.version %>.zip'
43
- },
44
- files: [
45
- {
46
- src: [
47
- '*', '**/*',
48
- '!restaurant-reservations-<%= pkg.version %>.zip',
49
- '!.*', '!Gruntfile.js', '!package.json', '!node_modules', '!node_modules/**/*',
50
- '!**/.*', '!**/Gruntfile.js', '!**/package.json', '!**/node_modules', '!**/node_modules/**/*',
51
- ],
52
- dest: 'restaurant-reservations/',
53
- }
54
- ]
55
- }
56
- }
57
-
58
- });
59
-
60
- // Load tasks
61
- grunt.loadNpmTasks('grunt-contrib-compress');
62
- grunt.loadNpmTasks('grunt-contrib-jshint');
63
- grunt.loadNpmTasks('grunt-contrib-watch');
64
- grunt.loadNpmTasks('grunt-wp-i18n');
65
-
66
- // Default task(s).
67
- grunt.registerTask('default', ['watch']);
68
- grunt.registerTask('build', ['jshint']);
69
- grunt.registerTask('package', ['build', 'makepot', 'compress']);
70
-
71
- };
1
+ 'use strict';
2
+
3
+ module.exports = function(grunt) {
4
+
5
+ // Project configuration.
6
+ grunt.initConfig({
7
+
8
+ pkg: grunt.file.readJSON('package.json'),
9
+
10
+ // Configure JSHint
11
+ jshint: {
12
+ test: {
13
+ src: ['assets/js/*.js', '!assets/js/block-booking-form.js', '!assets/js/blocks.build.js']
14
+ }
15
+ },
16
+
17
+ // Watch for changes on some files and auto-compile them
18
+ watch: {
19
+ js: {
20
+ files: ['assets/js/*.js'],
21
+ tasks: ['jshint']
22
+ },
23
+ },
24
+
25
+ // Create a .pot file
26
+ makepot: {
27
+ target: {
28
+ options: {
29
+ processPot: function( pot, options ) {
30
+ pot.headers['report-msgid-bugs-to'] = 'https://themeofthecrop.com';
31
+ return pot;
32
+ },
33
+ type: 'wp-plugin',
34
+ }
35
+ }
36
+ },
37
+
38
+ // Build a package for distribution
39
+ compress: {
40
+ main: {
41
+ options: {
42
+ archive: 'restaurant-reservations-<%= pkg.version %>.zip'
43
+ },
44
+ files: [
45
+ {
46
+ src: [
47
+ '*', '**/*',
48
+ '!restaurant-reservations-<%= pkg.version %>.zip',
49
+ '!.*', '!Gruntfile.js', '!package.json', '!node_modules', '!node_modules/**/*',
50
+ '!**/.*', '!**/Gruntfile.js', '!**/package.json', '!**/node_modules', '!**/node_modules/**/*',
51
+ ],
52
+ dest: 'restaurant-reservations/',
53
+ }
54
+ ]
55
+ }
56
+ }
57
+
58
+ });
59
+
60
+ // Load tasks
61
+ grunt.loadNpmTasks('grunt-contrib-compress');
62
+ grunt.loadNpmTasks('grunt-contrib-jshint');
63
+ grunt.loadNpmTasks('grunt-contrib-watch');
64
+ grunt.loadNpmTasks('grunt-wp-i18n');
65
+
66
+ // Default task(s).
67
+ grunt.registerTask('default', ['watch']);
68
+ grunt.registerTask('build', ['jshint']);
69
+ grunt.registerTask('package', ['build', 'makepot', 'compress']);
70
+
71
+ };
assets/css/admin-rtb-welcome-screen.css CHANGED
@@ -1,367 +1,367 @@
1
- .rtb-hidden {
2
- display: none;
3
- }
4
- .rtb-welcome-clear {
5
- clear: both;
6
- }
7
-
8
- .rtb-welcome-screen-header h1 {
9
- padding: 48px 0 16px 0;
10
- margin: 0;
11
- }
12
- .rtb-welcome-screen-header p {
13
- margin-bottom: 32px;
14
- font-size: 15px;
15
- }
16
- .rtb-welcome-screen h2 {
17
- background: #1b335f;
18
- color: #fff;
19
- padding: 18px;
20
- margin: 0;
21
- z-index: 2;
22
- position: relative;
23
- float: left;
24
- width: calc(100% - 36px);
25
- border-top: 1px solid #42639F;
26
- }
27
- .rtb-welcome-screen h2:hover {
28
- background: #365388;
29
- }
30
-
31
- .rtb-welcome-screen-box .rtb-welcome-screen-box-content {
32
- display: none;
33
- }
34
-
35
- .rtb-welcome-screen-box.rtb-welcome-screen-open .rtb-welcome-screen-box-content {
36
- display: block;
37
- }
38
-
39
- .rtb-welcome-screen-box {
40
- padding: 0px;
41
- max-width: 800px;
42
- margin: 0 auto;
43
- }
44
-
45
- .rtb-welcome-screen-header,
46
- .rtb-welcome-screen-skip-container {
47
- max-width: 800px;
48
- margin: 0 auto;
49
- }
50
-
51
- .rtb-welcome-screen-header {
52
- text-align: center;
53
- }
54
-
55
- .rtb-welcome-screen-box h2 {
56
- cursor: pointer;
57
- }
58
-
59
- .rtb-welcome-screen-box-content {
60
- position: relative;
61
- float: left;
62
- background: #fff;
63
- width: 90%;
64
- margin: 0 auto 16px;
65
- box-shadow: 0 1px 12px #ddd;
66
- display: block;
67
- padding: 18px 5%;
68
- }
69
-
70
- .rtb-welcome-screen-add-reservations-page-button,
71
- .rtb-welcome-screen-save-schedule-open-button,
72
- .rtb-welcome-screen-save-options-button {
73
- position: relative;
74
- float: left;
75
- cursor: pointer;
76
- padding: 4px 8px;
77
- border: 2px solid #1b335f;
78
- color: #fff;
79
- border-radius: 2px;
80
- background: #1b335f;
81
- width: auto;
82
- text-align: center;
83
- margin-top: 12px;
84
- }
85
- .rtb-welcome-screen-add-reservations-page-button:hover,
86
- .rtb-welcome-screen-save-schedule-open-button:hover,
87
- .rtb-welcome-screen-save-options-button:hover {
88
- border-color: #365388;
89
- background: #365388;
90
- }
91
-
92
- .rtb-welcome-screen-next-button,
93
- .rtb-welcome-screen-previous-button,
94
- .rtb-welcome-screen-finish-button a {
95
- position: relative;
96
- float: right;
97
- cursor: pointer;
98
- padding: 4px 8px;
99
- border: 2px solid #1b335f;
100
- color: #1b335f;
101
- border-radius: 2px;
102
- background: transparent;
103
- width: auto;
104
- text-align: center;
105
- margin-top: 32px;
106
- text-decoration: none;
107
- }
108
- .rtb-welcome-screen-next-button:hover,
109
- .rtb-welcome-screen-previous-button:hover,
110
- .rtb-welcome-screen-finish-button a:hover {
111
- color: #fff;
112
- background: #1b335f;
113
- text-decoration: none;
114
- }
115
- .rtb-welcome-screen-next-button-not-top-margin {
116
- margin-top: 12px;
117
- }
118
- .rtb-welcome-screen-previous-button {
119
- float: left;
120
- }
121
-
122
- .rtb-welcome-screen-skip-button {
123
- position: relative;
124
- float: right;
125
- cursor: pointer;
126
- padding: 4px 8px;
127
- border: 2px solid #ccc;
128
- color: #999;
129
- border-radius: 2px;
130
- background: transparent;
131
- width: auto;
132
- text-align: center;
133
- margin-top: 12px;
134
- }
135
- .rtb-welcome-screen-skip-button:hover {
136
- border-color: #aaa;
137
- color: #fff;
138
- background: #aaa;
139
- }
140
-
141
- .rtb-welcome-screen-box-content label {
142
- width: 160px;
143
- position: relative;
144
- float: left;
145
- font-weight: bold;
146
- }
147
- .rtb-welcome-screen-option {
148
- margin: 7px 0;
149
- }
150
- .rtb-welcome-screen-option label {
151
- width: 100px;
152
- float: left;
153
- font-weight: normal;
154
- }
155
- .rtb-welcome-screen-option label.rtb-admin-switch {
156
- width: 40px
157
- }
158
-
159
- .rtb-welcome-screen-box-content input {
160
- margin: 0;
161
- }
162
- .rtb-welcome-screen-box-content-divs {
163
- margin-bottom: 8px;
164
- }
165
-
166
-
167
- .rtb-welcome-screen-show-created-sections {
168
- display: none;
169
- }
170
-
171
- .rtb-welcome-screen-show-created-sections-name {
172
- position: relative;
173
- float: left;
174
- width: 40%;
175
- width: calc(40% - 2px);
176
- padding: 9px 5%;
177
- border-left: 1px solid #ccc;
178
- border-right: 1px solid #ccc;
179
- font-size: 15px;
180
- font-weight: bold;
181
- border-top: 1px solid #ccc;
182
- border-bottom: 1px solid #ccc;
183
- }
184
- .rtb-welcome-screen-show-created-sections-description {
185
- position: relative;
186
- float: right;
187
- width: 40%;
188
- width: calc(40% - 1px);
189
- border-right: 1px solid #ccc;
190
- padding: 9px 5%;
191
- font-size: 15px;
192
- font-weight: bold;
193
- border-top: 1px solid #ccc;
194
- border-bottom: 1px solid #ccc;
195
- }
196
- .rtb-welcome-screen-section-name {
197
- position: relative;
198
- float: left;
199
- width: 40%;
200
- width: calc(40% - 2px);
201
- padding: 7px 5%;
202
- height: 18px;
203
- overflow: hidden;
204
- border-left: 1px solid #ccc;
205
- border-right: 1px solid #ccc;
206
- border-bottom: 1px solid #ccc;
207
- }
208
- .rtb-welcome-screen-section-description {
209
- position: relative;
210
- float: right;
211
- width: 40%;
212
- width: calc(40% - 1px);
213
- padding: 7px 5%;
214
- height: 18px;
215
- overflow: hidden;
216
- border-right: 1px solid #ccc;
217
- border-bottom: 1px solid #ccc;
218
- }
219
-
220
-
221
- .rtb-welcome-screen-show-created-menu_items {
222
- display: none;
223
- }
224
-
225
- .rtb-welcome-screen-show-created-menu_items-image {
226
- position: relative;
227
- float: left;
228
- width: 20%;
229
- width: calc(20% - 2px);
230
- padding: 9px 2.5%;
231
- border-left: 1px solid #ccc;
232
- border-right: 1px solid #ccc;
233
- font-size: 15px;
234
- font-weight: bold;
235
- border-top: 1px solid #ccc;
236
- border-bottom: 1px solid #ccc;
237
- }
238
- .rtb-welcome-screen-show-created-menu_items-name {
239
- position: relative;
240
- float: left;
241
- width: 20%;
242
- width: calc(20% - 1px);
243
- border-right: 1px solid #ccc;
244
- padding: 9px 2.5%;
245
- font-size: 15px;
246
- font-weight: bold;
247
- border-top: 1px solid #ccc;
248
- border-bottom: 1px solid #ccc;
249
- }
250
- .rtb-welcome-screen-show-created-menu_items-description {
251
- position: relative;
252
- float: left;
253
- width: 20%;
254
- width: calc(20% - 1px);
255
- border-right: 1px solid #ccc;
256
- padding: 9px 2.5%;
257
- font-size: 15px;
258
- font-weight: bold;
259
- border-top: 1px solid #ccc;
260
- border-bottom: 1px solid #ccc;
261
- }
262
- .rtb-welcome-screen-show-created-menu_items-price {
263
- position: relative;
264
- float: left;
265
- width: 20%;
266
- width: calc(20% - 1px);
267
- border-right: 1px solid #ccc;
268
- padding: 9px 2.5%;
269
- font-size: 15px;
270
- font-weight: bold;
271
- border-top: 1px solid #ccc;
272
- border-bottom: 1px solid #ccc;
273
- }
274
- .rtb-welcome-screen-menu_item-image {
275
- position: relative;
276
- float: left;
277
- width: 20%;
278
- width: calc(20% - 2px);
279
- padding: 7px 2.5%;
280
- height: 100px;
281
- overflow: hidden;
282
- border-left: 1px solid #ccc;
283
- border-right: 1px solid #ccc;
284
- border-bottom: 1px solid #ccc;
285
- }
286
- .rtb-welcome-screen-menu_item-image img {
287
- width: auto;
288
- height: auto;
289
- max-width: 100%;
290
- max-height: 100%;
291
- }
292
- .rtb-welcome-screen-menu_item-name {
293
- position: relative;
294
- float: left;
295
- width: 20%;
296
- width: calc(20% - 1px);
297
- padding: 7px 2.5%;
298
- height: 100px;
299
- overflow: hidden;
300
- border-right: 1px solid #ccc;
301
- border-bottom: 1px solid #ccc;
302
- }
303
- .rtb-welcome-screen-menu_item-description {
304
- position: relative;
305
- float: left;
306
- width: 20%;
307
- width: calc(20% - 1px);
308
- padding: 7px 2.5%;
309
- height: 100px;
310
- overflow: hidden;
311
- border-right: 1px solid #ccc;
312
- border-bottom: 1px solid #ccc;
313
- }
314
- .rtb-welcome-screen-menu_item-price {
315
- position: relative;
316
- float: left;
317
- width: 20%;
318
- width: calc(20% - 1px);
319
- padding: 7px 2.5%;
320
- height: 100px;
321
- overflow: hidden;
322
- border-right: 1px solid #ccc;
323
- border-bottom: 1px solid #ccc;
324
- }
325
-
326
- .rtb-welcome-screen-add-create_menu-sections h3 {
327
- padding-bottom: 0;
328
- margin-bottom: 0;
329
- }
330
-
331
- .rtb-save-message {
332
- clear: both;
333
- position: relative;
334
- float: left;
335
- width: 100%;
336
- margin: 16px 0;
337
- }
338
- .rtb-save-message-inside {
339
- position: relative;
340
- float: left;
341
- padding: 8px 12px;
342
- background: #eee;
343
- }
344
-
345
- .rtb-welcome-screen-image-preview-container {
346
- float: left;
347
- width: calc(100% - 160px);
348
- margin-bottom: 10px;
349
- }
350
- .rtb-welcome-screen-image-preview img {
351
- width: 180px;
352
- height: auto;
353
- }
354
-
355
- /*scheduler*/
356
- .rtb-welcome-screen-box-content .sap-scheduler label {
357
- width: auto;
358
- float: none;
359
- text-align: center;
360
- font-weight: normal;
361
- }
362
- .rtb-welcome-screen-box-content .sap-scheduler .sap-scheduler-time-input label {
363
- text-align: left;
364
- }
365
- .rtb-welcome-screen-box-content .sap-scheduler input[type="checkbox"] {
366
- margin: 6px 4px 7px 0;
367
- }
1
+ .rtb-hidden {
2
+ display: none;
3
+ }
4
+ .rtb-welcome-clear {
5
+ clear: both;
6
+ }
7
+
8
+ .rtb-welcome-screen-header h1 {
9
+ padding: 48px 0 16px 0;
10
+ margin: 0;
11
+ }
12
+ .rtb-welcome-screen-header p {
13
+ margin-bottom: 32px;
14
+ font-size: 15px;
15
+ }
16
+ .rtb-welcome-screen h2 {
17
+ background: #1b335f;
18
+ color: #fff;
19
+ padding: 18px;
20
+ margin: 0;
21
+ z-index: 2;
22
+ position: relative;
23
+ float: left;
24
+ width: calc(100% - 36px);
25
+ border-top: 1px solid #42639F;
26
+ }
27
+ .rtb-welcome-screen h2:hover {
28
+ background: #365388;
29
+ }
30
+
31
+ .rtb-welcome-screen-box .rtb-welcome-screen-box-content {
32
+ display: none;
33
+ }
34
+
35
+ .rtb-welcome-screen-box.rtb-welcome-screen-open .rtb-welcome-screen-box-content {
36
+ display: block;
37
+ }
38
+
39
+ .rtb-welcome-screen-box {
40
+ padding: 0px;
41
+ max-width: 800px;
42
+ margin: 0 auto;
43
+ }
44
+
45
+ .rtb-welcome-screen-header,
46
+ .rtb-welcome-screen-skip-container {
47
+ max-width: 800px;
48
+ margin: 0 auto;
49
+ }
50
+
51
+ .rtb-welcome-screen-header {
52
+ text-align: center;
53
+ }
54
+
55
+ .rtb-welcome-screen-box h2 {
56
+ cursor: pointer;
57
+ }
58
+
59
+ .rtb-welcome-screen-box-content {
60
+ position: relative;
61
+ float: left;
62
+ background: #fff;
63
+ width: 90%;
64
+ margin: 0 auto 16px;
65
+ box-shadow: 0 1px 12px #ddd;
66
+ display: block;
67
+ padding: 18px 5%;
68
+ }
69
+
70
+ .rtb-welcome-screen-add-reservations-page-button,
71
+ .rtb-welcome-screen-save-schedule-open-button,
72
+ .rtb-welcome-screen-save-options-button {
73
+ position: relative;
74
+ float: left;
75
+ cursor: pointer;
76
+ padding: 4px 8px;
77
+ border: 2px solid #1b335f;
78
+ color: #fff;
79
+ border-radius: 2px;
80
+ background: #1b335f;
81
+ width: auto;
82
+ text-align: center;
83
+ margin-top: 12px;
84
+ }
85
+ .rtb-welcome-screen-add-reservations-page-button:hover,
86
+ .rtb-welcome-screen-save-schedule-open-button:hover,
87
+ .rtb-welcome-screen-save-options-button:hover {
88
+ border-color: #365388;
89
+ background: #365388;
90
+ }
91
+
92
+ .rtb-welcome-screen-next-button,
93
+ .rtb-welcome-screen-previous-button,
94
+ .rtb-welcome-screen-finish-button a {
95
+ position: relative;
96
+ float: right;
97
+ cursor: pointer;
98
+ padding: 4px 8px;
99
+ border: 2px solid #1b335f;
100
+ color: #1b335f;
101
+ border-radius: 2px;
102
+ background: transparent;
103
+ width: auto;
104
+ text-align: center;
105
+ margin-top: 32px;
106
+ text-decoration: none;
107
+ }
108
+ .rtb-welcome-screen-next-button:hover,
109
+ .rtb-welcome-screen-previous-button:hover,
110
+ .rtb-welcome-screen-finish-button a:hover {
111
+ color: #fff;
112
+ background: #1b335f;
113
+ text-decoration: none;
114
+ }
115
+ .rtb-welcome-screen-next-button-not-top-margin {
116
+ margin-top: 12px;
117
+ }
118
+ .rtb-welcome-screen-previous-button {
119
+ float: left;
120
+ }
121
+
122
+ .rtb-welcome-screen-skip-button {
123
+ position: relative;
124
+ float: right;
125
+ cursor: pointer;
126
+ padding: 4px 8px;
127
+ border: 2px solid #ccc;
128
+ color: #999;
129
+ border-radius: 2px;
130
+ background: transparent;
131
+ width: auto;
132
+ text-align: center;
133
+ margin-top: 12px;
134
+ }
135
+ .rtb-welcome-screen-skip-button:hover {
136
+ border-color: #aaa;
137
+ color: #fff;
138
+ background: #aaa;
139
+ }
140
+
141
+ .rtb-welcome-screen-box-content label {
142
+ width: 160px;
143
+ position: relative;
144
+ float: left;
145
+ font-weight: bold;
146
+ }
147
+ .rtb-welcome-screen-option {
148
+ margin: 7px 0;
149
+ }
150
+ .rtb-welcome-screen-option label {
151
+ width: 100px;
152
+ float: left;
153
+ font-weight: normal;
154
+ }
155
+ .rtb-welcome-screen-option label.rtb-admin-switch {
156
+ width: 40px
157
+ }
158
+
159
+ .rtb-welcome-screen-box-content input {
160
+ margin: 0;
161
+ }
162
+ .rtb-welcome-screen-box-content-divs {
163
+ margin-bottom: 8px;
164
+ }
165
+
166
+
167
+ .rtb-welcome-screen-show-created-sections {
168
+ display: none;
169
+ }
170
+
171
+ .rtb-welcome-screen-show-created-sections-name {
172
+ position: relative;
173
+ float: left;
174
+ width: 40%;
175
+ width: calc(40% - 2px);
176
+ padding: 9px 5%;
177
+ border-left: 1px solid #ccc;
178
+ border-right: 1px solid #ccc;
179
+ font-size: 15px;
180
+ font-weight: bold;
181
+ border-top: 1px solid #ccc;
182
+ border-bottom: 1px solid #ccc;
183
+ }
184
+ .rtb-welcome-screen-show-created-sections-description {
185
+ position: relative;
186
+ float: right;
187
+ width: 40%;
188
+ width: calc(40% - 1px);
189
+ border-right: 1px solid #ccc;
190
+ padding: 9px 5%;
191
+ font-size: 15px;
192
+ font-weight: bold;
193
+ border-top: 1px solid #ccc;
194
+ border-bottom: 1px solid #ccc;
195
+ }
196
+ .rtb-welcome-screen-section-name {
197
+ position: relative;
198
+ float: left;
199
+ width: 40%;
200
+ width: calc(40% - 2px);
201
+ padding: 7px 5%;
202
+ height: 18px;
203
+ overflow: hidden;
204
+ border-left: 1px solid #ccc;
205
+ border-right: 1px solid #ccc;
206
+ border-bottom: 1px solid #ccc;
207
+ }
208
+ .rtb-welcome-screen-section-description {
209
+ position: relative;
210
+ float: right;
211
+ width: 40%;
212
+ width: calc(40% - 1px);
213
+ padding: 7px 5%;
214
+ height: 18px;
215
+ overflow: hidden;
216
+ border-right: 1px solid #ccc;
217
+ border-bottom: 1px solid #ccc;
218
+ }
219
+
220
+
221
+ .rtb-welcome-screen-show-created-menu_items {
222
+ display: none;
223
+ }
224
+
225
+ .rtb-welcome-screen-show-created-menu_items-image {
226
+ position: relative;
227
+ float: left;
228
+ width: 20%;
229
+ width: calc(20% - 2px);
230
+ padding: 9px 2.5%;
231
+ border-left: 1px solid #ccc;
232
+ border-right: 1px solid #ccc;
233
+ font-size: 15px;
234
+ font-weight: bold;
235
+ border-top: 1px solid #ccc;
236
+ border-bottom: 1px solid #ccc;
237
+ }
238
+ .rtb-welcome-screen-show-created-menu_items-name {
239
+ position: relative;
240
+ float: left;
241
+ width: 20%;
242
+ width: calc(20% - 1px);
243
+ border-right: 1px solid #ccc;
244
+ padding: 9px 2.5%;
245
+ font-size: 15px;
246
+ font-weight: bold;
247
+ border-top: 1px solid #ccc;
248
+ border-bottom: 1px solid #ccc;
249
+ }
250
+ .rtb-welcome-screen-show-created-menu_items-description {
251
+ position: relative;
252
+ float: left;
253
+ width: 20%;
254
+ width: calc(20% - 1px);
255
+ border-right: 1px solid #ccc;
256
+ padding: 9px 2.5%;
257
+ font-size: 15px;
258
+ font-weight: bold;
259
+ border-top: 1px solid #ccc;
260
+ border-bottom: 1px solid #ccc;
261
+ }
262
+ .rtb-welcome-screen-show-created-menu_items-price {
263
+ position: relative;
264
+ float: left;
265
+ width: 20%;
266
+ width: calc(20% - 1px);
267
+ border-right: 1px solid #ccc;
268
+ padding: 9px 2.5%;
269
+ font-size: 15px;
270
+ font-weight: bold;
271
+ border-top: 1px solid #ccc;
272
+ border-bottom: 1px solid #ccc;
273
+ }
274
+ .rtb-welcome-screen-menu_item-image {
275
+ position: relative;
276
+ float: left;
277
+ width: 20%;
278
+ width: calc(20% - 2px);
279
+ padding: 7px 2.5%;
280
+ height: 100px;
281
+ overflow: hidden;
282
+ border-left: 1px solid #ccc;
283
+ border-right: 1px solid #ccc;
284
+ border-bottom: 1px solid #ccc;
285
+ }
286
+ .rtb-welcome-screen-menu_item-image img {
287
+ width: auto;
288
+ height: auto;
289
+ max-width: 100%;
290
+ max-height: 100%;
291
+ }
292
+ .rtb-welcome-screen-menu_item-name {
293
+ position: relative;
294
+ float: left;
295
+ width: 20%;
296
+ width: calc(20% - 1px);
297
+ padding: 7px 2.5%;
298
+ height: 100px;
299
+ overflow: hidden;
300
+ border-right: 1px solid #ccc;
301
+ border-bottom: 1px solid #ccc;
302
+ }
303
+ .rtb-welcome-screen-menu_item-description {
304
+ position: relative;
305
+ float: left;
306
+ width: 20%;
307
+ width: calc(20% - 1px);
308
+ padding: 7px 2.5%;
309
+ height: 100px;
310
+ overflow: hidden;
311
+ border-right: 1px solid #ccc;
312
+ border-bottom: 1px solid #ccc;
313
+ }
314
+ .rtb-welcome-screen-menu_item-price {
315
+ position: relative;
316
+ float: left;
317
+ width: 20%;
318
+ width: calc(20% - 1px);
319
+ padding: 7px 2.5%;
320
+ height: 100px;
321
+ overflow: hidden;
322
+ border-right: 1px solid #ccc;
323
+ border-bottom: 1px solid #ccc;
324
+ }
325
+
326
+ .rtb-welcome-screen-add-create_menu-sections h3 {
327
+ padding-bottom: 0;
328
+ margin-bottom: 0;
329
+ }
330
+
331
+ .rtb-save-message {
332
+ clear: both;
333
+ position: relative;
334
+ float: left;
335
+ width: 100%;
336
+ margin: 16px 0;
337
+ }
338
+ .rtb-save-message-inside {
339
+ position: relative;
340
+ float: left;
341
+ padding: 8px 12px;
342
+ background: #eee;
343
+ }
344
+
345
+ .rtb-welcome-screen-image-preview-container {
346
+ float: left;
347
+ width: calc(100% - 160px);
348
+ margin-bottom: 10px;
349
+ }
350
+ .rtb-welcome-screen-image-preview img {
351
+ width: 180px;
352
+ height: auto;
353
+ }
354
+
355
+ /*scheduler*/
356
+ .rtb-welcome-screen-box-content .sap-scheduler label {
357
+ width: auto;
358
+ float: none;
359
+ text-align: center;
360
+ font-weight: normal;
361
+ }
362
+ .rtb-welcome-screen-box-content .sap-scheduler .sap-scheduler-time-input label {
363
+ text-align: left;
364
+ }
365
+ .rtb-welcome-screen-box-content .sap-scheduler input[type="checkbox"] {
366
+ margin: 6px 4px 7px 0;
367
+ }
assets/css/admin.css CHANGED
@@ -103,6 +103,29 @@
103
  color: #000;
104
  }
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  #rtb-bookings-table .tablenav .actions .button {
107
  margin-top: 1px;
108
  margin-bottom: 1px;
@@ -2483,22 +2506,22 @@ RESPONSIVE
2483
  }
2484
 
2485
  /* Subscribe list and merge fields */
2486
- .mcfrtb_loading {
2487
  opacity: 0;
2488
  }
2489
- .mcfrtb_loading span {
2490
  display: inline-block;
2491
  float: none;
2492
  vertical-align: middle;
2493
  opacity: 0.7;
2494
  }
2495
 
2496
- .mcfrtb_loading .spinner {
2497
  visibility: visible;
2498
  }
2499
 
2500
  #mcfrtb-merge-controls .error,
2501
- .mcfrtb-list-select .error {
2502
  padding: 5px;
2503
  background: #ff3d3d;
2504
  color: #fff;
@@ -2532,7 +2555,7 @@ RESPONSIVE
2532
  @media screen and (max-width: 782px) {
2533
 
2534
  .mcfrtb-status,
2535
- .mcfrtb_loading,
2536
  #mcfrtb-merge-controls {
2537
  display: inline-block;
2538
  margin: 0.5em 2px;
103
  color: #000;
104
  }
105
 
106
+ #rtb-filters li.filter_name input {
107
+ width: 100%;
108
+ height: 30px;
109
+ max-width: 150px;
110
+ padding: 0 0.5em;
111
+ margin: 0 0 0 0.2em;
112
+ border-radius: 5px 0 0 5px;
113
+ }
114
+
115
+ #rtb-filters li.filter_name a {
116
+ height: 28px;
117
+ padding: 0 0.2em;
118
+ display: inline-block;
119
+ vertical-align: bottom;
120
+ border: 1px solid #8c8f94;
121
+ border-left: 0;
122
+ border-radius: 0 4px 4px 0;
123
+ }
124
+
125
+ #rtb-filters li.filter_name .dashicons {
126
+ vertical-align: middle;
127
+ }
128
+
129
  #rtb-bookings-table .tablenav .actions .button {
130
  margin-top: 1px;
131
  margin-bottom: 1px;
2506
  }
2507
 
2508
  /* Subscribe list and merge fields */
2509
+ .mcf-sap_loading {
2510
  opacity: 0;
2511
  }
2512
+ .mcf-sap_loading span {
2513
  display: inline-block;
2514
  float: none;
2515
  vertical-align: middle;
2516
  opacity: 0.7;
2517
  }
2518
 
2519
+ .mcf-sap_loading .spinner {
2520
  visibility: visible;
2521
  }
2522
 
2523
  #mcfrtb-merge-controls .error,
2524
+ .mcf-list-select .error {
2525
  padding: 5px;
2526
  background: #ff3d3d;
2527
  color: #fff;
2555
  @media screen and (max-width: 782px) {
2556
 
2557
  .mcfrtb-status,
2558
+ .mcf-sap_loading,
2559
  #mcfrtb-merge-controls {
2560
  display: inline-block;
2561
  margin: 0.5em 2px;
assets/css/booking-form.css CHANGED
@@ -1,309 +1,309 @@
1
- /* Frontend CSS Stylesheet for Restaurant Reservations */
2
-
3
- .rtb-clear {
4
- clear: both;
5
- }
6
- .rtb-hidden {
7
- display: none;
8
- }
9
- .rtb-booking-form {
10
- position: relative;
11
- /*float: left;*/
12
- width: 100%;
13
- clear: both;
14
- margin-bottom: 48px;
15
- }
16
- .rtb-booking-form fieldset {
17
- padding-bottom: 1em;
18
- margin-bottom: 1em;
19
- }
20
- .rtb-booking-form legend {
21
- padding: 0 0.5em;
22
- }
23
- .rtb-booking-form fieldset>div {
24
- margin-top: 1em;
25
- }
26
- .rtb-booking-form fieldset>div:first-child {
27
- margin-top: 0;
28
- }
29
- .rtb-booking-form label {
30
- display: block;
31
- }
32
- .rtb-booking-form input,
33
- #stripe-payment-form #cardElement {
34
- width: 100%;
35
- max-width: 15em;
36
- }
37
- #stripe-payment-form #cardElement {
38
- max-width: 500px;
39
- width: 100%;
40
- display: block;
41
- border: 1px solid #ccc;
42
- border-radius: 0;
43
- background-color: white;
44
- color: #777;
45
- padding: 12px 21px;
46
- margin: 18px 0;
47
- }
48
- .rtb-booking-form input[type="checkbox"],
49
- .rtb-booking-form input[type="radio"] {
50
- width: auto;
51
- margin-right: 0.25em;
52
- vertical-align: middle;
53
- }
54
- .rtb-booking-form textarea {
55
- width: 100%;
56
- max-width: 30em;
57
- }
58
- .rtb-booking-form .add-message {
59
- margin-top: 1em;
60
- }
61
- .rtb-booking-form .message {
62
- position: relative;
63
- top: auto;
64
- left: auto;
65
- display: none;
66
- }
67
- .rtb-booking-form .message-open {
68
- display: block;
69
- }
70
- .rtb-booking-form .message textarea {
71
- min-height: 6em;
72
- }
73
- .rtb-booking-form .rtb-error {
74
- font-size: 0.85em;
75
- padding: 0.25em;
76
- background: #f50;
77
- color: #fff;
78
- }
79
- .rtb-booking-form .rtb-error:before {
80
- content: ' ';
81
- border-top: 4px solid;
82
- border-right: 4px solid transparent;
83
- border-left: 4px solid transparent;
84
- width: 0;
85
- height: 0;
86
- display: inline-block;
87
- margin: 0.25em 0.5em;
88
- }
89
-
90
- /* Compatibility styles for pickadate on common themes */
91
- .picker {
92
- outline: 0;
93
- }
94
- #rtb-date_root .picker__button--clear,
95
- #rtb-date_root .picker__button--today,
96
- #rtb-date_root .picker__button--close {
97
- /* don't adopt the theme's button text color */
98
- color: #000;
99
- }
100
- #rtb-date_root .picker__nav--next,
101
- #rtb-date_root .picker__nav--prev {
102
- /* next/prev calendar arrows button areas sometimes don't cover the arrows */
103
- min-height: 1em;
104
- }
105
- #rtb-time_root .picker__list,
106
- #rtb-time_root .picker__list li {
107
- /* override some theme's list styles */
108
- list-style: none;
109
- margin: 0 0 0 1px;
110
- }
111
-
112
-
113
- /* ARRIVAL LIGHTBOX */
114
- .rtb-view-bookings-form-confirmation-background-div {
115
- position: fixed;
116
- top: 0;
117
- left: 0;
118
- width: 100%;
119
- height: 100%;
120
- z-index: 999999;
121
- background: rgba(0,0,0,0.5);
122
- }
123
- .rtb-view-bookings-form-confirmation-div {
124
- position: fixed;
125
- top: 200px;
126
- width: 480px;
127
- left: calc(50% - 240px);
128
- z-index: 1000000;
129
- margin: 0;
130
- background: #fff;
131
- font-size: 18px;
132
- text-transform: uppercase;
133
- font-weight: bold;
134
- text-align: center;
135
- }
136
- .rtb-view-bookings-form-confirmation-div-inside {
137
- position: relative;
138
- float: left;
139
- width: calc(100% - 6px);
140
- height: calc(100% - 12px);
141
- border: 3px solid #ddd;
142
- margin: 3px;
143
- }
144
- .rtb-view-bookings-form-confirmation-div-title {
145
- position: relative;
146
- float: left;
147
- width: 90%;
148
- margin: 24px 5%;
149
- color: #333;
150
- }
151
- .rtb-view-bookings-form-confirmation-accept {
152
- position: relative;
153
- float: left;
154
- width: 32%;
155
- margin: 24px 34% 0;
156
- padding: 8px 0;
157
- background: #444;
158
- color: #fff;
159
- cursor: pointer;
160
- }
161
- .rtb-view-bookings-form-confirmation-accept:hover {
162
- background: #555;
163
- }
164
- .rtb-view-bookings-form-confirmation-decline {
165
- position: relative;
166
- float: left;
167
- width: 32%;
168
- margin: 8px 34% 24px;
169
- padding: 8px 0;
170
- background: #bbb;
171
- color: #444;
172
- cursor: pointer;
173
- }
174
- .rtb-view-bookings-form-confirmation-decline:hover {
175
- background: #ccc;
176
- }
177
-
178
- #rtb-view-bookings-form-close {
179
- position: absolute;
180
- top: 0;
181
- right: 0;
182
- width: 24px;
183
- height: 24px;
184
- background: #ddd;
185
- font-size: 15px;
186
- cursor: pointer;
187
- }
188
-
189
- @media screen and (max-width: 568px) {
190
- .rtb-view-bookings-form-confirmation-div {
191
- top: 100px;
192
- width: 300px;
193
- left: calc(50% - 150px);
194
- }
195
- }
196
-
197
-
198
- /*CANCEL LINK*/
199
- .rtb-modification-toggle {
200
- position: relative;
201
- float: left;
202
- padding: 10px 15px;
203
- margin-bottom: 24px;
204
- background: #444;
205
- color: #fff;
206
- border-radius: 3px;
207
- cursor: pointer;
208
- }
209
- label[for="rtb-modification-email"],
210
- input[name="modification"] {
211
- float: left;
212
- margin-top: 20px;
213
- }
214
- label[for="rtb-modification-email"] {
215
- margin-right: 12px;
216
- }
217
- .rtb-find-reservation-button {
218
- position: relative;
219
- float: left;
220
- padding: 10px 15px;
221
- margin-top: 24px;
222
- background: #444;
223
- color: #fff;
224
- border-radius: 3px;
225
- cursor: pointer;
226
- }
227
-
228
- .rtb-bookings-results {
229
- position: relative;
230
- float: left;
231
- width: 100%;
232
- margin-top: 16px;
233
- }
234
- .rtb-cancel-booking-div {
235
- border: 1px solid #ddd;
236
- margin-bottom: 8px;
237
- }
238
- .rtb-cancel-booking-div + .alert {
239
- margin: 0;
240
- }
241
- .rtb-cancel-booking-div + .alert.error {
242
- color: #f24a4d;
243
- background: #f24a4d47;
244
- }
245
- .rtb-cancel-booking-div *:not(:first-child) {
246
- margin-left: -5px;
247
- }
248
-
249
- .rtb-cancel-booking {
250
- text-align: center;
251
- padding: 10px 0;
252
- background: #fe4e4e;
253
- color: #fff;
254
- cursor: pointer;
255
- display: inline-block;
256
- max-width: 100px;
257
- min-width: 100px;
258
- width: 100%;
259
- }
260
- .rtb-cancel-booking:hover {
261
- background: #ff6b6b;
262
- color: #fff;
263
- }
264
- .rtb-cancel-booking.cancelled {
265
- background: #24b124;
266
- }
267
-
268
- .rtb-deposit-booking {
269
- text-align: center;
270
- padding: 10px 0;
271
- background: green;
272
- color: #fff;
273
- cursor: pointer;
274
- display: inline-block;
275
- max-width: 100px;
276
- min-width: 100px;
277
- width: 100%;
278
- }
279
- .rtb-deposit-booking:hover {
280
- background: #008000cf;
281
- color: #fff;
282
- }
283
-
284
- .rtb-booking-information {
285
- padding-left: 5px;
286
- display: inline-block;
287
- }
288
-
289
- #rtb_recaptcha {
290
- position: relative;
291
- float: left;
292
- width: 100%;
293
- margin: 16px 0;
294
- }
295
-
296
- .stripe-payment-help-text {
297
- display: none;
298
- }
299
- .payment-errors {
300
- margin: 32px 0;
301
- border-left: 4px solid #000;
302
- padding-left: 16px;
303
- }
304
- :is(.rtb-booking-form, #stripe-booking-form) button:disabled {
305
- background-color: gray;
306
- }
307
- :is(.rtb-booking-form, #stripe-booking-form) button:disabled:hover {
308
- text-decoration: none;
309
  }
1
+ /* Frontend CSS Stylesheet for Restaurant Reservations */
2
+
3
+ .rtb-clear {
4
+ clear: both;
5
+ }
6
+ .rtb-hidden {
7
+ display: none;
8
+ }
9
+ .rtb-booking-form {
10
+ position: relative;
11
+ /*float: left;*/
12
+ width: 100%;
13
+ clear: both;
14
+ margin-bottom: 48px;
15
+ }
16
+ .rtb-booking-form fieldset {
17
+ padding-bottom: 1em;
18
+ margin-bottom: 1em;
19
+ }
20
+ .rtb-booking-form legend {
21
+ padding: 0 0.5em;
22
+ }
23
+ .rtb-booking-form fieldset>div {
24
+ margin-top: 1em;
25
+ }
26
+ .rtb-booking-form fieldset>div:first-child {
27
+ margin-top: 0;
28
+ }
29
+ .rtb-booking-form label {
30
+ display: block;
31
+ }
32
+ .rtb-booking-form input,
33
+ #stripe-payment-form #cardElement {
34
+ width: 100%;
35
+ max-width: 15em;
36
+ }
37
+ #stripe-payment-form #cardElement {
38
+ max-width: 500px;
39
+ width: 100%;
40
+ display: block;
41
+ border: 1px solid #ccc;
42
+ border-radius: 0;
43
+ background-color: white;
44
+ color: #777;
45
+ padding: 12px 21px;
46
+ margin: 18px 0;
47
+ }
48
+ .rtb-booking-form input[type="checkbox"],
49
+ .rtb-booking-form input[type="radio"] {
50
+ width: auto;
51
+ margin-right: 0.25em;
52
+ vertical-align: middle;
53
+ }
54
+ .rtb-booking-form textarea {
55
+ width: 100%;
56
+ max-width: 30em;
57
+ }
58
+ .rtb-booking-form .add-message {
59
+ margin-top: 1em;
60
+ }
61
+ .rtb-booking-form .message {
62
+ position: relative;
63
+ top: auto;
64
+ left: auto;
65
+ display: none;
66
+ }
67
+ .rtb-booking-form .message-open {
68
+ display: block;
69
+ }
70
+ .rtb-booking-form .message textarea {
71
+ min-height: 6em;
72
+ }
73
+ .rtb-booking-form .rtb-error {
74
+ font-size: 0.85em;
75
+ padding: 0.25em;
76
+ background: #f50;
77
+ color: #fff;
78
+ }
79
+ .rtb-booking-form .rtb-error:before {
80
+ content: ' ';
81
+ border-top: 4px solid;
82
+ border-right: 4px solid transparent;
83
+ border-left: 4px solid transparent;
84
+ width: 0;
85
+ height: 0;
86
+ display: inline-block;
87
+ margin: 0.25em 0.5em;
88
+ }
89
+
90
+ /* Compatibility styles for pickadate on common themes */
91
+ .picker {
92
+ outline: 0;
93
+ }
94
+ #rtb-date_root .picker__button--clear,
95
+ #rtb-date_root .picker__button--today,
96
+ #rtb-date_root .picker__button--close {
97
+ /* don't adopt the theme's button text color */
98
+ color: #000;
99
+ }
100
+ #rtb-date_root .picker__nav--next,
101
+ #rtb-date_root .picker__nav--prev {
102
+ /* next/prev calendar arrows button areas sometimes don't cover the arrows */
103
+ min-height: 1em;
104
+ }
105
+ #rtb-time_root .picker__list,
106
+ #rtb-time_root .picker__list li {
107
+ /* override some theme's list styles */
108
+ list-style: none;
109
+ margin: 0 0 0 1px;
110
+ }
111
+
112
+
113
+ /* ARRIVAL LIGHTBOX */
114
+ .rtb-view-bookings-form-confirmation-background-div {
115
+ position: fixed;
116
+ top: 0;
117
+ left: 0;
118
+ width: 100%;
119
+ height: 100%;
120
+ z-index: 999999;
121
+ background: rgba(0,0,0,0.5);
122
+ }
123
+ .rtb-view-bookings-form-confirmation-div {
124
+ position: fixed;
125
+ top: 200px;
126
+ width: 480px;
127
+ left: calc(50% - 240px);
128
+ z-index: 1000000;
129
+ margin: 0;
130
+ background: #fff;
131
+ font-size: 18px;
132
+ text-transform: uppercase;
133
+ font-weight: bold;
134
+ text-align: center;
135
+ }
136
+ .rtb-view-bookings-form-confirmation-div-inside {
137
+ position: relative;
138
+ float: left;
139
+ width: calc(100% - 6px);
140
+ height: calc(100% - 12px);
141
+ border: 3px solid #ddd;
142
+ margin: 3px;
143
+ }
144
+ .rtb-view-bookings-form-confirmation-div-title {
145
+ position: relative;
146
+ float: left;
147
+ width: 90%;
148
+ margin: 24px 5%;
149
+ color: #333;
150
+ }
151
+ .rtb-view-bookings-form-confirmation-accept {
152
+ position: relative;
153
+ float: left;
154
+ width: 32%;
155
+ margin: 24px 34% 0;
156
+ padding: 8px 0;
157
+ background: #444;
158
+ color: #fff;
159
+ cursor: pointer;
160
+ }
161
+ .rtb-view-bookings-form-confirmation-accept:hover {
162
+ background: #555;
163
+ }
164
+ .rtb-view-bookings-form-confirmation-decline {
165
+ position: relative;
166
+ float: left;
167
+ width: 32%;
168
+ margin: 8px 34% 24px;
169
+ padding: 8px 0;
170
+ background: #bbb;
171
+ color: #444;
172
+ cursor: pointer;
173
+ }
174
+ .rtb-view-bookings-form-confirmation-decline:hover {
175
+ background: #ccc;
176
+ }
177
+
178
+ #rtb-view-bookings-form-close {
179
+ position: absolute;
180
+ top: 0;
181
+ right: 0;
182
+ width: 24px;
183
+ height: 24px;
184
+ background: #ddd;
185
+ font-size: 15px;
186
+ cursor: pointer;
187
+ }
188
+
189
+ @media screen and (max-width: 568px) {
190
+ .rtb-view-bookings-form-confirmation-div {
191
+ top: 100px;
192
+ width: 300px;
193
+ left: calc(50% - 150px);
194
+ }
195
+ }
196
+
197
+
198
+ /*CANCEL LINK*/
199
+ .rtb-modification-toggle {
200
+ position: relative;
201
+ float: left;
202
+ padding: 10px 15px;
203
+ margin-bottom: 24px;
204
+ background: #444;
205
+ color: #fff;
206
+ border-radius: 3px;
207
+ cursor: pointer;
208
+ }
209
+ label[for="rtb-modification-email"],
210
+ input[name="modification"] {
211
+ float: left;
212
+ margin-top: 20px;
213
+ }
214
+ label[for="rtb-modification-email"] {
215
+ margin-right: 12px;
216
+ }
217
+ .rtb-find-reservation-button {
218
+ position: relative;
219
+ float: left;
220
+ padding: 10px 15px;
221
+ margin-top: 24px;
222
+ background: #444;
223
+ color: #fff;
224
+ border-radius: 3px;
225
+ cursor: pointer;
226
+ }
227
+
228
+ .rtb-bookings-results {
229
+ position: relative;
230
+ float: left;
231
+ width: 100%;
232
+ margin-top: 16px;
233
+ }
234
+ .rtb-cancel-booking-div {
235
+ border: 1px solid #ddd;
236
+ margin-bottom: 8px;
237
+ }
238
+ .rtb-cancel-booking-div + .alert {
239
+ margin: 0;
240
+ }
241
+ .rtb-cancel-booking-div + .alert.error {
242
+ color: #f24a4d;
243
+ background: #f24a4d47;
244
+ }
245
+ .rtb-cancel-booking-div *:not(:first-child) {
246
+ margin-left: -5px;
247
+ }
248
+
249
+ .rtb-cancel-booking {
250
+ text-align: center;
251
+ padding: 10px 0;
252
+ background: #fe4e4e;
253
+ color: #fff;
254
+ cursor: pointer;
255
+ display: inline-block;
256
+ max-width: 100px;
257
+ min-width: 100px;
258
+ width: 100%;
259
+ }
260
+ .rtb-cancel-booking:hover {
261
+ background: #ff6b6b;
262
+ color: #fff;
263
+ }
264
+ .rtb-cancel-booking.cancelled {
265
+ background: #24b124;
266
+ }
267
+
268
+ .rtb-deposit-booking {
269
+ text-align: center;
270
+ padding: 10px 0;
271
+ background: green;
272
+ color: #fff;
273
+ cursor: pointer;
274
+ display: inline-block;
275
+ max-width: 100px;
276
+ min-width: 100px;
277
+ width: 100%;
278
+ }
279
+ .rtb-deposit-booking:hover {
280
+ background: #008000cf;
281
+ color: #fff;
282
+ }
283
+
284
+ .rtb-booking-information {
285
+ padding-left: 5px;
286
+ display: inline-block;
287
+ }
288
+
289
+ #rtb_recaptcha {
290
+ position: relative;
291
+ float: left;
292
+ width: 100%;
293
+ margin: 16px 0;
294
+ }
295
+
296
+ .stripe-payment-help-text {
297
+ display: none;
298
+ }
299
+ .payment-errors {
300
+ margin: 32px 0;
301
+ border-left: 4px solid #000;
302
+ padding-left: 16px;
303
+ }
304
+ :is(.rtb-booking-form, #stripe-booking-form) button:disabled {
305
+ background-color: gray;
306
+ }
307
+ :is(.rtb-booking-form, #stripe-booking-form) button:disabled:hover {
308
+ text-decoration: none;
309
  }
assets/css/dashboard-review-ask.css CHANGED
@@ -1,61 +1,61 @@
1
- .rtb-hidden {
2
- display: none;
3
- }
4
- .rtb-clear {
5
- clear: both;
6
- }
7
- .rtb-main-dashboard-review-ask {
8
- border-left-color: #1b335f;
9
- }
10
- .rtb-review-ask-plugin-icon {
11
- height: 100px;
12
- width: 100px;
13
- float: left;
14
- margin: 12px 24px 16px 10px;
15
- background-image: url(../img/rtb-icon.png);
16
- background-size: contain;
17
- }
18
- .rtb-review-ask-text p {
19
- padding: 10px 20px;
20
- font-weight: 600;
21
- font-size: 20px;
22
- }
23
- .rtb-review-ask-action {
24
- width: 90px;
25
- padding: 6px;
26
- margin-right: 18px;
27
- text-align: center;
28
- float: left;
29
- border-radius: 4px;
30
- cursor: pointer;
31
- }
32
- .rtb-review-ask-white {
33
- color: #999999;
34
- border: 1px solid #bbbbbb;
35
- }
36
- .rtb-review-ask-green {
37
- color: #ffffff;
38
- background: #7CA3BF;
39
- border: 1px solid #7CA3BF;
40
- font-weight: 600;
41
- }
42
- .rtb-review-ask-green a {
43
- color: #ffffff;
44
- text-decoration: none;
45
- }
46
- .rtb-review-ask-feedback-form {
47
- position: relative;
48
- float: left;
49
- }.rtb-review-ask-feedback-explanation textarea {
50
- height: 70px;
51
- width: 480px;
52
- }
53
- .rtb-review-ask-send-feedback {
54
- margin: 10px 0px;
55
- }
56
-
57
-
58
- .rtb-review-ask-review-text span {
59
- font-size: .8em;
60
- font-weight: normal;
61
  }
1
+ .rtb-hidden {
2
+ display: none;
3
+ }
4
+ .rtb-clear {
5
+ clear: both;
6
+ }
7
+ .rtb-main-dashboard-review-ask {
8
+ border-left-color: #1b335f;
9
+ }
10
+ .rtb-review-ask-plugin-icon {
11
+ height: 100px;
12
+ width: 100px;
13
+ float: left;
14
+ margin: 12px 24px 16px 10px;
15
+ background-image: url(../img/rtb-icon.png);
16
+ background-size: contain;
17
+ }
18
+ .rtb-review-ask-text p {
19
+ padding: 10px 20px;
20
+ font-weight: 600;
21
+ font-size: 20px;
22
+ }
23
+ .rtb-review-ask-action {
24
+ width: 90px;
25
+ padding: 6px;
26
+ margin-right: 18px;
27
+ text-align: center;
28
+ float: left;
29
+ border-radius: 4px;
30
+ cursor: pointer;
31
+ }
32
+ .rtb-review-ask-white {
33
+ color: #999999;
34
+ border: 1px solid #bbbbbb;
35
+ }
36
+ .rtb-review-ask-green {
37
+ color: #ffffff;
38
+ background: #7CA3BF;
39
+ border: 1px solid #7CA3BF;
40
+ font-weight: 600;
41
+ }
42
+ .rtb-review-ask-green a {
43
+ color: #ffffff;
44
+ text-decoration: none;
45
+ }
46
+ .rtb-review-ask-feedback-form {
47
+ position: relative;
48
+ float: left;
49
+ }.rtb-review-ask-feedback-explanation textarea {
50
+ height: 70px;
51
+ width: 480px;
52
+ }
53
+ .rtb-review-ask-send-feedback {
54
+ margin: 10px 0px;
55
+ }
56
+
57
+
58
+ .rtb-review-ask-review-text span {
59
+ font-size: .8em;
60
+ font-weight: normal;
61
  }
assets/css/editor.css CHANGED
@@ -1,582 +1,582 @@
1
- @keyframes cffrtbrotate {
2
- 0% {
3
- transform: rotateZ(-360deg);
4
- -webkit-transform: rotateZ(-360deg);
5
- -moz-transform: rotateZ(-360deg);
6
- -o-transform: rotateZ(-360deg);
7
- }
8
- 100% {
9
- transform: rotateZ(0deg);
10
- -webkit-transform: rotateZ(0deg);
11
- -moz-transform: rotateZ(0deg);
12
- -o-transform: rotateZ(0deg);
13
- }
14
- }
15
- @-webkit-keyframes cffrtbrotate {
16
- 0% {
17
- transform: rotateZ(-360deg);
18
- -webkit-transform: rotateZ(-360deg);
19
- -moz-transform: rotateZ(-360deg);
20
- -o-transform: rotateZ(-360deg);
21
- }
22
- 100% {
23
- transform: rotateZ(0deg);
24
- -webkit-transform: rotateZ(0deg);
25
- -moz-transform: rotateZ(0deg);
26
- -o-transform: rotateZ(0deg);
27
- }
28
- }
29
- @-moz-keyframes cffrtbrotate {
30
- 0% {
31
- transform: rotateZ(-360deg);
32
- -webkit-transform: rotateZ(-360deg);
33
- -moz-transform: rotateZ(-360deg);
34
- -o-transform: rotateZ(-360deg);
35
- }
36
- 100% {
37
- transform: rotateZ(0deg);
38
- -webkit-transform: rotateZ(0deg);
39
- -moz-transform: rotateZ(0deg);
40
- -o-transform: rotateZ(0deg);
41
- }
42
- }
43
- @-o-keyframes cffrtbrotate {
44
- 0% {
45
- transform: rotateZ(-360deg);
46
- -webkit-transform: rotateZ(-360deg);
47
- -moz-transform: rotateZ(-360deg);
48
- -o-transform: rotateZ(-360deg);
49
- }
50
- 100% {
51
- transform: rotateZ(0deg);
52
- -webkit-transform: rotateZ(0deg);
53
- -moz-transform: rotateZ(0deg);
54
- -o-transform: rotateZ(0deg);
55
- }
56
- }
57
- #cffrtb-editor .load-spinner:after,
58
- #cffrtb-field-editor-form .load-spinner:after {
59
- display: block;
60
- position: relative;
61
- width: 20px;
62
- height: 20px;
63
- -webkit-animation: cffrtbrotate 0.6s linear infinite;
64
- -moz-animation: cffrtbrotate 0.6s linear infinite;
65
- -ms-animation: cffrtbrotate 0.6s linear infinite;
66
- -o-animation: cffrtbrotate 0.6s linear infinite;
67
- animation: cffrtbrotate 0.6s linear infinite;
68
- border-radius: 100%;
69
- border-top: 1px solid #777;
70
- border-bottom: 1px solid #D1DDE2;
71
- border-left: 1px solid #777;
72
- border-right: 1px solid #D1DDE2;
73
- content: '';
74
- opacity: 0.5;
75
- }
76
- #cffrtb-editor {
77
- margin-top: 2em;
78
- }
79
- .cffrtb-list {
80
- margin: 0;
81
- padding: 0;
82
- /* Ensure there is space to drop a field into an empty fieldset */
83
- }
84
- .cffrtb-list .title {
85
- position: relative;
86
- padding: 1em;
87
- min-height: 1.4em;
88
- background: #D1DDE2;
89
- border: 1px solid #82878c;
90
- margin-bottom: 30px;
91
- overflow: hidden;
92
- cursor: pointer;
93
- cursor: hand;
94
- opacity: 1;
95
- -webkit-transition: opacity 0.3s ease-in-out;
96
- -moz-transition: opacity 0.3s ease-in-out;
97
- -ms-transition: opacity 0.3s ease-in-out;
98
- -o-transition: opacity 0.3s ease-in-out;
99
- transition: opacity 0.3s ease-in-out;
100
- }
101
- .cffrtb-list .title .view .controls .load-spinner {
102
- float: left;
103
- margin: 1em;
104
- }
105
- .cffrtb-list .title .edit {
106
- position: absolute;
107
- overflow: hidden;
108
- background: #fff;
109
- width: 0;
110
- top: 0;
111
- left: 0;
112
- -webkit-transition: width 0.3s ease-in-out;
113
- -moz-transition: width 0.3s ease-in-out;
114
- -ms-transition: width 0.3s ease-in-out;
115
- -o-transition: width 0.3s ease-in-out;
116
- transition: width 0.3s ease-in-out;
117
- }
118
- .cffrtb-list .title .edit input {
119
- box-shadow: none;
120
- margin: 1em;
121
- padding: 0;
122
- font-size: 13px;
123
- line-height: 1.4em;
124
- border: none;
125
- border-bottom: 1px solid #aaa;
126
- }
127
- .cffrtb-list .title .edit .controls {
128
- opacity: 0;
129
- -webkit-transition: opacity 0.6s ease-in-out;
130
- -moz-transition: opacity 0.6s ease-in-out;
131
- -ms-transition: opacity 0.6s ease-in-out;
132
- -o-transition: opacity 0.6s ease-in-out;
133
- transition: opacity 0.6s ease-in-out;
134
- }
135
- .cffrtb-list .title .controls,
136
- .cffrtb-list .title .status {
137
- position: absolute;
138
- top: 0;
139
- right: 0;
140
- display: none;
141
- }
142
- .cffrtb-list .title .controls {
143
- display: block;
144
- }
145
- .cffrtb-list .title .controls a {
146
- display: block;
147
- float: left;
148
- padding: 1em;
149
- margin-left: 1px;
150
- text-decoration: none;
151
- background: #0073aa;
152
- color: #fff;
153
- transition: none;
154
- }
155
- .cffrtb-list .title .controls a:hover,
156
- .cffrtb-list .title .controls a:active,
157
- .cffrtb-list .title .controls a:focus {
158
- background: #00b9eb;
159
- box-shadow: none;
160
- }
161
- .cffrtb-list .title .controls a:active {
162
- background: #fff;
163
- color: #0073aa;
164
- }
165
- .cffrtb-list .title .controls a.save {
166
- background: #fff;
167
- color: #0073aa;
168
- }
169
- .cffrtb-list .title .controls a.save:active,
170
- .cffrtb-list .title .controls a.save:focus {
171
- color: #00b9eb;
172
- }
173
- .cffrtb-list .title .status {
174
- padding: 1em;
175
- }
176
- .cffrtb-list .title .status span {
177
- display: block;
178
- text-decoration: none;
179
- color: #777;
180
- }
181
- .cffrtb-list .title.editing {
182
- border-color: #fff;
183
- border-radius: 4px;
184
- box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
185
- }
186
- .cffrtb-list .title.editing .edit {
187
- width: 100%;
188
- }
189
- .cffrtb-list .title.editing .edit .controls {
190
- opacity: 1;
191
- }
192
- .cffrtb-list .title.saving .controls {
193
- display: none;
194
- }
195
- .cffrtb-list .title.saving .status {
196
- display: block;
197
- }
198
- .cffrtb-list .fieldset > .title {
199
- background: #d54e21;
200
- border: none;
201
- color: #fff;
202
- }
203
- .cffrtb-list .fieldset > .title .view .controls a {
204
- background: #933617;
205
- }
206
- .cffrtb-list .fieldset > .title .view .controls a:hover,
207
- .cffrtb-list .fieldset > .title .view .controls a:focus {
208
- background: #e67f5d;
209
- }
210
- .cffrtb-list .fieldset > .title .view .controls a:active {
211
- background: #fff;
212
- color: #d54e21;
213
- }
214
- .cffrtb-list .cffrtb-list-placeholder {
215
- height: 1.4em;
216
- padding: 1em;
217
- margin-bottom: 30px;
218
- border: 1px dashed #D1DDE2;
219
- background: #fff;
220
- }
221
- .cffrtb-list .fields {
222
- min-height: 3.4em;
223
- }
224
- #cffrtb-editor .add-field {
225
- display: block;
226
- line-height: 1.4em;
227
- text-align: center;
228
- text-decoration: none;
229
- padding: 1em;
230
- background: #fff;
231
- border: 1px dashed #b2c6ce;
232
- opacity: 0.7;
233
- }
234
- #cffrtb-editor .add-field:hover,
235
- #cffrtb-editor .add-field:focus {
236
- color: #fff;
237
- background: #0073aa;
238
- opacity: 1;
239
- border-color: transparent;
240
- }
241
- #cffrtb-editor .add-field:active {
242
- background: #00b9eb;
243
- }
244
- #cffrtb-disabled h3 {
245
- border-bottom: 1px solid #ddd;
246
- padding-bottom: 0.5em;
247
- color: #777;
248
- }
249
- #cffrtb-disabled .field,
250
- #cffrtb-disabled .fieldset {
251
- min-width: 20em;
252
- max-width: 20em;
253
- opacity: 0.7;
254
- display: inline-block;
255
- margin-right: 1em;
256
- }
257
- #cffrtb-disabled .field:hover,
258
- #cffrtb-disabled .fieldset:hover,
259
- #cffrtb-disabled .field:focus,
260
- #cffrtb-disabled .fieldset:focus {
261
- opacity: 1;
262
- }
263
- #cffrtb-disabled .field .title,
264
- #cffrtb-disabled .fieldset .title {
265
- border-color: #aaadb1;
266
- background: #ecedee;
267
- }
268
- #cffrtb-disabled .field .controls a,
269
- #cffrtb-disabled .fieldset .controls a {
270
- background: #82878c;
271
- }
272
- #cffrtb-disabled .field .controls a:hover,
273
- #cffrtb-disabled .fieldset .controls a:hover,
274
- #cffrtb-disabled .field .controls a:active,
275
- #cffrtb-disabled .fieldset .controls a:active,
276
- #cffrtb-disabled .field .controls a:focus,
277
- #cffrtb-disabled .fieldset .controls a:focus {
278
- background: #aaadb1;
279
- }
280
- #cffrtb-disabled .field .controls a:active,
281
- #cffrtb-disabled .fieldset .controls a:active {
282
- color: #82878c;
283
- }
284
- #cffrtb-disabled .fieldset .title {
285
- color: #444;
286
- }
287
- #cffrtb-disabled .fieldset .title .view .controls a {
288
- background: #82878c;
289
- }
290
- #cffrtb-disabled .fieldset .title .view .controls a:hover,
291
- #cffrtb-disabled .fieldset .title .view .controls a:active,
292
- #cffrtb-disabled .fieldset .title .view .controls a:focus {
293
- background: #aaadb1;
294
- }
295
- #cffrtb-disabled .fieldset .title .view .controls a:active {
296
- color: #82878c;
297
- }
298
- #cffrtb-disabled .reset {
299
- opacity: 0;
300
- display: hidden;
301
- -webkit-transition: opacity 0.3s ease-in-out;
302
- -moz-transition: opacity 0.3s ease-in-out;
303
- -ms-transition: opacity 0.3s ease-in-out;
304
- -o-transition: opacity 0.3s ease-in-out;
305
- transition: opacity 0.3s ease-in-out;
306
- }
307
- #cffrtb-disabled .is-visible {
308
- display: block;
309
- opacity: 1;
310
- }
311
- #cffrtb-disabled .learn-more {
312
- line-height: 26px;
313
- margin-left: 1em;
314
- }
315
- #cffrtb-disabled .description {
316
- margin: 1em 0;
317
- max-width: 20em;
318
- }
319
- #cffrtb-disabled .no-disabled-fields {
320
- margin-bottom: 30px;
321
- padding-bottom: 30px;
322
- }
323
- #cffrtb-disabled .learn-more-details {
324
- max-height: 0;
325
- overflow: hidden;
326
- -webkit-transition: max-height 0.5s ease-in-out;
327
- -moz-transition: max-height 0.5s ease-in-out;
328
- -ms-transition: max-height 0.5s ease-in-out;
329
- -o-transition: max-height 0.5s ease-in-out;
330
- transition: max-height 0.5s ease-in-out;
331
- }
332
- #cffrtb-disabled .learn-more-details.is-visible {
333
- max-height: 15em;
334
- }
335
- .rtb-admin-modal {
336
- position: fixed;
337
- top: 0;
338
- left: 0;
339
- width: 100%;
340
- height: 100%;
341
- background: rgba(0, 0, 0, 0.8);
342
- z-index: 3;
343
- overflow-y: auto;
344
- cursor: pointer;
345
- visibility: hidden;
346
- opacity: 0;
347
- -webkit-transition: opacity 0.3s 0, visibility 0 0.3s;
348
- -moz-transition: opacity 0.3s 0, visibility 0 0.3s;
349
- transition: opacity 0.3s 0, visibility 0 0.3s;
350
- }
351
- .rtb-admin-modal.is-visible {
352
- visibility: visible;
353
- opacity: 1;
354
- -webkit-transition: opacity 0.3s 0, visibility 0 0;
355
- -moz-transition: opacity 0.3s 0, visibility 0 0;
356
- transition: opacity 0.3s 0, visibility 0 0;
357
- }
358
- .rtb-admin-modal.is-visible .rtb-container {
359
- -webkit-transform: translateY(0);
360
- -moz-transform: translateY(0);
361
- -ms-transform: translateY(0);
362
- -o-transform: translateY(0);
363
- transform: translateY(0);
364
- }
365
- .rtb-admin-modal .rtb-container {
366
- position: relative;
367
- width: 90%;
368
- max-width: 20em;
369
- padding: 2em;
370
- background: #FFF;
371
- margin: 3em auto 4em;
372
- cursor: auto;
373
- border-radius: 0.25em;
374
- -webkit-transform: translateY(-30px);
375
- -moz-transform: translateY(-30px);
376
- -ms-transform: translateY(-30px);
377
- -o-transform: translateY(-30px);
378
- transform: translateY(-30px);
379
- -webkit-transition-property: -webkit-transform;
380
- -moz-transition-property: -moz-transform;
381
- transition-property: transform;
382
- -webkit-transition-duration: 0.3s;
383
- -moz-transition-duration: 0.3s;
384
- transition-duration: 0.3s;
385
- }
386
- /* Hide the outer scrollbar when the modal is open */
387
- .rtb-hide-body-scroll {
388
- overflow: hidden !important;
389
- }
390
- #cffrtb-field-editor h2 {
391
- margin: 0;
392
- }
393
- #cffrtb-field-editor a {
394
- text-decoration: none;
395
- }
396
- #cffrtb-field-editor label {
397
- color: #777;
398
- font-style: italic;
399
- }
400
- #cffrtb-field-editor .type {
401
- margin: 2em 0 0;
402
- }
403
- #cffrtb-field-editor .type label {
404
- display: block;
405
- }
406
- #cffrtb-field-editor .type .selector {
407
- background: #ddd;
408
- border: 1px solid #ddd;
409
- border-radius: 4px;
410
- }
411
- #cffrtb-field-editor .type ul {
412
- text-align: center;
413
- }
414
- #cffrtb-field-editor .types {
415
- margin: 0;
416
- background: #fff;
417
- border-top-left-radius: 4px;
418
- border-top-right-radius: 4px;
419
- }
420
- #cffrtb-field-editor .types li {
421
- display: inline-block;
422
- margin: 0;
423
- }
424
- #cffrtb-field-editor .types li a {
425
- display: block;
426
- padding: 0.5em 1em;
427
- }
428
- #cffrtb-field-editor .types li a.current {
429
- font-weight: 600;
430
- color: #666;
431
- position: relative;
432
- }
433
- #cffrtb-field-editor .types li a.current:after {
434
- content: ' ';
435
- position: absolute;
436
- left: 50%;
437
- margin-left: -6px;
438
- width: 0;
439
- height: 0;
440
- border-left: 6px solid transparent;
441
- border-right: 6px solid transparent;
442
- border-bottom: 6px solid #ddd;
443
- }
444
- #cffrtb-field-editor .subtypes {
445
- display: none;
446
- margin: 0;
447
- }
448
- #cffrtb-field-editor .subtypes.current {
449
- display: block;
450
- }
451
- #cffrtb-field-editor .subtypes li {
452
- margin: 0;
453
- display: inline-block;
454
- }
455
- #cffrtb-field-editor .subtypes li a {
456
- display: block;
457
- padding: 0.5em 1em;
458
- }
459
- #cffrtb-field-editor .subtypes li a.current {
460
- font-weight: 600;
461
- color: #666;
462
- }
463
- #cffrtb-field-editor .settings {
464
- margin-top: 2em;
465
- }
466
- #cffrtb-field-editor .settings .item {
467
- margin: 0 0 2em;
468
- }
469
- #cffrtb-field-editor .settings label,
470
- #cffrtb-field-editor .settings input {
471
- display: block;
472
- }
473
- #cffrtb-field-editor .settings input {
474
- width: 100%;
475
- }
476
- #cffrtb-field-editor .settings-panel {
477
- height: auto;
478
- overflow: hidden;
479
- max-height: 0;
480
- -webkit-transition: max-height 0.6s ease-in-out;
481
- -moz-transition: max-height 0.6s ease-in-out;
482
- -ms-transition: max-height 0.6s ease-in-out;
483
- -o-transition: max-height 0.6s ease-in-out;
484
- transition: max-height 0.6s ease-in-out;
485
- }
486
- #cffrtb-field-editor .settings-panel.current {
487
- overflow: visible;
488
- max-height: 40em;
489
- }
490
- #cffrtb-field-editor .settings-panel.options .add {
491
- position: relative;
492
- }
493
- #cffrtb-field-editor .settings-panel.options .add a {
494
- position: absolute;
495
- top: 0;
496
- right: 0;
497
- padding: 4px;
498
- }
499
- #cffrtb-field-editor .settings-panel.options .add input {
500
- padding-right: 4em;
501
- }
502
- #cffrtb-field-editor .settings-panel.options .options {
503
- margin: 1em 0 0;
504
- max-height: 325px;
505
- }
506
- #cffrtb-field-editor .settings-panel.options .options.scroll {
507
- overflow-y: scroll;
508
- }
509
- #cffrtb-field-editor .settings-panel.options .options li {
510
- margin: 0.5em 0 0;
511
- padding: 0.25em 0.5em;
512
- height: 1.4em;
513
- background: #eee;
514
- border: 1px solid #ddd;
515
- border-radius: 4px;
516
- cursor: pointer;
517
- cursor: hand;
518
- }
519
- #cffrtb-field-editor .settings-panel.options .options li.cffrtb-editor-options-placeholder {
520
- background: #fff;
521
- border: 1px dashed #aaa;
522
- }
523
- #cffrtb-field-editor .settings-panel.options .options a {
524
- display: inline-block;
525
- color: #a00;
526
- }
527
- #cffrtb-field-editor .settings-panel.options .options a:hover,
528
- #cffrtb-field-editor .settings-panel.options .options a:focus {
529
- color: red;
530
- }
531
- #cffrtb-field-editor .required {
532
- padding: 1em 0;
533
- }
534
- #cffrtb-field-editor .actions {
535
- padding: 1em 0 0;
536
- }
537
- #cffrtb-field-editor .actions .save {
538
- vertical-align: top;
539
- }
540
- #cffrtb-field-editor .actions .save.fieldset {
541
- background: #d54e21;
542
- color: #fff;
543
- box-shadow: inset 0 1px 0 #e67f5d, 0 1px 0 rgba(0, 0, 0, 0.08);
544
- border-color: #933617;
545
- text-shadow: none;
546
- }
547
- #cffrtb-field-editor .actions .status {
548
- display: none;
549
- padding: 4px;
550
- height: 14px;
551
- }
552
- #cffrtb-field-editor .actions.working .status {
553
- display: inline-block;
554
- }
555
- #cffrtb-field-editor > .fieldset .type,
556
- #cffrtb-field-editor > .fieldset .settings-panel,
557
- #cffrtb-field-editor > .fieldset .required {
558
- display: none;
559
- }
560
- #cffrtb-field-editor-option .fieldset.button {
561
- background: #d54e21;
562
- color: #fff;
563
- box-shadow: inset 0 1px 0 #e67f5d, 0 1px 0 rgba(0, 0, 0, 0.08);
564
- border-color: #933617;
565
- margin-top: 2em;
566
- }
567
- #cffrtb-field-editor-option .option p {
568
- margin-bottom: 0;
569
- }
570
- #rtb-error-modal .rtb-error-msg {
571
- margin-bottom: 1em;
572
- }
573
- @media (min-width: 767px) {
574
- .cffrtb-lft {
575
- width: 30em;
576
- float: left;
577
- }
578
- .cffrtb-rgt {
579
- margin-left: 33em;
580
- max-width: 100%;
581
- }
582
  }
1
+ @keyframes cffrtbrotate {
2
+ 0% {
3
+ transform: rotateZ(-360deg);
4
+ -webkit-transform: rotateZ(-360deg);
5
+ -moz-transform: rotateZ(-360deg);
6
+ -o-transform: rotateZ(-360deg);
7
+ }
8
+ 100% {
9
+ transform: rotateZ(0deg);
10
+ -webkit-transform: rotateZ(0deg);
11
+ -moz-transform: rotateZ(0deg);
12
+ -o-transform: rotateZ(0deg);
13
+ }
14
+ }
15
+ @-webkit-keyframes cffrtbrotate {
16
+ 0% {
17
+ transform: rotateZ(-360deg);
18
+ -webkit-transform: rotateZ(-360deg);
19
+ -moz-transform: rotateZ(-360deg);
20
+ -o-transform: rotateZ(-360deg);
21
+ }
22
+ 100% {
23
+ transform: rotateZ(0deg);
24
+ -webkit-transform: rotateZ(0deg);
25
+ -moz-transform: rotateZ(0deg);
26
+ -o-transform: rotateZ(0deg);
27
+ }
28
+ }
29
+ @-moz-keyframes cffrtbrotate {
30
+ 0% {
31
+ transform: rotateZ(-360deg);
32
+ -webkit-transform: rotateZ(-360deg);
33
+ -moz-transform: rotateZ(-360deg);
34
+ -o-transform: rotateZ(-360deg);
35
+ }
36
+ 100% {
37
+ transform: rotateZ(0deg);
38
+ -webkit-transform: rotateZ(0deg);
39
+ -moz-transform: rotateZ(0deg);
40
+ -o-transform: rotateZ(0deg);
41
+ }
42
+ }
43
+ @-o-keyframes cffrtbrotate {
44
+ 0% {
45
+ transform: rotateZ(-360deg);
46
+ -webkit-transform: rotateZ(-360deg);
47
+ -moz-transform: rotateZ(-360deg);
48
+ -o-transform: rotateZ(-360deg);
49
+ }
50
+ 100% {
51
+ transform: rotateZ(0deg);
52
+ -webkit-transform: rotateZ(0deg);
53
+ -moz-transform: rotateZ(0deg);
54
+ -o-transform: rotateZ(0deg);
55
+ }
56
+ }
57
+ #cffrtb-editor .load-spinner:after,
58
+ #cffrtb-field-editor-form .load-spinner:after {
59
+ display: block;
60
+ position: relative;
61
+ width: 20px;
62
+ height: 20px;
63
+ -webkit-animation: cffrtbrotate 0.6s linear infinite;
64
+ -moz-animation: cffrtbrotate 0.6s linear infinite;
65
+ -ms-animation: cffrtbrotate 0.6s linear infinite;
66
+ -o-animation: cffrtbrotate 0.6s linear infinite;
67
+ animation: cffrtbrotate 0.6s linear infinite;
68
+ border-radius: 100%;
69
+ border-top: 1px solid #777;
70
+ border-bottom: 1px solid #D1DDE2;
71
+ border-left: 1px solid #777;
72
+ border-right: 1px solid #D1DDE2;
73
+ content: '';
74
+ opacity: 0.5;
75
+ }
76
+ #cffrtb-editor {
77
+ margin-top: 2em;
78
+ }
79
+ .cffrtb-list {
80
+ margin: 0;
81
+ padding: 0;
82
+ /* Ensure there is space to drop a field into an empty fieldset */
83
+ }
84
+ .cffrtb-list .title {
85
+ position: relative;
86
+ padding: 1em;
87
+ min-height: 1.4em;
88
+ background: #D1DDE2;
89
+ border: 1px solid #82878c;
90
+ margin-bottom: 30px;
91
+ overflow: hidden;
92
+ cursor: pointer;
93
+ cursor: hand;
94
+ opacity: 1;
95
+ -webkit-transition: opacity 0.3s ease-in-out;
96
+ -moz-transition: opacity 0.3s ease-in-out;
97
+ -ms-transition: opacity 0.3s ease-in-out;
98
+ -o-transition: opacity 0.3s ease-in-out;
99
+ transition: opacity 0.3s ease-in-out;
100
+ }
101
+ .cffrtb-list .title .view .controls .load-spinner {
102
+ float: left;
103
+ margin: 1em;
104
+ }
105
+ .cffrtb-list .title .edit {
106
+ position: absolute;
107
+ overflow: hidden;
108
+ background: #fff;
109
+ width: 0;
110
+ top: 0;
111
+ left: 0;
112
+ -webkit-transition: width 0.3s ease-in-out;
113
+ -moz-transition: width 0.3s ease-in-out;
114
+ -ms-transition: width 0.3s ease-in-out;
115
+ -o-transition: width 0.3s ease-in-out;
116
+ transition: width 0.3s ease-in-out;
117
+ }
118
+ .cffrtb-list .title .edit input {
119
+ box-shadow: none;
120
+ margin: 1em;
121
+ padding: 0;
122
+ font-size: 13px;
123
+ line-height: 1.4em;
124
+ border: none;
125
+ border-bottom: 1px solid #aaa;
126
+ }
127
+ .cffrtb-list .title .edit .controls {
128
+ opacity: 0;
129
+ -webkit-transition: opacity 0.6s ease-in-out;
130
+ -moz-transition: opacity 0.6s ease-in-out;
131
+ -ms-transition: opacity 0.6s ease-in-out;
132
+ -o-transition: opacity 0.6s ease-in-out;
133
+ transition: opacity 0.6s ease-in-out;
134
+ }
135
+ .cffrtb-list .title .controls,
136
+ .cffrtb-list .title .status {
137
+ position: absolute;
138
+ top: 0;
139
+ right: 0;
140
+ display: none;
141
+ }
142
+ .cffrtb-list .title .controls {
143
+ display: block;
144
+ }
145
+ .cffrtb-list .title .controls a {
146
+ display: block;
147
+ float: left;
148
+ padding: 1em;
149
+ margin-left: 1px;
150
+ text-decoration: none;
151
+ background: #0073aa;
152
+ color: #fff;
153
+ transition: none;
154
+ }
155
+ .cffrtb-list .title .controls a:hover,
156
+ .cffrtb-list .title .controls a:active,
157
+ .cffrtb-list .title .controls a:focus {
158
+ background: #00b9eb;
159
+ box-shadow: none;
160
+ }
161
+ .cffrtb-list .title .controls a:active {
162
+ background: #fff;
163
+ color: #0073aa;
164
+ }
165
+ .cffrtb-list .title .controls a.save {
166
+ background: #fff;
167
+ color: #0073aa;
168
+ }
169
+ .cffrtb-list .title .controls a.save:active,
170
+ .cffrtb-list .title .controls a.save:focus {
171
+ color: #00b9eb;
172
+ }
173
+ .cffrtb-list .title .status {
174
+ padding: 1em;
175
+ }
176
+ .cffrtb-list .title .status span {
177
+ display: block;
178
+ text-decoration: none;
179
+ color: #777;
180
+ }
181
+ .cffrtb-list .title.editing {
182
+ border-color: #fff;
183
+ border-radius: 4px;
184
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
185
+ }
186
+ .cffrtb-list .title.editing .edit {
187
+ width: 100%;
188
+ }
189
+ .cffrtb-list .title.editing .edit .controls {
190
+ opacity: 1;
191
+ }
192
+ .cffrtb-list .title.saving .controls {
193
+ display: none;
194
+ }
195
+ .cffrtb-list .title.saving .status {
196
+ display: block;
197
+ }
198
+ .cffrtb-list .fieldset > .title {
199
+ background: #d54e21;
200
+ border: none;
201
+ color: #fff;
202
+ }
203
+ .cffrtb-list .fieldset > .title .view .controls a {
204
+ background: #933617;
205
+ }
206
+ .cffrtb-list .fieldset > .title .view .controls a:hover,
207
+ .cffrtb-list .fieldset > .title .view .controls a:focus {
208
+ background: #e67f5d;
209
+ }
210
+ .cffrtb-list .fieldset > .title .view .controls a:active {
211
+ background: #fff;
212
+ color: #d54e21;
213
+ }
214
+ .cffrtb-list .cffrtb-list-placeholder {
215
+ height: 1.4em;
216
+ padding: 1em;
217
+ margin-bottom: 30px;
218
+ border: 1px dashed #D1DDE2;
219
+ background: #fff;
220
+ }
221
+ .cffrtb-list .fields {
222
+ min-height: 3.4em;
223
+ }
224
+ #cffrtb-editor .add-field {
225
+ display: block;
226
+ line-height: 1.4em;
227
+ text-align: center;
228
+ text-decoration: none;
229
+ padding: 1em;
230
+ background: #fff;
231
+ border: 1px dashed #b2c6ce;
232
+ opacity: 0.7;
233
+ }
234
+ #cffrtb-editor .add-field:hover,
235
+ #cffrtb-editor .add-field:focus {
236
+ color: #fff;
237
+ background: #0073aa;
238
+ opacity: 1;
239
+ border-color: transparent;
240
+ }
241
+ #cffrtb-editor .add-field:active {
242
+ background: #00b9eb;
243
+ }
244
+ #cffrtb-disabled h3 {
245
+ border-bottom: 1px solid #ddd;
246
+ padding-bottom: 0.5em;
247
+ color: #777;
248
+ }
249
+ #cffrtb-disabled .field,
250
+ #cffrtb-disabled .fieldset {
251
+ min-width: 20em;
252
+ max-width: 20em;
253
+ opacity: 0.7;
254
+ display: inline-block;
255
+ margin-right: 1em;
256
+ }
257
+ #cffrtb-disabled .field:hover,
258
+ #cffrtb-disabled .fieldset:hover,
259
+ #cffrtb-disabled .field:focus,
260
+ #cffrtb-disabled .fieldset:focus {
261
+ opacity: 1;
262
+ }
263
+ #cffrtb-disabled .field .title,
264
+ #cffrtb-disabled .fieldset .title {
265
+ border-color: #aaadb1;
266
+ background: #ecedee;
267
+ }
268
+ #cffrtb-disabled .field .controls a,
269
+ #cffrtb-disabled .fieldset .controls a {
270
+ background: #82878c;
271
+ }
272
+ #cffrtb-disabled .field .controls a:hover,
273
+ #cffrtb-disabled .fieldset .controls a:hover,
274
+ #cffrtb-disabled .field .controls a:active,
275
+ #cffrtb-disabled .fieldset .controls a:active,
276
+ #cffrtb-disabled .field .controls a:focus,
277
+ #cffrtb-disabled .fieldset .controls a:focus {
278
+ background: #aaadb1;
279
+ }
280
+ #cffrtb-disabled .field .controls a:active,
281
+ #cffrtb-disabled .fieldset .controls a:active {
282
+ color: #82878c;
283
+ }
284
+ #cffrtb-disabled .fieldset .title {
285
+ color: #444;
286
+ }
287
+ #cffrtb-disabled .fieldset .title .view .controls a {
288
+ background: #82878c;
289
+ }
290
+ #cffrtb-disabled .fieldset .title .view .controls a:hover,
291
+ #cffrtb-disabled .fieldset .title .view .controls a:active,
292
+ #cffrtb-disabled .fieldset .title .view .controls a:focus {
293
+ background: #aaadb1;
294
+ }
295
+ #cffrtb-disabled .fieldset .title .view .controls a:active {
296
+ color: #82878c;
297
+ }
298
+ #cffrtb-disabled .reset {
299
+ opacity: 0;
300
+ display: hidden;
301
+ -webkit-transition: opacity 0.3s ease-in-out;
302
+ -moz-transition: opacity 0.3s ease-in-out;
303
+ -ms-transition: opacity 0.3s ease-in-out;
304
+ -o-transition: opacity 0.3s ease-in-out;
305
+ transition: opacity 0.3s ease-in-out;
306
+ }
307
+ #cffrtb-disabled .is-visible {
308
+ display: block;
309
+ opacity: 1;
310
+ }
311
+ #cffrtb-disabled .learn-more {
312
+ line-height: 26px;
313
+ margin-left: 1em;
314
+ }
315
+ #cffrtb-disabled .description {
316
+ margin: 1em 0;
317
+ max-width: 20em;
318
+ }
319
+ #cffrtb-disabled .no-disabled-fields {
320
+ margin-bottom: 30px;
321
+ padding-bottom: 30px;
322
+ }
323
+ #cffrtb-disabled .learn-more-details {
324
+ max-height: 0;
325
+ overflow: hidden;
326
+ -webkit-transition: max-height 0.5s ease-in-out;
327
+ -moz-transition: max-height 0.5s ease-in-out;
328
+ -ms-transition: max-height 0.5s ease-in-out;
329
+ -o-transition: max-height 0.5s ease-in-out;
330
+ transition: max-height 0.5s ease-in-out;
331
+ }
332
+ #cffrtb-disabled .learn-more-details.is-visible {
333
+ max-height: 15em;
334
+ }
335
+ .rtb-admin-modal {
336
+ position: fixed;
337
+ top: 0;
338
+ left: 0;
339
+ width: 100%;
340
+ height: 100%;
341
+ background: rgba(0, 0, 0, 0.8);
342
+ z-index: 3;
343
+ overflow-y: auto;
344
+ cursor: pointer;
345
+ visibility: hidden;
346
+ opacity: 0;
347
+ -webkit-transition: opacity 0.3s 0, visibility 0 0.3s;
348
+ -moz-transition: opacity 0.3s 0, visibility 0 0.3s;
349
+ transition: opacity 0.3s 0, visibility 0 0.3s;
350
+ }
351
+ .rtb-admin-modal.is-visible {
352
+ visibility: visible;
353
+ opacity: 1;
354
+ -webkit-transition: opacity 0.3s 0, visibility 0 0;
355
+ -moz-transition: opacity 0.3s 0, visibility 0 0;
356
+ transition: opacity 0.3s 0, visibility 0 0;
357
+ }
358
+ .rtb-admin-modal.is-visible .rtb-container {
359
+ -webkit-transform: translateY(0);
360
+ -moz-transform: translateY(0);
361
+ -ms-transform: translateY(0);
362
+ -o-transform: translateY(0);
363
+ transform: translateY(0);
364
+ }
365
+ .rtb-admin-modal .rtb-container {
366
+ position: relative;
367
+ width: 90%;
368
+ max-width: 20em;
369
+ padding: 2em;
370
+ background: #FFF;
371
+ margin: 3em auto 4em;
372
+ cursor: auto;
373
+ border-radius: 0.25em;
374
+ -webkit-transform: translateY(-30px);
375
+ -moz-transform: translateY(-30px);
376
+ -ms-transform: translateY(-30px);
377
+ -o-transform: translateY(-30px);
378
+ transform: translateY(-30px);
379
+ -webkit-transition-property: -webkit-transform;
380
+ -moz-transition-property: -moz-transform;
381
+ transition-property: transform;
382
+ -webkit-transition-duration: 0.3s;
383
+ -moz-transition-duration: 0.3s;
384
+ transition-duration: 0.3s;
385
+ }
386
+ /* Hide the outer scrollbar when the modal is open */
387
+ .rtb-hide-body-scroll {
388
+ overflow: hidden !important;
389
+ }
390
+ #cffrtb-field-editor h2 {
391
+ margin: 0;
392
+ }
393
+ #cffrtb-field-editor a {
394
+ text-decoration: none;
395
+ }
396
+ #cffrtb-field-editor label {
397
+ color: #777;
398
+ font-style: italic;
399
+ }
400
+ #cffrtb-field-editor .type {
401
+ margin: 2em 0 0;
402
+ }
403
+ #cffrtb-field-editor .type label {
404
+ display: block;
405
+ }
406
+ #cffrtb-field-editor .type .selector {
407
+ background: #ddd;
408
+ border: 1px solid #ddd;
409
+ border-radius: 4px;
410
+ }
411
+ #cffrtb-field-editor .type ul {
412
+ text-align: center;
413
+ }
414
+ #cffrtb-field-editor .types {
415
+ margin: 0;
416
+ background: #fff;
417
+ border-top-left-radius: 4px;
418
+ border-top-right-radius: 4px;
419
+ }
420
+ #cffrtb-field-editor .types li {
421
+ display: inline-block;
422
+ margin: 0;
423
+ }
424
+ #cffrtb-field-editor .types li a {
425
+ display: block;
426
+ padding: 0.5em 1em;
427
+ }
428
+ #cffrtb-field-editor .types li a.current {
429
+ font-weight: 600;
430
+ color: #666;
431
+ position: relative;
432
+ }
433
+ #cffrtb-field-editor .types li a.current:after {
434
+ content: ' ';
435
+ position: absolute;
436
+ left: 50%;
437
+ margin-left: -6px;
438
+ width: 0;
439
+ height: 0;
440
+ border-left: 6px solid transparent;
441
+ border-right: 6px solid transparent;
442
+ border-bottom: 6px solid #ddd;
443
+ }
444
+ #cffrtb-field-editor .subtypes {
445
+ display: none;
446
+ margin: 0;
447
+ }
448
+ #cffrtb-field-editor .subtypes.current {
449
+ display: block;
450
+ }
451
+ #cffrtb-field-editor .subtypes li {
452
+ margin: 0;
453
+ display: inline-block;
454
+ }
455
+ #cffrtb-field-editor .subtypes li a {
456
+ display: block;
457
+ padding: 0.5em 1em;
458
+ }
459
+ #cffrtb-field-editor .subtypes li a.current {
460
+ font-weight: 600;
461
+ color: #666;
462
+ }
463
+ #cffrtb-field-editor .settings {
464
+ margin-top: 2em;
465
+ }
466
+ #cffrtb-field-editor .settings .item {
467
+ margin: 0 0 2em;
468
+ }
469
+ #cffrtb-field-editor .settings label,
470
+ #cffrtb-field-editor .settings input {
471
+ display: block;
472
+ }
473
+ #cffrtb-field-editor .settings input {
474
+ width: 100%;
475
+ }
476
+ #cffrtb-field-editor .settings-panel {
477
+ height: auto;
478
+ overflow: hidden;
479
+ max-height: 0;
480
+ -webkit-transition: max-height 0.6s ease-in-out;
481
+ -moz-transition: max-height 0.6s ease-in-out;
482
+ -ms-transition: max-height 0.6s ease-in-out;
483
+ -o-transition: max-height 0.6s ease-in-out;
484
+ transition: max-height 0.6s ease-in-out;
485
+ }
486
+ #cffrtb-field-editor .settings-panel.current {
487
+ overflow: visible;
488
+ max-height: 40em;
489
+ }
490
+ #cffrtb-field-editor .settings-panel.options .add {
491
+ position: relative;
492
+ }
493
+ #cffrtb-field-editor .settings-panel.options .add a {
494
+ position: absolute;
495
+ top: 0;
496
+ right: 0;
497
+ padding: 4px;
498
+ }
499
+ #cffrtb-field-editor .settings-panel.options .add input {
500
+ padding-right: 4em;
501
+ }
502
+ #cffrtb-field-editor .settings-panel.options .options {
503
+ margin: 1em 0 0;
504
+ max-height: 325px;
505
+ }
506
+ #cffrtb-field-editor .settings-panel.options .options.scroll {
507
+ overflow-y: scroll;
508
+ }
509
+ #cffrtb-field-editor .settings-panel.options .options li {
510
+ margin: 0.5em 0 0;
511
+ padding: 0.25em 0.5em;
512
+ height: 1.4em;
513
+ background: #eee;
514
+ border: 1px solid #ddd;
515
+ border-radius: 4px;
516
+ cursor: pointer;
517
+ cursor: hand;
518
+ }
519
+ #cffrtb-field-editor .settings-panel.options .options li.cffrtb-editor-options-placeholder {
520
+ background: #fff;
521
+ border: 1px dashed #aaa;
522
+ }
523
+ #cffrtb-field-editor .settings-panel.options .options a {
524
+ display: inline-block;
525
+ color: #a00;
526
+ }
527
+ #cffrtb-field-editor .settings-panel.options .options a:hover,
528
+ #cffrtb-field-editor .settings-panel.options .options a:focus {
529
+ color: red;
530
+ }
531
+ #cffrtb-field-editor .required {
532
+ padding: 1em 0;
533
+ }
534
+ #cffrtb-field-editor .actions {
535
+ padding: 1em 0 0;
536
+ }
537
+ #cffrtb-field-editor .actions .save {
538
+ vertical-align: top;
539
+ }
540
+ #cffrtb-field-editor .actions .save.fieldset {
541
+ background: #d54e21;
542
+ color: #fff;
543
+ box-shadow: inset 0 1px 0 #e67f5d, 0 1px 0 rgba(0, 0, 0, 0.08);
544
+ border-color: #933617;
545
+ text-shadow: none;
546
+ }
547
+ #cffrtb-field-editor .actions .status {
548
+ display: none;
549
+ padding: 4px;
550
+ height: 14px;
551
+ }
552
+ #cffrtb-field-editor .actions.working .status {
553
+ display: inline-block;
554
+ }
555
+ #cffrtb-field-editor > .fieldset .type,
556
+ #cffrtb-field-editor > .fieldset .settings-panel,
557
+ #cffrtb-field-editor > .fieldset .required {
558
+ display: none;
559
+ }
560
+ #cffrtb-field-editor-option .fieldset.button {
561
+ background: #d54e21;
562
+ color: #fff;
563
+ box-shadow: inset 0 1px 0 #e67f5d, 0 1px 0 rgba(0, 0, 0, 0.08);
564
+ border-color: #933617;
565
+ margin-top: 2em;
566
+ }
567
+ #cffrtb-field-editor-option .option p {
568
+ margin-bottom: 0;
569
+ }
570
+ #rtb-error-modal .rtb-error-msg {
571
+ margin-bottom: 1em;
572
+ }
573
+ @media (min-width: 767px) {
574
+ .cffrtb-lft {
575
+ width: 30em;
576
+ float: left;
577
+ }
578
+ .cffrtb-rgt {
579
+ margin-left: 33em;
580
+ max-width: 100%;
581
+ }
582
  }
assets/css/plugin-deactivation.css CHANGED
@@ -1,68 +1,68 @@
1
- .rtb-deactivate-survey-modal {
2
- display: none;
3
- table-layout: fixed;
4
- position: fixed;
5
- z-index: 9999;
6
- width: 100%;
7
- height: 100%;
8
- text-align: center;
9
- font-size: 14px;
10
- top: 0;
11
- left: 0;
12
- background: rgba(0,0,0,0.8);
13
- }
14
- .rtb-deactivate-survey-wrap {
15
- display: table-cell;
16
- vertical-align: middle;
17
- }
18
- .rtb-deactivate-survey {
19
- background-color: #fff;
20
- max-width: 550px;
21
- margin: 0 auto;
22
- padding: 30px;
23
- text-align: left;
24
- }
25
- .rtb-deactivate-survey .error {
26
- display: block;
27
- color: red;
28
- margin: 0 0 10px 0;
29
- }
30
- .rtb-deactivate-survey-title {
31
- display: block;
32
- font-size: 18px;
33
- font-weight: 700;
34
- text-transform: uppercase;
35
- border-bottom: 1px solid #ddd;
36
- padding: 0 0 18px 0;
37
- margin: 0 0 18px 0;
38
- }
39
- .rtb-deactivate-survey-title span {
40
- color: #999;
41
- margin-right: 10px;
42
- }
43
- .rtb-deactivate-survey-desc {
44
- display: block;
45
- font-weight: 600;
46
- margin: 0 0 18px 0;
47
- }
48
- .rtb-deactivate-survey-option {
49
- margin: 0 0 10px 0;
50
- }
51
- .rtb-deactivate-survey-option-input {
52
- margin-right: 10px !important;
53
- }
54
- .rtb-deactivate-survey-option-details {
55
- display: none;
56
- width: 90%;
57
- margin: 10px 0 0 30px;
58
- }
59
- .rtb-deactivate-survey-footer {
60
- margin-top: 18px;
61
- }
62
- .rtb-deactivate-survey-deactivate {
63
- float: right;
64
- font-size: 13px;
65
- color: #ccc;
66
- text-decoration: none;
67
- padding-top: 7px;
68
  }
1
+ .rtb-deactivate-survey-modal {
2
+ display: none;
3
+ table-layout: fixed;
4
+ position: fixed;
5
+ z-index: 9999;
6
+ width: 100%;
7
+ height: 100%;
8
+ text-align: center;
9
+ font-size: 14px;
10
+ top: 0;
11
+ left: 0;
12
+ background: rgba(0,0,0,0.8);
13
+ }
14
+ .rtb-deactivate-survey-wrap {
15
+ display: table-cell;
16
+ vertical-align: middle;
17
+ }
18
+ .rtb-deactivate-survey {
19
+ background-color: #fff;
20
+ max-width: 550px;
21
+ margin: 0 auto;
22
+ padding: 30px;
23
+ text-align: left;
24
+ }
25
+ .rtb-deactivate-survey .error {
26
+ display: block;
27
+ color: red;
28
+ margin: 0 0 10px 0;
29
+ }
30
+ .rtb-deactivate-survey-title {
31
+ display: block;
32
+ font-size: 18px;
33
+ font-weight: 700;
34
+ text-transform: uppercase;
35
+ border-bottom: 1px solid #ddd;
36
+ padding: 0 0 18px 0;
37
+ margin: 0 0 18px 0;
38
+ }
39
+ .rtb-deactivate-survey-title span {
40
+ color: #999;
41
+ margin-right: 10px;
42
+ }
43
+ .rtb-deactivate-survey-desc {
44
+ display: block;
45
+ font-weight: 600;
46
+ margin: 0 0 18px 0;
47
+ }
48
+ .rtb-deactivate-survey-option {
49
+ margin: 0 0 10px 0;
50
+ }
51
+ .rtb-deactivate-survey-option-input {
52
+ margin-right: 10px !important;
53
+ }
54
+ .rtb-deactivate-survey-option-details {
55
+ display: none;
56
+ width: 90%;
57
+ margin: 10px 0 0 30px;
58
+ }
59
+ .rtb-deactivate-survey-footer {
60
+ margin-top: 18px;
61
+ }
62
+ .rtb-deactivate-survey-deactivate {
63
+ float: right;
64
+ font-size: 13px;
65
+ color: #ccc;
66
+ text-decoration: none;
67
+ padding-top: 7px;
68
  }
assets/js/admin-rtb-welcome-screen.js CHANGED
@@ -1,71 +1,71 @@
1
- jQuery(document).ready(function() {
2
- jQuery('.rtb-welcome-screen-box h2').on('click', function() {
3
- var section = jQuery(this).parent().data('screen');
4
- rtb_toggle_section(section);
5
- });
6
-
7
- jQuery('.rtb-welcome-screen-next-button').on('click', function() {
8
- var section = jQuery(this).data('nextaction');
9
- rtb_toggle_section(section);
10
- });
11
-
12
- jQuery('.rtb-welcome-screen-previous-button').on('click', function() {
13
- var section = jQuery(this).data('previousaction');
14
- rtb_toggle_section(section);
15
- });
16
-
17
- jQuery('.rtb-welcome-screen-add-reservations-page-button').on('click', function() {
18
- var reservations_page_title = jQuery('.rtb-welcome-screen-add-reservations-page-name input').val();
19
-
20
- var data = 'reservations_page_title=' + reservations_page_title + '&action=rtb_welcome_add_menu_page';
21
- jQuery.post(ajaxurl, data, function(response) {});
22
-
23
- var section = jQuery(this).data('nextaction');
24
- rtb_toggle_section(section);
25
- });
26
-
27
- jQuery('.rtb-welcome-screen-save-schedule-open-button').on('click', function() {
28
-
29
- var schedule_open = [];
30
-
31
- jQuery('.sap-scheduler-rule').each(function() {
32
- var weekdays ={};
33
-
34
- jQuery(this).find('.sap-scheduler-weekdays input[type="checkbox"]').each(function() {
35
- if ( jQuery(this).is(':checked') ) { weekdays[jQuery(this).data('day')] = "1" ; }
36
- });
37
-
38
- var start = jQuery(this).find('.sap-scheduler-time-input .start input').first().val();
39
- var end = jQuery(this).find('.sap-scheduler-time-input .end input').first().val();
40
-
41
- schedule_open.push({'weekdays': weekdays, 'time': {'start': start, 'end': end }});
42
- });
43
-
44
- var data = 'schedule_open=' + JSON.stringify(schedule_open) + '&action=rtb_welcome_set_schedule';
45
- jQuery.post(ajaxurl, data, function(response) {
46
-
47
- jQuery( '.rtb-welcome-screen-save-schedule-open-button' ).after( '<div class="rtb-save-message"><div class="rtb-save-message-inside">Schedule has been saved.</div></div>' );
48
- jQuery( '.rtb-save-message' ).delay( 2000 ).fadeOut( 400, function() { jQuery( '.rtb-save-message' ).remove(); } );
49
- });
50
- });
51
-
52
- jQuery('.rtb-welcome-screen-save-options-button').on('click', function() {
53
- var party_size_min = jQuery('select[name="min-party-size"]').val();
54
- var party_size = jQuery('select[name="party-size"]').val();
55
- var early_bookings = jQuery('select[name="early-bookings"]').val();
56
- var late_bookings = jQuery('select[name="late-bookings"]').val();
57
- var time_interval = jQuery('select[name="time-interval"]').val();
58
-
59
- var data = 'party_size_min=' + party_size_min + '&party_size=' + party_size + '&early_bookings=' + early_bookings + '&late_bookings=' + late_bookings + '&time_interval=' + time_interval + '&action=rtb_welcome_set_options';
60
- jQuery.post(ajaxurl, data, function(response) {
61
-
62
- jQuery( '.rtb-welcome-screen-save-options-button' ).after( '<div class="rtb-save-message"><div class="rtb-save-message-inside">Options have been saved.</div></div>' );
63
- jQuery( '.rtb-save-message' ).delay( 2000 ).fadeOut( 400, function() { jQuery( '.rtb-save-message' ).remove(); } );
64
- });
65
- });
66
- });
67
-
68
- function rtb_toggle_section(page) {
69
- jQuery('.rtb-welcome-screen-box').removeClass('rtb-welcome-screen-open');
70
- jQuery('.rtb-welcome-screen-' + page).addClass('rtb-welcome-screen-open');
71
  }
1
+ jQuery(document).ready(function() {
2
+ jQuery('.rtb-welcome-screen-box h2').on('click', function() {
3
+ var section = jQuery(this).parent().data('screen');
4
+ rtb_toggle_section(section);
5
+ });
6
+
7
+ jQuery('.rtb-welcome-screen-next-button').on('click', function() {
8
+ var section = jQuery(this).data('nextaction');
9
+ rtb_toggle_section(section);
10
+ });
11
+
12
+ jQuery('.rtb-welcome-screen-previous-button').on('click', function() {
13
+ var section = jQuery(this).data('previousaction');
14
+ rtb_toggle_section(section);
15
+ });
16
+
17
+ jQuery('.rtb-welcome-screen-add-reservations-page-button').on('click', function() {
18
+ var reservations_page_title = jQuery('.rtb-welcome-screen-add-reservations-page-name input').val();
19
+
20
+ var data = 'reservations_page_title=' + reservations_page_title + '&action=rtb_welcome_add_menu_page';
21
+ jQuery.post(ajaxurl, data, function(response) {});
22
+
23
+ var section = jQuery(this).data('nextaction');
24
+ rtb_toggle_section(section);
25
+ });
26
+
27
+ jQuery('.rtb-welcome-screen-save-schedule-open-button').on('click', function() {
28
+
29
+ var schedule_open = [];
30
+
31
+ jQuery('.sap-scheduler-rule').each(function() {
32
+ var weekdays ={};
33
+
34
+ jQuery(this).find('.sap-scheduler-weekdays input[type="checkbox"]').each(function() {
35
+ if ( jQuery(this).is(':checked') ) { weekdays[jQuery(this).data('day')] = "1" ; }
36
+ });
37
+
38
+ var start = jQuery(this).find('.sap-scheduler-time-input .start input').first().val();
39
+ var end = jQuery(this).find('.sap-scheduler-time-input .end input').first().val();
40
+
41
+ schedule_open.push({'weekdays': weekdays, 'time': {'start': start, 'end': end }});
42
+ });
43
+
44
+ var data = 'schedule_open=' + JSON.stringify(schedule_open) + '&action=rtb_welcome_set_schedule';
45
+ jQuery.post(ajaxurl, data, function(response) {
46
+
47
+ jQuery( '.rtb-welcome-screen-save-schedule-open-button' ).after( '<div class="rtb-save-message"><div class="rtb-save-message-inside">Schedule has been saved.</div></div>' );
48
+ jQuery( '.rtb-save-message' ).delay( 2000 ).fadeOut( 400, function() { jQuery( '.rtb-save-message' ).remove(); } );
49
+ });
50
+ });
51
+
52
+ jQuery('.rtb-welcome-screen-save-options-button').on('click', function() {
53
+ var party_size_min = jQuery('select[name="min-party-size"]').val();
54
+ var party_size = jQuery('select[name="party-size"]').val();
55
+ var early_bookings = jQuery('select[name="early-bookings"]').val();
56
+ var late_bookings = jQuery('select[name="late-bookings"]').val();
57
+ var time_interval = jQuery('select[name="time-interval"]').val();
58
+
59
+ var data = 'party_size_min=' + party_size_min + '&party_size=' + party_size + '&early_bookings=' + early_bookings + '&late_bookings=' + late_bookings + '&time_interval=' + time_interval + '&action=rtb_welcome_set_options';
60
+ jQuery.post(ajaxurl, data, function(response) {
61
+
62
+ jQuery( '.rtb-welcome-screen-save-options-button' ).after( '<div class="rtb-save-message"><div class="rtb-save-message-inside">Options have been saved.</div></div>' );
63
+ jQuery( '.rtb-save-message' ).delay( 2000 ).fadeOut( 400, function() { jQuery( '.rtb-save-message' ).remove(); } );
64
+ });
65
+ });
66
+ });
67
+
68
+ function rtb_toggle_section(page) {
69
+ jQuery('.rtb-welcome-screen-box').removeClass('rtb-welcome-screen-open');
70
+ jQuery('.rtb-welcome-screen-' + page).addClass('rtb-welcome-screen-open');
71
  }
assets/js/admin-settings.js CHANGED
@@ -1,73 +1,73 @@
1
- /* Hiding empty settings sections */
2
- jQuery( document ).ready( function() {
3
-
4
- jQuery( 'select[name="rtb-settings[location-select]"]' ).change(function() {
5
-
6
- manageHidingSections();
7
- });
8
-
9
- manageHidingSections();
10
- });
11
-
12
- function manageHidingSections() {
13
-
14
- jQuery( '.sap-settings-page .form-table tr.sap-hidden' ).each(function(){
15
-
16
- var associatedEmptyTable = jQuery( this ).parent().parent();
17
-
18
- associatedEmptyTable.hide();
19
- associatedEmptyTable.prev( 'h2' ).hide();
20
- });
21
-
22
- jQuery( '.sap-settings-page .form-table tr:not(.sap-hidden)' ).each(function(){
23
-
24
- var associatedNonEmptyTable = jQuery( this ).parent().parent();
25
-
26
- associatedNonEmptyTable.show();
27
- associatedNonEmptyTable.prev( 'h2' ).show();
28
- });
29
- }
30
-
31
-
32
- jQuery(document).ready(function() {
33
- jQuery('.rtb-spectrum').spectrum({
34
- showInput: true,
35
- showInitial: true,
36
- preferredFormat: "hex",
37
- allowEmpty: true
38
- });
39
-
40
- jQuery('.rtb-spectrum').css('display', 'inline');
41
-
42
- jQuery('.rtb-spectrum').on('change', function() {
43
- if (jQuery(this).val() != "") {
44
- jQuery(this).css('background', jQuery(this).val());
45
- var rgb = RTB_hexToRgb(jQuery(this).val());
46
- var Brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
47
- if (Brightness < 100) {jQuery(this).css('color', '#ffffff');}
48
- else {jQuery(this).css('color', '#000000');}
49
- }
50
- else {
51
- jQuery(this).css('background', 'none');
52
- }
53
- });
54
-
55
- jQuery('.rtb-spectrum').each(function() {
56
- if (jQuery(this).val() != "") {
57
- jQuery(this).css('background', jQuery(this).val());
58
- var rgb = RTB_hexToRgb(jQuery(this).val());
59
- var Brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
60
- if (Brightness < 100) {jQuery(this).css('color', '#ffffff');}
61
- else {jQuery(this).css('color', '#000000');}
62
- }
63
- });
64
- });
65
-
66
- function RTB_hexToRgb(hex) {
67
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
68
- return result ? {
69
- r: parseInt(result[1], 16),
70
- g: parseInt(result[2], 16),
71
- b: parseInt(result[3], 16)
72
- } : null;
73
- }
1
+ /* Hiding empty settings sections */
2
+ jQuery( document ).ready( function() {
3
+
4
+ jQuery( 'select[name="rtb-settings[location-select]"]' ).change(function() {
5
+
6
+ manageHidingSections();
7
+ });
8
+
9
+ manageHidingSections();
10
+ });
11
+
12
+ function manageHidingSections() {
13
+
14
+ jQuery( '.sap-settings-page .form-table tr.sap-hidden' ).each(function(){
15
+
16
+ var associatedEmptyTable = jQuery( this ).parent().parent();
17
+
18
+ associatedEmptyTable.hide();
19
+ associatedEmptyTable.prev( 'h2' ).hide();
20
+ });
21
+
22
+ jQuery( '.sap-settings-page .form-table tr:not(.sap-hidden)' ).each(function(){
23
+
24
+ var associatedNonEmptyTable = jQuery( this ).parent().parent();
25
+
26
+ associatedNonEmptyTable.show();
27
+ associatedNonEmptyTable.prev( 'h2' ).show();
28
+ });
29
+ }
30
+
31
+
32
+ jQuery(document).ready(function() {
33
+ jQuery('.rtb-spectrum').spectrum({
34
+ showInput: true,
35
+ showInitial: true,
36
+ preferredFormat: "hex",
37
+ allowEmpty: true
38
+ });
39
+
40
+ jQuery('.rtb-spectrum').css('display', 'inline');
41
+
42
+ jQuery('.rtb-spectrum').on('change', function() {
43
+ if (jQuery(this).val() != "") {
44
+ jQuery(this).css('background', jQuery(this).val());
45
+ var rgb = RTB_hexToRgb(jQuery(this).val());
46
+ var Brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
47
+ if (Brightness < 100) {jQuery(this).css('color', '#ffffff');}
48
+ else {jQuery(this).css('color', '#000000');}
49
+ }
50
+ else {
51
+ jQuery(this).css('background', 'none');
52
+ }
53
+ });
54
+
55
+ jQuery('.rtb-spectrum').each(function() {
56
+ if (jQuery(this).val() != "") {
57
+ jQuery(this).css('background', jQuery(this).val());
58
+ var rgb = RTB_hexToRgb(jQuery(this).val());
59
+ var Brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
60
+ if (Brightness < 100) {jQuery(this).css('color', '#ffffff');}
61
+ else {jQuery(this).css('color', '#000000');}
62
+ }
63
+ });
64
+ });
65
+
66
+ function RTB_hexToRgb(hex) {
67
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
68
+ return result ? {
69
+ r: parseInt(result[1], 16),
70
+ g: parseInt(result[2], 16),
71
+ b: parseInt(result[3], 16)
72
+ } : null;
73
+ }
assets/js/admin.js CHANGED
@@ -1,11 +1,73 @@
1
  /* Javascript for Restaurant Reservations admin */
2
  jQuery(document).ready(function ($) {
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  // Show/hide date filter in bookings list
5
  $( '#rtb-date-filter-link' ).click( function() {
6
  $( '#rtb-filters' ).toggleClass( 'date-filters-visible' );
7
  });
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  // Add date picker to date filter in admin
10
  $( '#start-date, #end-date' ).each( function() {
11
  var input = $(this);
@@ -61,8 +123,6 @@ jQuery(document).ready(function ($) {
61
  // Register clicks on action links
62
  $( '#rtb-bookings-table tr .actions' ).click( function(e) {
63
 
64
- e.stopPropagation();
65
-
66
  var target = $(e.target);
67
  var action = target.data( 'action' );
68
 
@@ -291,6 +351,10 @@ jQuery(document).ready(function ($) {
291
  rtb_details_modal.find( '.rtb-details-data' ).empty();
292
  }, 300 );
293
  }
 
 
 
 
294
  }
295
 
296
  /**
@@ -1067,35 +1131,6 @@ jQuery(document).ready(function($){
1067
  });
1068
  });
1069
 
1070
- /*LOCK BOXES*/
1071
- jQuery( document ).ready( function() {
1072
-
1073
- setTimeout( resizeLockdownBoxes, 3000 );
1074
-
1075
- jQuery( window ).on( 'resize', resizeLockdownBoxes );
1076
-
1077
- jQuery( '.mcfrtb-list-select select' ).on( 'click.resizeLockdownBoxes', function() {
1078
- setTimeout( resizeLockdownBoxes, 3000 );
1079
- } );
1080
-
1081
- });
1082
-
1083
- function resizeLockdownBoxes() {
1084
- jQuery('.rtb-premium-options-table-overlay').each(function(){
1085
-
1086
- var eachProTableOverlay = jQuery( this );
1087
- var associatedTable = eachProTableOverlay.next();
1088
- associatedTable.css('min-height', '260px');
1089
- var tablePosition = associatedTable.position();
1090
-
1091
- eachProTableOverlay.css( 'width', associatedTable.outerWidth(true) + 'px' );
1092
- eachProTableOverlay.css( 'height', associatedTable.outerHeight() + 'px' );
1093
- eachProTableOverlay.css( 'left', tablePosition.left + 'px' );
1094
- eachProTableOverlay.css( 'top', tablePosition.top + 'px' );
1095
- });
1096
- }
1097
-
1098
-
1099
 
1100
  //OPTIONS PAGE YES/NO TOGGLE SWITCHES
1101
  jQuery(document).ready(function($){
1
  /* Javascript for Restaurant Reservations admin */
2
  jQuery(document).ready(function ($) {
3
 
4
+ // Only if on bookings page
5
+ if(
6
+ 'rtb-bookings' == ( new URL( window.location.href ) ).searchParams.get( 'page' )
7
+ &&
8
+ 0 < parseInt( rtb_admin.refresh_booking_listing )
9
+ ) {
10
+ let refresh_interval = ( parseInt( rtb_admin.refresh_booking_listing ) * 1000);
11
+
12
+ let refresh = setTimeout(function() {
13
+ window.location = window.location.href;
14
+ }, refresh_interval);
15
+
16
+ $(document).on('click', function(event) {
17
+ clearTimeout(refresh);
18
+
19
+ refresh = setTimeout(function() {
20
+ window.location = window.location.href;
21
+ }, refresh_interval);
22
+ });
23
+ }
24
+
25
  // Show/hide date filter in bookings list
26
  $( '#rtb-date-filter-link' ).click( function() {
27
  $( '#rtb-filters' ).toggleClass( 'date-filters-visible' );
28
  });
29
 
30
+ // Name filter helper
31
+ $(document)
32
+ .on('click', '#rtb-filters .filter_name a' , function(ev) {
33
+ ev.preventDefault();
34
+ filterByName();
35
+ })
36
+ .on('keydown', '#rtb-filters .filter_name input' , function(ev) {
37
+ if(event.keyCode == 13) {
38
+ event.preventDefault();
39
+ filterByName();
40
+ return false;
41
+ }
42
+ });
43
+
44
+ function filterByName() {
45
+ let text = $('#rtb-filters .filter_name input').val();
46
+ let href = $('#rtb-filters .filter_name a').prop('href');
47
+ href += '='+encodeURIComponent(text);
48
+ href += '&date_range=all';
49
+ console.log(href);
50
+ window.location = href;
51
+ }
52
+
53
+ // Construct anchor URL for Specific date filter to send a GET request instead
54
+ // of post request. This will let pagniation work properly
55
+ $(document).on('click', '.date-filters input[type="submit"]', function(event) {
56
+ event.preventDefault();
57
+
58
+ let args = [];
59
+ let url = new URL(window.location.href);
60
+
61
+ $('.date-filters input[type="hidden"]').each((i, x) => {
62
+ '' === $(x).val() ? null : args.push([$(x).prop('name'), $(x).val()]);
63
+ });
64
+
65
+ args.push(['page', url.searchParams.get('page')]);
66
+ args = new URLSearchParams(args);
67
+
68
+ window.location = `${url.origin}${url.pathname}?${args.toString()}`;
69
+ });
70
+
71
  // Add date picker to date filter in admin
72
  $( '#start-date, #end-date' ).each( function() {
73
  var input = $(this);
123
  // Register clicks on action links
124
  $( '#rtb-bookings-table tr .actions' ).click( function(e) {
125
 
 
 
126
  var target = $(e.target);
127
  var action = target.data( 'action' );
128
 
351
  rtb_details_modal.find( '.rtb-details-data' ).empty();
352
  }, 300 );
353
  }
354
+
355
+ $('#rtb-details-modal .details .value').each((i, x) => {
356
+ $(x).html( $(x).html().replace(/&lt;(.*?)br(.*?)\/&gt;/gmi, '<br>') );
357
+ });
358
  }
359
 
360
  /**
1131
  });
1132
  });
1133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1134
 
1135
  //OPTIONS PAGE YES/NO TOGGLE SWITCHES
1136
  jQuery(document).ready(function($){
assets/js/block-booking-form.js CHANGED
@@ -1,48 +1,48 @@
1
- const { __ } = wp.i18n;
2
- const { registerBlockType } = wp.blocks;
3
- const { SelectControl, PanelBody, ServerSideRender, Disabled } = wp.components;
4
- const { InspectorControls } = wp.editor;
5
- const { locationsEnabled, locations } = rtb_blocks;
6
-
7
- registerBlockType( 'restaurant-reservations/booking-form', {
8
- title: __( 'Booking Form', 'restaurant-reservations' ),
9
- icon: 'calendar',
10
- category: 'rtb-blocks',
11
- attributes: {
12
- location: {
13
- type: 'number',
14
- default: 0
15
- }
16
- },
17
- supports: {
18
- html: false,
19
- reusable: false,
20
- multiple: false,
21
- },
22
- edit( { attributes, setAttributes } ) {
23
- const { location } = attributes;
24
-
25
- return (
26
- <div>
27
- {locationsEnabled ? (
28
- <InspectorControls>
29
- <PanelBody>
30
- <SelectControl
31
- label={ __( 'Location' ) }
32
- value={ location }
33
- onChange={ ( location ) => setAttributes( { location: parseInt( location, 10 ) } ) }
34
- options={ locations }
35
- />
36
- </PanelBody>
37
- </InspectorControls>
38
- ) : '' }
39
- <Disabled>
40
- <ServerSideRender block="restaurant-reservations/booking-form" attributes={ attributes } />
41
- </Disabled>
42
- </div>
43
- );
44
- },
45
- save() {
46
- return null;
47
- },
48
- } );
1
+ const { __ } = wp.i18n;
2
+ const { registerBlockType } = wp.blocks;
3
+ const { SelectControl, PanelBody, ServerSideRender, Disabled } = wp.components;
4
+ const { InspectorControls } = wp.editor;
5
+ const { locationsEnabled, locations } = rtb_blocks;
6
+
7
+ registerBlockType( 'restaurant-reservations/booking-form', {
8
+ title: __( 'Booking Form', 'restaurant-reservations' ),
9
+ icon: 'calendar',
10
+ category: 'rtb-blocks',
11
+ attributes: {
12
+ location: {
13
+ type: 'number',
14
+ default: 0
15
+ }
16
+ },
17
+ supports: {
18
+ html: false,
19
+ reusable: false,
20
+ multiple: false,
21
+ },
22
+ edit( { attributes, setAttributes } ) {
23
+ const { location } = attributes;
24
+
25
+ return (
26
+ <div>
27
+ {locationsEnabled ? (
28
+ <InspectorControls>
29
+ <PanelBody>
30
+ <SelectControl
31
+ label={ __( 'Location' ) }
32
+ value={ location }
33
+ onChange={ ( location ) => setAttributes( { location: parseInt( location, 10 ) } ) }
34
+ options={ locations }
35
+ />
36
+ </PanelBody>
37
+ </InspectorControls>
38
+ ) : '' }
39
+ <Disabled>
40
+ <ServerSideRender block="restaurant-reservations/booking-form" attributes={ attributes } />
41
+ </Disabled>
42
+ </div>
43
+ );
44
+ },
45
+ save() {
46
+ return null;
47
+ },
48
+ } );
assets/js/booking-form.js CHANGED
@@ -1,797 +1,797 @@
1
- /* Javascript for Restaurant Reservations booking form */
2
-
3
- var rtb_booking_form = rtb_booking_form || {};
4
-
5
- jQuery(document).ready(function ($) {
6
-
7
- /**
8
- * Initialize the booking form when loaded
9
- */
10
- rtb_booking_form.init = function() {
11
-
12
- // Scroll to the first error message on the booking form
13
- if ( $( '.rtb-booking-form .rtb-error' ).length ) {
14
- $('html, body').animate({
15
- scrollTop: $( '.rtb-booking-form .rtb-error' ).first().offset().top + -40
16
- }, 500);
17
- }
18
-
19
- // Show the message field on the booking form
20
- $( '.rtb-booking-form .add-message a' ).click( function() {
21
- $(this).hide();
22
- $(this).parent().siblings( '.message' ).addClass( 'message-open' )
23
- .find( 'label' ).focus();
24
-
25
- return false;
26
- });
27
-
28
- // Show the message field on load if not empty
29
- if ( $.trim( $( '.rtb-booking-form .message textarea' ).val() ) ) {
30
- $( '.rtb-booking-form .add-message a' ).trigger( 'click' );
31
- }
32
-
33
- // Disable the submit button when the booking form is submitted
34
- $( '.rtb-booking-form form' ).submit( function() {
35
- $(this).find( 'button[type="submit"]' ).prop( 'disabled', 'disabled' );
36
- return true;
37
- } );
38
-
39
- // Enable datepickers on load
40
- if ( typeof rtb_pickadate !== 'undefined' ) {
41
-
42
- // Declare datepicker
43
- var $date_input = $( '#rtb-date' );
44
- if ( $date_input.length ) {
45
- var date_input = $date_input.pickadate({
46
- format: rtb_pickadate.date_format,
47
- formatSubmit: 'yyyy/mm/dd',
48
- hiddenName: true,
49
- min: !rtb_pickadate.allow_past,
50
- container: 'body',
51
- firstDay: rtb_pickadate.first_day,
52
-
53
- onStart: function() {
54
-
55
- // Block dates beyond early bookings window
56
- if ( rtb_pickadate.early_bookings !== '' ) {
57
- this.set( 'max', parseInt( rtb_pickadate.early_bookings, 10 ) );
58
- }
59
-
60
- // Select the value when loaded if a value has been set
61
- if ( $date_input.val() !== '' ) {
62
- var date = new Date( $date_input.val() );
63
- if ( Object.prototype.toString.call( date ) === "[object Date]" ) {
64
- this.set( 'select', date );
65
- }
66
- }
67
- }
68
- });
69
-
70
- rtb_booking_form.datepicker = date_input.pickadate( 'picker' );
71
- }
72
-
73
- // Declare timepicker
74
- var $time_input = $( '#rtb-time' );
75
- if ( $time_input.length ) {
76
- var time_input = $time_input.pickatime({
77
- format: rtb_pickadate.time_format,
78
- formatSubmit: 'h:i A',
79
- hiddenName: true,
80
- interval: parseInt( rtb_pickadate.time_interval, 10 ),
81
- container: 'body',
82
-
83
- // Select the value when loaded if a value has been set
84
- onStart: function() {
85
- if ( $time_input.val() !== '' ) {
86
- var today = new Date();
87
- var today_date = today.getFullYear() + '/' + ( today.getMonth() + 1 ) + '/' + today.getDate();
88
- var time = new Date( today_date + ' ' + $time_input.val() );
89
- if ( Object.prototype.toString.call( time ) === "[object Date]" ) {
90
- this.set( 'select', time );
91
- }
92
-
93
- }
94
- }
95
- });
96
-
97
- rtb_booking_form.timepicker = time_input.pickatime( 'picker' );
98
- }
99
-
100
- // We need to check both to support different jQuery versions loaded
101
- // by older versions of WordPress. In jQuery v1.10.2, the property
102
- // is undefined. But in v1.11.3 it's set to null.
103
- if ( rtb_booking_form.datepicker === null || typeof rtb_booking_form.datepicker == 'undefined' ) {
104
- return;
105
- }
106
-
107
- // Pass conditional configuration parameters
108
- if ( rtb_pickadate.disable_dates.length ) {
109
-
110
- // Update weekday dates if start of the week has been modified
111
- var disable_dates = jQuery.extend( true, [], rtb_pickadate.disable_dates );
112
- if ( typeof rtb_booking_form.datepicker.component.settings.firstDay == 'number' ) {
113
- var weekday_num = 0;
114
- for ( var disable_key in rtb_pickadate.disable_dates ) {
115
- if ( typeof rtb_pickadate.disable_dates[disable_key] == 'number' ) {
116
- weekday_num = rtb_pickadate.disable_dates[disable_key] - rtb_booking_form.datepicker.component.settings.firstDay;
117
- if ( weekday_num < 1 ) {
118
- weekday_num = 7;
119
- }
120
- disable_dates[disable_key] = weekday_num;
121
- }
122
- }
123
- }
124
-
125
- rtb_booking_form.datepicker.set( 'disable', disable_dates );
126
- }
127
-
128
- if ( typeof rtb_pickadate.late_bookings === 'string' ) {
129
- if ( rtb_pickadate.late_bookings == 'same_day' ) {
130
- rtb_booking_form.datepicker.set( 'min', 1 );
131
- } else if ( rtb_pickadate.late_bookings !== '' ) {
132
- rtb_pickadate.late_bookings = parseInt( rtb_pickadate.late_bookings, 10 );
133
- if ( rtb_pickadate.late_bookings % 1 === 0 && rtb_pickadate.late_bookings >= 1440 ) {
134
- var min = Math.floor( rtb_pickadate.late_bookings / 1440 );
135
- rtb_booking_form.datepicker.set( 'min', min );
136
- }
137
- }
138
- }
139
-
140
- // If no date has been set, select today's date if it's a valid
141
- // date. User may opt not to do this in the settings.
142
- if ( $date_input.val() === '' && !$( '.rtb-booking-form .date .rtb-error' ).length ) {
143
-
144
- if ( rtb_pickadate.date_onload == 'soonest' ) {
145
- rtb_booking_form.datepicker.set( 'select', new Date() );
146
- } else if ( rtb_pickadate.date_onload !== 'empty' ) {
147
- var dateToVerify = rtb_booking_form.datepicker.component.create( new Date() );
148
- var isDisabled = rtb_booking_form.datepicker.component.disabled( dateToVerify );
149
- if ( !isDisabled ) {
150
- rtb_booking_form.datepicker.set( 'select', dateToVerify );
151
- }
152
- }
153
- }
154
-
155
- if ( rtb_booking_form.timepicker === null || typeof rtb_booking_form.timepicker == 'undefined' ) {
156
- return;
157
- }
158
-
159
- // Update timepicker on pageload and whenever the datepicker is closed
160
- rtb_booking_form.update_timepicker_range();
161
- rtb_booking_form.datepicker.on( {
162
- open: function () {
163
-
164
- rtb_booking_form.before_change_value = rtb_booking_form.datepicker.get();
165
- },
166
-
167
- close: function() {
168
-
169
- rtb_booking_form.after_change_value = rtb_booking_form.datepicker.get();
170
-
171
- if(rtb_booking_form.before_change_value != rtb_booking_form.after_change_value) {
172
- // clear time value if date changed
173
- rtb_booking_form.timepicker.clear();
174
- }
175
-
176
- rtb_booking_form.update_timepicker_range();
177
- rtb_booking_form.update_party_size_select();
178
- rtb_booking_form.update_possible_tables();
179
- }
180
- });
181
-
182
- rtb_booking_form.timepicker.on( {
183
- close: function() {
184
- rtb_booking_form.update_party_size_select();
185
- rtb_booking_form.update_possible_tables();
186
- }
187
- });
188
-
189
- $( '#rtb-party' ).on( 'change', function() {
190
- rtb_booking_form.update_possible_tables();
191
- });
192
-
193
- $( '#rtb-location' ).on( 'change', function() {
194
-
195
- if ( ! rtb_pickadate.multiple_locations_enabled ) { return; }
196
-
197
- rtb_booking_form.timepicker.clear();
198
-
199
- rtb_booking_form.update_timepicker_range();
200
-
201
- rtb_booking_form.update_party_size_select();
202
- });
203
-
204
- rtb_booking_form.update_possible_tables();
205
- }
206
- };
207
-
208
- /**
209
- * Update the timepicker's range based on the currently selected date
210
- */
211
- rtb_booking_form.update_timepicker_range = function() {
212
-
213
- // Reset enabled/disabled rules on this timepicker
214
- rtb_booking_form.timepicker.set( 'enable', false );
215
- rtb_booking_form.timepicker.set( 'disable', false );
216
-
217
- if ( rtb_booking_form.datepicker.get() === '' ) {
218
- rtb_booking_form.timepicker.set( 'disable', true );
219
- return;
220
- }
221
-
222
- var selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
223
- selected_date_year = selected_date.getFullYear(),
224
- selected_date_month = selected_date.getMonth(),
225
- selected_date_date = selected_date.getDate(),
226
- current_date = new Date();
227
-
228
- // Declaring the first element true inverts the timepicker settings. All
229
- // times subsequently declared are valid. Any time that doesn't fall
230
- // within those declarations is invalid.
231
- // See: http://amsul.ca/pickadate.js/time/#disable-times-all
232
- var valid_times = [ rtb_booking_form.get_outer_time_range() ];
233
-
234
- if ( rtb_pickadate.enable_max_reservations || rtb_pickadate.multiple_locations_enabled ) {
235
- selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '';
236
-
237
- let hidden_location = jQuery('.rtb-booking-form-form input[name="rtb-location"]');
238
- if('' == selected_location && hidden_location.length ) {
239
- selected_location = hidden_location.val();
240
- }
241
-
242
- selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
243
- selected_date_date = ('0' + selected_date_date).slice(-2);
244
-
245
- var data = 'year=' + selected_date_year + '&month=' + selected_date_month + '&day=' + selected_date_date + '&location=' + selected_location + '&action=rtb_get_available_time_slots';
246
- jQuery.post( ajaxurl, data, function( response ) {
247
- if ( ! response ) {
248
- rtb_booking_form.timepicker.set( 'disable', true );
249
-
250
- return;
251
- }
252
-
253
- var additional_valid_times = jQuery.parseJSON( response );
254
- var all_valid_times = valid_times.concat( additional_valid_times );
255
- rtb_booking_form.timepicker.set( 'disable', all_valid_times );
256
- });
257
- }
258
-
259
- else {
260
- // Check if this date is an exception to the rules
261
- if ( typeof rtb_pickadate.schedule_closed !== 'undefined' ) {
262
-
263
- var excp_date = [];
264
- var excp_start_date = [];
265
- var excp_start_time = [];
266
- var excp_end_date = [];
267
- var excp_end_time = [];
268
- for ( var closed_key in rtb_pickadate.schedule_closed ) {
269
-
270
- excp_date = new Date( rtb_pickadate.schedule_closed[closed_key].date );
271
- if ( excp_date.getFullYear() == selected_date_year &&
272
- excp_date.getMonth() == selected_date_month &&
273
- excp_date.getDate() == selected_date_date
274
- ) {
275
-
276
- // Closed all day
277
- if ( typeof rtb_pickadate.schedule_closed[closed_key].time == 'undefined' ) {
278
- rtb_booking_form.timepicker.set( 'disable', [ true ] );
279
-
280
- return;
281
- }
282
-
283
- if ( typeof rtb_pickadate.schedule_closed[closed_key].time.start !== 'undefined' ) {
284
- excp_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.start );
285
- excp_start_time = [ excp_start_date.getHours(), excp_start_date.getMinutes() ];
286
- } else {
287
- excp_start_time = [ 0, 0 ]; // Start of the day
288
- }
289
-
290
- if ( typeof rtb_pickadate.schedule_closed[closed_key].time.end !== 'undefined' ) {
291
- excp_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.end );
292
- excp_end_time = [ excp_end_date.getHours(), excp_end_date.getMinutes() ];
293
- } else {
294
- excp_end_time = [ 24, 0 ]; // End of the day
295
- }
296
-
297
- excp_start_time = rtb_booking_form.get_earliest_time( excp_start_time, selected_date, current_date );
298
-
299
- valid_times.push( { from: excp_start_time, to: excp_end_time, inverted: true } );
300
- }
301
- }
302
-
303
- excp_date = excp_start_date = excp_start_time = excp_end_date = excp_end_time = null;
304
-
305
- // Exit early if this date is an exception
306
- if ( valid_times.length > 1 ) {
307
- rtb_booking_form.timepicker.set( 'disable', valid_times );
308
-
309
- return;
310
- }
311
- }
312
-
313
- // Get any rules which apply to this weekday
314
- if ( typeof rtb_pickadate.schedule_open != 'undefined' ) {
315
-
316
- var selected_date_weekday = selected_date.getDay();
317
-
318
- var weekdays = {
319
- sunday: 0,
320
- monday: 1,
321
- tuesday: 2,
322
- wednesday: 3,
323
- thursday: 4,
324
- friday: 5,
325
- saturday: 6,
326
- };
327
-
328
- var rule_start_date = [];
329
- var rule_start_time = [];
330
- var rule_end_date = [];
331
- var rule_end_time = [];
332
- for ( var open_key in rtb_pickadate.schedule_open ) {
333
-
334
- if ( typeof rtb_pickadate.schedule_open[open_key].weekdays !== 'undefined' ) {
335
- for ( var weekdays_key in rtb_pickadate.schedule_open[open_key].weekdays ) {
336
- if ( weekdays[weekdays_key] == selected_date_weekday ) {
337
-
338
- // Closed all day
339
- if ( typeof rtb_pickadate.schedule_open[open_key].time == 'undefined' ) {
340
- rtb_booking_form.timepicker.set( 'disable', [ true ] );
341
-
342
- return;
343
- }
344
-
345
- if ( typeof rtb_pickadate.schedule_open[open_key].time.start !== 'undefined' ) {
346
- rule_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.start );
347
- rule_start_time = [ rule_start_date.getHours(), rule_start_date.getMinutes() ];
348
- } else {
349
- rule_start_time = [ 0, 0 ]; // Start of the day
350
- }
351
-
352
- if ( typeof rtb_pickadate.schedule_open[open_key].time.end !== 'undefined' ) {
353
- rule_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.end );
354
- rule_end_time = rtb_booking_form.get_latest_viable_time( rule_end_date.getHours(), rule_end_date.getMinutes() );
355
- } else {
356
- rule_end_time = [ 24, 0 ]; // End of the day
357
- }
358
-
359
- rule_start_time = rtb_booking_form.get_earliest_time( rule_start_time, selected_date, current_date );
360
-
361
- valid_times.push( { from: rule_start_time, to: rule_end_time, inverted: true } );
362
-
363
- }
364
- }
365
- }
366
- }
367
-
368
- rule_start_date = rule_start_time = rule_end_date = rule_end_time = null;
369
-
370
- // Pass any valid times located
371
- if ( valid_times.length > 1 ) {
372
- rtb_booking_form.timepicker.set( 'disable', valid_times );
373
-
374
- return;
375
- }
376
-
377
- }
378
-
379
- // Set it to always open if no rules have been defined
380
- rtb_booking_form.timepicker.set( 'enable', true );
381
- rtb_booking_form.timepicker.set( 'disable', false );
382
- }
383
-
384
- return;
385
- };
386
-
387
- /**
388
- * Get the outer times to exclude based on the time interval
389
- *
390
- * This is a work-around for a bug in pickadate.js
391
- * See: https://github.com/amsul/pickadate.js/issues/614
392
- */
393
- rtb_booking_form.get_outer_time_range = function() {
394
-
395
- var interval = rtb_booking_form.timepicker.get( 'interval' );
396
-
397
- var hour = 24;
398
-
399
- while ( interval >= 60 ) {
400
- hour--;
401
- interval -= 60;
402
- }
403
-
404
- if ( interval > 0 ) {
405
- hour--;
406
- interval = 60 - interval;
407
- }
408
-
409
- return { from: [0, 0], to: [ hour, interval ] };
410
- };
411
-
412
- /**
413
- * Get the latest working opening hour/minute value
414
- *
415
- * This is a workaround for a bug in pickadate.js. The end time of a valid
416
- * time value must NOT fall within the last timepicker interval and midnight
417
- * See: https://github.com/amsul/pickadate.js/issues/614
418
- */
419
- rtb_booking_form.get_latest_viable_time = function( hour, minute ) {
420
-
421
- var interval = rtb_booking_form.timepicker.get( 'interval' );
422
-
423
- var outer_time_range = this.get_outer_time_range();
424
-
425
- /*
426
- * Adjust the last time for wide intervals, so that the last time entered
427
- * corresponds to an interval time. A pickadate bug causes a later time to
428
- * be available for booking otherwise.
429
- */
430
- if ( interval > 60) {
431
-
432
- var last_hour = 0;
433
- var last_minute = 0;
434
- var last_time_minutes = 0;
435
-
436
- var end_time_minutes = 60 * hour + minute;
437
-
438
- while ( ( last_time_minutes + interval ) <= end_time_minutes ) {
439
-
440
- var remainder = interval + last_minute;
441
-
442
- while ( remainder >= 60 ) {
443
- last_hour++;
444
- remainder -= 60;
445
- }
446
-
447
- last_minute = remainder;
448
-
449
- last_time_minutes = 60 * last_hour + last_minute;
450
- }
451
-
452
- var long_interval_viable_time = [ last_hour, last_minute ];
453
- }
454
-
455
-
456
- if ( interval > 60 ) {
457
-
458
- return long_interval_viable_time;
459
- }
460
- else if ( hour > outer_time_range.to[0] || minute > outer_time_range.to[1] ) {
461
-
462
- return [ outer_time_range.to[0], outer_time_range.to[1] ];
463
- } else {
464
-
465
- return [ hour, minute ];
466
- }
467
- };
468
-
469
- /**
470
- * Get the earliest valid time
471
- *
472
- * This checks the valid time for the day and, if a current day, applies
473
- * any late booking restrictions. It also ensures that times in the past
474
- * are not availabe.
475
- *
476
- * @param array start_time
477
- * @param array selected_date
478
- * @param array current_date
479
- */
480
- rtb_booking_form.get_earliest_time = function( start_time, selected_date, current_date ) {
481
-
482
- // Only make adjustments for current day selections
483
- if ( selected_date.toDateString() !== current_date.toDateString() ) {
484
- return start_time;
485
- }
486
-
487
- // Get the number of minutes after midnight to compare
488
- var start_minutes = ( start_time[0] * 60 ) + start_time[1],
489
- current_minutes = ( current_date.getHours() * 60 ) + current_date.getMinutes(),
490
- late_booking_minutes;
491
-
492
- start_minutes = start_minutes > current_minutes ? start_minutes : current_minutes;
493
-
494
- if ( typeof rtb_pickadate.late_bookings === 'number' && rtb_pickadate.late_bookings % 1 === 0 ) {
495
- late_booking_minutes = current_minutes + rtb_pickadate.late_bookings;
496
- if ( late_booking_minutes > start_minutes ) {
497
- start_minutes = late_booking_minutes;
498
- }
499
- }
500
-
501
- start_time = [ Math.floor( start_minutes / 60 ), start_minutes % 60 ];
502
-
503
- return start_time;
504
- };
505
-
506
- rtb_booking_form.update_party_size_select = function() {
507
-
508
- if ( rtb_pickadate.enable_max_reservations && ( rtb_pickadate.max_people || rtb_pickadate.multiple_locations_enabled ) ) {
509
- var partySelect = $('#rtb-party'),
510
- selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '',
511
- selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
512
- selected_date_year = selected_date.getFullYear(),
513
- selected_date_month = selected_date.getMonth(),
514
- selected_date_date = selected_date.getDate(),
515
- selected_time = rtb_booking_form.timepicker.get('value');
516
-
517
- if ( ! selected_time ) { return; }
518
-
519
- selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
520
- selected_date_date = ('0' + selected_date_date).slice(-2);
521
-
522
- //reset party size
523
- partySelect.prop("selectedIndex", 0).change();
524
-
525
- var data = 'year=' + selected_date_year + '&month=' + selected_date_month + '&day=' + selected_date_date + '&time=' + selected_time + '&location=' + selected_location + '&action=rtb_get_available_party_size';
526
- jQuery.post( ajaxurl, data, function( response ) {
527
- if ( ! response ) {
528
- return;
529
- }
530
-
531
- response = jQuery.parseJSON(response);
532
-
533
- var available_spots = response.available_spots;
534
-
535
- partySelect.prop('disabled', false);
536
-
537
- partySelect.find('> option').each(function() {
538
- var that = $(this);
539
- if (this.value > available_spots) {
540
- that.prop('disabled', true);
541
- } else {
542
- that.prop('disabled', false);
543
- }
544
- });
545
- });
546
- }
547
- }
548
-
549
- rtb_booking_form.update_possible_tables = function() {
550
-
551
- if ( rtb_pickadate.enable_tables ) {
552
-
553
- var table_select = $('#rtb-table'),
554
- party = $('#rtb-party').val(),
555
- selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
556
- selected_date_year = selected_date.getFullYear(),
557
- selected_date_month = selected_date.getMonth(),
558
- selected_date_date = selected_date.getDate(),
559
- selected_time = rtb_booking_form.timepicker.get('value');
560
-
561
- if ( ! selected_time || ! party ) { return; }
562
-
563
- selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
564
- selected_date_date = ('0' + selected_date_date).slice(-2);
565
-
566
- //reset table selection
567
- table_select.prop("selectedIndex", 0).change();
568
-
569
- //remove table combinations
570
- table_select.find('> option').each( function() {
571
- if ( $( this ).val().indexOf( ',' ) !== -1 ) { $( this ).remove(); }
572
- });
573
-
574
- var booking_id = $( '.rtb-booking-form form input[name="ID"]').length ? $( '.rtb-booking-form form input[name="ID"]').val() : 0;
575
-
576
- var data = 'booking_id=' + booking_id + '&year=' + selected_date_year + '&month=' + selected_date_month + '&day=' + selected_date_date + '&time=' + selected_time + '&party=' + party + '&action=rtb_get_available_tables';
577
- jQuery.post( ajaxurl, data, function( response ) {
578
- if ( ! response ) {
579
- return;
580
- }
581
-
582
- response = jQuery.parseJSON(response);
583
-
584
- var available_tables = response.available_tables;
585
-
586
- table_select.prop('disabled', false);
587
-
588
- table_select.find('> option').hide();
589
- table_select.find('> option').attr('disabled', 'disabled');
590
-
591
- jQuery.each(available_tables, function(index, element) {
592
-
593
- if ( index.indexOf( ',' ) === -1 ) {
594
- table_select.find('> option[value="' + index + '"]').show();
595
- table_select.find('> option[value="' + index + '"]').removeAttr('disabled', 'disabled');
596
- }
597
- else {
598
- table_select.append( '<option value="' + index + '">' + element + '</option>' );
599
- }
600
-
601
- });
602
-
603
- if ( response.selected_table != -1 ) {
604
- table_select.val( response.selected_table );
605
- }
606
-
607
- });
608
- }
609
-
610
- }
611
-
612
- rtb_booking_form.init();
613
- });
614
-
615
- //Handle reservation modification
616
- jQuery(document).ready(function() {
617
- jQuery('.rtb-modification-toggle').on('click', function() {
618
- jQuery('.rtb-modification-form, .rtb-booking-form-form').toggleClass('rtb-hidden');
619
-
620
- if (jQuery('.rtb-modification-form').hasClass('rtb-hidden')) {
621
- jQuery('.rtb-modification-toggle').html(rtb_booking_form_js_localize.want_to_modify);
622
- }
623
- else {
624
- jQuery('.rtb-modification-toggle').html(rtb_booking_form_js_localize.make);
625
- }
626
- });
627
-
628
- var modify_booking = function(ev) {
629
- var booking_email = jQuery('input[name="rtb_modification_email"]').val();
630
-
631
- var data = 'booking_email=' + booking_email + '&action=rtb_find_reservations';
632
- jQuery.post(ajaxurl, data, function(response) {
633
-
634
- if (response.success) {
635
- var booking_html = '';
636
- var guest_txt = '';
637
- var pay_btn = '';
638
-
639
- jQuery(response.data.bookings).each(function( index, val) {
640
- pay_btn = '';
641
- guest_txt = val.party > 1 ? rtb_booking_form_js_localize.guests : rtb_booking_form_js_localize.guest;
642
-
643
- if('payment_pending' == val.status || 'payment_failed' == val.status) {
644
- pay_btn = `
645
- <div class="rtb-deposit-booking" data-bookingid="${val.ID}" data-bookingemail="${val.email}">
646
- ${rtb_booking_form_js_localize.deposit}
647
- </div>
648
- `;
649
- }
650
-
651
- booking_html += `
652
- <div class="rtb-cancel-booking-div">
653
- <div class="rtb-cancel-booking" data-bookingid="${val.ID}" data-bookingemail="${val.email}">
654
- ${rtb_booking_form_js_localize.cancel}
655
- </div>
656
- ${pay_btn}
657
- <div class="rtb-booking-information">${val.datetime} - ${val.party} ${guest_txt} (${val.status_lbl})</div>
658
- </div>
659
- `;
660
- });
661
-
662
- jQuery('.rtb-bookings-results').html(booking_html);
663
-
664
- cancellationHandler();
665
- delayedPaymentHandler();
666
- }
667
- else {jQuery('.rtb-bookings-results').html(response.data.msg);}
668
- });
669
- };
670
-
671
- jQuery(document).on('click', '.rtb-find-reservation-button', modify_booking);
672
- jQuery(document).on('keypress', '.rtb-modification-form input', function (ev) {
673
- // Capture enter key
674
- if(13 == ev.which) {
675
- ev.preventDefault();
676
- modify_booking(ev);
677
- }
678
- });
679
- });
680
-
681
- function cancellationHandler() {
682
- jQuery('.rtb-cancel-booking:not(.cancelled)').off('click');
683
- jQuery('.rtb-cancel-booking:not(.cancelled)').on('click', function() {
684
- var btn = jQuery(this);
685
-
686
- if(btn.hasClass('processing')) {
687
- return;
688
- }
689
-
690
- btn.addClass('processing');
691
-
692
- var booking_id = btn.data('bookingid');
693
- var booking_email = btn.data('bookingemail');
694
-
695
- var data = {
696
- 'booking_id': booking_id,
697
- 'booking_email': booking_email,
698
- 'action': 'rtb_cancel_reservations'
699
- };
700
-
701
- jQuery.post(ajaxurl, data, function(response) {
702
- if (response.success) {
703
- if (response.data.hasOwnProperty('cancelled_redirect')) {
704
- window.location.href = response.data.cancelled_redirect;
705
- }
706
- else {
707
- btn.off('click');
708
- btn.addClass('cancelled');
709
- btn.text(rtb_booking_form_js_localize.cancelled);
710
- }
711
- }
712
- else {
713
- btn.parent().after(`<p class="alert error">${response.data.msg}</p>`);
714
- }
715
-
716
- btn.removeClass('processing');
717
- });
718
- });
719
- }
720
-
721
- function delayedPaymentHandler() {
722
- jQuery('.rtb-deposit-booking').off('click');
723
- jQuery('.rtb-deposit-booking').on('click', function() {
724
- var btn = jQuery(this);
725
-
726
- if(btn.hasClass('processing')) {
727
- return;
728
- }
729
-
730
- btn.addClass('processing');
731
-
732
- var booking_id = btn.data('bookingid');
733
- var booking_email = btn.data('bookingemail');
734
-
735
- var data = {
736
- 'booking_id': booking_id,
737
- 'booking_email': booking_email,
738
- 'payment': 'rtb-delayed-deposit'
739
- };
740
-
741
- let current_loc = window.location;
742
- let params = new URLSearchParams();
743
- Object.keys( data ).map( function( param ) { params.append( param, data[ param ] ) } );
744
-
745
- window.location = current_loc.origin + current_loc.pathname + '?' + params.toString();
746
-
747
- });
748
- }
749
-
750
- // Functions for the 'View Bookings' shortcode
751
- jQuery(document).ready(function ($) {
752
- jQuery('.rtb-view-bookings-form-date-selector').on('change', function() {
753
- window.location.href = replaceUrlParam(window.location.href, 'date', jQuery(this).val());
754
- });
755
-
756
- jQuery('.rtb-edit-view-booking').on('click', function() {
757
- jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').removeClass('rtb-hidden');
758
-
759
- jQuery('.rtb-view-bookings-form-confirmation-div').data('bookingid', jQuery(this).data('bookingid'));
760
-
761
- jQuery(this).prop('checked', false);
762
- });
763
-
764
- jQuery('.rtb-view-bookings-form-confirmation-accept').on('click', function() {
765
- var booking_id = jQuery('.rtb-view-bookings-form-confirmation-div').data('bookingid');
766
-
767
- var data = 'booking_id=' + booking_id + '&action=rtb_set_reservation_arrived';
768
- jQuery.post(ajaxurl, data, function(response) {
769
-
770
- if (response.success) {window.location.href = window.location.href}
771
- else {jQuery('.rtb-view-bookings-form-confirmation-div').html(response.data.msg);}
772
- });
773
- });
774
-
775
- jQuery('.rtb-view-bookings-form-confirmation-decline').on('click', function() {
776
- jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
777
- });
778
- jQuery('.rtb-view-bookings-form-confirmation-background-div').on('click', function() {
779
- jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
780
- });
781
- jQuery('#rtb-view-bookings-form-close').on('click', function() {
782
- jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
783
- });
784
- });
785
-
786
- function replaceUrlParam(url, paramName, paramValue)
787
- {
788
- if (paramValue == null) {
789
- paramValue = '';
790
- }
791
- var pattern = new RegExp('\\b('+paramName+'=).*?(&|#|$)');
792
- if (url.search(pattern)>=0) {
793
- return url.replace(pattern,'$1' + paramValue + '$2');
794
- }
795
- url = url.replace(/[?#]$/,'');
796
- return url + (url.indexOf('?')>0 ? '&' : '?') + paramName + '=' + paramValue;
797
  }
1
+ /* Javascript for Restaurant Reservations booking form */
2
+
3
+ var rtb_booking_form = rtb_booking_form || {};
4
+
5
+ jQuery(document).ready(function ($) {
6
+
7
+ /**
8
+ * Initialize the booking form when loaded
9
+ */
10
+ rtb_booking_form.init = function() {
11
+
12
+ // Scroll to the first error message on the booking form
13
+ if ( $( '.rtb-booking-form .rtb-error' ).length ) {
14
+ $('html, body').animate({
15
+ scrollTop: $( '.rtb-booking-form .rtb-error' ).first().offset().top + -40
16
+ }, 500);
17
+ }
18
+
19
+ // Show the message field on the booking form
20
+ $( '.rtb-booking-form .add-message a' ).click( function() {
21
+ $(this).hide();
22
+ $(this).parent().siblings( '.message' ).addClass( 'message-open' )
23
+ .find( 'label' ).focus();
24
+
25
+ return false;
26
+ });
27
+
28
+ // Show the message field on load if not empty
29
+ if ( $.trim( $( '.rtb-booking-form .message textarea' ).val() ) ) {
30
+ $( '.rtb-booking-form .add-message a' ).trigger( 'click' );
31
+ }
32
+
33
+ // Disable the submit button when the booking form is submitted
34
+ $( '.rtb-booking-form form' ).submit( function() {
35
+ $(this).find( 'button[type="submit"]' ).prop( 'disabled', 'disabled' );
36
+ return true;
37
+ } );
38
+
39
+ // Enable datepickers on load
40
+ if ( typeof rtb_pickadate !== 'undefined' ) {
41
+
42
+ // Declare datepicker
43
+ var $date_input = $( '#rtb-date' );
44
+ if ( $date_input.length ) {
45
+ var date_input = $date_input.pickadate({
46
+ format: rtb_pickadate.date_format,
47
+ formatSubmit: 'yyyy/mm/dd',
48
+ hiddenName: true,
49
+ min: !rtb_pickadate.allow_past,
50
+ container: 'body',
51
+ firstDay: rtb_pickadate.first_day,
52
+
53
+ onStart: function() {
54
+
55
+ // Block dates beyond early bookings window
56
+ if ( rtb_pickadate.early_bookings !== '' ) {
57
+ this.set( 'max', parseInt( rtb_pickadate.early_bookings, 10 ) );
58
+ }
59
+
60
+ // Select the value when loaded if a value has been set
61
+ if ( $date_input.val() !== '' ) {
62
+ var date = new Date( $date_input.val() );
63
+ if ( Object.prototype.toString.call( date ) === "[object Date]" ) {
64
+ this.set( 'select', date );
65
+ }
66
+ }
67
+ }
68
+ });
69
+
70
+ rtb_booking_form.datepicker = date_input.pickadate( 'picker' );
71
+ }
72
+
73
+ // Declare timepicker
74
+ var $time_input = $( '#rtb-time' );
75
+ if ( $time_input.length ) {
76
+ var time_input = $time_input.pickatime({
77
+ format: rtb_pickadate.time_format,
78
+ formatSubmit: 'h:i A',
79
+ hiddenName: true,
80
+ interval: parseInt( rtb_pickadate.time_interval, 10 ),
81
+ container: 'body',
82
+
83
+ // Select the value when loaded if a value has been set
84
+ onStart: function() {
85
+ if ( $time_input.val() !== '' ) {
86
+ var today = new Date();
87
+ var today_date = today.getFullYear() + '/' + ( today.getMonth() + 1 ) + '/' + today.getDate();
88
+ var time = new Date( today_date + ' ' + $time_input.val() );
89
+ if ( Object.prototype.toString.call( time ) === "[object Date]" ) {
90
+ this.set( 'select', time );
91
+ }
92
+
93
+ }
94
+ }
95
+ });
96
+
97
+ rtb_booking_form.timepicker = time_input.pickatime( 'picker' );
98
+ }
99
+
100
+ // We need to check both to support different jQuery versions loaded
101
+ // by older versions of WordPress. In jQuery v1.10.2, the property
102
+ // is undefined. But in v1.11.3 it's set to null.
103
+ if ( rtb_booking_form.datepicker === null || typeof rtb_booking_form.datepicker == 'undefined' ) {
104
+ return;
105
+ }
106
+
107
+ // Pass conditional configuration parameters
108
+ if ( rtb_pickadate.disable_dates.length ) {
109
+
110
+ // Update weekday dates if start of the week has been modified
111
+ var disable_dates = jQuery.extend( true, [], rtb_pickadate.disable_dates );
112
+ if ( typeof rtb_booking_form.datepicker.component.settings.firstDay == 'number' ) {
113
+ var weekday_num = 0;
114
+ for ( var disable_key in rtb_pickadate.disable_dates ) {
115
+ if ( typeof rtb_pickadate.disable_dates[disable_key] == 'number' ) {
116
+ weekday_num = rtb_pickadate.disable_dates[disable_key] - rtb_booking_form.datepicker.component.settings.firstDay;
117
+ if ( weekday_num < 1 ) {
118
+ weekday_num = 7;
119
+ }
120
+ disable_dates[disable_key] = weekday_num;
121
+ }
122
+ }
123
+ }
124
+
125
+ rtb_booking_form.datepicker.set( 'disable', disable_dates );
126
+ }
127
+
128
+ if ( typeof rtb_pickadate.late_bookings === 'string' ) {
129
+ if ( rtb_pickadate.late_bookings == 'same_day' ) {
130
+ rtb_booking_form.datepicker.set( 'min', 1 );
131
+ } else if ( rtb_pickadate.late_bookings !== '' ) {
132
+ rtb_pickadate.late_bookings = parseInt( rtb_pickadate.late_bookings, 10 );
133
+ if ( rtb_pickadate.late_bookings % 1 === 0 && rtb_pickadate.late_bookings >= 1440 ) {
134
+ var min = Math.floor( rtb_pickadate.late_bookings / 1440 );
135
+ rtb_booking_form.datepicker.set( 'min', min );
136
+ }
137
+ }
138
+ }
139
+
140
+ // If no date has been set, select today's date if it's a valid
141
+ // date. User may opt not to do this in the settings.
142
+ if ( $date_input.val() === '' && !$( '.rtb-booking-form .date .rtb-error' ).length ) {
143
+
144
+ if ( rtb_pickadate.date_onload == 'soonest' ) {
145
+ rtb_booking_form.datepicker.set( 'select', new Date() );
146
+ } else if ( rtb_pickadate.date_onload !== 'empty' ) {
147
+ var dateToVerify = rtb_booking_form.datepicker.component.create( new Date() );
148
+ var isDisabled = rtb_booking_form.datepicker.component.disabled( dateToVerify );
149
+ if ( !isDisabled ) {
150
+ rtb_booking_form.datepicker.set( 'select', dateToVerify );
151
+ }
152
+ }
153
+ }
154
+
155
+ if ( rtb_booking_form.timepicker === null || typeof rtb_booking_form.timepicker == 'undefined' ) {
156
+ return;
157
+ }
158
+
159
+ // Update timepicker on pageload and whenever the datepicker is closed
160
+ rtb_booking_form.update_timepicker_range();
161
+ rtb_booking_form.datepicker.on( {
162
+ open: function () {
163
+
164
+ rtb_booking_form.before_change_value = rtb_booking_form.datepicker.get();
165
+ },
166
+
167
+ close: function() {
168
+
169
+ rtb_booking_form.after_change_value = rtb_booking_form.datepicker.get();
170
+
171
+ if(rtb_booking_form.before_change_value != rtb_booking_form.after_change_value) {
172
+ // clear time value if date changed
173
+ rtb_booking_form.timepicker.clear();
174
+ }
175
+
176
+ rtb_booking_form.update_timepicker_range();
177
+ rtb_booking_form.update_party_size_select();
178
+ rtb_booking_form.update_possible_tables();
179
+ }
180
+ });
181
+
182
+ rtb_booking_form.timepicker.on( {
183
+ close: function() {
184
+ rtb_booking_form.update_party_size_select();
185
+ rtb_booking_form.update_possible_tables();
186
+ }
187
+ });
188
+
189
+ $( '#rtb-party' ).on( 'change', function() {
190
+ rtb_booking_form.update_possible_tables();
191
+ });
192
+
193
+ $( '#rtb-location' ).on( 'change', function() {
194
+
195
+ if ( ! rtb_pickadate.multiple_locations_enabled ) { return; }
196
+
197
+ rtb_booking_form.timepicker.clear();
198
+
199
+ rtb_booking_form.update_timepicker_range();
200
+
201
+ rtb_booking_form.update_party_size_select();
202
+ });
203
+
204
+ rtb_booking_form.update_possible_tables();
205
+ }
206
+ };
207
+
208
+ /**
209
+ * Update the timepicker's range based on the currently selected date
210
+ */
211
+ rtb_booking_form.update_timepicker_range = function() {
212
+
213
+ // Reset enabled/disabled rules on this timepicker
214
+ rtb_booking_form.timepicker.set( 'enable', false );
215
+ rtb_booking_form.timepicker.set( 'disable', false );
216
+
217
+ if ( rtb_booking_form.datepicker.get() === '' ) {
218
+ rtb_booking_form.timepicker.set( 'disable', true );
219
+ return;
220
+ }
221
+
222
+ var selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
223
+ selected_date_year = selected_date.getFullYear(),
224
+ selected_date_month = selected_date.getMonth(),
225
+ selected_date_date = selected_date.getDate(),
226
+ current_date = new Date();
227
+
228
+ // Declaring the first element true inverts the timepicker settings. All
229
+ // times subsequently declared are valid. Any time that doesn't fall
230
+ // within those declarations is invalid.
231
+ // See: http://amsul.ca/pickadate.js/time/#disable-times-all
232
+ var valid_times = [ rtb_booking_form.get_outer_time_range() ];
233
+
234
+ if ( rtb_pickadate.enable_max_reservations || rtb_pickadate.multiple_locations_enabled ) {
235
+ selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '';
236
+
237
+ let hidden_location = jQuery('.rtb-booking-form-form input[name="rtb-location"]');
238
+ if('' == selected_location && hidden_location.length ) {
239
+ selected_location = hidden_location.val();
240
+ }
241
+
242
+ selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
243
+ selected_date_date = ('0' + selected_date_date).slice(-2);
244
+
245
+ var data = 'year=' + selected_date_year + '&month=' + selected_date_month + '&day=' + selected_date_date + '&location=' + selected_location + '&action=rtb_get_available_time_slots';
246
+ jQuery.post( ajaxurl, data, function( response ) {
247
+ if ( ! response ) {
248
+ rtb_booking_form.timepicker.set( 'disable', true );
249
+
250
+ return;
251
+ }
252
+
253
+ var additional_valid_times = jQuery.parseJSON( response );
254
+ var all_valid_times = valid_times.concat( additional_valid_times );
255
+ rtb_booking_form.timepicker.set( 'disable', all_valid_times );
256
+ });
257
+ }
258
+
259
+ else {
260
+ // Check if this date is an exception to the rules
261
+ if ( typeof rtb_pickadate.schedule_closed !== 'undefined' ) {
262
+
263
+ var excp_date = [];
264
+ var excp_start_date = [];
265
+ var excp_start_time = [];
266
+ var excp_end_date = [];
267
+ var excp_end_time = [];
268
+ for ( var closed_key in rtb_pickadate.schedule_closed ) {
269
+
270
+ excp_date = new Date( rtb_pickadate.schedule_closed[closed_key].date );
271
+ if ( excp_date.getFullYear() == selected_date_year &&
272
+ excp_date.getMonth() == selected_date_month &&
273
+ excp_date.getDate() == selected_date_date
274
+ ) {
275
+
276
+ // Closed all day
277
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time == 'undefined' ) {
278
+ rtb_booking_form.timepicker.set( 'disable', [ true ] );
279
+
280
+ return;
281
+ }
282
+
283
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time.start !== 'undefined' ) {
284
+ excp_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.start );
285
+ excp_start_time = [ excp_start_date.getHours(), excp_start_date.getMinutes() ];
286
+ } else {
287
+ excp_start_time = [ 0, 0 ]; // Start of the day
288
+ }
289
+
290
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time.end !== 'undefined' ) {
291
+ excp_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.end );
292
+ excp_end_time = [ excp_end_date.getHours(), excp_end_date.getMinutes() ];
293
+ } else {
294
+ excp_end_time = [ 24, 0 ]; // End of the day
295
+ }
296
+
297
+ excp_start_time = rtb_booking_form.get_earliest_time( excp_start_time, selected_date, current_date );
298
+
299
+ valid_times.push( { from: excp_start_time, to: excp_end_time, inverted: true } );
300
+ }
301
+ }
302
+
303
+ excp_date = excp_start_date = excp_start_time = excp_end_date = excp_end_time = null;
304
+
305
+ // Exit early if this date is an exception
306
+ if ( valid_times.length > 1 ) {
307
+ rtb_booking_form.timepicker.set( 'disable', valid_times );
308
+
309
+ return;
310
+ }
311
+ }
312
+
313
+ // Get any rules which apply to this weekday
314
+ if ( typeof rtb_pickadate.schedule_open != 'undefined' ) {
315
+
316
+ var selected_date_weekday = selected_date.getDay();
317
+
318
+ var weekdays = {
319
+ sunday: 0,
320
+ monday: 1,
321
+ tuesday: 2,
322
+ wednesday: 3,
323
+ thursday: 4,
324
+ friday: 5,
325
+ saturday: 6,
326
+ };
327
+
328
+ var rule_start_date = [];
329
+ var rule_start_time = [];
330
+ var rule_end_date = [];
331
+ var rule_end_time = [];
332
+ for ( var open_key in rtb_pickadate.schedule_open ) {
333
+
334
+ if ( typeof rtb_pickadate.schedule_open[open_key].weekdays !== 'undefined' ) {
335
+ for ( var weekdays_key in rtb_pickadate.schedule_open[open_key].weekdays ) {
336
+ if ( weekdays[weekdays_key] == selected_date_weekday ) {
337
+
338
+ // Closed all day
339
+ if ( typeof rtb_pickadate.schedule_open[open_key].time == 'undefined' ) {
340
+ rtb_booking_form.timepicker.set( 'disable', [ true ] );
341
+
342
+ return;
343
+ }
344
+
345
+ if ( typeof rtb_pickadate.schedule_open[open_key].time.start !== 'undefined' ) {
346
+ rule_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.start );
347
+ rule_start_time = [ rule_start_date.getHours(), rule_start_date.getMinutes() ];
348
+ } else {
349
+ rule_start_time = [ 0, 0 ]; // Start of the day
350
+ }
351
+
352
+ if ( typeof rtb_pickadate.schedule_open[open_key].time.end !== 'undefined' ) {
353
+ rule_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.end );
354
+ rule_end_time = rtb_booking_form.get_latest_viable_time( rule_end_date.getHours(), rule_end_date.getMinutes() );
355
+ } else {
356
+ rule_end_time = [ 24, 0 ]; // End of the day
357
+ }
358
+
359
+ rule_start_time = rtb_booking_form.get_earliest_time( rule_start_time, selected_date, current_date );
360
+
361
+ valid_times.push( { from: rule_start_time, to: rule_end_time, inverted: true } );
362
+
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ rule_start_date = rule_start_time = rule_end_date = rule_end_time = null;
369
+
370
+ // Pass any valid times located
371
+ if ( valid_times.length > 1 ) {
372
+ rtb_booking_form.timepicker.set( 'disable', valid_times );
373
+
374
+ return;
375
+ }
376
+
377
+ }
378
+
379
+ // Set it to always open if no rules have been defined
380
+ rtb_booking_form.timepicker.set( 'enable', true );
381
+ rtb_booking_form.timepicker.set( 'disable', false );
382
+ }
383
+
384
+ return;
385
+ };
386
+
387
+ /**
388
+ * Get the outer times to exclude based on the time interval
389
+ *
390
+ * This is a work-around for a bug in pickadate.js
391
+ * See: https://github.com/amsul/pickadate.js/issues/614
392
+ */
393
+ rtb_booking_form.get_outer_time_range = function() {
394
+
395
+ var interval = rtb_booking_form.timepicker.get( 'interval' );
396
+
397
+ var hour = 24;
398
+
399
+ while ( interval >= 60 ) {
400
+ hour--;
401
+ interval -= 60;
402
+ }
403
+
404
+ if ( interval > 0 ) {
405
+ hour--;
406
+ interval = 60 - interval;
407
+ }
408
+
409
+ return { from: [0, 0], to: [ hour, interval ] };
410
+ };
411
+
412
+ /**
413
+ * Get the latest working opening hour/minute value
414
+ *
415
+ * This is a workaround for a bug in pickadate.js. The end time of a valid
416
+ * time value must NOT fall within the last timepicker interval and midnight
417
+ * See: https://github.com/amsul/pickadate.js/issues/614
418
+ */
419
+ rtb_booking_form.get_latest_viable_time = function( hour, minute ) {
420
+
421
+ var interval = rtb_booking_form.timepicker.get( 'interval' );
422
+
423
+ var outer_time_range = this.get_outer_time_range();
424
+
425
+ /*
426
+ * Adjust the last time for wide intervals, so that the last time entered
427
+ * corresponds to an interval time. A pickadate bug causes a later time to
428
+ * be available for booking otherwise.
429
+ */
430
+ if ( interval > 60) {
431
+
432
+ var last_hour = 0;
433
+ var last_minute = 0;
434
+ var last_time_minutes = 0;
435
+
436
+ var end_time_minutes = 60 * hour + minute;
437
+
438
+ while ( ( last_time_minutes + interval ) <= end_time_minutes ) {
439
+
440
+ var remainder = interval + last_minute;
441
+
442
+ while ( remainder >= 60 ) {
443
+ last_hour++;
444
+ remainder -= 60;
445
+ }
446
+
447
+ last_minute = remainder;
448
+
449
+ last_time_minutes = 60 * last_hour + last_minute;
450
+ }
451
+
452
+ var long_interval_viable_time = [ last_hour, last_minute ];
453
+ }
454
+
455
+
456
+ if ( interval > 60 ) {
457
+
458
+ return long_interval_viable_time;
459
+ }
460
+ else if ( hour > outer_time_range.to[0] || minute > outer_time_range.to[1] ) {
461
+
462
+ return [ outer_time_range.to[0], outer_time_range.to[1] ];
463
+ } else {
464
+
465
+ return [ hour, minute ];
466
+ }
467
+ };
468
+
469
+ /**
470
+ * Get the earliest valid time
471
+ *
472
+ * This checks the valid time for the day and, if a current day, applies
473
+ * any late booking restrictions. It also ensures that times in the past
474
+ * are not availabe.
475
+ *
476
+ * @param array start_time
477
+ * @param array selected_date
478
+ * @param array current_date
479
+ */
480
+ rtb_booking_form.get_earliest_time = function( start_time, selected_date, current_date ) {
481
+
482
+ // Only make adjustments for current day selections
483
+ if ( selected_date.toDateString() !== current_date.toDateString() ) {
484
+ return start_time;
485
+ }
486
+
487
+ // Get the number of minutes after midnight to compare
488
+ var start_minutes = ( start_time[0] * 60 ) + start_time[1],
489
+ current_minutes = ( current_date.getHours() * 60 ) + current_date.getMinutes(),
490
+ late_booking_minutes;
491
+
492
+ start_minutes = start_minutes > current_minutes ? start_minutes : current_minutes;
493
+
494
+ if ( typeof rtb_pickadate.late_bookings === 'number' && rtb_pickadate.late_bookings % 1 === 0 ) {
495
+ late_booking_minutes = current_minutes + rtb_pickadate.late_bookings;
496
+ if ( late_booking_minutes > start_minutes ) {
497
+ start_minutes = late_booking_minutes;
498
+ }
499
+ }
500
+
501
+ start_time = [ Math.floor( start_minutes / 60 ), start_minutes % 60 ];
502
+
503
+ return start_time;
504
+ };
505
+
506
+ rtb_booking_form.update_party_size_select = function() {
507
+
508
+ if ( rtb_pickadate.enable_max_reservations && ( rtb_pickadate.max_people || rtb_pickadate.multiple_locations_enabled ) ) {
509
+ var partySelect = $('#rtb-party'),
510
+ selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '',
511
+ selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
512
+ selected_date_year = selected_date.getFullYear(),
513
+ selected_date_month = selected_date.getMonth(),
514
+ selected_date_date = selected_date.getDate(),
515
+ selected_time = rtb_booking_form.timepicker.get('value');
516
+
517
+ if ( ! selected_time ) { return; }
518
+
519
+ selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
520
+ selected_date_date = ('0' + selected_date_date).slice(-2);
521
+
522
+ //reset party size
523
+ partySelect.prop("selectedIndex", 0).change();
524
+
525
+ var data = 'year=' + selected_date_year + '&month=' + selected_date_month + '&day=' + selected_date_date + '&time=' + selected_time + '&location=' + selected_location + '&action=rtb_get_available_party_size';
526
+ jQuery.post( ajaxurl, data, function( response ) {
527
+ if ( ! response ) {
528
+ return;
529
+ }
530
+
531
+ response = jQuery.parseJSON(response);
532
+
533
+ var available_spots = response.available_spots;
534
+
535
+ partySelect.prop('disabled', false);
536
+
537
+ partySelect.find('> option').each(function() {
538
+ var that = $(this);
539
+ if (this.value > available_spots) {
540
+ that.prop('disabled', true);
541
+ } else {
542
+ that.prop('disabled', false);
543
+ }
544
+ });
545
+ });
546
+ }
547
+ }
548
+
549
+ rtb_booking_form.update_possible_tables = function() {
550
+
551
+ if ( rtb_pickadate.enable_tables ) {
552
+
553
+ var table_select = $('#rtb-table'),
554
+ party = $('#rtb-party').val(),
555
+ selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
556
+ selected_date_year = selected_date.getFullYear(),
557
+ selected_date_month = selected_date.getMonth(),
558
+ selected_date_date = selected_date.getDate(),
559
+ selected_time = rtb_booking_form.timepicker.get('value');
560
+
561
+ if ( ! selected_time || ! party ) { return; }
562
+
563
+ selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
564
+ selected_date_date = ('0' + selected_date_date).slice(-2);
565
+
566
+ //reset table selection
567
+ table_select.prop("selectedIndex", 0).change();
568
+
569
+ //remove table combinations
570
+ table_select.find('> option').each( function() {
571
+ if ( $( this ).val().indexOf( ',' ) !== -1 ) { $( this ).remove(); }
572
+ });
573
+
574
+ var booking_id = $( '.rtb-booking-form form input[name="ID"]').length ? $( '.rtb-booking-form form input[name="ID"]').val() : 0;
575
+
576
+ var data = 'booking_id=' + booking_id + '&year=' + selected_date_year + '&month=' + selected_date_month + '&day=' + selected_date_date + '&time=' + selected_time + '&party=' + party + '&action=rtb_get_available_tables';
577
+ jQuery.post( ajaxurl, data, function( response ) {
578
+ if ( ! response ) {
579
+ return;
580
+ }
581
+
582
+ response = jQuery.parseJSON(response);
583
+
584
+ var available_tables = response.available_tables;
585
+
586
+ table_select.prop('disabled', false);
587
+
588
+ table_select.find('> option').hide();
589
+ table_select.find('> option').attr('disabled', 'disabled');
590
+
591
+ jQuery.each(available_tables, function(index, element) {
592
+
593
+ if ( index.indexOf( ',' ) === -1 ) {
594
+ table_select.find('> option[value="' + index + '"]').show();
595
+ table_select.find('> option[value="' + index + '"]').removeAttr('disabled', 'disabled');
596
+ }
597
+ else {
598
+ table_select.append( '<option value="' + index + '">' + element + '</option>' );
599
+ }
600
+
601
+ });
602
+
603
+ if ( response.selected_table != -1 ) {
604
+ table_select.val( response.selected_table );
605
+ }
606
+
607
+ });
608
+ }
609
+
610
+ }
611
+
612
+ rtb_booking_form.init();
613
+ });
614
+
615
+ //Handle reservation modification
616
+ jQuery(document).ready(function() {
617
+ jQuery('.rtb-modification-toggle').on('click', function() {
618
+ jQuery('.rtb-modification-form, .rtb-booking-form-form').toggleClass('rtb-hidden');
619
+
620
+ if (jQuery('.rtb-modification-form').hasClass('rtb-hidden')) {
621
+ jQuery('.rtb-modification-toggle').html(rtb_booking_form_js_localize.want_to_modify);
622
+ }
623
+ else {
624
+ jQuery('.rtb-modification-toggle').html(rtb_booking_form_js_localize.make);
625
+ }
626
+ });
627
+
628
+ var modify_booking = function(ev) {
629
+ var booking_email = jQuery('input[name="rtb_modification_email"]').val();
630
+
631
+ var data = 'booking_email=' + booking_email + '&action=rtb_find_reservations';
632
+ jQuery.post(ajaxurl, data, function(response) {
633
+
634
+ if (response.success) {
635
+ var booking_html = '';
636
+ var guest_txt = '';
637
+ var pay_btn = '';
638
+
639
+ jQuery(response.data.bookings).each(function( index, val) {
640
+ pay_btn = '';
641
+ guest_txt = val.party > 1 ? rtb_booking_form_js_localize.guests : rtb_booking_form_js_localize.guest;
642
+
643
+ if('payment_pending' == val.status || 'payment_failed' == val.status) {
644
+ pay_btn = `
645
+ <div class="rtb-deposit-booking" data-bookingid="${val.ID}" data-bookingemail="${val.email}">
646
+ ${rtb_booking_form_js_localize.deposit}
647
+ </div>
648
+ `;
649
+ }
650
+
651
+ booking_html += `
652
+ <div class="rtb-cancel-booking-div">
653
+ <div class="rtb-cancel-booking" data-bookingid="${val.ID}" data-bookingemail="${val.email}">
654
+ ${rtb_booking_form_js_localize.cancel}
655
+ </div>
656
+ ${pay_btn}
657
+ <div class="rtb-booking-information">${val.datetime} - ${val.party} ${guest_txt} (${val.status_lbl})</div>
658
+ </div>
659
+ `;
660
+ });
661
+
662
+ jQuery('.rtb-bookings-results').html(booking_html);
663
+
664
+ cancellationHandler();
665
+ delayedPaymentHandler();
666
+ }
667
+ else {jQuery('.rtb-bookings-results').html(response.data.msg);}
668
+ });
669
+ };
670
+
671
+ jQuery(document).on('click', '.rtb-find-reservation-button', modify_booking);
672
+ jQuery(document).on('keypress', '.rtb-modification-form input', function (ev) {
673
+ // Capture enter key
674
+ if(13 == ev.which) {
675
+ ev.preventDefault();
676
+ modify_booking(ev);
677
+ }
678
+ });
679
+ });
680
+
681
+ function cancellationHandler() {
682
+ jQuery('.rtb-cancel-booking:not(.cancelled)').off('click');
683
+ jQuery('.rtb-cancel-booking:not(.cancelled)').on('click', function() {
684
+ var btn = jQuery(this);
685
+
686
+ if(btn.hasClass('processing')) {
687
+ return;
688
+ }
689
+
690
+ btn.addClass('processing');
691
+
692
+ var booking_id = btn.data('bookingid');
693
+ var booking_email = btn.data('bookingemail');
694
+
695
+ var data = {
696
+ 'booking_id': booking_id,
697
+ 'booking_email': booking_email,
698
+ 'action': 'rtb_cancel_reservations'
699
+ };
700
+
701
+ jQuery.post(ajaxurl, data, function(response) {
702
+ if (response.success) {
703
+ if (response.data.hasOwnProperty('cancelled_redirect')) {
704
+ window.location.href = response.data.cancelled_redirect;
705
+ }
706
+ else {
707
+ btn.off('click');
708
+ btn.addClass('cancelled');
709
+ btn.text(rtb_booking_form_js_localize.cancelled);
710
+ }
711
+ }
712
+ else {
713
+ btn.parent().after(`<p class="alert error">${response.data.msg}</p>`);
714
+ }
715
+
716
+ btn.removeClass('processing');
717
+ });
718
+ });
719
+ }
720
+
721
+ function delayedPaymentHandler() {
722
+ jQuery('.rtb-deposit-booking').off('click');
723
+ jQuery('.rtb-deposit-booking').on('click', function() {
724
+ var btn = jQuery(this);
725
+
726
+ if(btn.hasClass('processing')) {
727
+ return;
728
+ }
729
+
730
+ btn.addClass('processing');
731
+
732
+ var booking_id = btn.data('bookingid');
733
+ var booking_email = btn.data('bookingemail');
734
+
735
+ var data = {
736
+ 'booking_id': booking_id,
737
+ 'booking_email': booking_email,
738
+ 'payment': 'rtb-delayed-deposit'
739
+ };
740
+
741
+ let current_loc = window.location;
742
+ let params = new URLSearchParams();
743
+ Object.keys( data ).map( function( param ) { params.append( param, data[ param ] ) } );
744
+
745
+ window.location = current_loc.origin + current_loc.pathname + '?' + params.toString();
746
+
747
+ });
748
+ }
749
+
750
+ // Functions for the 'View Bookings' shortcode
751
+ jQuery(document).ready(function ($) {
752
+ jQuery('.rtb-view-bookings-form-date-selector').on('change', function() {
753
+ window.location.href = replaceUrlParam(window.location.href, 'date', jQuery(this).val());
754
+ });
755
+
756
+ jQuery('.rtb-edit-view-booking').on('click', function() {
757
+ jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').removeClass('rtb-hidden');
758
+
759
+ jQuery('.rtb-view-bookings-form-confirmation-div').data('bookingid', jQuery(this).data('bookingid'));
760
+
761
+ jQuery(this).prop('checked', false);
762
+ });
763
+
764
+ jQuery('.rtb-view-bookings-form-confirmation-accept').on('click', function() {
765
+ var booking_id = jQuery('.rtb-view-bookings-form-confirmation-div').data('bookingid');
766
+
767
+ var data = 'booking_id=' + booking_id + '&action=rtb_set_reservation_arrived';
768
+ jQuery.post(ajaxurl, data, function(response) {
769
+
770
+ if (response.success) {window.location.href = window.location.href}
771
+ else {jQuery('.rtb-view-bookings-form-confirmation-div').html(response.data.msg);}
772
+ });
773
+ });
774
+
775
+ jQuery('.rtb-view-bookings-form-confirmation-decline').on('click', function() {
776
+ jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
777
+ });
778
+ jQuery('.rtb-view-bookings-form-confirmation-background-div').on('click', function() {
779
+ jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
780
+ });
781
+ jQuery('#rtb-view-bookings-form-close').on('click', function() {
782
+ jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
783
+ });
784
+ });
785
+
786
+ function replaceUrlParam(url, paramName, paramValue)
787
+ {
788
+ if (paramValue == null) {
789
+ paramValue = '';
790
+ }
791
+ var pattern = new RegExp('\\b('+paramName+'=).*?(&|#|$)');
792
+ if (url.search(pattern)>=0) {
793
+ return url.replace(pattern,'$1' + paramValue + '$2');
794
+ }
795
+ url = url.replace(/[?#]$/,'');
796
+ return url + (url.indexOf('?')>0 ? '&' : '?') + paramName + '=' + paramValue;
797
  }
assets/js/columns.js CHANGED
@@ -1,11 +1,11 @@
1
- jQuery(document).ready(function($){
2
- $(function(){
3
- $(window).resize(function(){
4
- $('.rtb-booking-form form button').each(function(){
5
- var thisButton = $(this);
6
- var buttonHalfWidth = ( thisButton.outerWidth() / 2 );
7
- thisButton.css('margin-left', 'calc(50% - '+buttonHalfWidth+'px');
8
- });
9
- }).resize();
10
- });
11
  });
1
+ jQuery(document).ready(function($){
2
+ $(function(){
3
+ $(window).resize(function(){
4
+ $('.rtb-booking-form form button').each(function(){
5
+ var thisButton = $(this);
6
+ var buttonHalfWidth = ( thisButton.outerWidth() / 2 );
7
+ thisButton.css('margin-left', 'calc(50% - '+buttonHalfWidth+'px');
8
+ });
9
+ }).resize();
10
+ });
11
  });
assets/js/dashboard-review-ask.js CHANGED
@@ -1,62 +1,62 @@
1
- jQuery(document).ready(function($) {
2
- jQuery('.rtb-main-dashboard-review-ask').css('display', 'block');
3
-
4
- jQuery(document).on('click', '.rtb-main-dashboard-review-ask .notice-dismiss', function(event) {
5
- var data = 'ask_review_time=7&action=rtb_hide_review_ask';
6
- jQuery.post(ajaxurl, data, function() {});
7
- });
8
-
9
- jQuery('.rtb-review-ask-yes').on('click', function() {
10
- jQuery('.rtb-review-ask-feedback-text').removeClass('rtb-hidden');
11
- jQuery('.rtb-review-ask-starting-text').addClass('rtb-hidden');
12
-
13
- jQuery('.rtb-review-ask-no-thanks').removeClass('rtb-hidden');
14
- jQuery('.rtb-review-ask-review').removeClass('rtb-hidden');
15
-
16
- jQuery('.rtb-review-ask-not-really').addClass('rtb-hidden');
17
- jQuery('.rtb-review-ask-yes').addClass('rtb-hidden');
18
-
19
- var data = 'ask_review_time=7&action=rtb_hide_review_ask';
20
- jQuery.post(ajaxurl, data, function() {});
21
- });
22
-
23
- jQuery('.rtb-review-ask-not-really').on('click', function() {
24
- jQuery('.rtb-review-ask-review-text').removeClass('rtb-hidden');
25
- jQuery('.rtb-review-ask-starting-text').addClass('rtb-hidden');
26
-
27
- jQuery('.rtb-review-ask-feedback-form').removeClass('rtb-hidden');
28
- jQuery('.rtb-review-ask-actions').addClass('rtb-hidden');
29
-
30
- var data = 'ask_review_time=1000&action=rtb_hide_review_ask';
31
- jQuery.post(ajaxurl, data, function() {});
32
- });
33
-
34
- jQuery('.rtb-review-ask-no-thanks').on('click', function() {
35
- var data = 'ask_review_time=1000&action=rtb_hide_review_ask';
36
- jQuery.post(ajaxurl, data, function() {});
37
-
38
- jQuery('.rtb-main-dashboard-review-ask').css('display', 'none');
39
- });
40
-
41
- jQuery('.rtb-review-ask-review').on('click', function() {
42
- jQuery('.rtb-review-ask-feedback-text').addClass('rtb-hidden');
43
- jQuery('.rtb-review-ask-thank-you-text').removeClass('rtb-hidden');
44
-
45
- var data = 'ask_review_time=1000&action=rtb_hide_review_ask';
46
- jQuery.post(ajaxurl, data, function() {});
47
- });
48
-
49
- jQuery('.rtb-review-ask-send-feedback').on('click', function() {
50
- var feedback = jQuery('.rtb-review-ask-feedback-explanation textarea').val();
51
- var email_address = jQuery('.rtb-review-ask-feedback-explanation input[name="feedback_email_address"]').val();
52
- var data = 'feedback=' + feedback + '&email_address=' + email_address + '&action=rtb_send_feedback';
53
- jQuery.post(ajaxurl, data, function() {});
54
-
55
- var data = 'ask_review_time=1000&action=rtb_hide_review_ask';
56
- jQuery.post(ajaxurl, data, function() {});
57
-
58
- jQuery('.rtb-review-ask-feedback-form').addClass('rtb-hidden');
59
- jQuery('.rtb-review-ask-review-text').addClass('rtb-hidden');
60
- jQuery('.rtb-review-ask-thank-you-text').removeClass('rtb-hidden');
61
- });
62
  });
1
+ jQuery(document).ready(function($) {
2
+ jQuery('.rtb-main-dashboard-review-ask').css('display', 'block');
3
+
4
+ jQuery(document).on('click', '.rtb-main-dashboard-review-ask .notice-dismiss', function(event) {
5
+ var data = 'ask_review_time=7&action=rtb_hide_review_ask';
6
+ jQuery.post(ajaxurl, data, function() {});
7
+ });
8
+
9
+ jQuery('.rtb-review-ask-yes').on('click', function() {
10
+ jQuery('.rtb-review-ask-feedback-text').removeClass('rtb-hidden');
11
+ jQuery('.rtb-review-ask-starting-text').addClass('rtb-hidden');
12
+
13
+ jQuery('.rtb-review-ask-no-thanks').removeClass('rtb-hidden');
14
+ jQuery('.rtb-review-ask-review').removeClass('rtb-hidden');
15
+
16
+ jQuery('.rtb-review-ask-not-really').addClass('rtb-hidden');
17
+ jQuery('.rtb-review-ask-yes').addClass('rtb-hidden');
18
+
19
+ var data = 'ask_review_time=7&action=rtb_hide_review_ask';
20
+ jQuery.post(ajaxurl, data, function() {});
21
+ });
22
+
23
+ jQuery('.rtb-review-ask-not-really').on('click', function() {
24
+ jQuery('.rtb-review-ask-review-text').removeClass('rtb-hidden');
25
+ jQuery('.rtb-review-ask-starting-text').addClass('rtb-hidden');
26
+
27
+ jQuery('.rtb-review-ask-feedback-form').removeClass('rtb-hidden');
28
+ jQuery('.rtb-review-ask-actions').addClass('rtb-hidden');
29
+
30
+ var data = 'ask_review_time=1000&action=rtb_hide_review_ask';
31
+ jQuery.post(ajaxurl, data, function() {});
32
+ });
33
+
34
+ jQuery('.rtb-review-ask-no-thanks').on('click', function() {
35
+ var data = 'ask_review_time=1000&action=rtb_hide_review_ask';
36
+ jQuery.post(ajaxurl, data, function() {});
37
+
38
+ jQuery('.rtb-main-dashboard-review-ask').css('display', 'none');
39
+ });
40
+
41
+ jQuery('.rtb-review-ask-review').on('click', function() {
42
+ jQuery('.rtb-review-ask-feedback-text').addClass('rtb-hidden');
43
+ jQuery('.rtb-review-ask-thank-you-text').removeClass('rtb-hidden');
44
+
45
+ var data = 'ask_review_time=1000&action=rtb_hide_review_ask';
46
+ jQuery.post(ajaxurl, data, function() {});
47
+ });
48
+
49
+ jQuery('.rtb-review-ask-send-feedback').on('click', function() {
50
+ var feedback = jQuery('.rtb-review-ask-feedback-explanation textarea').val();
51
+ var email_address = jQuery('.rtb-review-ask-feedback-explanation input[name="feedback_email_address"]').val();
52
+ var data = 'feedback=' + feedback + '&email_address=' + email_address + '&action=rtb_send_feedback';
53
+ jQuery.post(ajaxurl, data, function() {});
54
+
55
+ var data = 'ask_review_time=1000&action=rtb_hide_review_ask';
56
+ jQuery.post(ajaxurl, data, function() {});
57
+
58
+ jQuery('.rtb-review-ask-feedback-form').addClass('rtb-hidden');
59
+ jQuery('.rtb-review-ask-review-text').addClass('rtb-hidden');
60
+ jQuery('.rtb-review-ask-thank-you-text').removeClass('rtb-hidden');
61
+ });
62
  });
assets/js/editor.js CHANGED
@@ -1,1236 +1,1236 @@
1
- /**
2
- * Custom fields editor for Custom Fields for Restaurant Reservations
3
- */
4
- var cffrtb_editor = cffrtb_editor || {};
5
-
6
- /**
7
- * Initialize the editor object after jQuery has loaded
8
- */
9
- jQuery(document).ready(function ($) {
10
-
11
- /**
12
- * jQuery reference for editor panel
13
- */
14
- cffrtb_editor.el = $( '#cffrtb-editor' );
15
-
16
- /**
17
- * Show the error modal
18
- */
19
- cffrtb_editor.show_error = function( msg ) {
20
-
21
- var rtb_error_modal = $( '#rtb-error-modal ' );
22
-
23
- rtb_error_modal.find( '.rtb-error-msg' ).html( msg );
24
- rtb_error_modal.addClass( 'is-visible' );
25
-
26
- $(document).keyup( function(e) {
27
- if ( e.which == '27' ) {
28
- cffrtb_editor.hide_error( rtb_error_modal );
29
- }
30
- });
31
-
32
- rtb_error_modal.click( function(e) {
33
- if ( $(e.target).is( rtb_error_modal ) || $(e.target).is( rtb_error_modal.find( 'a.button' ) ) ) {
34
-
35
- e.stopPropagation();
36
- e.preventDefault();
37
-
38
- cffrtb_editor.hide_error( rtb_error_modal );
39
- }
40
- });
41
- };
42
-
43
- /**
44
- * Hide the error modal
45
- */
46
- cffrtb_editor.hide_error = function( el ) {
47
- el.removeClass( 'is-visible' );
48
- el.off();
49
- };
50
-
51
- });
52
-
53
- /**
54
- * Initialize the field editor after jQuery has loaded
55
- */
56
- jQuery(document).ready(function ($) {
57
-
58
- /**
59
- * Manage the field editor
60
- */
61
- cffrtb_editor.editor = {
62
-
63
- el: $( '#cffrtb-field-editor' ),
64
-
65
- option_el: $( '#cffrtb-field-editor-option' ),
66
-
67
- init: function() {
68
-
69
- // Store common form references
70
- this.form = {
71
- el: this.el.find( '#cffrtb-field-editor-form' ),
72
- id: this.el.find( 'input[name="id"]' ),
73
- type: this.el.find( 'input[name="type"]' ),
74
- subtype: this.el.find( 'input[name="subtype"]' ),
75
- };
76
-
77
- // Show field editor option modal
78
- $( '.add-field' ).click( function(e) {
79
- e.stopPropagation();
80
- e.preventDefault();
81
-
82
- cffrtb_editor.editor.show_option();
83
- });
84
-
85
- // Register click events on options modal
86
- this.option_el.click( function(e) {
87
- e.stopPropagation();
88
- e.preventDefault();
89
-
90
- var target = $( e.target );
91
-
92
- if ( target.hasClass( 'field' ) ) {
93
- cffrtb_editor.editor.hide_option();
94
- cffrtb_editor.editor.show_editor();
95
-
96
- } else if ( target.hasClass( 'fieldset' ) ) {
97
- cffrtb_editor.editor.hide_option();
98
- cffrtb_editor.editor.show_editor( 'fieldset' );
99
-
100
- } else if ( target.is( cffrtb_editor.editor.option_el ) ) {
101
- cffrtb_editor.editor.hide_option();
102
- }
103
- });
104
-
105
- // Close field editor modal when background is clicked
106
- this.el.click( function(e) {
107
- if ( $( e.target ).is( cffrtb_editor.editor.el ) ) {
108
- cffrtb_editor.editor.hide_editor();
109
- }
110
- });
111
-
112
- // Close field editor modal when ESC is keyed
113
- $(document).keyup( function(e) {
114
- if ( e.which == '27' ) {
115
- cffrtb_editor.editor.hide_editor();
116
- }
117
- });
118
-
119
- // Form actions
120
- this.form.el.find( '> .actions' ).on( 'click', function(e) {
121
-
122
- e.stopPropagation();
123
- e.preventDefault();
124
-
125
- var target = $( e.target );
126
-
127
- // Exit early if the actions are disabled
128
- if ( typeof target.attr( 'disabled' ) !== 'undefined' ) {
129
- return;
130
- }
131
-
132
- // Save field
133
- if ( target.hasClass( 'save' ) ) {
134
- cffrtb_editor.editor.save_field();
135
-
136
- // Cancel and close editor
137
- } else if ( target.hasClass( 'cancel' ) ) {
138
- cffrtb_editor.editor.hide_editor();
139
- }
140
- });
141
-
142
- // Field type selections
143
- this.form.el.find( '.type .selector' ).on( 'click', function(e) {
144
-
145
- var target = $( e.target );
146
-
147
- if ( target.get(0).tagName != 'A' ) {
148
- return;
149
- }
150
-
151
- var level = target.parent().parent();
152
-
153
- if ( target.parent().parent().hasClass( 'types' ) ) {
154
- cffrtb_editor.editor.set_type( target.data( 'type' ), target );
155
-
156
- } else {
157
- cffrtb_editor.editor.set_subtype( target.data( 'subtype' ), target );
158
- }
159
-
160
- });
161
-
162
- // Add an option
163
- this.el.find( '.settings-panel.options .add a' ).on( 'click', function(e) {
164
- e.stopPropagation();
165
- e.preventDefault();
166
- cffrtb_editor.editor.add_option();
167
- });
168
-
169
- // Add an option with ENTER key
170
- this.get_add_option_el().keyup( function(e) {
171
- if ( e.which == '13' ) {
172
- e.stopPropagation();
173
- e.preventDefault();
174
-
175
- cffrtb_editor.editor.add_option();
176
- }
177
- });
178
-
179
- // Remove an option
180
- this.get_options_list_el().on( 'click', function(e) {
181
- e.stopPropagation();
182
- e.preventDefault();
183
-
184
- var target = $( e.target );
185
-
186
- if( target.is( 'a, a .dashicons' ) ) {
187
- cffrtb_editor.editor.remove_option( target.closest( 'li' ) );
188
- }
189
- });
190
-
191
- // Make the option list sortable
192
- this.get_options_list_el().sortable({
193
- placeholder: 'cffrtb-editor-options-placeholder',
194
- delay: 250
195
- });
196
-
197
- },
198
-
199
- /**
200
- * Get the add option input element
201
- */
202
- get_add_option_el: function() {
203
-
204
- if ( typeof this.form.add_option == 'undefined' ) {
205
- this.form.add_option = this.el.find( '.settings-panel.options .add input' );
206
- }
207
-
208
- return this.form.add_option;
209
- },
210
-
211
- /**
212
- * Get the options list element
213
- */
214
- get_options_list_el: function() {
215
-
216
- if ( typeof this.form.options_list == 'undefined' ) {
217
- this.form.options_list = this.el.find( '.settings-panel.options .options' );
218
- }
219
-
220
- return this.form.options_list;
221
- },
222
-
223
- /**
224
- * Update the editor values with a new field object
225
- */
226
- update_editor_values: function( field ) {
227
-
228
- this.form.id.val( field.ID );
229
- this.set_type( field.type );
230
- this.set_subtype( field.subtype );
231
- this.form.el.find( 'input[name="title"]' ).val( field.title );
232
-
233
- if ( field.required ) {
234
- this.form.el.find( '.required input' ).attr( 'checked', 'checked' );
235
- }
236
-
237
- if ( field.options && Object.keys( field.options ).length ) {
238
- var options = '';
239
- for( var i in field.options ) {
240
- if ( field.options[i].disabled ) {
241
- continue;
242
- }
243
- options += '<li data-id="' + field.options[i].id + '"><a href="#"><span class="dashicons dashicons-dismiss"></span></a> <span class="value">' + field.options[i].value + '</span></li>';
244
- }
245
- this.form.el.find( '.settings-panel.options .options' ).html( options );
246
- }
247
-
248
- this.el.trigger( 'cffrtb_update_editor_values', field );
249
- },
250
-
251
- /**
252
- * Show the field/fieldset type selection before opening the editor
253
- */
254
- show_option: function() {
255
-
256
- this.option_el.addClass( 'is-visible' );
257
- $( 'body' ).addClass( 'rtb-hide-body-scroll' );
258
-
259
- },
260
-
261
- /**
262
- * Hide the field/fieldset type selection
263
- */
264
- hide_option: function() {
265
-
266
- this.option_el.removeClass( 'is-visible' );
267
- $( 'body' ).removeClass( 'rtb-hide-body-scroll' );
268
-
269
- },
270
-
271
- /**
272
- * Show the editor
273
- */
274
- show_editor: function( mode ) {
275
-
276
- if ( mode === 'edit' ) {
277
- this.form.el.find( '> .title > h2' ).html( cffrtb_editor.strings.editor_edit_field );
278
- this.form.el.find( '.actions a.save' ).html( cffrtb_editor.strings.editor_save_field );
279
- } else if ( mode == 'fieldset' ) {
280
- this.form.el.find( '> .title > h2' ).html( cffrtb_editor.strings.editor_add_fieldset );
281
- this.form.el.find( '.actions a.save' ).addClass( 'fieldset' ).html( cffrtb_editor.strings.editor_save_fieldset );
282
- this.form.el.addClass( mode );
283
- this.form.type.val( 'fieldset' );
284
- this.form.subtype.val( 'fieldset' );
285
- } else {
286
- this.form.el.find( '> .title > h2' ).html( cffrtb_editor.strings.editor_add_field );
287
- this.form.el.find( '.actions a.save' ).html( cffrtb_editor.strings.editor_add_field );
288
- }
289
-
290
- this.el.addClass( 'is-visible' );
291
- $( 'body' ).addClass( 'rtb-hide-body-scroll' );
292
- },
293
-
294
- /**
295
- * Hide the editor
296
- */
297
- hide_editor: function() {
298
- this.el.removeClass( 'is-visible' );
299
- this.option_el.removeClass( 'is-hidden' );
300
- this.form.el.addClass( 'is-hidden' );
301
- this.form.el.removeClass( 'fieldset' );
302
- this.form.el.find( '.actions a.save' ).removeClass( 'fieldset' );
303
- $( 'body' ).removeClass( 'rtb-hide-body-scroll' );
304
- this.form.el.find( 'input, select, textarea' ).not( 'input[type="checkbox"]' ).val( '' );
305
- this.form.el.find( 'input[type="checkbox"]' ).removeAttr( 'checked' );
306
- this.get_options_list_el().empty();
307
- this.set_type( cffrtb_editor.default_type );
308
- this.set_subtype( cffrtb_editor.default_subtype );
309
- },
310
-
311
- /**
312
- * Set the type and select the new subtype
313
- */
314
- set_type: function( type, el ) {
315
-
316
- if ( type == this.form.type.val() ) {
317
- return;
318
- }
319
-
320
- // Set the value
321
- this.form.type.val( type );
322
-
323
- // Remove the `current` class from the selection
324
- this.el.find( '.type .types a' ).removeClass( 'current' );
325
-
326
- // Add the `current` class to this selection
327
- if ( typeof el == 'undefined' ) {
328
- el = this.el.find( '.type .types .' + type );
329
- }
330
- el.addClass( 'current' );
331
-
332
- // Show the settings if they exist
333
- this.el.find( '.settings-panel' ).each( function() {
334
- if ( $(this).hasClass( type ) ) {
335
- $(this).addClass( 'current' );
336
- } else {
337
- $(this).removeClass( 'current' );
338
- }
339
- });
340
-
341
- // Show the subtype list
342
- this.el.find( '.type .subtypes' ).each( function() {
343
- if ( $(this).hasClass( type ) ) {
344
- $(this).addClass( 'current' );
345
- } else {
346
- $(this).removeClass( 'current' );
347
- }
348
- });
349
-
350
- // Trigger a click on the first subtype for this type
351
- this.el.find( '.subtypes.' + type + ' li' ).first().find( 'a' ).trigger( 'click' );
352
- },
353
-
354
- /**
355
- * Select a subtype
356
- */
357
- set_subtype: function( subtype, el ) {
358
-
359
- if ( subtype == this.form.subtype.val() ) {
360
- return;
361
- }
362
-
363
- // Set the value
364
- this.form.subtype.val( subtype );
365
-
366
- // Remove the `current` class from the selection
367
- this.el.find( '.type .subtypes a' ).removeClass( 'current' );
368
-
369
- // Apply the `current` class to the right subtype
370
- if ( typeof el == 'undefined' ) {
371
- el = this.el.find( '.type .subtypes .' + subtype );
372
- }
373
- el.addClass( 'current' );
374
-
375
- },
376
-
377
- /**
378
- * Add an option to the list
379
- */
380
- add_option: function() {
381
- var list = this.get_options_list_el();
382
- var option = this.get_add_option_el();
383
-
384
- this.get_options_list_el().append( '<li><a href="#"><span class="dashicons dashicons-dismiss"></span></a> <span class="value">' + this.get_add_option_el().val() + '</span></li>' );
385
- this.get_add_option_el().val( '' );
386
-
387
- // Scroll the options list if they have more than 10
388
- if ( this.get_options_list_el().find( 'li' ).length > 10 ) {
389
- this.get_options_list_el().addClass( 'scroll' );
390
- }
391
- },
392
-
393
- /**
394
- * Remove an option from the list
395
- */
396
- remove_option: function( el ) {
397
-
398
- el.fadeOut( '200', function() {
399
- $(this).remove();
400
-
401
- // Remove scrollbar if the options list is less than 10 options
402
- if ( cffrtb_editor.editor.get_options_list_el().find( 'li' ).length <= 10 ) {
403
- cffrtb_editor.editor.get_options_list_el().removeClass( 'scroll' );
404
- }
405
- });
406
- },
407
-
408
- /**
409
- * Disable actions
410
- */
411
- disable_actions: function() {
412
- this.form.el.find( '.actions' ).addClass( 'working' ).find( '.save, .cancel' ).attr( 'disabled', 'disabled' );
413
- },
414
-
415
- /**
416
- * Enable actions
417
- */
418
- enable_actions: function() {
419
- this.form.el.find( '.actions' ).removeClass( 'working' ).find( '.save, .cancel' ).removeAttr( 'disabled' );
420
- },
421
-
422
- /**
423
- * Load field
424
- */
425
- load_field: function( item_slug ) {
426
-
427
- // Don't trigger if we're already saving
428
- if ( cffrtb_editor.list.get_title_el( item_slug ).hasClass( 'saving' ) ) {
429
- return;
430
- }
431
-
432
- var id = cffrtb_editor.list.items[item_slug].el.data( 'id' );
433
- if ( typeof id == 'undefined' ) {
434
- return;
435
- }
436
-
437
- cffrtb_editor.list.disable_sorting();
438
- cffrtb_editor.list.get_title_el( item_slug ).addClass( 'saving' );
439
- cffrtb_editor.list.get_title_el( item_slug ).find( '.view .controls' ).prepend( '<span class="load-spinner"></span>' );
440
-
441
- var params = {};
442
-
443
- params.action = 'cffrtb-load-field';
444
- params.nonce = cffrtb_editor.ajax_nonce;
445
- params.ID = id;
446
-
447
- var data = $.param( params );
448
-
449
- $.post( ajaxurl, data, function( r ) {
450
-
451
- if ( r.success ) {
452
- cffrtb_editor.editor.update_editor_values( r.data.field );
453
- cffrtb_editor.editor.show_editor( 'edit' );
454
-
455
- } else {
456
- if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
457
- cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
458
- } else {
459
- cffrtb_editor.show_error( r.data.msg );
460
- }
461
- }
462
-
463
- // Reset status
464
- cffrtb_editor.list.enable_sorting();
465
- cffrtb_editor.list.get_title_el( item_slug ).removeClass( 'saving' );
466
- cffrtb_editor.list.get_title_el( item_slug ).find( '.view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
467
- });
468
-
469
- },
470
-
471
- /**
472
- * Save a field
473
- */
474
- save_field: function() {
475
-
476
- this.disable_actions();
477
-
478
- var params = {};
479
-
480
- params.action = 'cffrtb-save-field';
481
- params.nonce = cffrtb_editor.ajax_nonce;
482
- params.request = 'save_field';
483
- params.field = {
484
- ID: this.form.id.val(),
485
- type: this.form.type.val(),
486
- subtype: this.form.subtype.val(),
487
- title: this.form.el.find( 'input[name="title"]' ).val(),
488
- };
489
-
490
- if ( this.form.el.find( 'input[name="required"]' ).is( ':checked' ) ) {
491
- params.field.required = 1;
492
- } else {
493
- params.field.required = 0;
494
- }
495
-
496
- if ( params.field.type == 'options' ) {
497
- params.field.options = {};
498
- this.get_options_list_el().find( 'li' ).each( function(i) {
499
-
500
- var id = $(this).data( 'id' );
501
- if ( typeof id == 'undefined' ) {
502
- id = 'new-' + Math.random().toString(36).substring(7);
503
- }
504
-
505
- params.field.options[i] = {
506
- id: id,
507
- value: $(this).find( '.value' ).html(),
508
- order: i
509
- };
510
- });
511
- }
512
-
513
- // Perform some basic validation checks here
514
- var errors = this.validate_field( params.field );
515
- if ( typeof errors !== 'undefined' ) {
516
- cffrtb_editor.show_error( errors );
517
- this.enable_actions();
518
-
519
- return;
520
- }
521
-
522
- var data = $.param( params );
523
-
524
- $.post( ajaxurl, data, function( r ) {
525
-
526
- if ( r.success ) {
527
- cffrtb_editor.editor.hide_editor();
528
- cffrtb_editor.list.add_item( r.data.field, r.data.ID, r.data.is_new_field, r.data.type );
529
-
530
- } else {
531
- if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
532
- cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
533
- } else {
534
- console.log( r.data );
535
- cffrtb_editor.show_error( r.data.msg );
536
- }
537
- }
538
-
539
- cffrtb_editor.editor.enable_actions();
540
- });
541
- },
542
-
543
- /**
544
- * Some quick validation on the field data before sending it off
545
- */
546
- validate_field: function( field ) {
547
-
548
- if ( field.title.length === 0 || !field.title.trim() ) {
549
- return cffrtb_editor.strings.field_missing_title;
550
- }
551
-
552
- if ( field.type == 'options' && field.options.length === 0 ) {
553
- return cffrtb_editor.strings.field_missing_options;
554
- }
555
- }
556
- };
557
-
558
- /**
559
- * Initialize the editor
560
- */
561
- cffrtb_editor.editor.init();
562
-
563
- });
564
-
565
- /**
566
- * Initialize the fields list object after jQuery has loaded
567
- */
568
- jQuery(document).ready(function ($) {
569
-
570
- /**
571
- * Manage the list of fields
572
- */
573
- cffrtb_editor.list = {
574
-
575
- // list element
576
- el: cffrtb_editor.el.find( '#cffrtb-list' ),
577
-
578
- // fieldsets and fields.
579
- // just storing jQuery references to prevent duplicate lookups
580
- items: {},
581
-
582
- // flag to disable events during view->edit transition
583
- in_transition: false,
584
-
585
- // disabled fields list
586
- disabled_el: cffrtb_editor.el.find( '#cffrtb-disabled' ),
587
-
588
- init: function() {
589
-
590
- // Store jQuery references to prevent duplicate lookups
591
- this.el.find( '.fieldset, .field' ).each( function() {
592
- cffrtb_editor.list.items[ $(this).data( 'slug' ) ] = {
593
- el: $(this)
594
- };
595
- });
596
-
597
- // Clear pre-existing listeners
598
- this.el.off( 'click keyup' );
599
-
600
- // Register click listeners
601
- this.el.on( 'click', function( e ) {
602
-
603
- e.stopPropagation();
604
- e.preventDefault();
605
-
606
- var target = $( e.target );
607
-
608
- // Any click outside one of the field titles
609
- if ( !target.hasClass( 'title' ) && !target.parents().hasClass( 'title' ) ) {
610
- cffrtb_editor.list.save_all();
611
- return;
612
- }
613
-
614
- var item_slug = target.parents( '.fieldset, .field' ).first().data( 'slug' );
615
-
616
- // Open options panel
617
- if ( cffrtb_editor.list.is_target( target, 'options' ) ) {
618
- cffrtb_editor.list.save_all();
619
- cffrtb_editor.editor.load_field( item_slug );
620
-
621
- // Delete field
622
- } else if ( cffrtb_editor.list.is_target( target, 'delete' ) ) {
623
- cffrtb_editor.list.save_all();
624
- cffrtb_editor.list.delete_item( item_slug );
625
-
626
- // Open label editing panel
627
- } else if ( !cffrtb_editor.list.is_editing( item_slug ) ) {
628
- cffrtb_editor.list.show_edit( item_slug );
629
-
630
- // Save label
631
- } else if ( cffrtb_editor.list.is_target( target, 'save' ) ) {
632
- cffrtb_editor.list.save_label( item_slug );
633
-
634
- // Give focus to input when editing panel is active
635
- } else if ( target.hasClass( 'edit' ) ) {
636
- cffrtb_editor.list.set_focus( item_slug );
637
- }
638
-
639
- });
640
-
641
- // Save label with ENTER key
642
- this.el.keyup( function(e) {
643
-
644
- if ( !cffrtb_editor.list.in_transition && e.which == '13' ) {
645
-
646
- var target = $( e.target );
647
-
648
- if ( target.is( 'input:focus' ) ) {
649
-
650
- e.stopPropagation();
651
- e.preventDefault();
652
-
653
- cffrtb_editor.list.save_label( target.parents( '.fieldset, .field' ).first().data( 'slug' ) );
654
- }
655
- }
656
- });
657
-
658
- // Make the list sortable
659
- this.el.sortable({
660
- placeholder: 'cffrtb-list-placeholder',
661
- delay: 250,
662
- update: this.sorting_complete
663
- });
664
- this.el.find( '.fieldset ul' ).sortable({
665
- placeholder: 'cffrtb-list-placeholder',
666
- connectWith: '#cffrtb-list .fieldset ul',
667
- delay: 250,
668
- update: this.sorting_complete
669
- });
670
-
671
- // Clear pre-existing listeners
672
- this.disabled_el.off( 'click' );
673
-
674
- // Register click events on disabled fields
675
- this.disabled_el.on( 'click', function(e) {
676
-
677
- e.stopPropagation();
678
- e.preventDefault();
679
-
680
- var target = $( e.target );
681
-
682
- // Restore field
683
- if ( cffrtb_editor.list.is_target( target, 'enable' ) ) {
684
- cffrtb_editor.list.enable_item( target.parents( '.fieldset, .field' ).first() );
685
-
686
- // Open learn more text
687
- } else if ( cffrtb_editor.list.is_target( target, 'learn-more' ) ) {
688
- cffrtb_editor.list.disabled_el.find( '.reset .description' ).addClass( 'is-visible' );
689
-
690
- // Revert to default
691
- } else if ( cffrtb_editor.list.is_target( target, 'reset-all' ) ) {
692
- cffrtb_editor.list.reset_all();
693
- }
694
- });
695
-
696
- },
697
-
698
- /**
699
- * Is the field being edited?
700
- */
701
- is_editing: function( item_slug ) {
702
- return this.get_title_el( item_slug ).hasClass( 'editing' );
703
- },
704
-
705
- /**
706
- * Is the click target opening the field options?
707
- */
708
- is_target: function( target, match ) {
709
- return target.hasClass( match ) || target.parents().hasClass( match );
710
- },
711
-
712
- /**
713
- * Get the title element for an item
714
- */
715
- get_title_el: function( item_slug ) {
716
-
717
- if ( typeof this.items[item_slug].title == 'undefined' ) {
718
- this.items[item_slug].title = this.items[item_slug].el.find( '> .title' );
719
- }
720
-
721
- return this.items[item_slug].title;
722
- },
723
-
724
- /**
725
- * Get the edit element for an item
726
- */
727
- get_edit_el: function( item_slug ) {
728
-
729
- if ( typeof this.items[item_slug].title == 'undefined' ) {
730
- this.items[item_slug].edit = this.items[item_slug].el.find( '> .title .edit' );
731
- }
732
-
733
- return this.items[item_slug].edit;
734
- },
735
-
736
- /**
737
- * Get the value of the input field
738
- */
739
- get_input: function( item_slug ) {
740
- return this.get_input_el( item_slug ).val();
741
- },
742
-
743
- /**
744
- * Get the input element of a field
745
- */
746
- get_input_el: function( item_slug ) {
747
-
748
- if ( typeof this.items[item_slug].input == 'undefined' ) {
749
- this.items[item_slug].input = this.items[item_slug].el.find( '> .title .edit input' );
750
- }
751
-
752
- return this.items[item_slug].input;
753
- },
754
-
755
- /**
756
- * Get the fieldset slug an item is attached to
757
- */
758
- get_fieldset: function( item_slug ) {
759
-
760
- if ( this.items[item_slug].el.hasClass( 'fieldset' ) ) {
761
- return item_slug;
762
- } else {
763
- return this.items[item_slug].el.parents( '.fieldset' ).first().data( 'slug' );
764
- }
765
- },
766
-
767
- /**
768
- * Update the value of the label in the view
769
- */
770
- update_view: function( item_slug ) {
771
-
772
- if ( typeof this.items[item_slug].view_value == 'undefined' ) {
773
- this.items[item_slug].view_value = this.items[item_slug].el.find( '> .title .view .value' );
774
- }
775
-
776
- this.items[item_slug].view_value.html( this.get_input( item_slug ) );
777
- },
778
-
779
- /**
780
- * Open an item's edit mode
781
- */
782
- show_edit: function( item_slug ) {
783
-
784
- // Save and close any other labels being edited
785
- this.save_all();
786
-
787
- // Set transition flag and timer
788
- this.in_transition = true;
789
- setTimeout( this.clear_transition_flag, 600 );
790
-
791
- // Open edit mode for this item
792
- this.enable_tabbing( item_slug );
793
- this.get_title_el( item_slug ).addClass( 'editing' );
794
- this.set_focus( item_slug );
795
- },
796
-
797
- /**
798
- * Focus and select the input field
799
- */
800
- set_focus: function( item_slug ) {
801
- this.get_input_el( item_slug ).focus().select();
802
- },
803
-
804
- /**
805
- * Clear the transition flag used when opening the editing panel
806
- */
807
- clear_transition_flag: function() {
808
- cffrtb_editor.list.in_transition = false;
809
- },
810
-
811
- /**
812
- * Return an item to view mode
813
- */
814
- show_view: function( item_slug ) {
815
- this.get_title_el( item_slug ).removeClass( 'editing' );
816
- this.disable_tabbing( item_slug );
817
- this.set_focus( item_slug );
818
- },
819
-
820
- /**
821
- * Disable tabbing through a hidden edit interface
822
- */
823
- disable_tabbing: function( item_slug ) {
824
- this.get_title_el( item_slug ).find( '> .edit input, > .edit .save' ).attr( 'tabindex', '-1' );
825
- },
826
-
827
- /**
828
- * Enable tabbing through a hidden edit interface
829
- */
830
- enable_tabbing: function( item_slug ) {
831
- this.get_title_el( item_slug ).find( '> .edit input, > .edit .save' ).removeAttr( 'tabindex' );
832
- },
833
-
834
- /**
835
- * Disable drag and drop sorting
836
- */
837
- disable_sorting: function() {
838
- this.el.sortable( 'option', 'disabled', true );
839
- this.el.find( '.fieldset ul' ).sortable( 'option', 'disabled', true );
840
- },
841
-
842
- /**
843
- * Enable drag and drop sorting
844
- */
845
- enable_sorting: function() {
846
- this.el.sortable( 'option', 'disabled', false );
847
- this.el.find( '.fieldset ul' ).sortable( 'option', 'disabled', false );
848
- },
849
-
850
- /**
851
- * Save and close any fields being edited
852
- */
853
- save_all: function() {
854
- this.el.find( '.title.editing' ).each( function() {
855
- cffrtb_editor.list.save_label( $(this ).parent().data( 'slug' ) );
856
- });
857
- },
858
-
859
- /**
860
- * Save an item's label
861
- */
862
- save_label: function( item_slug ) {
863
-
864
- // Don't trigger if we're already saving
865
- if ( this.get_title_el( item_slug ).hasClass( 'saving' ) ) {
866
- return;
867
- }
868
-
869
- // Indicate status
870
- this.get_title_el( item_slug ).addClass( 'saving' );
871
- this.get_input_el( item_slug ).attr( 'disabled', 'disabled' );
872
-
873
- var params = {};
874
-
875
- params.action = 'cffrtb-save-field';
876
- params.nonce = cffrtb_editor.ajax_nonce;
877
- params.request = 'save_label';
878
- params.field = {
879
- slug: item_slug,
880
- title: this.get_input( item_slug ),
881
- fieldset: this.get_fieldset( item_slug )
882
- };
883
-
884
- if( this.items[item_slug].el.data( 'id' ) ) {
885
- params.field.ID = this.items[item_slug].el.data( 'id' );
886
- }
887
-
888
- var data = $.param( params );
889
-
890
- $.post( ajaxurl, data, function( r ) {
891
-
892
- if ( r.success ) {
893
- cffrtb_editor.list.update_view( item_slug );
894
- cffrtb_editor.list.show_view( item_slug );
895
-
896
- } else {
897
- if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
898
- cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
899
- } else {
900
- console.log( r.data );
901
- cffrtb_editor.show_error( r.data.msg );
902
- }
903
- }
904
-
905
- // Reset status
906
- cffrtb_editor.list.get_title_el( item_slug ).removeClass( 'saving' );
907
- cffrtb_editor.list.get_input_el( item_slug ).removeAttr( 'disabled' );
908
- });
909
- },
910
-
911
- /**
912
- * Sorting complete
913
- */
914
- sorting_complete: function( event, ui ) {
915
- cffrtb_editor.list.save_sort( $( ui.item.context ) );
916
- },
917
-
918
- /**
919
- * Save the sort order after it's been changed
920
- */
921
- save_sort: function( target ) {
922
-
923
- if ( cffrtb_editor.el.hasClass( 'saving-order' ) ) {
924
- return;
925
- }
926
-
927
- // Indicate status
928
- cffrtb_editor.list.disable_sorting();
929
- cffrtb_editor.el.addClass( 'saving-order' );
930
- target.find( '> .title .view .controls' ).prepend( '<span class="load-spinner"></span>' );
931
-
932
- var params = {};
933
-
934
- params.action = 'cffrtb-save-order';
935
- params.nonce = cffrtb_editor.ajax_nonce;
936
- params.order = [];
937
-
938
- var i = 0;
939
- cffrtb_editor.list.el.find( '> li' ).each( function() {
940
- params.order.push( cffrtb_editor.list.get_item_order_obj( $(this), i ) );
941
- i++;
942
-
943
- $(this).find( '> ul > li' ).each( function() {
944
- params.order.push( cffrtb_editor.list.get_item_order_obj( $(this), i ) );
945
- i++;
946
- });
947
- });
948
-
949
- var data = $.param( params );
950
-
951
- $.post( ajaxurl, data, function( r ) {
952
-
953
- if ( r.success ) {
954
-
955
- } else {
956
- if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
957
- cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
958
- } else {
959
- console.log( r.data );
960
- cffrtb_editor.show_error( r.data.msg );
961
- }
962
- }
963
-
964
- // Reset status
965
- cffrtb_editor.el.removeClass( 'saving-order' );
966
- target.find( '> .title .view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
967
- cffrtb_editor.list.enable_sorting();
968
- });
969
- },
970
-
971
- /**
972
- * Get an object with an elements order and slug/id data
973
- */
974
- get_item_order_obj: function( el, i ) {
975
-
976
- var item = {};
977
-
978
- if ( el.data( 'slug' ) ) {
979
- item.slug = el.data( 'slug' );
980
- }
981
-
982
- if ( el.data( 'id' ) ) {
983
- item.ID = el.data( 'id' );
984
- }
985
-
986
- item.fieldset = cffrtb_editor.list.get_fieldset( item.slug );
987
-
988
- item.order = i;
989
-
990
- return item;
991
- },
992
-
993
- /**
994
- * Add an item
995
- *
996
- * Expects to receive an HTML string for the new <li> element
997
- */
998
- add_item: function( html, ID, is_new_field, type ) {
999
-
1000
- if ( is_new_field ) {
1001
- if ( type == 'fieldset' ) {
1002
- this.el.find( '> .fieldset' ).last().after( html );
1003
- } else {
1004
- this.el.find( '> .fieldset' ).last().find( '> .fields' ).append( html );
1005
- }
1006
- } else {
1007
- this.el.find( '.fieldset, .field' ).each( function() {
1008
- if ( $(this).data( 'id' ) == ID ) {
1009
- $(this).html( html ).hide().fadeIn();
1010
- }
1011
- });
1012
- }
1013
-
1014
- this.init();
1015
- this.save_sort( this.el.find( '.fieldset, .field' ).last() );
1016
- },
1017
-
1018
- /**
1019
- * Delete or disable an item from the list of fields
1020
- */
1021
- delete_item: function( item_slug ) {
1022
-
1023
- if ( cffrtb_editor.el.hasClass( 'deleting' ) ) {
1024
- return;
1025
- }
1026
-
1027
- var item_el = this.items[item_slug].el;
1028
- var is_fieldset = item_el.hasClass( 'fieldset' );
1029
-
1030
- // Indicate status
1031
- cffrtb_editor.list.disable_sorting();
1032
- cffrtb_editor.el.addClass( 'deleting' );
1033
- item_el.find( '> .title .view .controls' ).prepend( '<span class="load-spinner"></span>' );
1034
-
1035
- // Can't remove a fieldset with fields
1036
- if ( is_fieldset && item_el.find( '.field' ).length ) {
1037
- cffrtb_editor.show_error( cffrtb_editor.strings.fieldset_not_empty );
1038
- item_el.find( '> .title .view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
1039
- cffrtb_editor.el.removeClass( 'deleting' );
1040
- cffrtb_editor.list.enable_sorting();
1041
- return;
1042
- }
1043
-
1044
- var id = item_el.data( 'id' );
1045
-
1046
- var params = {};
1047
-
1048
- params.action = 'cffrtb-delete-field';
1049
- params.nonce = cffrtb_editor.ajax_nonce;
1050
-
1051
- if ( id ) {
1052
- params.ID = id;
1053
- } else {
1054
- params.slug = item_slug;
1055
- params.fieldset = this.get_fieldset( item_slug );
1056
- }
1057
-
1058
- var data = $.param( params );
1059
-
1060
- $.post( ajaxurl, data, function( r ) {
1061
-
1062
- if ( r.success ) {
1063
-
1064
- item_el.fadeOut( 400, function() { $(this).remove(); });
1065
-
1066
- if ( typeof r.data !== 'undefined' && typeof r.data.field !== 'undefined' ) {
1067
-
1068
- if ( !cffrtb_editor.list.disabled_el.find( '.fields' ).length ) {
1069
- cffrtb_editor.list.disabled_el.find( '.no-disabled-fields' ).remove();
1070
- cffrtb_editor.list.disabled_el.find( '.reset' ).before( '<ul class="fields"></ul>' );
1071
- }
1072
-
1073
- cffrtb_editor.list.disabled_el.find( '.fields' ).append( r.data.field );
1074
-
1075
- cffrtb_editor.list.disabled_el.find( '.reset' ).addClass( 'is-visible' );
1076
- }
1077
-
1078
- } else {
1079
-
1080
- if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
1081
- cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
1082
- } else {
1083
- console.log( r.data );
1084
- cffrtb_editor.show_error( r.data.msg );
1085
- }
1086
-
1087
- item_el.find( '> .title .view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
1088
- }
1089
-
1090
- // Reset status
1091
- cffrtb_editor.el.removeClass( 'deleting' );
1092
- cffrtb_editor.list.enable_sorting();
1093
- });
1094
- },
1095
-
1096
- /**
1097
- * Enable a field that has been disabled
1098
- */
1099
- enable_item: function( field ) {
1100
-
1101
- if ( field.hasClass( 'enabling' ) ) {
1102
- return;
1103
- }
1104
-
1105
- // Indicate status
1106
- field.addClass( 'enabling' );
1107
- field.find( '> .title .view .controls' ).prepend( '<span class="load-spinner"></span>' );
1108
-
1109
- var params = {};
1110
-
1111
- params.action = 'cffrtb-enable-field';
1112
- params.nonce = cffrtb_editor.ajax_nonce;
1113
-
1114
- params.slug = field.data( 'slug' );
1115
-
1116
- if ( field.hasClass( 'fieldset' ) ) {
1117
- params.type = 'fieldset';
1118
- } else {
1119
- params.type = 'field';
1120
- }
1121
-
1122
- var data = $.param( params );
1123
-
1124
- $.post( ajaxurl, data, function( r ) {
1125
-
1126
- if ( r.success ) {
1127
- cffrtb_editor.list.add_item( r.data.field, 0, true );
1128
- field.fadeOut( 400, function() { $(this).remove(); });
1129
-
1130
- } else {
1131
-
1132
- if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
1133
- cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
1134
- } else {
1135
- console.log( r.data );
1136
- cffrtb_editor.show_error( r.data.msg );
1137
- }
1138
-
1139
- field.find( '> .title .view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
1140
- field.removeClass( 'enabling' );
1141
- }
1142
-
1143
- cffrtb_editor.list.enable_sorting();
1144
- });
1145
- },
1146
-
1147
- /**
1148
- * Reset all modifications and custom fields created by the
1149
- * plugin
1150
- */
1151
- reset_all: function() {
1152
-
1153
- this.disabled_el.find( '.reset-all' ).attr( 'disabled', 'disabled' );
1154
-
1155
- if ( !window.confirm( cffrtb_editor.strings.confirm_reset_all ) ) {
1156
- this.disabled_el.find( '.reset-all' ).removeAttr( 'disabled' );
1157
- return;
1158
- }
1159
-
1160
- var params = {};
1161
-
1162
- params.action = 'cffrtb-reset-all';
1163
- params.nonce = cffrtb_editor.ajax_nonce;
1164
-
1165
- var data = $.param( params );
1166
-
1167
- $.post( ajaxurl, data, function( r ) {
1168
-
1169
- if ( r.success ) {
1170
-
1171
- // Refresh the page so that the new details are visible
1172
- window.location.reload();
1173
-
1174
- } else {
1175
-
1176
- if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
1177
- cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
1178
- } else {
1179
- console.log( r.data );
1180
- cffrtb_editor.show_error( r.data.msg );
1181
- }
1182
-
1183
- cffrtb_editor.list.disabled_el.find( '.reset-all' ).removeAttr( 'disabled' );
1184
- }
1185
- });
1186
- }
1187
- };
1188
-
1189
- /**
1190
- * Initialize the list
1191
- */
1192
- cffrtb_editor.list.init();
1193
-
1194
- });
1195
-
1196
- /**
1197
- * Display the pointer admin help tips
1198
- */
1199
- jQuery(document).ready(function ($) {
1200
-
1201
- if ( typeof cffrtb_editor.pointers == 'undefined' || !cffrtb_editor.pointers.length ) {
1202
- return;
1203
- }
1204
-
1205
- function cffrtb_pointer_show( pointers ) {
1206
-
1207
- var pointer = pointers.splice( 0, 1 )[0];
1208
- var options = $.extend( pointer.options, {
1209
- close: function() {
1210
- $.post( ajaxurl, {
1211
- pointer: pointer.id,
1212
- action: 'dismiss-wp-pointer'
1213
- }, function() {
1214
- if ( pointers.length ) {
1215
- cffrtb_pointer_show( pointers );
1216
- }
1217
- });
1218
- }
1219
- });
1220
-
1221
- var target = $( pointer.target );
1222
-
1223
- if ( !target ) {
1224
- return;
1225
- }
1226
-
1227
- target.first().pointer( options ).pointer( 'open' );
1228
-
1229
- $( 'html, body' ).animate({
1230
- scrollTop: target.offset().top - 100
1231
- }, 500);
1232
- }
1233
-
1234
- cffrtb_pointer_show( cffrtb_editor.pointers );
1235
-
1236
- });
1
+ /**
2
+ * Custom fields editor for Custom Fields for Restaurant Reservations
3
+ */
4
+ var cffrtb_editor = cffrtb_editor || {};
5
+
6
+ /**
7
+ * Initialize the editor object after jQuery has loaded
8
+ */
9
+ jQuery(document).ready(function ($) {
10
+
11
+ /**
12
+ * jQuery reference for editor panel
13
+ */
14
+ cffrtb_editor.el = $( '#cffrtb-editor' );
15
+
16
+ /**
17
+ * Show the error modal
18
+ */
19
+ cffrtb_editor.show_error = function( msg ) {
20
+
21
+ var rtb_error_modal = $( '#rtb-error-modal ' );
22
+
23
+ rtb_error_modal.find( '.rtb-error-msg' ).html( msg );
24
+ rtb_error_modal.addClass( 'is-visible' );
25
+
26
+ $(document).keyup( function(e) {
27
+ if ( e.which == '27' ) {
28
+ cffrtb_editor.hide_error( rtb_error_modal );
29
+ }
30
+ });
31
+
32
+ rtb_error_modal.click( function(e) {
33
+ if ( $(e.target).is( rtb_error_modal ) || $(e.target).is( rtb_error_modal.find( 'a.button' ) ) ) {
34
+
35
+ e.stopPropagation();
36
+ e.preventDefault();
37
+
38
+ cffrtb_editor.hide_error( rtb_error_modal );
39
+ }
40
+ });
41
+ };
42
+
43
+ /**
44
+ * Hide the error modal
45
+ */
46
+ cffrtb_editor.hide_error = function( el ) {
47
+ el.removeClass( 'is-visible' );
48
+ el.off();
49
+ };
50
+
51
+ });
52
+
53
+ /**
54
+ * Initialize the field editor after jQuery has loaded
55
+ */
56
+ jQuery(document).ready(function ($) {
57
+
58
+ /**
59
+ * Manage the field editor
60
+ */
61
+ cffrtb_editor.editor = {
62
+
63
+ el: $( '#cffrtb-field-editor' ),
64
+
65
+ option_el: $( '#cffrtb-field-editor-option' ),
66
+
67
+ init: function() {
68
+
69
+ // Store common form references
70
+ this.form = {
71
+ el: this.el.find( '#cffrtb-field-editor-form' ),
72
+ id: this.el.find( 'input[name="id"]' ),
73
+ type: this.el.find( 'input[name="type"]' ),
74
+ subtype: this.el.find( 'input[name="subtype"]' ),
75
+ };
76
+
77
+ // Show field editor option modal
78
+ $( '.add-field' ).click( function(e) {
79
+ e.stopPropagation();
80
+ e.preventDefault();
81
+
82
+ cffrtb_editor.editor.show_option();
83
+ });
84
+
85
+ // Register click events on options modal
86
+ this.option_el.click( function(e) {
87
+ e.stopPropagation();
88
+ e.preventDefault();
89
+
90
+ var target = $( e.target );
91
+
92
+ if ( target.hasClass( 'field' ) ) {
93
+ cffrtb_editor.editor.hide_option();
94
+ cffrtb_editor.editor.show_editor();
95
+
96
+ } else if ( target.hasClass( 'fieldset' ) ) {
97
+ cffrtb_editor.editor.hide_option();
98
+ cffrtb_editor.editor.show_editor( 'fieldset' );
99
+
100
+ } else if ( target.is( cffrtb_editor.editor.option_el ) ) {
101
+ cffrtb_editor.editor.hide_option();
102
+ }
103
+ });
104
+
105
+ // Close field editor modal when background is clicked
106
+ this.el.click( function(e) {
107
+ if ( $( e.target ).is( cffrtb_editor.editor.el ) ) {
108
+ cffrtb_editor.editor.hide_editor();
109
+ }
110
+ });
111
+
112
+ // Close field editor modal when ESC is keyed
113
+ $(document).keyup( function(e) {
114
+ if ( e.which == '27' ) {
115
+ cffrtb_editor.editor.hide_editor();
116
+ }
117
+ });
118
+
119
+ // Form actions
120
+ this.form.el.find( '> .actions' ).on( 'click', function(e) {
121
+
122
+ e.stopPropagation();
123
+ e.preventDefault();
124
+
125
+ var target = $( e.target );
126
+
127
+ // Exit early if the actions are disabled
128
+ if ( typeof target.attr( 'disabled' ) !== 'undefined' ) {
129
+ return;
130
+ }
131
+
132
+ // Save field
133
+ if ( target.hasClass( 'save' ) ) {
134
+ cffrtb_editor.editor.save_field();
135
+
136
+ // Cancel and close editor
137
+ } else if ( target.hasClass( 'cancel' ) ) {
138
+ cffrtb_editor.editor.hide_editor();
139
+ }
140
+ });
141
+
142
+ // Field type selections
143
+ this.form.el.find( '.type .selector' ).on( 'click', function(e) {
144
+
145
+ var target = $( e.target );
146
+
147
+ if ( target.get(0).tagName != 'A' ) {
148
+ return;
149
+ }
150
+
151
+ var level = target.parent().parent();
152
+
153
+ if ( target.parent().parent().hasClass( 'types' ) ) {
154
+ cffrtb_editor.editor.set_type( target.data( 'type' ), target );
155
+
156
+ } else {
157
+ cffrtb_editor.editor.set_subtype( target.data( 'subtype' ), target );
158
+ }
159
+
160
+ });
161
+
162
+ // Add an option
163
+ this.el.find( '.settings-panel.options .add a' ).on( 'click', function(e) {
164
+ e.stopPropagation();
165
+ e.preventDefault();
166
+ cffrtb_editor.editor.add_option();
167
+ });
168
+
169
+ // Add an option with ENTER key
170
+ this.get_add_option_el().keyup( function(e) {
171
+ if ( e.which == '13' ) {
172
+ e.stopPropagation();
173
+ e.preventDefault();
174
+
175
+ cffrtb_editor.editor.add_option();
176
+ }
177
+ });
178
+
179
+ // Remove an option
180
+ this.get_options_list_el().on( 'click', function(e) {
181
+ e.stopPropagation();
182
+ e.preventDefault();
183
+
184
+ var target = $( e.target );
185
+
186
+ if( target.is( 'a, a .dashicons' ) ) {
187
+ cffrtb_editor.editor.remove_option( target.closest( 'li' ) );
188
+ }
189
+ });
190
+
191
+ // Make the option list sortable
192
+ this.get_options_list_el().sortable({
193
+ placeholder: 'cffrtb-editor-options-placeholder',
194
+ delay: 250
195
+ });
196
+
197
+ },
198
+
199
+ /**
200
+ * Get the add option input element
201
+ */
202
+ get_add_option_el: function() {
203
+
204
+ if ( typeof this.form.add_option == 'undefined' ) {
205
+ this.form.add_option = this.el.find( '.settings-panel.options .add input' );
206
+ }
207
+
208
+ return this.form.add_option;
209
+ },
210
+
211
+ /**
212
+ * Get the options list element
213
+ */
214
+ get_options_list_el: function() {
215
+
216
+ if ( typeof this.form.options_list == 'undefined' ) {
217
+ this.form.options_list = this.el.find( '.settings-panel.options .options' );
218
+ }
219
+
220
+ return this.form.options_list;
221
+ },
222
+
223
+ /**
224
+ * Update the editor values with a new field object
225
+ */
226
+ update_editor_values: function( field ) {
227
+
228
+ this.form.id.val( field.ID );
229
+ this.set_type( field.type );
230
+ this.set_subtype( field.subtype );
231
+ this.form.el.find( 'input[name="title"]' ).val( field.title );
232
+
233
+ if ( field.required ) {
234
+ this.form.el.find( '.required input' ).attr( 'checked', 'checked' );
235
+ }
236
+
237
+ if ( field.options && Object.keys( field.options ).length ) {
238
+ var options = '';
239
+ for( var i in field.options ) {
240
+ if ( field.options[i].disabled ) {
241
+ continue;
242
+ }
243
+ options += '<li data-id="' + field.options[i].id + '"><a href="#"><span class="dashicons dashicons-dismiss"></span></a> <span class="value">' + field.options[i].value + '</span></li>';
244
+ }
245
+ this.form.el.find( '.settings-panel.options .options' ).html( options );
246
+ }
247
+
248
+ this.el.trigger( 'cffrtb_update_editor_values', field );
249
+ },
250
+
251
+ /**
252
+ * Show the field/fieldset type selection before opening the editor
253
+ */
254
+ show_option: function() {
255
+
256
+ this.option_el.addClass( 'is-visible' );
257
+ $( 'body' ).addClass( 'rtb-hide-body-scroll' );
258
+
259
+ },
260
+
261
+ /**
262
+ * Hide the field/fieldset type selection
263
+ */
264
+ hide_option: function() {
265
+
266
+ this.option_el.removeClass( 'is-visible' );
267
+ $( 'body' ).removeClass( 'rtb-hide-body-scroll' );
268
+
269
+ },
270
+
271
+ /**
272
+ * Show the editor
273
+ */
274
+ show_editor: function( mode ) {
275
+
276
+ if ( mode === 'edit' ) {
277
+ this.form.el.find( '> .title > h2' ).html( cffrtb_editor.strings.editor_edit_field );
278
+ this.form.el.find( '.actions a.save' ).html( cffrtb_editor.strings.editor_save_field );
279
+ } else if ( mode == 'fieldset' ) {
280
+ this.form.el.find( '> .title > h2' ).html( cffrtb_editor.strings.editor_add_fieldset );
281
+ this.form.el.find( '.actions a.save' ).addClass( 'fieldset' ).html( cffrtb_editor.strings.editor_save_fieldset );
282
+ this.form.el.addClass( mode );
283
+ this.form.type.val( 'fieldset' );
284
+ this.form.subtype.val( 'fieldset' );
285
+ } else {
286
+ this.form.el.find( '> .title > h2' ).html( cffrtb_editor.strings.editor_add_field );
287
+ this.form.el.find( '.actions a.save' ).html( cffrtb_editor.strings.editor_add_field );
288
+ }
289
+
290
+ this.el.addClass( 'is-visible' );
291
+ $( 'body' ).addClass( 'rtb-hide-body-scroll' );
292
+ },
293
+
294
+ /**
295
+ * Hide the editor
296
+ */
297
+ hide_editor: function() {
298
+ this.el.removeClass( 'is-visible' );
299
+ this.option_el.removeClass( 'is-hidden' );
300
+ this.form.el.addClass( 'is-hidden' );
301
+ this.form.el.removeClass( 'fieldset' );
302
+ this.form.el.find( '.actions a.save' ).removeClass( 'fieldset' );
303
+ $( 'body' ).removeClass( 'rtb-hide-body-scroll' );
304
+ this.form.el.find( 'input, select, textarea' ).not( 'input[type="checkbox"]' ).val( '' );
305
+ this.form.el.find( 'input[type="checkbox"]' ).removeAttr( 'checked' );
306
+ this.get_options_list_el().empty();
307
+ this.set_type( cffrtb_editor.default_type );
308
+ this.set_subtype( cffrtb_editor.default_subtype );
309
+ },
310
+
311
+ /**
312
+ * Set the type and select the new subtype
313
+ */
314
+ set_type: function( type, el ) {
315
+
316
+ if ( type == this.form.type.val() ) {
317
+ return;
318
+ }
319
+
320
+ // Set the value
321
+ this.form.type.val( type );
322
+
323
+ // Remove the `current` class from the selection
324
+ this.el.find( '.type .types a' ).removeClass( 'current' );
325
+
326
+ // Add the `current` class to this selection
327
+ if ( typeof el == 'undefined' ) {
328
+ el = this.el.find( '.type .types .' + type );
329
+ }
330
+ el.addClass( 'current' );
331
+
332
+ // Show the settings if they exist
333
+ this.el.find( '.settings-panel' ).each( function() {
334
+ if ( $(this).hasClass( type ) ) {
335
+ $(this).addClass( 'current' );
336
+ } else {
337
+ $(this).removeClass( 'current' );
338
+ }
339
+ });
340
+
341
+ // Show the subtype list
342
+ this.el.find( '.type .subtypes' ).each( function() {
343
+ if ( $(this).hasClass( type ) ) {
344
+ $(this).addClass( 'current' );
345
+ } else {
346
+ $(this).removeClass( 'current' );
347
+ }
348
+ });
349
+
350
+ // Trigger a click on the first subtype for this type
351
+ this.el.find( '.subtypes.' + type + ' li' ).first().find( 'a' ).trigger( 'click' );
352
+ },
353
+
354
+ /**
355
+ * Select a subtype
356
+ */
357
+ set_subtype: function( subtype, el ) {
358
+
359
+ if ( subtype == this.form.subtype.val() ) {
360
+ return;
361
+ }
362
+
363
+ // Set the value
364
+ this.form.subtype.val( subtype );
365
+
366
+ // Remove the `current` class from the selection
367
+ this.el.find( '.type .subtypes a' ).removeClass( 'current' );
368
+
369
+ // Apply the `current` class to the right subtype
370
+ if ( typeof el == 'undefined' ) {
371
+ el = this.el.find( '.type .subtypes .' + subtype );
372
+ }
373
+ el.addClass( 'current' );
374
+
375
+ },
376
+
377
+ /**
378
+ * Add an option to the list
379
+ */
380
+ add_option: function() {
381
+ var list = this.get_options_list_el();
382
+ var option = this.get_add_option_el();
383
+
384
+ this.get_options_list_el().append( '<li><a href="#"><span class="dashicons dashicons-dismiss"></span></a> <span class="value">' + this.get_add_option_el().val() + '</span></li>' );
385
+ this.get_add_option_el().val( '' );
386
+
387
+ // Scroll the options list if they have more than 10
388
+ if ( this.get_options_list_el().find( 'li' ).length > 10 ) {
389
+ this.get_options_list_el().addClass( 'scroll' );
390
+ }
391
+ },
392
+
393
+ /**
394
+ * Remove an option from the list
395
+ */
396
+ remove_option: function( el ) {
397
+
398
+ el.fadeOut( '200', function() {
399
+ $(this).remove();
400
+
401
+ // Remove scrollbar if the options list is less than 10 options
402
+ if ( cffrtb_editor.editor.get_options_list_el().find( 'li' ).length <= 10 ) {
403
+ cffrtb_editor.editor.get_options_list_el().removeClass( 'scroll' );
404
+ }
405
+ });
406
+ },
407
+
408
+ /**
409
+ * Disable actions
410
+ */
411
+ disable_actions: function() {
412
+ this.form.el.find( '.actions' ).addClass( 'working' ).find( '.save, .cancel' ).attr( 'disabled', 'disabled' );
413
+ },
414
+
415
+ /**
416
+ * Enable actions
417
+ */
418
+ enable_actions: function() {
419
+ this.form.el.find( '.actions' ).removeClass( 'working' ).find( '.save, .cancel' ).removeAttr( 'disabled' );
420
+ },
421
+
422
+ /**
423
+ * Load field
424
+ */
425
+ load_field: function( item_slug ) {
426
+
427
+ // Don't trigger if we're already saving
428
+ if ( cffrtb_editor.list.get_title_el( item_slug ).hasClass( 'saving' ) ) {
429
+ return;
430
+ }
431
+
432
+ var id = cffrtb_editor.list.items[item_slug].el.data( 'id' );
433
+ if ( typeof id == 'undefined' ) {
434
+ return;
435
+ }
436
+
437
+ cffrtb_editor.list.disable_sorting();
438
+ cffrtb_editor.list.get_title_el( item_slug ).addClass( 'saving' );
439
+ cffrtb_editor.list.get_title_el( item_slug ).find( '.view .controls' ).prepend( '<span class="load-spinner"></span>' );
440
+
441
+ var params = {};
442
+
443
+ params.action = 'cffrtb-load-field';
444
+ params.nonce = cffrtb_editor.ajax_nonce;
445
+ params.ID = id;
446
+
447
+ var data = $.param( params );
448
+
449
+ $.post( ajaxurl, data, function( r ) {
450
+
451
+ if ( r.success ) {
452
+ cffrtb_editor.editor.update_editor_values( r.data.field );
453
+ cffrtb_editor.editor.show_editor( 'edit' );
454
+
455
+ } else {
456
+ if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
457
+ cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
458
+ } else {
459
+ cffrtb_editor.show_error( r.data.msg );
460
+ }
461
+ }
462
+
463
+ // Reset status
464
+ cffrtb_editor.list.enable_sorting();
465
+ cffrtb_editor.list.get_title_el( item_slug ).removeClass( 'saving' );
466
+ cffrtb_editor.list.get_title_el( item_slug ).find( '.view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
467
+ });
468
+
469
+ },
470
+
471
+ /**
472
+ * Save a field
473
+ */
474
+ save_field: function() {
475
+
476
+ this.disable_actions();
477
+
478
+ var params = {};
479
+
480
+ params.action = 'cffrtb-save-field';
481
+ params.nonce = cffrtb_editor.ajax_nonce;
482
+ params.request = 'save_field';
483
+ params.field = {
484
+ ID: this.form.id.val(),
485
+ type: this.form.type.val(),
486
+ subtype: this.form.subtype.val(),
487
+ title: this.form.el.find( 'input[name="title"]' ).val(),
488
+ };
489
+
490
+ if ( this.form.el.find( 'input[name="required"]' ).is( ':checked' ) ) {
491
+ params.field.required = 1;
492
+ } else {
493
+ params.field.required = 0;
494
+ }
495
+
496
+ if ( params.field.type == 'options' ) {
497
+ params.field.options = {};
498
+ this.get_options_list_el().find( 'li' ).each( function(i) {
499
+
500
+ var id = $(this).data( 'id' );
501
+ if ( typeof id == 'undefined' ) {
502
+ id = 'new-' + Math.random().toString(36).substring(7);
503
+ }
504
+
505
+ params.field.options[i] = {
506
+ id: id,
507
+ value: $(this).find( '.value' ).html(),
508
+ order: i
509
+ };
510
+ });
511
+ }
512
+
513
+ // Perform some basic validation checks here
514
+ var errors = this.validate_field( params.field );
515
+ if ( typeof errors !== 'undefined' ) {
516
+ cffrtb_editor.show_error( errors );
517
+ this.enable_actions();
518
+
519
+ return;
520
+ }
521
+
522
+ var data = $.param( params );
523
+
524
+ $.post( ajaxurl, data, function( r ) {
525
+
526
+ if ( r.success ) {
527
+ cffrtb_editor.editor.hide_editor();
528
+ cffrtb_editor.list.add_item( r.data.field, r.data.ID, r.data.is_new_field, r.data.type );
529
+
530
+ } else {
531
+ if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
532
+ cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
533
+ } else {
534
+ console.log( r.data );
535
+ cffrtb_editor.show_error( r.data.msg );
536
+ }
537
+ }
538
+
539
+ cffrtb_editor.editor.enable_actions();
540
+ });
541
+ },
542
+
543
+ /**
544
+ * Some quick validation on the field data before sending it off
545
+ */
546
+ validate_field: function( field ) {
547
+
548
+ if ( field.title.length === 0 || !field.title.trim() ) {
549
+ return cffrtb_editor.strings.field_missing_title;
550
+ }
551
+
552
+ if ( field.type == 'options' && field.options.length === 0 ) {
553
+ return cffrtb_editor.strings.field_missing_options;
554
+ }
555
+ }
556
+ };
557
+
558
+ /**
559
+ * Initialize the editor
560
+ */
561
+ cffrtb_editor.editor.init();
562
+
563
+ });
564
+
565
+ /**
566
+ * Initialize the fields list object after jQuery has loaded
567
+ */
568
+ jQuery(document).ready(function ($) {
569
+
570
+ /**
571
+ * Manage the list of fields
572
+ */
573
+ cffrtb_editor.list = {
574
+
575
+ // list element
576
+ el: cffrtb_editor.el.find( '#cffrtb-list' ),
577
+
578
+ // fieldsets and fields.
579
+ // just storing jQuery references to prevent duplicate lookups
580
+ items: {},
581
+
582
+ // flag to disable events during view->edit transition
583
+ in_transition: false,
584
+
585
+ // disabled fields list
586
+ disabled_el: cffrtb_editor.el.find( '#cffrtb-disabled' ),
587
+
588
+ init: function() {
589
+
590
+ // Store jQuery references to prevent duplicate lookups
591
+ this.el.find( '.fieldset, .field' ).each( function() {
592
+ cffrtb_editor.list.items[ $(this).data( 'slug' ) ] = {
593
+ el: $(this)
594
+ };
595
+ });
596
+
597
+ // Clear pre-existing listeners
598
+ this.el.off( 'click keyup' );
599
+
600
+ // Register click listeners
601
+ this.el.on( 'click', function( e ) {
602
+
603
+ e.stopPropagation();
604
+ e.preventDefault();
605
+
606
+ var target = $( e.target );
607
+
608
+ // Any click outside one of the field titles
609
+ if ( !target.hasClass( 'title' ) && !target.parents().hasClass( 'title' ) ) {
610
+ cffrtb_editor.list.save_all();
611
+ return;
612
+ }
613
+
614
+ var item_slug = target.parents( '.fieldset, .field' ).first().data( 'slug' );
615
+
616
+ // Open options panel
617
+ if ( cffrtb_editor.list.is_target( target, 'options' ) ) {
618
+ cffrtb_editor.list.save_all();
619
+ cffrtb_editor.editor.load_field( item_slug );
620
+
621
+ // Delete field
622
+ } else if ( cffrtb_editor.list.is_target( target, 'delete' ) ) {
623
+ cffrtb_editor.list.save_all();
624
+ cffrtb_editor.list.delete_item( item_slug );
625
+
626
+ // Open label editing panel
627
+ } else if ( !cffrtb_editor.list.is_editing( item_slug ) ) {
628
+ cffrtb_editor.list.show_edit( item_slug );
629
+
630
+ // Save label
631
+ } else if ( cffrtb_editor.list.is_target( target, 'save' ) ) {
632
+ cffrtb_editor.list.save_label( item_slug );
633
+
634
+ // Give focus to input when editing panel is active
635
+ } else if ( target.hasClass( 'edit' ) ) {
636
+ cffrtb_editor.list.set_focus( item_slug );
637
+ }
638
+
639
+ });
640
+
641
+ // Save label with ENTER key
642
+ this.el.keyup( function(e) {
643
+
644
+ if ( !cffrtb_editor.list.in_transition && e.which == '13' ) {
645
+
646
+ var target = $( e.target );
647
+
648
+ if ( target.is( 'input:focus' ) ) {
649
+
650
+ e.stopPropagation();
651
+ e.preventDefault();
652
+
653
+ cffrtb_editor.list.save_label( target.parents( '.fieldset, .field' ).first().data( 'slug' ) );
654
+ }
655
+ }
656
+ });
657
+
658
+ // Make the list sortable
659
+ this.el.sortable({
660
+ placeholder: 'cffrtb-list-placeholder',
661
+ delay: 250,
662
+ update: this.sorting_complete
663
+ });
664
+ this.el.find( '.fieldset ul' ).sortable({
665
+ placeholder: 'cffrtb-list-placeholder',
666
+ connectWith: '#cffrtb-list .fieldset ul',
667
+ delay: 250,
668
+ update: this.sorting_complete
669
+ });
670
+
671
+ // Clear pre-existing listeners
672
+ this.disabled_el.off( 'click' );
673
+
674
+ // Register click events on disabled fields
675
+ this.disabled_el.on( 'click', function(e) {
676
+
677
+ e.stopPropagation();
678
+ e.preventDefault();
679
+
680
+ var target = $( e.target );
681
+
682
+ // Restore field
683
+ if ( cffrtb_editor.list.is_target( target, 'enable' ) ) {
684
+ cffrtb_editor.list.enable_item( target.parents( '.fieldset, .field' ).first() );
685
+
686
+ // Open learn more text
687
+ } else if ( cffrtb_editor.list.is_target( target, 'learn-more' ) ) {
688
+ cffrtb_editor.list.disabled_el.find( '.reset .description' ).addClass( 'is-visible' );
689
+
690
+ // Revert to default
691
+ } else if ( cffrtb_editor.list.is_target( target, 'reset-all' ) ) {
692
+ cffrtb_editor.list.reset_all();
693
+ }
694
+ });
695
+
696
+ },
697
+
698
+ /**
699
+ * Is the field being edited?
700
+ */
701
+ is_editing: function( item_slug ) {
702
+ return this.get_title_el( item_slug ).hasClass( 'editing' );
703
+ },
704
+
705
+ /**
706
+ * Is the click target opening the field options?
707
+ */
708
+ is_target: function( target, match ) {
709
+ return target.hasClass( match ) || target.parents().hasClass( match );
710
+ },
711
+
712
+ /**
713
+ * Get the title element for an item
714
+ */
715
+ get_title_el: function( item_slug ) {
716
+
717
+ if ( typeof this.items[item_slug].title == 'undefined' ) {
718
+ this.items[item_slug].title = this.items[item_slug].el.find( '> .title' );
719
+ }
720
+
721
+ return this.items[item_slug].title;
722
+ },
723
+
724
+ /**
725
+ * Get the edit element for an item
726
+ */
727
+ get_edit_el: function( item_slug ) {
728
+
729
+ if ( typeof this.items[item_slug].title == 'undefined' ) {
730
+ this.items[item_slug].edit = this.items[item_slug].el.find( '> .title .edit' );
731
+ }
732
+
733
+ return this.items[item_slug].edit;
734
+ },
735
+
736
+ /**
737
+ * Get the value of the input field
738
+ */
739
+ get_input: function( item_slug ) {
740
+ return this.get_input_el( item_slug ).val();
741
+ },
742
+
743
+ /**
744
+ * Get the input element of a field
745
+ */
746
+ get_input_el: function( item_slug ) {
747
+
748
+ if ( typeof this.items[item_slug].input == 'undefined' ) {
749
+ this.items[item_slug].input = this.items[item_slug].el.find( '> .title .edit input' );
750
+ }
751
+
752
+ return this.items[item_slug].input;
753
+ },
754
+
755
+ /**
756
+ * Get the fieldset slug an item is attached to
757
+ */
758
+ get_fieldset: function( item_slug ) {
759
+
760
+ if ( this.items[item_slug].el.hasClass( 'fieldset' ) ) {
761
+ return item_slug;
762
+ } else {
763
+ return this.items[item_slug].el.parents( '.fieldset' ).first().data( 'slug' );
764
+ }
765
+ },
766
+
767
+ /**
768
+ * Update the value of the label in the view
769
+ */
770
+ update_view: function( item_slug ) {
771
+
772
+ if ( typeof this.items[item_slug].view_value == 'undefined' ) {
773
+ this.items[item_slug].view_value = this.items[item_slug].el.find( '> .title .view .value' );
774
+ }
775
+
776
+ this.items[item_slug].view_value.html( this.get_input( item_slug ) );
777
+ },
778
+
779
+ /**
780
+ * Open an item's edit mode
781
+ */
782
+ show_edit: function( item_slug ) {
783
+
784
+ // Save and close any other labels being edited
785
+ this.save_all();
786
+
787
+ // Set transition flag and timer
788
+ this.in_transition = true;
789
+ setTimeout( this.clear_transition_flag, 600 );
790
+
791
+ // Open edit mode for this item
792
+ this.enable_tabbing( item_slug );
793
+ this.get_title_el( item_slug ).addClass( 'editing' );
794
+ this.set_focus( item_slug );
795
+ },
796
+
797
+ /**
798
+ * Focus and select the input field
799
+ */
800
+ set_focus: function( item_slug ) {
801
+ this.get_input_el( item_slug ).focus().select();
802
+ },
803
+
804
+ /**
805
+ * Clear the transition flag used when opening the editing panel
806
+ */
807
+ clear_transition_flag: function() {
808
+ cffrtb_editor.list.in_transition = false;
809
+ },
810
+
811
+ /**
812
+ * Return an item to view mode
813
+ */
814
+ show_view: function( item_slug ) {
815
+ this.get_title_el( item_slug ).removeClass( 'editing' );
816
+ this.disable_tabbing( item_slug );
817
+ this.set_focus( item_slug );
818
+ },
819
+
820
+ /**
821
+ * Disable tabbing through a hidden edit interface
822
+ */
823
+ disable_tabbing: function( item_slug ) {
824
+ this.get_title_el( item_slug ).find( '> .edit input, > .edit .save' ).attr( 'tabindex', '-1' );
825
+ },
826
+
827
+ /**
828
+ * Enable tabbing through a hidden edit interface
829
+ */
830
+ enable_tabbing: function( item_slug ) {
831
+ this.get_title_el( item_slug ).find( '> .edit input, > .edit .save' ).removeAttr( 'tabindex' );
832
+ },
833
+
834
+ /**
835
+ * Disable drag and drop sorting
836
+ */
837
+ disable_sorting: function() {
838
+ this.el.sortable( 'option', 'disabled', true );
839
+ this.el.find( '.fieldset ul' ).sortable( 'option', 'disabled', true );
840
+ },
841
+
842
+ /**
843
+ * Enable drag and drop sorting
844
+ */
845
+ enable_sorting: function() {
846
+ this.el.sortable( 'option', 'disabled', false );
847
+ this.el.find( '.fieldset ul' ).sortable( 'option', 'disabled', false );
848
+ },
849
+
850
+ /**
851
+ * Save and close any fields being edited
852
+ */
853
+ save_all: function() {
854
+ this.el.find( '.title.editing' ).each( function() {
855
+ cffrtb_editor.list.save_label( $(this ).parent().data( 'slug' ) );
856
+ });
857
+ },
858
+
859
+ /**
860
+ * Save an item's label
861
+ */
862
+ save_label: function( item_slug ) {
863
+
864
+ // Don't trigger if we're already saving
865
+ if ( this.get_title_el( item_slug ).hasClass( 'saving' ) ) {
866
+ return;
867
+ }
868
+
869
+ // Indicate status
870
+ this.get_title_el( item_slug ).addClass( 'saving' );
871
+ this.get_input_el( item_slug ).attr( 'disabled', 'disabled' );
872
+
873
+ var params = {};
874
+
875
+ params.action = 'cffrtb-save-field';
876
+ params.nonce = cffrtb_editor.ajax_nonce;
877
+ params.request = 'save_label';
878
+ params.field = {
879
+ slug: item_slug,
880
+ title: this.get_input( item_slug ),
881
+ fieldset: this.get_fieldset( item_slug )
882
+ };
883
+
884
+ if( this.items[item_slug].el.data( 'id' ) ) {
885
+ params.field.ID = this.items[item_slug].el.data( 'id' );
886
+ }
887
+
888
+ var data = $.param( params );
889
+
890
+ $.post( ajaxurl, data, function( r ) {
891
+
892
+ if ( r.success ) {
893
+ cffrtb_editor.list.update_view( item_slug );
894
+ cffrtb_editor.list.show_view( item_slug );
895
+
896
+ } else {
897
+ if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
898
+ cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
899
+ } else {
900
+ console.log( r.data );
901
+ cffrtb_editor.show_error( r.data.msg );
902
+ }
903
+ }
904
+
905
+ // Reset status
906
+ cffrtb_editor.list.get_title_el( item_slug ).removeClass( 'saving' );
907
+ cffrtb_editor.list.get_input_el( item_slug ).removeAttr( 'disabled' );
908
+ });
909
+ },
910
+
911
+ /**
912
+ * Sorting complete
913
+ */
914
+ sorting_complete: function( event, ui ) {
915
+ cffrtb_editor.list.save_sort( $( ui.item.context ) );
916
+ },
917
+
918
+ /**
919
+ * Save the sort order after it's been changed
920
+ */
921
+ save_sort: function( target ) {
922
+
923
+ if ( cffrtb_editor.el.hasClass( 'saving-order' ) ) {
924
+ return;
925
+ }
926
+
927
+ // Indicate status
928
+ cffrtb_editor.list.disable_sorting();
929
+ cffrtb_editor.el.addClass( 'saving-order' );
930
+ target.find( '> .title .view .controls' ).prepend( '<span class="load-spinner"></span>' );
931
+
932
+ var params = {};
933
+
934
+ params.action = 'cffrtb-save-order';
935
+ params.nonce = cffrtb_editor.ajax_nonce;
936
+ params.order = [];
937
+
938
+ var i = 0;
939
+ cffrtb_editor.list.el.find( '> li' ).each( function() {
940
+ params.order.push( cffrtb_editor.list.get_item_order_obj( $(this), i ) );
941
+ i++;
942
+
943
+ $(this).find( '> ul > li' ).each( function() {
944
+ params.order.push( cffrtb_editor.list.get_item_order_obj( $(this), i ) );
945
+ i++;
946
+ });
947
+ });
948
+
949
+ var data = $.param( params );
950
+
951
+ $.post( ajaxurl, data, function( r ) {
952
+
953
+ if ( r.success ) {
954
+
955
+ } else {
956
+ if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
957
+ cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
958
+ } else {
959
+ console.log( r.data );
960
+ cffrtb_editor.show_error( r.data.msg );
961
+ }
962
+ }
963
+
964
+ // Reset status
965
+ cffrtb_editor.el.removeClass( 'saving-order' );
966
+ target.find( '> .title .view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
967
+ cffrtb_editor.list.enable_sorting();
968
+ });
969
+ },
970
+
971
+ /**
972
+ * Get an object with an elements order and slug/id data
973
+ */
974
+ get_item_order_obj: function( el, i ) {
975
+
976
+ var item = {};
977
+
978
+ if ( el.data( 'slug' ) ) {
979
+ item.slug = el.data( 'slug' );
980
+ }
981
+
982
+ if ( el.data( 'id' ) ) {
983
+ item.ID = el.data( 'id' );
984
+ }
985
+
986
+ item.fieldset = cffrtb_editor.list.get_fieldset( item.slug );
987
+
988
+ item.order = i;
989
+
990
+ return item;
991
+ },
992
+
993
+ /**
994
+ * Add an item
995
+ *
996
+ * Expects to receive an HTML string for the new <li> element
997
+ */
998
+ add_item: function( html, ID, is_new_field, type ) {
999
+
1000
+ if ( is_new_field ) {
1001
+ if ( type == 'fieldset' ) {
1002
+ this.el.find( '> .fieldset' ).last().after( html );
1003
+ } else {
1004
+ this.el.find( '> .fieldset' ).last().find( '> .fields' ).append( html );
1005
+ }
1006
+ } else {
1007
+ this.el.find( '.fieldset, .field' ).each( function() {
1008
+ if ( $(this).data( 'id' ) == ID ) {
1009
+ $(this).html( html ).hide().fadeIn();
1010
+ }
1011
+ });
1012
+ }
1013
+
1014
+ this.init();
1015
+ this.save_sort( this.el.find( '.fieldset, .field' ).last() );
1016
+ },
1017
+
1018
+ /**
1019
+ * Delete or disable an item from the list of fields
1020
+ */
1021
+ delete_item: function( item_slug ) {
1022
+
1023
+ if ( cffrtb_editor.el.hasClass( 'deleting' ) ) {
1024
+ return;
1025
+ }
1026
+
1027
+ var item_el = this.items[item_slug].el;
1028
+ var is_fieldset = item_el.hasClass( 'fieldset' );
1029
+
1030
+ // Indicate status
1031
+ cffrtb_editor.list.disable_sorting();
1032
+ cffrtb_editor.el.addClass( 'deleting' );
1033
+ item_el.find( '> .title .view .controls' ).prepend( '<span class="load-spinner"></span>' );
1034
+
1035
+ // Can't remove a fieldset with fields
1036
+ if ( is_fieldset && item_el.find( '.field' ).length ) {
1037
+ cffrtb_editor.show_error( cffrtb_editor.strings.fieldset_not_empty );
1038
+ item_el.find( '> .title .view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
1039
+ cffrtb_editor.el.removeClass( 'deleting' );
1040
+ cffrtb_editor.list.enable_sorting();
1041
+ return;
1042
+ }
1043
+
1044
+ var id = item_el.data( 'id' );
1045
+
1046
+ var params = {};
1047
+
1048
+ params.action = 'cffrtb-delete-field';
1049
+ params.nonce = cffrtb_editor.ajax_nonce;
1050
+
1051
+ if ( id ) {
1052
+ params.ID = id;
1053
+ } else {
1054
+ params.slug = item_slug;
1055
+ params.fieldset = this.get_fieldset( item_slug );
1056
+ }
1057
+
1058
+ var data = $.param( params );
1059
+
1060
+ $.post( ajaxurl, data, function( r ) {
1061
+
1062
+ if ( r.success ) {
1063
+
1064
+ item_el.fadeOut( 400, function() { $(this).remove(); });
1065
+
1066
+ if ( typeof r.data !== 'undefined' && typeof r.data.field !== 'undefined' ) {
1067
+
1068
+ if ( !cffrtb_editor.list.disabled_el.find( '.fields' ).length ) {
1069
+ cffrtb_editor.list.disabled_el.find( '.no-disabled-fields' ).remove();
1070
+ cffrtb_editor.list.disabled_el.find( '.reset' ).before( '<ul class="fields"></ul>' );
1071
+ }
1072
+
1073
+ cffrtb_editor.list.disabled_el.find( '.fields' ).append( r.data.field );
1074
+
1075
+ cffrtb_editor.list.disabled_el.find( '.reset' ).addClass( 'is-visible' );
1076
+ }
1077
+
1078
+ } else {
1079
+
1080
+ if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
1081
+ cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
1082
+ } else {
1083
+ console.log( r.data );
1084
+ cffrtb_editor.show_error( r.data.msg );
1085
+ }
1086
+
1087
+ item_el.find( '> .title .view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
1088
+ }
1089
+
1090
+ // Reset status
1091
+ cffrtb_editor.el.removeClass( 'deleting' );
1092
+ cffrtb_editor.list.enable_sorting();
1093
+ });
1094
+ },
1095
+
1096
+ /**
1097
+ * Enable a field that has been disabled
1098
+ */
1099
+ enable_item: function( field ) {
1100
+
1101
+ if ( field.hasClass( 'enabling' ) ) {
1102
+ return;
1103
+ }
1104
+
1105
+ // Indicate status
1106
+ field.addClass( 'enabling' );
1107
+ field.find( '> .title .view .controls' ).prepend( '<span class="load-spinner"></span>' );
1108
+
1109
+ var params = {};
1110
+
1111
+ params.action = 'cffrtb-enable-field';
1112
+ params.nonce = cffrtb_editor.ajax_nonce;
1113
+
1114
+ params.slug = field.data( 'slug' );
1115
+
1116
+ if ( field.hasClass( 'fieldset' ) ) {
1117
+ params.type = 'fieldset';
1118
+ } else {
1119
+ params.type = 'field';
1120
+ }
1121
+
1122
+ var data = $.param( params );
1123
+
1124
+ $.post( ajaxurl, data, function( r ) {
1125
+
1126
+ if ( r.success ) {
1127
+ cffrtb_editor.list.add_item( r.data.field, 0, true );
1128
+ field.fadeOut( 400, function() { $(this).remove(); });
1129
+
1130
+ } else {
1131
+
1132
+ if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
1133
+ cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
1134
+ } else {
1135
+ console.log( r.data );
1136
+ cffrtb_editor.show_error( r.data.msg );
1137
+ }
1138
+
1139
+ field.find( '> .title .view .controls .load-spinner' ).fadeOut( 400, function() { $(this).remove(); });
1140
+ field.removeClass( 'enabling' );
1141
+ }
1142
+
1143
+ cffrtb_editor.list.enable_sorting();
1144
+ });
1145
+ },
1146
+
1147
+ /**
1148
+ * Reset all modifications and custom fields created by the
1149
+ * plugin
1150
+ */
1151
+ reset_all: function() {
1152
+
1153
+ this.disabled_el.find( '.reset-all' ).attr( 'disabled', 'disabled' );
1154
+
1155
+ if ( !window.confirm( cffrtb_editor.strings.confirm_reset_all ) ) {
1156
+ this.disabled_el.find( '.reset-all' ).removeAttr( 'disabled' );
1157
+ return;
1158
+ }
1159
+
1160
+ var params = {};
1161
+
1162
+ params.action = 'cffrtb-reset-all';
1163
+ params.nonce = cffrtb_editor.ajax_nonce;
1164
+
1165
+ var data = $.param( params );
1166
+
1167
+ $.post( ajaxurl, data, function( r ) {
1168
+
1169
+ if ( r.success ) {
1170
+
1171
+ // Refresh the page so that the new details are visible
1172
+ window.location.reload();
1173
+
1174
+ } else {
1175
+
1176
+ if ( typeof r.data === 'undefined' || typeof r.data.error === 'undefined' ) {
1177
+ cffrtb_editor.show_error( cffrtb_editor.strings.unknown_error );
1178
+ } else {
1179
+ console.log( r.data );
1180
+ cffrtb_editor.show_error( r.data.msg );
1181
+ }
1182
+
1183
+ cffrtb_editor.list.disabled_el.find( '.reset-all' ).removeAttr( 'disabled' );
1184
+ }
1185
+ });
1186
+ }
1187
+ };
1188
+
1189
+ /**
1190
+ * Initialize the list
1191
+ */
1192
+ cffrtb_editor.list.init();
1193
+
1194
+ });
1195
+
1196
+ /**
1197
+ * Display the pointer admin help tips
1198
+ */
1199
+ jQuery(document).ready(function ($) {
1200
+
1201
+ if ( typeof cffrtb_editor.pointers == 'undefined' || !cffrtb_editor.pointers.length ) {
1202
+ return;
1203
+ }
1204
+
1205
+ function cffrtb_pointer_show( pointers ) {
1206
+
1207
+ var pointer = pointers.splice( 0, 1 )[0];
1208
+ var options = $.extend( pointer.options, {
1209
+ close: function() {
1210
+ $.post( ajaxurl, {
1211
+ pointer: pointer.id,
1212
+ action: 'dismiss-wp-pointer'
1213
+ }, function() {
1214
+ if ( pointers.length ) {
1215
+ cffrtb_pointer_show( pointers );
1216
+ }
1217
+ });
1218
+ }
1219
+ });
1220
+
1221
+ var target = $( pointer.target );
1222
+
1223
+ if ( !target ) {
1224
+ return;
1225
+ }
1226
+
1227
+ target.first().pointer( options ).pointer( 'open' );
1228
+
1229
+ $( 'html, body' ).animate({
1230
+ scrollTop: target.offset().top - 100
1231
+ }, 500);
1232
+ }
1233
+
1234
+ cffrtb_pointer_show( cffrtb_editor.pointers );
1235
+
1236
+ });
assets/js/mailchimp-admin.js CHANGED
@@ -1,2 +1,2 @@
1
  /*! mailchimp-for-rtb 2018-10-05 */
2
- jQuery(document).ready(function(a){function b(){var b=a(".mcfrtb_loading");b.animate({opacity:1},500,function(){});var d={action:"mcfrtb-get-lists",nonce:rtb_admin_mc.ajax_nonce},e=a.param(d);a.post(ajaxurl,e,function(d){if(d.success){"undefined"==typeof d.data&&(console.log("no data returned from ajax request"),console.log(d)),b.stop(),b.css("opacity","0");var e=a("#mcfrtb-merge-controls").data("input-name")+"[list]";a(".mcfrtb-list-select").append('<select name="'+e+'" id="'+e+'"><option></option></select>');var f=a(".mcfrtb-list-select select");for(var g in d.data.lists){var h=a("<option></option>"),i=d.data.lists[g];h.attr("value",i.id).text(i.name),f.append(h)}a(f).change(function(b){c(a("option:selected",this).val())}),null!==rtb_admin_mc.lists&&"undefined"!=typeof rtb_admin_mc.lists.list&&""!==rtb_admin_mc.lists.list&&f.find("option[value="+rtb_admin_mc.lists.list+"]").attr("selected","selected").trigger("change")}else"undefined"==typeof d.data||"undefined"==typeof d.data.msg?a(".mcfrtb-list-select").html('<span class="error">'+rtb_admin_mc.strings.api_unknown_error+"</span>"):a(".mcfrtb-list-select").html('<span class="error">'+d.data.msg+"</span>"),b.animate({opacity:0},500,function(){})})}function c(b){var c=a(".mcfrtb_loading"),e=a("#mcfrtb-merge-controls");if(c.animate({opacity:1},500,function(){}),e.empty(),e.data("list-id",b),e.removeClass("active"),""===b)return void c.animate({opacity:0},500,function(){});var f={list:b,action:"mcfrtb-load-merge-fields",nonce:rtb_admin_mc.ajax_nonce},g=a.param(f);a.post(ajaxurl,g,function(f){if(f.success){if("undefined"==typeof f.data&&(console.log("no data returned from ajax request"),console.log(f)),f.data.list_id!=e.data("list-id"))return;if(e.empty(),"undefined"!=typeof rtb_admin_mc.merge_fields){var g=e.data("input-name")+"[fields]",h=a("<select><option></option></select>");for(var i in f.data.merge_fields){var j=a("<option></option>"),k=f.data.merge_fields[i];j.attr("value",k.tag).text(k.name),h.append(j)}e.hide(),e.append("<table></table>");var l=e.find("table");l.append("<tr><th>"+rtb_admin_mc.strings.merge_booking_data+"</th><th>"+rtb_admin_mc.strings.merge_list_field+"</th></tr>"),l.append("<tr><td>"+rtb_admin_mc.strings.merge_email_label+'</td><td><span class="description">'+rtb_admin_mc.strings.merge_email_description+"</span></td></tr>");for(var m in rtb_admin_mc.merge_fields)l.append('<tr><td><label for="mcfrtb-'+m+'">'+rtb_admin_mc.merge_fields[m]+'</label></td><td><select name="'+g+"["+m+']" id="mcfrtb-'+m+'">'+h.html()+"</select></td></tr>"),null!==rtb_admin_mc.lists&&b===rtb_admin_mc.lists.list&&"undefined"!=typeof rtb_admin_mc.lists.fields[m]&&""!==rtb_admin_mc.lists.fields[m]&&l.find("#mcfrtb-"+m+" option[value="+rtb_admin_mc.lists.fields[m]+"]").attr("selected","selected");e.find("select").change(function(){d(a(this),a("option:selected",this).val(),e)}),e.append('<p class="description">'+rtb_admin_mc.strings.merge_description+"</p>"),e.addClass("active"),e.fadeIn()}}else"undefined"==typeof f.data||"undefined"==typeof f.data.msg?e.html('<p class="error">'+rtb_admin_mc.strings.api_unknown_error+"</p>"):e.html('<p class="error">'+f.data.msg+"</p>");c.animate({opacity:0},500,function(){})})}function d(b,c,d){if(""!==c){var e=0,f=[];if(a("select option:selected",d).each(function(){a(this).val()===c&&(f.push(a(this)),e++)}),e>1){b.find("option[value="+c+"]").prop("selected","");for(var g in f)f[g].parent().after('<span class="merge-duplicate-warning dashicons dashicons-lock"></span>');a(".merge-duplicate-warning").fadeOut(2e3,function(){a(this).remove()})}}}a(".mcfrtb-list-select").length&&b()});
1
  /*! mailchimp-for-rtb 2018-10-05 */
2
+ jQuery(document).ready(function(a){function b(){var b=a(".mcf-sap_loading");b.animate({opacity:1},500,function(){});var d={action:"mcfrtb-get-lists",nonce:rtb_admin_mc.ajax_nonce},e=a.param(d);a.post(ajaxurl,e,function(d){if(d.success){"undefined"==typeof d.data&&(console.log("no data returned from ajax request"),console.log(d)),b.stop(),b.css("opacity","0");var e=a("#mcfrtb-merge-controls").data("input-name")+"[list]";a(".mcf-list-select").append('<select name="'+e+'" id="'+e+'"><option></option></select>');var f=a(".mcf-list-select select");for(var g in d.data.lists){var h=a("<option></option>"),i=d.data.lists[g];h.attr("value",i.id).text(i.name),f.append(h)}a(f).change(function(b){c(a("option:selected",this).val())}),null!==rtb_admin_mc.lists&&"undefined"!=typeof rtb_admin_mc.lists.list&&""!==rtb_admin_mc.lists.list&&f.find("option[value="+rtb_admin_mc.lists.list+"]").attr("selected","selected").trigger("change")}else"undefined"==typeof d.data||"undefined"==typeof d.data.msg?a(".mcf-list-select").html('<span class="error">'+rtb_admin_mc.strings.api_unknown_error+"</span>"):a(".mcf-list-select").html('<span class="error">'+d.data.msg+"</span>"),b.animate({opacity:0},500,function(){})})}function c(b){var c=a(".mcf-sap_loading"),e=a("#mcfrtb-merge-controls");if(c.animate({opacity:1},500,function(){}),e.empty(),e.data("list-id",b),e.removeClass("active"),""===b)return void c.animate({opacity:0},500,function(){});var f={list:b,action:"mcfrtb-load-merge-fields",nonce:rtb_admin_mc.ajax_nonce},g=a.param(f);a.post(ajaxurl,g,function(f){if(f.success){if("undefined"==typeof f.data&&(console.log("no data returned from ajax request"),console.log(f)),f.data.list_id!=e.data("list-id"))return;if(e.empty(),"undefined"!=typeof rtb_admin_mc.merge_fields){var g=e.data("input-name")+"[fields]",h=a("<select><option></option></select>");for(var i in f.data.merge_fields){var j=a("<option></option>"),k=f.data.merge_fields[i];j.attr("value",k.tag).text(k.name),h.append(j)}e.hide(),e.append("<table></table>");var l=e.find("table");l.append("<tr><th>"+rtb_admin_mc.strings.merge_booking_data+"</th><th>"+rtb_admin_mc.strings.merge_list_field+"</th></tr>"),l.append("<tr><td>"+rtb_admin_mc.strings.merge_email_label+'</td><td><span class="description">'+rtb_admin_mc.strings.merge_email_description+"</span></td></tr>");for(var m in rtb_admin_mc.merge_fields)l.append('<tr><td><label for="mcfrtb-'+m+'">'+rtb_admin_mc.merge_fields[m]+'</label></td><td><select name="'+g+"["+m+']" id="mcfrtb-'+m+'">'+h.html()+"</select></td></tr>"),null!==rtb_admin_mc.lists&&b===rtb_admin_mc.lists.list&&"undefined"!=typeof rtb_admin_mc.lists.fields[m]&&""!==rtb_admin_mc.lists.fields[m]&&l.find("#mcfrtb-"+m+" option[value="+rtb_admin_mc.lists.fields[m]+"]").attr("selected","selected");e.find("select").change(function(){d(a(this),a("option:selected",this).val(),e)}),e.append('<p class="description">'+rtb_admin_mc.strings.merge_description+"</p>"),e.addClass("active"),e.fadeIn()}}else"undefined"==typeof f.data||"undefined"==typeof f.data.msg?e.html('<p class="error">'+rtb_admin_mc.strings.api_unknown_error+"</p>"):e.html('<p class="error">'+f.data.msg+"</p>");c.animate({opacity:0},500,function(){})})}function d(b,c,d){if(""!==c){var e=0,f=[];if(a("select option:selected",d).each(function(){a(this).val()===c&&(f.push(a(this)),e++)}),e>1){b.find("option[value="+c+"]").prop("selected","");for(var g in f)f[g].parent().after('<span class="merge-duplicate-warning dashicons dashicons-lock"></span>');a(".merge-duplicate-warning").fadeOut(2e3,function(){a(this).remove()})}}}a(".mcf-list-select").length&&b()});
assets/js/plugin-deactivation.js CHANGED
@@ -1,53 +1,53 @@
1
- jQuery(function($){
2
- var $deactivateLink = $('#the-list').find('[data-slug="restaurant-reservations"] span.deactivate a'),
3
- $overlay = $('#rtb-deactivate-survey-restaurant-reservations'),
4
- $form = $overlay.find('form'),
5
- formOpen = false;
6
- // Plugin listing table deactivate link.
7
- $deactivateLink.on('click', function(event) {
8
- event.preventDefault();
9
- $overlay.css('display', 'table');
10
- formOpen = true;
11
- $form.find('.rtb-deactivate-survey-option:first-of-type input[type=radio]').focus();
12
- });
13
- // Survey radio option selected.
14
- $form.on('change', 'input[type=radio]', function(event) {
15
- event.preventDefault();
16
- $form.find('input[type=text], .error').hide();
17
- $form.find('.rtb-deactivate-survey-option').removeClass('selected');
18
- $(this).closest('.rtb-deactivate-survey-option').addClass('selected').find('input[type=text]').show();
19
- });
20
- // Survey Skip & Deactivate.
21
- $form.on('click', '.rtb-deactivate-survey-deactivate', function(event) {
22
- event.preventDefault();
23
- location.href = $deactivateLink.attr('href');
24
- });
25
- // Survey submit.
26
- $form.submit(function(event) {
27
- event.preventDefault();
28
- if (! $form.find('input[type=radio]:checked').val()) {
29
- $form.find('.rtb-deactivate-survey-footer').prepend('<span class="error">Please select an option below</span>');
30
- return;
31
- }
32
- var data = {
33
- code: $form.find('.selected input[type=radio]').val(),
34
- install_time: $form.data('installtime'),
35
- reason: $form.find('.selected .rtb-deactivate-survey-option-reason').text(),
36
- details: $form.find('.selected input[type=text]').val(),
37
- site: rtb_deactivation_data.site_url,
38
- plugin: 'Five-Star Restaurant Reservations'
39
- }
40
- var submitSurvey = $.post('https://www.fivestarplugins.com/key-check/Deactivation_Surveys.php', data);
41
- submitSurvey.always(function() {
42
- location.href = $deactivateLink.attr('href');
43
- });
44
- });
45
- // Exit key closes survey when open.
46
- $(document).keyup(function(event) {
47
- if (27 === event.keyCode && formOpen) {
48
- $overlay.hide();
49
- formOpen = false;
50
- $deactivateLink.focus();
51
- }
52
- });
53
  });
1
+ jQuery(function($){
2
+ var $deactivateLink = $('#the-list').find('[data-slug="restaurant-reservations"] span.deactivate a'),
3
+ $overlay = $('#rtb-deactivate-survey-restaurant-reservations'),
4
+ $form = $overlay.find('form'),
5
+ formOpen = false;
6
+ // Plugin listing table deactivate link.
7
+ $deactivateLink.on('click', function(event) {
8
+ event.preventDefault();
9
+ $overlay.css('display', 'table');
10
+ formOpen = true;
11
+ $form.find('.rtb-deactivate-survey-option:first-of-type input[type=radio]').focus();
12
+ });
13
+ // Survey radio option selected.
14
+ $form.on('change', 'input[type=radio]', function(event) {
15
+ event.preventDefault();
16
+ $form.find('input[type=text], .error').hide();
17
+ $form.find('.rtb-deactivate-survey-option').removeClass('selected');
18
+ $(this).closest('.rtb-deactivate-survey-option').addClass('selected').find('input[type=text]').show();
19
+ });
20
+ // Survey Skip & Deactivate.
21
+ $form.on('click', '.rtb-deactivate-survey-deactivate', function(event) {
22
+ event.preventDefault();
23
+ location.href = $deactivateLink.attr('href');
24
+ });
25
+ // Survey submit.
26
+ $form.submit(function(event) {
27
+ event.preventDefault();
28
+ if (! $form.find('input[type=radio]:checked').val()) {
29
+ $form.find('.rtb-deactivate-survey-footer').prepend('<span class="error">Please select an option below</span>');
30
+ return;
31
+ }
32
+ var data = {
33
+ code: $form.find('.selected input[type=radio]').val(),
34
+ install_time: $form.data('installtime'),
35
+ reason: $form.find('.selected .rtb-deactivate-survey-option-reason').text(),
36
+ details: $form.find('.selected input[type=text]').val(),
37
+ site: rtb_deactivation_data.site_url,
38
+ plugin: 'Five-Star Restaurant Reservations'
39
+ }
40
+ var submitSurvey = $.post('https://www.fivestarplugins.com/key-check/Deactivation_Surveys.php', data);
41
+ submitSurvey.always(function() {
42
+ location.href = $deactivateLink.attr('href');
43
+ });
44
+ });
45
+ // Exit key closes survey when open.
46
+ $(document).keyup(function(event) {
47
+ if (27 === event.keyCode && formOpen) {
48
+ $overlay.hide();
49
+ formOpen = false;
50
+ $deactivateLink.focus();
51
+ }
52
+ });
53
  });
assets/js/rtb-recaptcha.js CHANGED
@@ -1,5 +1,5 @@
1
- var rtbLoadRecaptcha = function() {
2
- grecaptcha.render('rtb_recaptcha', {
3
- 'sitekey' : rtb_recaptcha.site_key
4
- });
5
  }
1
+ var rtbLoadRecaptcha = function() {
2
+ grecaptcha.render('rtb_recaptcha', {
3
+ 'sitekey' : rtb_recaptcha.site_key
4
+ });
5
  }
assets/js/stripe-payment.js CHANGED
@@ -1,191 +1,191 @@
1
- var key = null;
2
-
3
- if ( rtb_stripe_payment.stripe_mode == 'test' ) {
4
- key = rtb_stripe_payment.test_publishable_key;
5
- }
6
- else {
7
- key = rtb_stripe_payment.live_publishable_key;
8
- }
9
-
10
- if( rtb_stripe_payment.stripe_sca ) {
11
- var _stripe = Stripe(key);
12
- }
13
- else {
14
- Stripe.setPublishableKey(key);
15
- }
16
-
17
- function stripeResponseHandler(status, response) {
18
- if (response.error) {
19
- // show errors returned by Stripe
20
- jQuery(".payment-errors").html(response.error.message);
21
- // re-enable the submit button
22
- jQuery('#stripe-submit').attr("disabled", false);
23
- }
24
- else {
25
- var form$ = jQuery("#stripe-payment-form");
26
- // token contains id, last4, and card type
27
- var token = response['id'];
28
- // insert the token into the form so it gets submitted to the server
29
- form$.append("<input type='hidden' name='stripeToken' value='" + token + "'/>");
30
- // and submit
31
- form$.get(0).submit();
32
- }
33
- }
34
-
35
- function error_handler(msg = '') {
36
- jQuery('.payment-errors').html(msg);
37
- enable_payment_form();
38
- }
39
-
40
- function disable_payment_form() {
41
- jQuery('.payment-errors').html('');
42
- rtb_stripe_payment.stripe_sca && jQuery('.stripe-payment-help-text').slideDown();
43
- jQuery('#stripe-submit').prop('disabled', true);
44
- }
45
- function enable_payment_form() {
46
- rtb_stripe_payment.stripe_sca && jQuery('.stripe-payment-help-text').slideUp();
47
- jQuery('#stripe-submit').prop('disabled', false);
48
- }
49
-
50
- jQuery(document).ready(function($) {
51
-
52
- var cardElement = null;
53
-
54
- // setup card element
55
- if( rtb_stripe_payment.stripe_sca ) {
56
- var stripeElement = _stripe.elements();
57
- cardElement = stripeElement.create('card', { hidePostalCode: true });
58
- cardElement.mount('#cardElement');
59
-
60
- cardElement.on('change', function(ev) {
61
- if (ev.complete) {
62
- // enable payment button
63
- enable_payment_form();
64
- }
65
- else {
66
- if (ev.error) {
67
- error_handler(ev.error.message);
68
- }
69
- }
70
- });
71
- }
72
-
73
- $('#stripe-payment-form .single-masked').on('keyup', function (ev) {
74
- let value = $(this).val();
75
-
76
- if ( /\//.test(value) ) {
77
- value = value.replace( /(\/)+/, '/' );
78
- }
79
-
80
- if(value.length > 2 && !/\//.test(value)) {
81
- value = value.split('');
82
- value.splice(2, 0, '/');
83
- value = value.join('');
84
- }
85
-
86
- $(this).val(value);
87
- });
88
-
89
- $("#stripe-payment-form").submit(function(event) {
90
- // disable the submit button to prevent repeated clicks
91
- disable_payment_form();
92
-
93
- // send the card details to Stripe
94
- if( rtb_stripe_payment.stripe_sca ) {
95
-
96
- var booking_id = $(this).data( 'booking_id' );
97
- // Call your backend to create the Checkout Session
98
- var params = {
99
- 'action': 'rtb_stripe_get_intent',
100
- 'booking_id': booking_id
101
- };
102
-
103
- $.post(ajaxurl, params, function(result) {
104
- result = JSON.parse(result);
105
- if( result.success ) {
106
-
107
- _stripe.confirmCardPayment(result.clientSecret, {
108
- payment_method: {
109
- card: cardElement,
110
- billing_details: {
111
- name: result.name,
112
- email: result.email
113
- }
114
- }
115
- }).then(function(result) {
116
- params = {
117
- action: 'rtb_stripe_pmt_succeed',
118
- booking_id: booking_id
119
- };
120
-
121
- if (result.error) {
122
- // Show error to your customer (e.g., insufficient funds)
123
- params['success'] = false;
124
- params['message'] = result.error.message;
125
- error_handler(result.error.message);
126
- }
127
- else {
128
- var pi = result.paymentIntent;
129
-
130
- // The payment has been processed!
131
- if (pi.status === 'succeeded') {
132
- params['success'] = true;
133
- params['payment_amount'] = pi.amount;
134
- params['payment_id'] = pi.id;
135
- // params['payment_intent'] = pi;
136
- }
137
- else {
138
- params['success'] = false;
139
- params['message'] = 'Unknown error';
140
- }
141
- }
142
-
143
- $.post(ajaxurl, params, function (result) {
144
- result = JSON.parse(result);
145
-
146
- if(true == result.success) {
147
- var url = new URL(window.location.pathname, window.location.origin);
148
-
149
- for(const [key, value] of Object.entries(result.urlParams)) {
150
- url.searchParams.append(key, value);
151
- }
152
-
153
- window.location = url.href;
154
- }
155
- });
156
- });
157
- }
158
- else {
159
- error_handler(result.message);
160
- console.log('RTB-Stripe error: ', result.message);
161
- }
162
- });
163
- }
164
- else {
165
-
166
- let exp_month, exp_year;
167
-
168
- let single_field = $('#stripe-payment-form .single-masked').length;
169
- if(single_field) {
170
- let data = $('#stripe-payment-form .single-masked').val().split('/');
171
- exp_month = data[0];
172
- exp_year = data[1];
173
- }
174
- else {
175
- exp_month = $('input[data-stripe="exp_month"]').val();
176
- exp_year = $('input[data-stripe="exp_year"]').val();
177
- }
178
-
179
- Stripe.createToken({
180
- number: $('input[data-stripe="card_number"]').val(),
181
- cvc: $('input[data-stripe="card_cvc"]').val(),
182
- exp_month: exp_month,
183
- exp_year: exp_year,
184
- currency: $('input[data-stripe="currency"]').val()
185
- }, stripeResponseHandler);
186
- }
187
-
188
- // prevent the form from submitting with the default action
189
- return false;
190
- });
191
  });
1
+ var key = null;
2
+
3
+ if ( rtb_stripe_payment.stripe_mode == 'test' ) {
4
+ key = rtb_stripe_payment.test_publishable_key;
5
+ }
6
+ else {
7
+ key = rtb_stripe_payment.live_publishable_key;
8
+ }
9
+
10
+ if( rtb_stripe_payment.stripe_sca ) {
11
+ var _stripe = Stripe(key);
12
+ }
13
+ else {
14
+ Stripe.setPublishableKey(key);
15
+ }
16
+
17
+ function stripeResponseHandler(status, response) {
18
+ if (response.error) {
19
+ // show errors returned by Stripe
20
+ jQuery(".payment-errors").html(response.error.message);
21
+ // re-enable the submit button
22
+ jQuery('#stripe-submit').attr("disabled", false);
23
+ }
24
+ else {
25
+ var form$ = jQuery("#stripe-payment-form");
26
+ // token contains id, last4, and card type
27
+ var token = response['id'];
28
+ // insert the token into the form so it gets submitted to the server
29
+ form$.append("<input type='hidden' name='stripeToken' value='" + token + "'/>");
30
+ // and submit
31
+ form$.get(0).submit();
32
+ }
33
+ }
34
+
35
+ function error_handler(msg = '') {
36
+ jQuery('.payment-errors').html(msg);
37
+ enable_payment_form();
38
+ }
39
+
40
+ function disable_payment_form() {
41
+ jQuery('.payment-errors').html('');
42
+ rtb_stripe_payment.stripe_sca && jQuery('.stripe-payment-help-text').slideDown();
43
+ jQuery('#stripe-submit').prop('disabled', true);
44
+ }
45
+ function enable_payment_form() {
46
+ rtb_stripe_payment.stripe_sca && jQuery('.stripe-payment-help-text').slideUp();
47
+ jQuery('#stripe-submit').prop('disabled', false);
48
+ }
49
+
50
+ jQuery(document).ready(function($) {
51
+
52
+ var cardElement = null;
53
+
54
+ // setup card element
55
+ if( rtb_stripe_payment.stripe_sca ) {
56
+ var stripeElement = _stripe.elements();
57
+ cardElement = stripeElement.create('card', { hidePostalCode: true });
58
+ cardElement.mount('#cardElement');
59
+
60
+ cardElement.on('change', function(ev) {
61
+ if (ev.complete) {
62
+ // enable payment button
63
+ enable_payment_form();
64
+ }
65
+ else {
66
+ if (ev.error) {
67
+ error_handler(ev.error.message);
68
+ }
69
+ }
70
+ });
71
+ }
72
+
73
+ $('#stripe-payment-form .single-masked').on('keyup', function (ev) {
74
+ let value = $(this).val();
75
+
76
+ if ( /\//.test(value) ) {
77
+ value = value.replace( /(\/)+/, '/' );
78
+ }
79
+
80
+ if(value.length > 2 && !/\//.test(value)) {
81
+ value = value.split('');
82
+ value.splice(2, 0, '/');
83
+ value = value.join('');
84
+ }
85
+
86
+ $(this).val(value);
87
+ });
88
+
89
+ $("#stripe-payment-form").submit(function(event) {
90
+ // disable the submit button to prevent repeated clicks
91
+ disable_payment_form();
92
+
93
+ // send the card details to Stripe
94
+ if( rtb_stripe_payment.stripe_sca ) {
95
+
96
+ var booking_id = $(this).data( 'booking_id' );
97
+ // Call your backend to create the Checkout Session
98
+ var params = {
99
+ 'action': 'rtb_stripe_get_intent',
100
+ 'booking_id': booking_id
101
+ };
102
+
103
+ $.post(ajaxurl, params, function(result) {
104
+ result = JSON.parse(result);
105
+ if( result.success ) {
106
+
107
+ _stripe.confirmCardPayment(result.clientSecret, {
108
+ payment_method: {
109
+ card: cardElement,
110
+ billing_details: {
111
+ name: result.name,
112
+ email: result.email
113
+ }
114
+ }
115
+ }).then(function(result) {
116
+ params = {
117
+ action: 'rtb_stripe_pmt_succeed',
118
+ booking_id: booking_id
119
+ };
120
+
121
+ if (result.error) {
122
+ // Show error to your customer (e.g., insufficient funds)
123
+ params['success'] = false;
124
+ params['message'] = result.error.message;
125
+ error_handler(result.error.message);
126
+ }
127
+ else {
128
+ var pi = result.paymentIntent;
129
+
130
+ // The payment has been processed!
131
+ if (pi.status === 'succeeded') {
132
+ params['success'] = true;
133
+ params['payment_amount'] = pi.amount;
134
+ params['payment_id'] = pi.id;
135
+ // params['payment_intent'] = pi;
136
+ }
137
+ else {
138
+ params['success'] = false;
139
+ params['message'] = 'Unknown error';
140
+ }
141
+ }
142
+
143
+ $.post(ajaxurl, params, function (result) {
144
+ result = JSON.parse(result);
145
+
146
+ if(true == result.success) {
147
+ var url = new URL(window.location.pathname, window.location.origin);
148
+
149
+ for(const [key, value] of Object.entries(result.urlParams)) {
150
+ url.searchParams.append(key, value);
151
+ }
152
+
153
+ window.location = url.href;
154
+ }
155
+ });
156
+ });
157
+ }
158
+ else {
159
+ error_handler(result.message);
160
+ console.log('RTB-Stripe error: ', result.message);
161
+ }
162
+ });
163
+ }
164
+ else {
165
+
166
+ let exp_month, exp_year;
167
+
168
+ let single_field = $('#stripe-payment-form .single-masked').length;
169
+ if(single_field) {
170
+ let data = $('#stripe-payment-form .single-masked').val().split('/');
171
+ exp_month = data[0];
172
+ exp_year = data[1];
173
+ }
174
+ else {
175
+ exp_month = $('input[data-stripe="exp_month"]').val();
176
+ exp_year = $('input[data-stripe="exp_year"]').val();
177
+ }
178
+
179
+ Stripe.createToken({
180
+ number: $('input[data-stripe="card_number"]').val(),
181
+ cvc: $('input[data-stripe="card_cvc"]').val(),
182
+ exp_month: exp_month,
183
+ exp_year: exp_year,
184
+ currency: $('input[data-stripe="currency"]').val()
185
+ }, stripeResponseHandler);
186
+ }
187
+
188
+ // prevent the form from submitting with the default action
189
+ return false;
190
+ });
191
  });
includes/Addons.class.php CHANGED
@@ -1,362 +1,362 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbAddons' ) ) {
5
- /**
6
- * Class to handle the addons page for Restaurant Reservations
7
- *
8
- * @since 1.3
9
- */
10
- class rtbAddons {
11
-
12
- public function __construct( ) {
13
-
14
- // Add the admin menu
15
- add_action( 'admin_menu', array( $this, 'add_menu_page' ), 100 );
16
-
17
- // Add a newsletter subscription prompt above the addons
18
- add_action( 'rtb_addons_pre', array( $this, 'add_subscribe_pompt' ) );
19
- }
20
-
21
- /**
22
- * Add the addons page to the admin menu
23
- */
24
- public function add_menu_page() {
25
-
26
- add_submenu_page(
27
- 'rtb-bookings',
28
- _x( 'Addons', 'Title of addons page', 'restaurant-reservations' ),
29
- _x( 'Addons', 'Title of addons page in the admin menu', 'restaurant-reservations' ),
30
- 'manage_options',
31
- 'rtb-addons',
32
- array( $this, 'show_admin_addons_page' )
33
- );
34
-
35
- }
36
-
37
- /**
38
- * Display the addons page
39
- */
40
- public function show_admin_addons_page() {
41
-
42
- // Set campaign parameters for addon URLs
43
- $url_params = '?utm_source=Plugin&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations';
44
- ?>
45
-
46
- <div class="wrap">
47
- <h1><?php _e( 'Addons for Restaurant Reservations', 'restaurant-reservations' ); ?></h1>
48
- <?php do_action( 'rtb_addons_pre' ); ?>
49
- <div class="rtb-addons">
50
- <div class="addon addon-custom-fields">
51
- <a href="https://themeofthecrop.com/plugins/restaurant-reservations/custom-fields/<?php echo $url_params; ?>">
52
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/custom-fields.png'; ?>">
53
- </a>
54
- <h3><?php esc_html_e( 'Custom Fields', 'restaurant-reservations' ); ?></h3>
55
- <div class="details">
56
- <div class="description">
57
- <?php esc_html_e( 'Plan your dinner service better by asking for special seating requests, dietary needs and more when customers book online.', 'restaurant-reservations' ); ?>
58
- </div>
59
- <div class="action">
60
- <a href="https://themeofthecrop.com/plugins/restaurant-reservations/custom-fields/<?php echo $url_params; ?>" class="button button-primary" target="_blank">
61
- <?php esc_html_e( 'Learn More', 'restaurant-reservations' ); ?>
62
- </a>
63
- </div>
64
- </div>
65
- </div>
66
- <div class="addon addon-export-bookings">
67
- <a href="https://themeofthecrop.com/plugins/restaurant-reservations/export-bookings/<?php echo $url_params; ?>">
68
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/export-bookings.png'; ?>">
69
- </a>
70
- <h3><?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?></h3>
71
- <div class="details">
72
- <div class="description">
73
- <?php esc_html_e( 'Easily print your bookings in a PDF or export them to an Excel/CSV file so you can analyze patterns, cull customer data and import bookings into other services.', 'restaurant-reservations' ); ?>
74
- </div>
75
- <div class="action">
76
- <a href="https://themeofthecrop.com/plugins/restaurant-reservations/export-bookings/<?php echo $url_params; ?>" class="button button-primary" target="_blank">
77
- <?php esc_html_e( 'Learn More', 'restaurant-reservations' ); ?>
78
- </a>
79
- </div>
80
- </div>
81
- </div>
82
- <div class="addon addon-email-templates">
83
- <a href="https://themeofthecrop.com/plugins/restaurant-reservations/email-templates/<?php echo $url_params; ?>">
84
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/email-templates.png'; ?>">
85
- </a>
86
- <h3><?php esc_html_e( 'Email Templates', 'restaurant-reservations' ); ?></h3>
87
- <div class="details">
88
- <div class="description">
89
- <?php esc_html_e( 'Send beautiful email notifications with your own logo and brand colors when your customers make a reservation.', 'restaurant-reservations' ); ?>
90
- </div>
91
- <div class="action">
92
- <a href="https://themeofthecrop.com/plugins/restaurant-reservations/email-templates/<?php echo $url_params; ?>" class="button button-primary" target="_blank">
93
- <?php esc_html_e( 'Learn More', 'restaurant-reservations' ); ?>
94
- </a>
95
- </div>
96
- </div>
97
- </div>
98
- <div class="addon addon-mailchimp">
99
- <a href="https://themeofthecrop.com/plugins/restaurant-reservations/mailchimp/<?php echo $url_params; ?>">
100
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/mailchimp.png'; ?>">
101
- </a>
102
- <h3><?php esc_html_e( 'MailChimp', 'restaurant-reservations' ); ?></h3>
103
- <div class="details">
104
- <div class="description">
105
- <?php esc_html_e( 'Subscribe requests to your MailChimp mailing list and watch your subscription rates grow effortlessly.', 'restaurant-reservations' ); ?>
106
- </div>
107
- <div class="action">
108
- <a href="https://themeofthecrop.com/plugins/restaurant-reservations/mailchimp/<?php echo $url_params; ?>" class="button button-primary" target="_blank">
109
- <?php esc_html_e( 'Learn More', 'restaurant-reservations' ); ?>
110
- </a>
111
- </div>
112
- </div>
113
- </div>
114
- </div><?php /*
115
- <h2>Recommended Themes</h2>
116
- <p>The following restaurant themes integrate beautifully with Restaurant Reservations, providing a clean, stylized booking form that matches your site's design.</p>
117
- <div class="rtb-addons">
118
- <div class="addon addon-themes">
119
- <a href="https://themeofthecrop.com/themes/augustan<?php echo $url_params; ?>">
120
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-augustan.jpg'; ?>">
121
- </a>
122
- <h3><?php esc_html_e( 'Augustan', 'restaurant-reservations' ); ?></h3>
123
- <div class="details">
124
- <div class="description">
125
- <?php esc_html_e( 'A traditionally elegant theme for high-class restaurants, with simple setup and powerful features.', 'restaurant-reservations' ); ?>
126
- </div>
127
- <div class="action">
128
- <a href="https://themeofthecrop.com/themes/augustan<?php echo $url_params; ?>" class="button" target="_blank">
129
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
130
- </a>
131
- <span class="rtb-by">
132
- by <a href="https://themeofthecrop.com/<?php echo $url_params; ?>">Theme of the Crop</a>
133
- </span>
134
- </div>
135
- </div>
136
- </div>
137
- <div class="addon addon-themes">
138
- <a href="https://themeofthecrop.com/themes/luigi<?php echo $url_params; ?>">
139
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-luigi.jpg'; ?>">
140
- </a>
141
- <h3><?php esc_html_e( 'Luigi', 'restaurant-reservations' ); ?></h3>
142
- <div class="details">
143
- <div class="description">
144
- <?php esc_html_e( 'A smart theme for upscale bistros and fine Italian restaurants. Get up and running quickly.', 'restaurant-reservations' ); ?>
145
- </div>
146
- <div class="action">
147
- <a href="https://themeofthecrop.com/themes/luigi<?php echo $url_params; ?>" class="button" target="_blank">
148
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
149
- </a>
150
- <span class="rtb-by">
151
- by <a href="https://themeofthecrop.com/<?php echo $url_params; ?>">Theme of the Crop</a>
152
- </span>
153
- </div>
154
- </div>
155
- </div>
156
- <div class="addon addon-themes">
157
- <a href="https://themeofthecrop.com/themes/the-spot<?php echo $url_params; ?>">
158
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-the-spot.jpg'; ?>">
159
- </a>
160
- <h3><?php esc_html_e( 'The Spot', 'restaurant-reservations' ); ?></h3>
161
- <div class="details">
162
- <div class="description">
163
- <?php esc_html_e( 'A vibrant theme for bars, pubs and destination restaurants with an attention-grabbing homepage.', 'restaurant-reservations' ); ?>
164
- </div>
165
- <div class="action">
166
- <a href="https://themeofthecrop.com/themes/the-spot<?php echo $url_params; ?>" class="button" target="_blank">
167
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
168
- </a>
169
- <span class="rtb-by">
170
- by <a href="https://themeofthecrop.com/<?php echo $url_params; ?>">Theme of the Crop</a>
171
- </span>
172
- </div>
173
- </div>
174
- </div>
175
- <div class="addon addon-themes">
176
- <a href="https://themeofthecrop.com/themes/plate-up<?php echo $url_params; ?>">
177
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-plate-up.jpg'; ?>">
178
- </a>
179
- <h3><?php esc_html_e( 'Plate Up', 'restaurant-reservations' ); ?></h3>
180
- <div class="details">
181
- <div class="description">
182
- <?php esc_html_e( 'A refined theme for sophisticated, modern restaurants to drive customers to your booking form.', 'restaurant-reservations' ); ?>
183
- </div>
184
- <div class="action">
185
- <a href="https://themeofthecrop.com/themes/plate-up<?php echo $url_params; ?>" class="button" target="_blank">
186
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
187
- </a>
188
- <span class="rtb-by">
189
- by <a href="https://themeofthecrop.com/<?php echo $url_params; ?>">Theme of the Crop</a>
190
- </span>
191
- </div>
192
- </div>
193
- </div>
194
- <div class="addon addon-themes">
195
- <a href="https://themebeans.com/themes/plate?utm_source=totc_addons_plate&utm_medium=banner&utm_campaign=TOTC%20Addons%20Link%2C%20Plate">
196
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-plate.jpg'; ?>">
197
- </a>
198
- <h3><?php esc_html_e( 'Plate', 'restaurant-reservations' ); ?></h3>
199
- <div class="details">
200
- <div class="description">
201
- <?php esc_html_e( 'A delightfully beautiful WordPress theme designed to help you build a stunning restaurant website.', 'restaurant-reservations' ); ?>
202
- </div>
203
- <div class="action">
204
- <a href="https://themebeans.com/themes/plate?utm_source=totc_addons_plate&utm_medium=banner&utm_campaign=TOTC%20Addons%20Link%2C%20Plate" class="button" target="_blank">
205
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
206
- </a>
207
- <span class="rtb-by">
208
- by <a href="https://themebeans.com?utm_source=totc_addons_plate&utm_medium=banner&utm_campaign=TOTC%20Addons%20Link%2C%20Plate">ThemeBeans</a>
209
- </span>
210
- </div>
211
- </div>
212
- </div>
213
- <div class="addon addon-themes">
214
- <a href="https://wordpress.org/themes/auberge/">
215
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-auberge.jpg'; ?>">
216
- </a>
217
- <h3><?php esc_html_e( 'Auberge', 'restaurant-reservations' ); ?></h3>
218
- <div class="details">
219
- <div class="description">
220
- <?php esc_html_e( 'Display a menu of your restaurant, café or bar stylishly with this free mobile-friendly WordPress theme.', 'restaurant-reservations' ); ?>
221
- </div>
222
- <div class="action">
223
- <a href="https://wordpress.org/themes/auberge/" class="button" target="_blank">
224
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
225
- </a>
226
- <span class="rtb-by">
227
- by <a href="https://www.webmandesign.eu/">Webman Design</a>
228
- </span>
229
- </div>
230
- </div>
231
- </div>
232
- <div class="addon addon-themes">
233
- <a href="http://www.anarieldesign.com/themes/restaurant-bar-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">
234
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-liber.jpg'; ?>">
235
- </a>
236
- <h3><?php esc_html_e( 'Liber', 'restaurant-reservations' ); ?></h3>
237
- <div class="details">
238
- <div class="description">
239
- <?php esc_html_e( 'A responsive theme optimized for restaurants and bars supporting features these websites need.', 'restaurant-reservations' ); ?>
240
- </div>
241
- <div class="action">
242
- <a href="http://www.anarieldesign.com/themes/restaurant-bar-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations" class="button" target="_blank">
243
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
244
- </a>
245
- <span class="rtb-by">
246
- by <a href="http://www.anarieldesign.com/">Anariel Design</a>
247
- </span>
248
- </div>
249
- </div>
250
- </div>
251
- <div class="addon addon-themes">
252
- <a href="https://wordpress.org/themes/brasserie/">
253
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-brasserie.jpg'; ?>">
254
- </a>
255
- <h3><?php esc_html_e( 'Brasserie', 'restaurant-reservations' ); ?></h3>
256
- <div class="details">
257
- <div class="description">
258
- <?php esc_html_e( 'A delightfully simple to use and beautifully crafted free theme for any food establishment.', 'restaurant-reservations' ); ?>
259
- </div>
260
- <div class="action">
261
- <a href="https://wordpress.org/themes/brasserie/" class="button" target="_blank">
262
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
263
- </a>
264
- <span class="rtb-by">
265
- by <a href="https://www.templateexpress.com/">Template Express</a>
266
- </span>
267
- </div>
268
- </div>
269
- </div>
270
- <div class="addon addon-themes">
271
- <a href="http://www.anarieldesign.com/themes/food-blog-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">
272
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-veggie.jpg'; ?>">
273
- </a>
274
- <h3><?php esc_html_e( 'Veggie', 'restaurant-reservations' ); ?></h3>
275
- <div class="details">
276
- <div class="description">
277
- <?php esc_html_e( 'A food blogging and restaurant theme with modern, easy-to-read typography and minimalist design.', 'restaurant-reservations' ); ?>
278
- </div>
279
- <div class="action">
280
- <a href="http://www.anarieldesign.com/themes/food-blog-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations" class="button" target="_blank">
281
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
282
- </a>
283
- <span class="rtb-by">
284
- by <a href="http://www.anarieldesign.com/">Anariel Design</a>
285
- </span>
286
- </div>
287
- </div>
288
- </div>
289
- <div class="addon addon-themes">
290
- <a href="http://www.anarieldesign.com/themes/wine-and-winery-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">
291
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-good-ol-wine.jpg'; ?>">
292
- </a>
293
- <h3><?php esc_html_e( "Good Ol' Wine", 'restaurant-reservations' ); ?></h3>
294
- <div class="details">
295
- <div class="description">
296
- <?php esc_html_e( 'A beautiful responsive theme that is suitable for wine enthusiasts, wineries and wine bars.', 'restaurant-reservations' ); ?>
297
- </div>
298
- <div class="action">
299
- <a href="http://www.anarieldesign.com/themes/wine-and-winery-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations" class="button" target="_blank">
300
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
301
- </a>
302
- <span class="rtb-by">
303
- by <a href="http://www.anarieldesign.com/">Anariel Design</a>
304
- </span>
305
- </div>
306
- </div>
307
- </div>
308
- <div class="addon addon-themes">
309
- <a href="http://www.anarieldesign.com/themes/simple-and-fresh-blogging-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">
310
- <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-healthy-living.jpg'; ?>">
311
- </a>
312
- <h3><?php esc_html_e( 'Healthy Living', 'restaurant-reservations' ); ?></h3>
313
- <div class="details">
314
- <div class="description">
315
- <?php esc_html_e( 'A modern, clean healthy food blogging theme that can be used for a restaurant as well.', 'restaurant-reservations' ); ?>
316
- </div>
317
- <div class="action">
318
- <a href="http://www.anarieldesign.com/themes/simple-and-fresh-blogging-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations" class="button" target="_blank">
319
- <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
320
- </a>
321
- <span class="rtb-by">
322
- by <a href="http://www.anarieldesign.com/">Anariel Design</a>
323
- </span>
324
- </div>
325
- </div>
326
- </div>
327
- </div>*/ ?>
328
- <?php do_action( 'rtb_addons_post' ); ?>
329
- </div>
330
-
331
- <?php
332
- }
333
-
334
- /**
335
- * Add a prompt for users to subscribe to the Theme of the Crop mailing list
336
- * below the addons list.
337
- *
338
- * @since 0.1
339
- */
340
- public function add_subscribe_pompt() {
341
-
342
- ?>
343
-
344
- <p>
345
- <?php
346
- echo sprintf(
347
- esc_html_x( 'Find out when new addons are available by subscribing to the %smonthly newsletter%s, liking %sTheme of the Crop%s on Facebook, or following %sTheme of the Crop%s on Twitter.', 'restaurant-reservations' ),
348
- '<a target="_blank" href="https://themeofthecrop.com/about/mailing-list/?utm_source=Plugin&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">',
349
- '</a>',
350
- '<a target="_blank" href="https://www.facebook.com/themeofthecrop/">',
351
- '</a>',
352
- '<a target="_blank" href="http://twitter.com/themeofthecrop">',
353
- '</a>'
354
- );
355
- ?>
356
- </p>
357
-
358
- <?php
359
- }
360
-
361
- }
362
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbAddons' ) ) {
5
+ /**
6
+ * Class to handle the addons page for Restaurant Reservations
7
+ *
8
+ * @since 1.3
9
+ */
10
+ class rtbAddons {
11
+
12
+ public function __construct( ) {
13
+
14
+ // Add the admin menu
15
+ add_action( 'admin_menu', array( $this, 'add_menu_page' ), 100 );
16
+
17
+ // Add a newsletter subscription prompt above the addons
18
+ add_action( 'rtb_addons_pre', array( $this, 'add_subscribe_pompt' ) );
19
+ }
20
+
21
+ /**
22
+ * Add the addons page to the admin menu
23
+ */
24
+ public function add_menu_page() {
25
+
26
+ add_submenu_page(
27
+ 'rtb-bookings',
28
+ _x( 'Addons', 'Title of addons page', 'restaurant-reservations' ),
29
+ _x( 'Addons', 'Title of addons page in the admin menu', 'restaurant-reservations' ),
30
+ 'manage_options',
31
+ 'rtb-addons',
32
+ array( $this, 'show_admin_addons_page' )
33
+ );
34
+
35
+ }
36
+
37
+ /**
38
+ * Display the addons page
39
+ */
40
+ public function show_admin_addons_page() {
41
+
42
+ // Set campaign parameters for addon URLs
43
+ $url_params = '?utm_source=Plugin&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations';
44
+ ?>
45
+
46
+ <div class="wrap">
47
+ <h1><?php _e( 'Addons for Restaurant Reservations', 'restaurant-reservations' ); ?></h1>
48
+ <?php do_action( 'rtb_addons_pre' ); ?>
49
+ <div class="rtb-addons">
50
+ <div class="addon addon-custom-fields">
51
+ <a href="https://themeofthecrop.com/plugins/restaurant-reservations/custom-fields/<?php echo $url_params; ?>">
52
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/custom-fields.png'; ?>">
53
+ </a>
54
+ <h3><?php esc_html_e( 'Custom Fields', 'restaurant-reservations' ); ?></h3>
55
+ <div class="details">
56
+ <div class="description">
57
+ <?php esc_html_e( 'Plan your dinner service better by asking for special seating requests, dietary needs and more when customers book online.', 'restaurant-reservations' ); ?>
58
+ </div>
59
+ <div class="action">
60
+ <a href="https://themeofthecrop.com/plugins/restaurant-reservations/custom-fields/<?php echo $url_params; ?>" class="button button-primary" target="_blank">
61
+ <?php esc_html_e( 'Learn More', 'restaurant-reservations' ); ?>
62
+ </a>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ <div class="addon addon-export-bookings">
67
+ <a href="https://themeofthecrop.com/plugins/restaurant-reservations/export-bookings/<?php echo $url_params; ?>">
68
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/export-bookings.png'; ?>">
69
+ </a>
70
+ <h3><?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?></h3>
71
+ <div class="details">
72
+ <div class="description">
73
+ <?php esc_html_e( 'Easily print your bookings in a PDF or export them to an Excel/CSV file so you can analyze patterns, cull customer data and import bookings into other services.', 'restaurant-reservations' ); ?>
74
+ </div>
75
+ <div class="action">
76
+ <a href="https://themeofthecrop.com/plugins/restaurant-reservations/export-bookings/<?php echo $url_params; ?>" class="button button-primary" target="_blank">
77
+ <?php esc_html_e( 'Learn More', 'restaurant-reservations' ); ?>
78
+ </a>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ <div class="addon addon-email-templates">
83
+ <a href="https://themeofthecrop.com/plugins/restaurant-reservations/email-templates/<?php echo $url_params; ?>">
84
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/email-templates.png'; ?>">
85
+ </a>
86
+ <h3><?php esc_html_e( 'Email Templates', 'restaurant-reservations' ); ?></h3>
87
+ <div class="details">
88
+ <div class="description">
89
+ <?php esc_html_e( 'Send beautiful email notifications with your own logo and brand colors when your customers make a reservation.', 'restaurant-reservations' ); ?>
90
+ </div>
91
+ <div class="action">
92
+ <a href="https://themeofthecrop.com/plugins/restaurant-reservations/email-templates/<?php echo $url_params; ?>" class="button button-primary" target="_blank">
93
+ <?php esc_html_e( 'Learn More', 'restaurant-reservations' ); ?>
94
+ </a>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ <div class="addon addon-mailchimp">
99
+ <a href="https://themeofthecrop.com/plugins/restaurant-reservations/mailchimp/<?php echo $url_params; ?>">
100
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/mailchimp.png'; ?>">
101
+ </a>
102
+ <h3><?php esc_html_e( 'MailChimp', 'restaurant-reservations' ); ?></h3>
103
+ <div class="details">
104
+ <div class="description">
105
+ <?php esc_html_e( 'Subscribe requests to your MailChimp mailing list and watch your subscription rates grow effortlessly.', 'restaurant-reservations' ); ?>
106
+ </div>
107
+ <div class="action">
108
+ <a href="https://themeofthecrop.com/plugins/restaurant-reservations/mailchimp/<?php echo $url_params; ?>" class="button button-primary" target="_blank">
109
+ <?php esc_html_e( 'Learn More', 'restaurant-reservations' ); ?>
110
+ </a>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div><?php /*
115
+ <h2>Recommended Themes</h2>
116
+ <p>The following restaurant themes integrate beautifully with Restaurant Reservations, providing a clean, stylized booking form that matches your site's design.</p>
117
+ <div class="rtb-addons">
118
+ <div class="addon addon-themes">
119
+ <a href="https://themeofthecrop.com/themes/augustan<?php echo $url_params; ?>">
120
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-augustan.jpg'; ?>">
121
+ </a>
122
+ <h3><?php esc_html_e( 'Augustan', 'restaurant-reservations' ); ?></h3>
123
+ <div class="details">
124
+ <div class="description">
125
+ <?php esc_html_e( 'A traditionally elegant theme for high-class restaurants, with simple setup and powerful features.', 'restaurant-reservations' ); ?>
126
+ </div>
127
+ <div class="action">
128
+ <a href="https://themeofthecrop.com/themes/augustan<?php echo $url_params; ?>" class="button" target="_blank">
129
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
130
+ </a>
131
+ <span class="rtb-by">
132
+ by <a href="https://themeofthecrop.com/<?php echo $url_params; ?>">Theme of the Crop</a>
133
+ </span>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ <div class="addon addon-themes">
138
+ <a href="https://themeofthecrop.com/themes/luigi<?php echo $url_params; ?>">
139
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-luigi.jpg'; ?>">
140
+ </a>
141
+ <h3><?php esc_html_e( 'Luigi', 'restaurant-reservations' ); ?></h3>
142
+ <div class="details">
143
+ <div class="description">
144
+ <?php esc_html_e( 'A smart theme for upscale bistros and fine Italian restaurants. Get up and running quickly.', 'restaurant-reservations' ); ?>
145
+ </div>
146
+ <div class="action">
147
+ <a href="https://themeofthecrop.com/themes/luigi<?php echo $url_params; ?>" class="button" target="_blank">
148
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
149
+ </a>
150
+ <span class="rtb-by">
151
+ by <a href="https://themeofthecrop.com/<?php echo $url_params; ?>">Theme of the Crop</a>
152
+ </span>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ <div class="addon addon-themes">
157
+ <a href="https://themeofthecrop.com/themes/the-spot<?php echo $url_params; ?>">
158
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-the-spot.jpg'; ?>">
159
+ </a>
160
+ <h3><?php esc_html_e( 'The Spot', 'restaurant-reservations' ); ?></h3>
161
+ <div class="details">
162
+ <div class="description">
163
+ <?php esc_html_e( 'A vibrant theme for bars, pubs and destination restaurants with an attention-grabbing homepage.', 'restaurant-reservations' ); ?>
164
+ </div>
165
+ <div class="action">
166
+ <a href="https://themeofthecrop.com/themes/the-spot<?php echo $url_params; ?>" class="button" target="_blank">
167
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
168
+ </a>
169
+ <span class="rtb-by">
170
+ by <a href="https://themeofthecrop.com/<?php echo $url_params; ?>">Theme of the Crop</a>
171
+ </span>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ <div class="addon addon-themes">
176
+ <a href="https://themeofthecrop.com/themes/plate-up<?php echo $url_params; ?>">
177
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-plate-up.jpg'; ?>">
178
+ </a>
179
+ <h3><?php esc_html_e( 'Plate Up', 'restaurant-reservations' ); ?></h3>
180
+ <div class="details">
181
+ <div class="description">
182
+ <?php esc_html_e( 'A refined theme for sophisticated, modern restaurants to drive customers to your booking form.', 'restaurant-reservations' ); ?>
183
+ </div>
184
+ <div class="action">
185
+ <a href="https://themeofthecrop.com/themes/plate-up<?php echo $url_params; ?>" class="button" target="_blank">
186
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
187
+ </a>
188
+ <span class="rtb-by">
189
+ by <a href="https://themeofthecrop.com/<?php echo $url_params; ?>">Theme of the Crop</a>
190
+ </span>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ <div class="addon addon-themes">
195
+ <a href="https://themebeans.com/themes/plate?utm_source=totc_addons_plate&utm_medium=banner&utm_campaign=TOTC%20Addons%20Link%2C%20Plate">
196
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-plate.jpg'; ?>">
197
+ </a>
198
+ <h3><?php esc_html_e( 'Plate', 'restaurant-reservations' ); ?></h3>
199
+ <div class="details">
200
+ <div class="description">
201
+ <?php esc_html_e( 'A delightfully beautiful WordPress theme designed to help you build a stunning restaurant website.', 'restaurant-reservations' ); ?>
202
+ </div>
203
+ <div class="action">
204
+ <a href="https://themebeans.com/themes/plate?utm_source=totc_addons_plate&utm_medium=banner&utm_campaign=TOTC%20Addons%20Link%2C%20Plate" class="button" target="_blank">
205
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
206
+ </a>
207
+ <span class="rtb-by">
208
+ by <a href="https://themebeans.com?utm_source=totc_addons_plate&utm_medium=banner&utm_campaign=TOTC%20Addons%20Link%2C%20Plate">ThemeBeans</a>
209
+ </span>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ <div class="addon addon-themes">
214
+ <a href="https://wordpress.org/themes/auberge/">
215
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-auberge.jpg'; ?>">
216
+ </a>
217
+ <h3><?php esc_html_e( 'Auberge', 'restaurant-reservations' ); ?></h3>
218
+ <div class="details">
219
+ <div class="description">
220
+ <?php esc_html_e( 'Display a menu of your restaurant, café or bar stylishly with this free mobile-friendly WordPress theme.', 'restaurant-reservations' ); ?>
221
+ </div>
222
+ <div class="action">
223
+ <a href="https://wordpress.org/themes/auberge/" class="button" target="_blank">
224
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
225
+ </a>
226
+ <span class="rtb-by">
227
+ by <a href="https://www.webmandesign.eu/">Webman Design</a>
228
+ </span>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ <div class="addon addon-themes">
233
+ <a href="http://www.anarieldesign.com/themes/restaurant-bar-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">
234
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-liber.jpg'; ?>">
235
+ </a>
236
+ <h3><?php esc_html_e( 'Liber', 'restaurant-reservations' ); ?></h3>
237
+ <div class="details">
238
+ <div class="description">
239
+ <?php esc_html_e( 'A responsive theme optimized for restaurants and bars supporting features these websites need.', 'restaurant-reservations' ); ?>
240
+ </div>
241
+ <div class="action">
242
+ <a href="http://www.anarieldesign.com/themes/restaurant-bar-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations" class="button" target="_blank">
243
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
244
+ </a>
245
+ <span class="rtb-by">
246
+ by <a href="http://www.anarieldesign.com/">Anariel Design</a>
247
+ </span>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ <div class="addon addon-themes">
252
+ <a href="https://wordpress.org/themes/brasserie/">
253
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-brasserie.jpg'; ?>">
254
+ </a>
255
+ <h3><?php esc_html_e( 'Brasserie', 'restaurant-reservations' ); ?></h3>
256
+ <div class="details">
257
+ <div class="description">
258
+ <?php esc_html_e( 'A delightfully simple to use and beautifully crafted free theme for any food establishment.', 'restaurant-reservations' ); ?>
259
+ </div>
260
+ <div class="action">
261
+ <a href="https://wordpress.org/themes/brasserie/" class="button" target="_blank">
262
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
263
+ </a>
264
+ <span class="rtb-by">
265
+ by <a href="https://www.templateexpress.com/">Template Express</a>
266
+ </span>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ <div class="addon addon-themes">
271
+ <a href="http://www.anarieldesign.com/themes/food-blog-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">
272
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-veggie.jpg'; ?>">
273
+ </a>
274
+ <h3><?php esc_html_e( 'Veggie', 'restaurant-reservations' ); ?></h3>
275
+ <div class="details">
276
+ <div class="description">
277
+ <?php esc_html_e( 'A food blogging and restaurant theme with modern, easy-to-read typography and minimalist design.', 'restaurant-reservations' ); ?>
278
+ </div>
279
+ <div class="action">
280
+ <a href="http://www.anarieldesign.com/themes/food-blog-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations" class="button" target="_blank">
281
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
282
+ </a>
283
+ <span class="rtb-by">
284
+ by <a href="http://www.anarieldesign.com/">Anariel Design</a>
285
+ </span>
286
+ </div>
287
+ </div>
288
+ </div>
289
+ <div class="addon addon-themes">
290
+ <a href="http://www.anarieldesign.com/themes/wine-and-winery-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">
291
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-good-ol-wine.jpg'; ?>">
292
+ </a>
293
+ <h3><?php esc_html_e( "Good Ol' Wine", 'restaurant-reservations' ); ?></h3>
294
+ <div class="details">
295
+ <div class="description">
296
+ <?php esc_html_e( 'A beautiful responsive theme that is suitable for wine enthusiasts, wineries and wine bars.', 'restaurant-reservations' ); ?>
297
+ </div>
298
+ <div class="action">
299
+ <a href="http://www.anarieldesign.com/themes/wine-and-winery-wordpress-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations" class="button" target="_blank">
300
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
301
+ </a>
302
+ <span class="rtb-by">
303
+ by <a href="http://www.anarieldesign.com/">Anariel Design</a>
304
+ </span>
305
+ </div>
306
+ </div>
307
+ </div>
308
+ <div class="addon addon-themes">
309
+ <a href="http://www.anarieldesign.com/themes/simple-and-fresh-blogging-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">
310
+ <img src="<?php echo RTB_PLUGIN_URL . '/assets/img/theme-healthy-living.jpg'; ?>">
311
+ </a>
312
+ <h3><?php esc_html_e( 'Healthy Living', 'restaurant-reservations' ); ?></h3>
313
+ <div class="details">
314
+ <div class="description">
315
+ <?php esc_html_e( 'A modern, clean healthy food blogging theme that can be used for a restaurant as well.', 'restaurant-reservations' ); ?>
316
+ </div>
317
+ <div class="action">
318
+ <a href="http://www.anarieldesign.com/themes/simple-and-fresh-blogging-theme/?utm_source=Theme%20of%20the%20Crop&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations" class="button" target="_blank">
319
+ <?php esc_html_e( 'View Theme', 'restaurant-reservations' ); ?>
320
+ </a>
321
+ <span class="rtb-by">
322
+ by <a href="http://www.anarieldesign.com/">Anariel Design</a>
323
+ </span>
324
+ </div>
325
+ </div>
326
+ </div>
327
+ </div>*/ ?>
328
+ <?php do_action( 'rtb_addons_post' ); ?>
329
+ </div>
330
+
331
+ <?php
332
+ }
333
+
334
+ /**
335
+ * Add a prompt for users to subscribe to the Theme of the Crop mailing list
336
+ * below the addons list.
337
+ *
338
+ * @since 0.1
339
+ */
340
+ public function add_subscribe_pompt() {
341
+
342
+ ?>
343
+
344
+ <p>
345
+ <?php
346
+ echo sprintf(
347
+ esc_html_x( 'Find out when new addons are available by subscribing to the %smonthly newsletter%s, liking %sTheme of the Crop%s on Facebook, or following %sTheme of the Crop%s on Twitter.', 'restaurant-reservations' ),
348
+ '<a target="_blank" href="https://themeofthecrop.com/about/mailing-list/?utm_source=Plugin&utm_medium=Addon%20List&utm_campaign=Restaurant%20Reservations">',
349
+ '</a>',
350
+ '<a target="_blank" href="https://www.facebook.com/themeofthecrop/">',
351
+ '</a>',
352
+ '<a target="_blank" href="http://twitter.com/themeofthecrop">',
353
+ '</a>'
354
+ );
355
+ ?>
356
+ </p>
357
+
358
+ <?php
359
+ }
360
+
361
+ }
362
+ } // endif;
includes/AdminBookings.class.php CHANGED
@@ -1,922 +1,922 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbAdminBookings' ) ) {
5
- /**
6
- * Class to handle the admin bookings page for Restaurant Reservations
7
- *
8
- * @since 1.3
9
- */
10
- class rtbAdminBookings {
11
-
12
- /**
13
- * The bookings table
14
- *
15
- * This is only instantiated on the bookings admin page at the moment when
16
- * it is generated.
17
- *
18
- * @see self::show_admin_bookings_page()
19
- * @see WP_List_table.BookingsTable.class.php
20
- * @since 1.6
21
- */
22
- public $bookings_table;
23
-
24
- public function __construct() {
25
-
26
- // Add the admin menu
27
- add_action( 'admin_menu', array( $this, 'add_menu_page' ) );
28
-
29
- // Print the modals
30
- add_action( 'admin_footer-toplevel_page_rtb-bookings', array( $this, 'print_modals' ) );
31
-
32
- // Receive Ajax requests
33
- add_action( 'wp_ajax_nopriv_rtb-admin-booking-modal' , array( $this , 'nopriv_ajax' ) );
34
- add_action( 'wp_ajax_rtb-admin-booking-modal', array( $this, 'booking_modal_ajax' ) );
35
- add_action( 'wp_ajax_nopriv_rtb-admin-trash-booking' , array( $this , 'nopriv_ajax' ) );
36
- add_action( 'wp_ajax_rtb-admin-trash-booking', array( $this, 'trash_booking_ajax' ) );
37
- add_action( 'wp_ajax_nopriv_rtb-admin-email-modal' , array( $this , 'nopriv_ajax' ) );
38
- add_action( 'wp_ajax_rtb-admin-email-modal', array( $this, 'email_modal_ajax' ) );
39
- add_action( 'wp_ajax_nopriv_rtb-admin-column-modal' , array( $this , 'nopriv_ajax' ) );
40
- add_action( 'wp_ajax_rtb-admin-column-modal', array( $this, 'column_modal_ajax' ) );
41
- add_action( 'wp_ajax_nopriv_rtb-admin-ban-modal' , array( $this , 'nopriv_ajax' ) );
42
- add_action( 'wp_ajax_rtb-admin-ban-modal', array( $this, 'ban_modal_ajax' ) );
43
- add_action( 'wp_ajax_nopriv_rtb-admin-delete-modal' , array( $this , 'nopriv_ajax' ) );
44
- add_action( 'wp_ajax_rtb-admin-delete-modal', array( $this, 'delete_modal_ajax' ) );
45
-
46
- add_action( 'wp_ajax_nopriv_rtb_set_reservation_arrived' , array( $this , 'nopriv_ajax' ) );
47
- add_action( 'wp_ajax_rtb_set_reservation_arrived', array( $this, 'set_booking_arrived' ) );
48
-
49
- // Validate post status and notification fields
50
- add_action( 'rtb_validate_booking_submission', array( $this, 'validate_admin_fields' ) );
51
-
52
- // Set post status when adding to the database
53
- add_filter( 'rtb_insert_booking_data', array( $this, 'insert_booking_data' ), 10, 2 );
54
-
55
- // Add the columns configuration button to the table
56
- add_action( 'rtb_bookings_table_actions', array( $this, 'print_columns_config_button' ), 9 );
57
-
58
- }
59
-
60
- /**
61
- * Add the top-level admin menu page
62
- * @since 0.0.1
63
- */
64
- public function add_menu_page() {
65
-
66
- add_menu_page(
67
- _x( 'Bookings', 'Title of admin page that lists bookings', 'restaurant-reservations' ),
68
- _x( 'Bookings', 'Title of bookings admin menu item', 'restaurant-reservations' ),
69
- 'manage_bookings',
70
- 'rtb-bookings',
71
- array( $this, 'show_admin_bookings_page' ),
72
- 'dashicons-calendar',
73
- '26.2987'
74
- );
75
-
76
- }
77
-
78
- /**
79
- * Display the admin bookings page
80
- * @since 0.0.1
81
- */
82
- public function show_admin_bookings_page() {
83
-
84
- require_once( RTB_PLUGIN_DIR . '/includes/WP_List_Table.BookingsTable.class.php' );
85
- $this->bookings_table = new rtbBookingsTable();
86
- $this->bookings_table->prepare_items();
87
- ?>
88
-
89
- <div class="wrap">
90
- <h1>
91
- <?php _e( 'Restaurant Bookings', 'restaurant-reservations' ); ?>
92
- <a href="#" class="add-new-h2 page-title-action add-booking"><?php _e( 'Add New', 'restaurant-reservations' ); ?></a>
93
- </h1>
94
-
95
- <?php do_action( 'rtb_bookings_table_top' ); ?>
96
- <form id="rtb-bookings-table" method="POST" action="">
97
- <input type="hidden" name="post_type" value="<?php echo RTB_BOOKING_POST_TYPE; ?>" />
98
- <input type="hidden" name="page" value="rtb-bookings">
99
-
100
- <div class="rtb-primary-controls clearfix">
101
- <div class="rtb-views">
102
- <?php $this->bookings_table->views(); ?>
103
- </div>
104
- <?php $this->bookings_table->advanced_filters(); ?>
105
- </div>
106
-
107
- <?php $this->bookings_table->display(); ?>
108
- </form>
109
- <?php do_action( 'rtb_bookings_table_btm' ); ?>
110
- </div>
111
-
112
- <?php
113
- }
114
-
115
- /**
116
- * Print button for configuring columns
117
- *
118
- * @param string pos Position of this tablenav: top|btm
119
- * @since 0.1
120
- */
121
- public function print_columns_config_button( $pos ) {
122
- if ( $pos != 'top' ) {
123
- return;
124
- }
125
- ?>
126
-
127
- <div class="alignleft actions rtb-actions">
128
- <a href="#" class="button rtb-columns-button">
129
- <span class="dashicons dashicons-admin-settings"></span>
130
- <?php esc_html_e( 'Columns', 'restaurant-reservations' ); ?>
131
- </a>
132
- </div>
133
-
134
- <?php
135
- }
136
-
137
- /**
138
- * Print the modal containers
139
- *
140
- * New/edit bookings, send email, configure columns, errors.
141
- *
142
- * @since 0.0.1
143
- */
144
- public function print_modals() {
145
-
146
- global $rtb_controller;
147
- ?>
148
-
149
- <!-- Restaurant Reservations add/edit booking modal -->
150
- <div id="rtb-booking-modal" class="rtb-admin-modal">
151
- <div class="rtb-booking-form rtb-container">
152
- <form method="POST">
153
- <input type="hidden" name="action" value="admin_booking_request">
154
- <input type="hidden" name="ID" value="">
155
-
156
- <?php
157
- /**
158
- * The generated fields are wrapped in a div so we can
159
- * replace its contents with an HTML blob passed back from
160
- * an Ajax request. This way the field data and error
161
- * messages are always populated from the same server-side
162
- * code.
163
- */
164
- ?>
165
- <div id="rtb-booking-form-fields">
166
- <?php echo $this->print_booking_form_fields(); ?>
167
- </div>
168
-
169
- <button type="submit" class="button button-primary">
170
- <?php _e( 'Add Booking', 'restaurant-reservations' ); ?>
171
- </button>
172
- <a href="#" class="button" id="rtb-cancel-booking-modal">
173
- <?php _e( 'Cancel', 'restaurant-reservations' ); ?>
174
- </a>
175
- <div class="action-status">
176
- <span class="spinner loading"></span>
177
- <span class="dashicons dashicons-no-alt error"></span>
178
- <span class="dashicons dashicons-yes success"></span>
179
- </div>
180
- </form>
181
- </div>
182
- </div>
183
-
184
- <!-- Restaurant Reservations send email modal -->
185
- <div id="rtb-email-modal" class="rtb-admin-modal">
186
- <div class="rtb-email-form rtb-container">
187
- <form method="POST">
188
- <input type="hidden" name="action" value="admin_send_email">
189
- <input type="hidden" name="ID" value="">
190
- <input type="hidden" name="name" value="">
191
- <input type="hidden" name="email" value="">
192
-
193
- <fieldset>
194
- <legend><?php _e( 'Send Email', 'retaurant-reservations' ); ?></legend>
195
-
196
- <div class="to">
197
- <label for="rtb-email-to"><?php _ex( 'To', 'Label next to the email address to which an email will be sent', 'restaurant-reservations' ); ?></label>
198
- <span class="rtb-email-to"></span>
199
- </div>
200
- <div class="subject">
201
- <label for="rtb-email-subject"><?php _e( 'Subject', 'restaurant-reservations' ); ?></label>
202
- <input type="text" name="rtb-email-subject" placeholder="<?php echo $rtb_controller->settings->get_setting( 'subject-admin-notice'); ?>">
203
- </div>
204
- <div class="message">
205
- <label for="rtb-email-message"><?php _e( 'Message', 'restaurant-reservations' ); ?></label>
206
- <textarea name="rtb-email-message" id="rtb-email-message"></textarea>
207
- </div>
208
- </fieldset>
209
-
210
- <button type="submit" class="button button-primary">
211
- <?php _e( 'Send Email', 'restaurant-reservations' ); ?>
212
- </button>
213
- <a href="#" class="button" id="rtb-cancel-email-modal">
214
- <?php _e( 'Cancel', 'restaurant-reservations' ); ?>
215
- </a>
216
- <div class="action-status">
217
- <span class="spinner loading"></span>
218
- <span class="dashicons dashicons-no-alt error"></span>
219
- <span class="dashicons dashicons-yes success"></span>
220
- </div>
221
- </form>
222
- </div>
223
- </div>
224
-
225
- <!-- Restaurant Reservations column configuration modal -->
226
- <div id="rtb-column-modal" class="rtb-admin-modal">
227
- <div class="rtb-column-form rtb-container">
228
- <form method="POST">
229
- <input type="hidden" name="action" value="admin_column_config">
230
-
231
- <fieldset>
232
- <legend><?php esc_html_e( 'Columns', 'restaurant-reservations' ); ?></legend>
233
- <ul>
234
- <?php
235
- $columns = $this->bookings_table->get_all_columns();
236
- $visible = $this->bookings_table->get_columns();
237
- foreach( $columns as $column => $label ) :
238
- // Don't allow these columns to be hidden
239
- if ( $column == 'cb' || $column == 'details' || $column == 'date' ) {
240
- continue;
241
- }
242
- ?>
243
- <li>
244
- <label>
245
- <input type="checkbox" name="rtb-columns-config" value="<?php esc_attr_e( $column ); ?>"<?php if ( array_key_exists( $column, $visible ) ) : ?> checked<?php endif; ?>>
246
- <?php esc_html_e( $label ); ?>
247
- </label>
248
- </li>
249
- <?php
250
- endforeach;
251
- ?>
252
- </ul>
253
- </fieldset>
254
-
255
- <button type="submit" class="button button-primary">
256
- <?php _e( 'Update', 'restaurant-reservations' ); ?>
257
- </button>
258
- <a href="#" class="button" id="rtb-cancel-column-modal">
259
- <?php _e( 'Cancel', 'restaurant-reservations' ); ?>
260
- </a>
261
- <div class="action-status">
262
- <span class="spinner loading"></span>
263
- <span class="dashicons dashicons-no-alt error"></span>
264
- <span class="dashicons dashicons-yes success"></span>
265
- </div>
266
- </form>
267
- </div>
268
- </div>
269
-
270
- <!-- Restaurant Reservations details modal -->
271
- <div id="rtb-details-modal" class="rtb-admin-modal">
272
- <div class="rtb-details-form rtb-container">
273
- <div class="rtb-details-data"></div>
274
- <a href="#" class="button" id="rtb-cancel-details-modal">
275
- <?php _e( 'Close', 'restaurant-reservations' ); ?>
276
- </a>
277
- </div>
278
- </div>
279
-
280
- <!-- Restaurant Reservations ban email/ip modal -->
281
- <div id="rtb-ban-modal" class="rtb-admin-modal">
282
- <div class="rtb-ban-form rtb-container">
283
- <div class="rtb-ban-msg">
284
- <p class="intro">
285
- <?php
286
- printf(
287
- __( 'Ban future bookings from the email address %s or the IP address %s?', 'restaurant-reservations' ),
288
- '<span id="rtb-ban-modal-email"></span>',
289
- '<span id="rtb-ban-modal-ip"></span>'
290
- );
291
- ?>
292
- </p>
293
- <p>
294
- <?php
295
- esc_html_e( 'It is recommended to ban by email address instead of IP. Only ban by IP address to block a malicious user who is using different email addresses to avoid a previous ban.', 'restaurant-reservations' );
296
- ?>
297
- </p>
298
- </div>
299
- <button class="button button-primary" id="rtb-ban-modal-email-btn">Ban Email</button>
300
- <button class="button button-primary" id="rtb-ban-modal-ip-btn">Ban IP</button>
301
- <a href="#" id="rtb-cancel-ban-modal" class="button"><?php _e( 'Close', 'restaurant-reservations' ); ?></a>
302
- <a class="button-link" href="<?php echo esc_url( admin_url( '/admin.php?page=rtb-settings' ) ); ?>" target="_blank">
303
- <?php esc_html_e( 'View all bans', 'restaurant-reservations' ); ?>
304
- </a>
305
- <div class="action-status">
306
- <span class="spinner loading"></span>
307
- <span class="dashicons dashicons-no-alt error"></span>
308
- <span class="dashicons dashicons-yes success"></span>
309
- </div>
310
- </div>
311
- </div>
312
-
313
- <!-- Restaurant Reservations delete customer modal -->
314
- <div id="rtb-delete-modal" class="rtb-admin-modal">
315
- <div class="rtb-delete-form rtb-container">
316
- <div class="rtb-delete-msg">
317
- <?php
318
- printf(
319
- __( 'Delete all booking records related to email address %s? This action can not be undone.', 'restaurant-reservations' ),
320
- '<span id="rtb-delete-modal-email"></span>'
321
- );
322
- ?>
323
- </div>
324
- <div id="rtb-delete-status">
325
- <span class="rtb-delete-status-total">
326
- <span id="rtb-delete-status-progress" class="rtb-delete-status-progress"></span>
327
- </span>
328
- <div id="rtb-delete-status-deleted"></div>
329
- </div>
330
- <button class="button button-primary" id="rtb-delete-modal-btn">Delete Bookings</button>
331
- <button id="rtb-cancel-delete-modal" class="button"><?php _e( 'Close', 'restaurant-reservations' ); ?></button>
332
- <div class="action-status">
333
- <span class="spinner loading"></span>
334
- <span class="dashicons dashicons-no-alt error"></span>
335
- <span class="dashicons dashicons-yes success"></span>
336
- </div>
337
- </div>
338
- </div>
339
-
340
- <!-- Restaurant Reservations error message modal -->
341
- <div id="rtb-error-modal" class="rtb-admin-modal">
342
- <div class="rtb-error rtb-container">
343
- <div class="rtb-error-msg"></div>
344
- <a href="#" class="button"><?php _e( 'Close', 'restaurant-reservations' ); ?></a>
345
- </div>
346
- </div>
347
-
348
- <?php
349
- }
350
-
351
- /**
352
- * Retrieve booking form fields used in the admin booking modal. These
353
- * fields are also passed back with ajax requests since they render error
354
- * messages and populate fields with validated data.
355
- * @since 0.0.1
356
- */
357
- public function print_booking_form_fields() {
358
-
359
- global $rtb_controller;
360
-
361
- // Add post status and notification fields to admin booking form
362
- add_filter( 'rtb_booking_form_fields', array( $this, 'add_admin_fields' ), 20, 2 );
363
-
364
- // Retrieve the form fields
365
- $fields = $rtb_controller->settings->get_booking_form_fields( $rtb_controller->request );
366
-
367
- ob_start();
368
- ?>
369
-
370
- <?php foreach( $fields as $fieldset => $contents ) : ?>
371
- <fieldset class="<?php echo $fieldset; ?>">
372
- <?php
373
- foreach( $contents['fields'] as $slug => $field ) {
374
-
375
- $args = empty( $field['callback_args'] ) ? null : $field['callback_args'];
376
-
377
- call_user_func( $field['callback'], $slug, $field['title'], $field['request_input'], $args );
378
- }
379
- ?>
380
- </fieldset>
381
- <?php endforeach;
382
-
383
- // Remove the admin fields filter
384
- remove_filter( 'rtb_booking_form_fields', array( $this, 'add_admin_fields' ) );
385
-
386
- return ob_get_clean();
387
- }
388
-
389
- /**
390
- * Add the post status and notifications fields to the booking form fields
391
- * @since 1.3
392
- */
393
- public function add_admin_fields( $fields, $request ) {
394
-
395
- if ( !is_admin() || !current_user_can( 'manage_bookings' ) ) {
396
- return $fields;
397
- }
398
-
399
- global $rtb_controller;
400
-
401
- // Get all valid booking statuses
402
- $booking_statuses = array();
403
- foreach( $rtb_controller->cpts->booking_statuses as $status => $args ) {
404
- $booking_statuses[ $status ] = $args['label'];
405
- }
406
-
407
- $fields['admin'] = array(
408
- 'fields' => array(
409
- 'post-status' => array(
410
- 'title' => __( 'Booking Status', 'restaurant-reservations' ),
411
- 'request_input' => empty( $request->post_status ) ? '' : $request->post_status,
412
- 'callback' => 'rtb_print_form_select_field',
413
- 'callback_args' => array(
414
- 'options' => $booking_statuses,
415
- )
416
- ),
417
- 'notifications' => array(
418
- 'title' => __( 'Send notifications', 'restaurant-reservations' ),
419
- 'request_input' => empty( $request->send_notifications ) ? false : $request->send_notifications,
420
- 'callback' => array( $this, 'print_form_send_notifications_field' ),
421
- 'callback_args' => array(
422
- 'description' => array(
423
- 'prompt' => __( 'Learn more', 'restaurant-reservations' ),
424
- 'text' => __( "When adding a booking or changing a booking's status with this form, no email notifications will be sent. Check this option if you want to send email notifications.", 'restaurant-reservations' ),
425
- ),
426
- ),
427
- ),
428
- ),
429
- );
430
-
431
- return $fields;
432
- }
433
-
434
-
435
- /**
436
- * Print a field to toggle notifications when adding/editing a booking from
437
- * the admin
438
- * @since 1.3
439
- */
440
- function print_form_send_notifications_field( $slug, $title, $value, $args ) {
441
-
442
- $slug = esc_attr( $slug );
443
- $title = esc_attr( $title );
444
- $value = (bool) $value;
445
- $description = empty( $args['description'] ) || empty( $args['description']['prompt'] ) || empty( $args['description']['text'] ) ? null : $args['description'];
446
- ?>
447
-
448
- <div class="<?php echo $slug; ?>">
449
- <?php echo rtb_print_form_error( $slug ); ?>
450
- <label>
451
- <input type="checkbox" name="rtb-<?php echo esc_attr( $slug ); ?>" value="1"<?php checked( $value ); ?>>
452
- <?php echo $title; ?>
453
- <?php if ( !empty( $description ) ) : ?>
454
- <a href="#" class="rtb-description-prompt">
455
- <?php echo $description['prompt']; ?>
456
- </a>
457
- <?php endif; ?>
458
- </label>
459
- <?php if ( !empty( $description ) ) : ?>
460
- <div class="rtb-description">
461
- <?php echo $description['text']; ?>
462
- </div>
463
- <?php endif; ?>
464
- </div>
465
-
466
- <?php
467
- }
468
-
469
- /**
470
- * Handle ajax requests from the admin bookings area from logged out users
471
- * @since 1.3
472
- */
473
- public function nopriv_ajax() {
474
-
475
- wp_send_json_error(
476
- array(
477
- 'error' => 'loggedout',
478
- 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( admin_url( 'admin.php?page=rtb-bookings&status=pending' ) ) . '">', '</a>' ),
479
- )
480
- );
481
- }
482
-
483
- /**
484
- * Handle ajax requests from the admin bookings area
485
- * @since 1.3
486
- */
487
- public function booking_modal_ajax() {
488
-
489
- global $rtb_controller;
490
-
491
- // Authenticate request
492
- if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
493
- $this->nopriv_ajax();
494
- }
495
-
496
- // Retrieve a booking with a GET request
497
- if ( !empty( $_GET['booking'] ) && !empty( $_GET['booking']['ID'] ) ) {
498
-
499
- $id = (int) $_GET['booking']['ID'];
500
-
501
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
502
- $rtb_controller->request = new rtbBooking();
503
- $result = $rtb_controller->request->load_post( $id );
504
-
505
- if ( $result ) {
506
-
507
- // Don't allow editing of trashed bookings. This wil force
508
- // appropriate use of the trash status and (hopefully) prevent
509
- // mistakes in booking management.
510
- if ( $rtb_controller->request->post_status == 'trash' ) {
511
- wp_send_json_error(
512
- array(
513
- 'error' => 'booking_trashed',
514
- 'msg' => sprintf( __( 'This booking has been sent to the %sTrash%s where it can not be edited. Set the booking to Pending or Confirmed to edit it.', 'restaurant-reservations' ), '<a href="' . admin_url( 'admin.php?page=rtb-bookings&status=trash' ) . '">', '</a>' ),
515
- )
516
- );
517
- }
518
-
519
- $rtb_controller->request->prepare_request_data();
520
- wp_send_json_success(
521
- array(
522
- 'booking' => $rtb_controller->request,
523
- 'fields' => $this->print_booking_form_fields(),
524
- )
525
- );
526
-
527
- } else {
528
- wp_send_json_error(
529
- array(
530
- 'error' => 'booking_not_found',
531
- 'msg' => __( 'The booking could not be retrieved. Please reload the page and try again.', 'restaurant-reservations' ),
532
- )
533
- );
534
- }
535
-
536
- // Insert or update a booking with a POST request
537
- } elseif ( !empty( $_POST['booking'] ) ) {
538
-
539
- // Set up $_POST object for validation
540
- foreach( $_POST['booking'] as $field ) {
541
-
542
- // $field is setup by jQuery's serializeArray(), which will preserve
543
- // array indicators in field names. So name[] is passed as "name[]"
544
- // instead of "name". Let's strip out any trailing brackets to match
545
- // the normal behaviour when receiving $_POST data on the server.
546
- if ( substr( $field['name'], -2 ) === '[]' ) {
547
- $name = substr( $field['name'], 0, -2 );
548
- if ( !isset( $_POST[ $name ] ) ) {
549
- $_POST[ $name ] = array();
550
- }
551
- $_POST[ $name ][] = $field['value'];
552
- } else {
553
- $_POST[ $field['name'] ] = $field['value'];
554
- }
555
-
556
- }
557
-
558
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
559
- $rtb_controller->request = new rtbBooking();
560
-
561
- // Add an ID if we're updating the post
562
- if ( !empty( $_POST['ID'] ) ) {
563
- $result = $rtb_controller->request->load_post((int) $_POST['ID']);
564
- if (!$result) {
565
- wp_send_json_error(
566
- array(
567
- 'error' => 'no_booking_found',
568
- 'booking' => $rtb_controller->request,
569
- 'fields' => $this->print_booking_form_fields(),
570
- )
571
- );
572
- }
573
- }
574
-
575
- // Disable notifications
576
- $this->maybe_disable_notifications();
577
-
578
- $result = $rtb_controller->request->insert_booking($by_admin = true);
579
-
580
- if ( $result ) {
581
- wp_send_json_success(
582
- array(
583
- 'booking' => $rtb_controller->request,
584
- )
585
- );
586
- } else {
587
- wp_send_json_error(
588
- array(
589
- 'error' => 'invalid_booking_data',
590
- 'booking' => $rtb_controller->request,
591
- 'fields' => $this->print_booking_form_fields(),
592
- )
593
- );
594
- }
595
- }
596
-
597
- // Fallback to a valid error
598
- wp_send_json_error();
599
- }
600
-
601
- /**
602
- * Set booking status to trash
603
- * @since 1.3
604
- */
605
- public function trash_booking_ajax() {
606
-
607
- global $rtb_controller;
608
-
609
- // Authenticate request
610
- if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) || empty( $_POST['booking'] ) ) {
611
- $this->nopriv_ajax();
612
- }
613
-
614
- $id = (int) $_POST['booking'];
615
-
616
- $result = wp_trash_post( $id );
617
-
618
- if ( $result === false ) {
619
- wp_send_json_error(
620
- array(
621
- 'error' => 'trash_failed',
622
- 'msg' => __( 'Unable to trash this post. Please try again. If you continue to have trouble, please refresh the page.', 'restaurant-reservations' ),
623
- )
624
- );
625
-
626
- } else {
627
- wp_send_json_success(
628
- array(
629
- 'booking' => $id,
630
- )
631
- );
632
- }
633
- }
634
-
635
- /**
636
- * Handle ajax requests to send an email
637
- *
638
- * @since 1.3.1
639
- */
640
- public function email_modal_ajax() {
641
-
642
- global $rtb_controller;
643
-
644
- // Authenticate request
645
- if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
646
- $this->nopriv_ajax();
647
- }
648
-
649
- // Set up $_POST object for validation
650
- foreach( $_POST['email'] as $field ) {
651
- $_POST[ $field['name'] ] = $field['value'];
652
- }
653
-
654
- $ids = ( isset( $_POST['ID'] ) and $_POST['ID'] != '' ) ? explode( ',', $_POST['ID'] ) : array();
655
- $subject = stripcslashes( sanitize_text_field( $_POST['rtb-email-subject'] ) );
656
- $message = stripcslashes( wp_kses_post( $_POST['rtb-email-message'] ) );
657
-
658
- if ( empty( $message ) ) {
659
- wp_send_json_error(
660
- array(
661
- 'error' => 'email_missing_message',
662
- 'msg' => __( 'Please enter a message before sending the email.', 'restaurant-reservations' ),
663
- )
664
- );
665
- }
666
-
667
- if ( empty( $ids ) ) {
668
- wp_send_json_error(
669
- array(
670
- 'error' => 'email_missing_data',
671
- 'msg' => __( 'The email could not be sent because some critical information was missing.', 'restaurant-reservations' ),
672
- )
673
- );
674
- }
675
-
676
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
677
-
678
- foreach ( $ids as $id ) {
679
-
680
- $booking = new rtbBooking();
681
- $booking->load_post( $id );
682
-
683
- $email = new rtbNotificationEmail( 'admin_email_notice', 'user' );
684
- $email->subject = empty( $subject ) ? $rtb_controller->settings->get_setting( 'subject-admin-notice' ) : $subject;
685
- $email->message = $message;
686
- $email->set_booking( $booking );
687
- if ( $email->prepare_notification() ) {
688
- do_action( 'rtb_send_notification_before', $email );
689
- $email->send_notification();
690
- do_action( 'rtb_send_notification_after', $email );
691
- }
692
-
693
- // Store email in postmeta for log
694
- $booking->add_log( 'email', $email->subject, $email->message );
695
- $booking->insert_post_data();
696
- }
697
-
698
- wp_send_json_success();
699
- }
700
-
701
- /**
702
- * Handle ajax requests to configure columns
703
- *
704
- * @since 1.3.1
705
- */
706
- public function column_modal_ajax() {
707
-
708
- global $rtb_controller;
709
-
710
- // Authenticate request
711
- if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
712
- $this->nopriv_ajax();
713
- }
714
-
715
- if ( !isset( $_POST['columns'] ) || !is_array( $_POST['columns'] ) || empty( $_POST['columns'] ) ) {
716
- wp_send_json_error(
717
- array(
718
- 'error' => 'no_columns',
719
- 'msg' => __( 'You must select at least one column to display.', 'restaurant-reservations' ),
720
- )
721
- );
722
- }
723
-
724
- $settings = get_option( 'rtb-settings' );
725
- $settings['bookings-table-columns'] = array_map( 'sanitize_key', $_POST['columns'] );
726
- update_option( 'rtb-settings', $settings );
727
-
728
- wp_send_json_success();
729
- }
730
-
731
- /**
732
- * Handle ajax requests to ban by IP or email address
733
- *
734
- * @since 1.3.1
735
- */
736
- public function ban_modal_ajax() {
737
-
738
- global $rtb_controller;
739
-
740
- // Authenticate request
741
- if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
742
- $this->nopriv_ajax();
743
- }
744
-
745
- // Ban an email address
746
- if ( isset( $_POST['email'] ) && !empty( $_POST['email'] ) ) {
747
- $email = trim( sanitize_text_field( $_POST['email'] ) );
748
- $banned_emails = preg_split( '/\r\n|\r|\n/', (string) $rtb_controller->settings->get_setting( 'ban-emails' ) );
749
-
750
- if ( !in_array( $email, $banned_emails ) ) {
751
- $banned_emails[] = $email;
752
- $rtb_controller->settings->settings['ban-emails'] = join( "\n", $banned_emails );
753
- update_option( 'rtb-settings', $rtb_controller->settings->settings );
754
- }
755
-
756
- wp_send_json_success();
757
-
758
- // Ban an IP address
759
- } elseif ( isset( $_POST['ip'] ) && !empty( $_POST['ip'] ) ) {
760
- $ip = trim( sanitize_text_field( $_POST['ip'] ) );
761
- $banned_ips = preg_split( '/\r\n|\r|\n/', (string) $rtb_controller->settings->get_setting( 'ban-ips' ) );
762
-
763
- if ( !in_array( $ip, $banned_ips ) ) {
764
- $banned_ips[] = $ip;
765
- $rtb_controller->settings->settings['ban-ips'] = join( "\n", $banned_ips );
766
- update_option( 'rtb-settings', $rtb_controller->settings->settings );
767
- }
768
-
769
- wp_send_json_success();
770
- }
771
-
772
- wp_send_json_error(
773
- array(
774
- 'error' => 'no_data',
775
- 'msg' => __( 'No IP or email address could be found for this ban request.', 'restaurant-reservations' ),
776
- )
777
- );
778
- }
779
-
780
- /**
781
- * Handle ajax requests to delete bookings by email
782
- *
783
- * @since 1.7.7
784
- */
785
- public function delete_modal_ajax() {
786
-
787
- global $rtb_controller;
788
-
789
- // Authenticate request
790
- if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
791
- $this->nopriv_ajax();
792
- }
793
-
794
- if ( !empty( $_POST['email'] ) ) {
795
- $email = sanitize_email( $_POST['email'] );
796
-
797
- $args = array(
798
- 'date_range' => null,
799
- 'posts_per_page' => 100,
800
- 'paged' => !empty( $_POST['page'] ) ? (int) $_POST['page'] : 1,
801
- );
802
-
803
- $query = new rtbQuery( $args, 'delete-by-email' );
804
- $query->prepare_args();
805
-
806
- $bookings = $query->get_bookings();
807
- $deleted = 0;
808
- foreach( $bookings as $booking ) {
809
- if ( isset( $booking->email ) && $booking->email === $email) {
810
- wp_delete_post( $booking->ID, true );
811
- $deleted++;
812
- }
813
- }
814
-
815
- // Get count of all bookings
816
- global $wpdb;
817
- $where = "WHERE p.post_type = '" . RTB_BOOKING_POST_TYPE . "'";
818
- $query = "SELECT count( * ) AS num_posts
819
- FROM $wpdb->posts p
820
- $where
821
- ";
822
-
823
- $count = $wpdb->get_results( $query );
824
- $count = (int) $count[0]->num_posts;
825
-
826
- wp_send_json_success(array(
827
- 'processed' => count($bookings),
828
- 'deleted' => $deleted,
829
- 'total' => $count,
830
- ));
831
- }
832
-
833
- wp_send_json_error(
834
- array(
835
- 'error' => 'no_data',
836
- 'msg' => __( 'No email address could be found for this delete request.', 'restaurant-reservations' ),
837
- )
838
- );
839
- }
840
-
841
- /**
842
- * Register a party as having arrived
843
- * @since 2.0.0
844
- */
845
- public function set_booking_arrived() {
846
- $booking_id = isset($_POST['booking_id']) ? intval( $_POST['booking_id'] ) : 0;
847
-
848
- $booking_id = wp_update_post(array(
849
- 'ID' => $booking_id,
850
- 'post_status' => 'arrived'
851
- ) );
852
-
853
- if ( $booking_id ) {
854
- wp_send_json_success();
855
- }
856
- else {
857
- wp_send_json_error(
858
- array(
859
- 'error' => 'loggedout',
860
- 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( ) . '">', '</a>' ),
861
- )
862
- );
863
- }
864
- }
865
-
866
- /**
867
- * Validate post status and notification fields
868
- * @since 1.3
869
- */
870
- public function validate_admin_fields( $booking ) {
871
-
872
- // Only validate in the admin
873
- if ( !$_POST['action'] || $_POST['action'] !== 'admin_booking_request' ) {
874
- return;
875
- }
876
-
877
- global $rtb_controller;
878
-
879
- // Disable Notifications
880
- $booking->send_notifications = empty( $_POST['rtb-notifications'] ) ? false : true;
881
- }
882
-
883
- /**
884
- * Adjust post status when adding/editing a booking from the admin area
885
- * @since 1.3
886
- */
887
- public function insert_booking_data( $args, $booking ) {
888
-
889
- // Validate user request
890
- if ( empty( $_POST['action'] ) || $_POST['action'] !== 'admin_booking_request' || !current_user_can( 'manage_bookings' ) ) {
891
- return $args;
892
- }
893
-
894
- if ( !empty( $booking->post_status ) ) {
895
- $args['post_status'] = $booking->post_status;
896
- }
897
-
898
- return $args;
899
- }
900
-
901
- /**
902
- * Maybe disable notifications when adding/editing bookings from the
903
- * admin booking modal
904
- * @since 1.3
905
- */
906
- public function maybe_disable_notifications() {
907
-
908
- // Don't disable notifications if they have opted to send them
909
- if ( !empty( $_POST['rtb-notifications'] ) ) {
910
- return;
911
- }
912
-
913
- // Disable all notifications. This filter is here in case a
914
- // third-party sets up a notification that they don't want to be
915
- // disabled even if the user has opted not to send notifications
916
- // To exempt a notification, hook into the filter and copy it
917
- // from $rtb_notifications to the empty array.
918
- global $rtb_controller;
919
- $rtb_controller->notifications->notifications = apply_filters( 'rtb_admin_disabled_notifications_exemption', array(), $rtb_controller->notifications->notifications );
920
- }
921
- }
922
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbAdminBookings' ) ) {
5
+ /**
6
+ * Class to handle the admin bookings page for Restaurant Reservations
7
+ *
8
+ * @since 1.3
9
+ */
10
+ class rtbAdminBookings {
11
+
12
+ /**
13
+ * The bookings table
14
+ *
15
+ * This is only instantiated on the bookings admin page at the moment when
16
+ * it is generated.
17
+ *
18
+ * @see self::show_admin_bookings_page()
19
+ * @see WP_List_table.BookingsTable.class.php
20
+ * @since 1.6
21
+ */
22
+ public $bookings_table;
23
+
24
+ public function __construct() {
25
+
26
+ // Add the admin menu
27
+ add_action( 'admin_menu', array( $this, 'add_menu_page' ) );
28
+
29
+ // Print the modals
30
+ add_action( 'admin_footer-toplevel_page_rtb-bookings', array( $this, 'print_modals' ) );
31
+
32
+ // Receive Ajax requests
33
+ add_action( 'wp_ajax_nopriv_rtb-admin-booking-modal' , array( $this , 'nopriv_ajax' ) );
34
+ add_action( 'wp_ajax_rtb-admin-booking-modal', array( $this, 'booking_modal_ajax' ) );
35
+ add_action( 'wp_ajax_nopriv_rtb-admin-trash-booking' , array( $this , 'nopriv_ajax' ) );
36
+ add_action( 'wp_ajax_rtb-admin-trash-booking', array( $this, 'trash_booking_ajax' ) );
37
+ add_action( 'wp_ajax_nopriv_rtb-admin-email-modal' , array( $this , 'nopriv_ajax' ) );
38
+ add_action( 'wp_ajax_rtb-admin-email-modal', array( $this, 'email_modal_ajax' ) );
39
+ add_action( 'wp_ajax_nopriv_rtb-admin-column-modal' , array( $this , 'nopriv_ajax' ) );
40
+ add_action( 'wp_ajax_rtb-admin-column-modal', array( $this, 'column_modal_ajax' ) );
41
+ add_action( 'wp_ajax_nopriv_rtb-admin-ban-modal' , array( $this , 'nopriv_ajax' ) );
42
+ add_action( 'wp_ajax_rtb-admin-ban-modal', array( $this, 'ban_modal_ajax' ) );
43
+ add_action( 'wp_ajax_nopriv_rtb-admin-delete-modal' , array( $this , 'nopriv_ajax' ) );
44
+ add_action( 'wp_ajax_rtb-admin-delete-modal', array( $this, 'delete_modal_ajax' ) );
45
+
46
+ add_action( 'wp_ajax_nopriv_rtb_set_reservation_arrived' , array( $this , 'nopriv_ajax' ) );
47
+ add_action( 'wp_ajax_rtb_set_reservation_arrived', array( $this, 'set_booking_arrived' ) );
48
+
49
+ // Validate post status and notification fields
50
+ add_action( 'rtb_validate_booking_submission', array( $this, 'validate_admin_fields' ) );
51
+
52
+ // Set post status when adding to the database
53
+ add_filter( 'rtb_insert_booking_data', array( $this, 'insert_booking_data' ), 10, 2 );
54
+
55
+ // Add the columns configuration button to the table
56
+ add_action( 'rtb_bookings_table_actions', array( $this, 'print_columns_config_button' ), 9 );
57
+
58
+ }
59
+
60
+ /**
61
+ * Add the top-level admin menu page
62
+ * @since 0.0.1
63
+ */
64
+ public function add_menu_page() {
65
+
66
+ add_menu_page(
67
+ _x( 'Bookings', 'Title of admin page that lists bookings', 'restaurant-reservations' ),
68
+ _x( 'Bookings', 'Title of bookings admin menu item', 'restaurant-reservations' ),
69
+ 'manage_bookings',
70
+ 'rtb-bookings',
71
+ array( $this, 'show_admin_bookings_page' ),
72
+ 'dashicons-calendar',
73
+ '26.2987'
74
+ );
75
+
76
+ }
77
+
78
+ /**
79
+ * Display the admin bookings page
80
+ * @since 0.0.1
81
+ */
82
+ public function show_admin_bookings_page() {
83
+
84
+ require_once( RTB_PLUGIN_DIR . '/includes/WP_List_Table.BookingsTable.class.php' );
85
+ $this->bookings_table = new rtbBookingsTable();
86
+ $this->bookings_table->prepare_items();
87
+ ?>
88
+
89
+ <div class="wrap">
90
+ <h1>
91
+ <?php _e( 'Restaurant Bookings', 'restaurant-reservations' ); ?>
92
+ <a href="#" class="add-new-h2 page-title-action add-booking"><?php _e( 'Add New', 'restaurant-reservations' ); ?></a>
93
+ </h1>
94
+
95
+ <?php do_action( 'rtb_bookings_table_top' ); ?>
96
+ <form id="rtb-bookings-table" method="POST" action="">
97
+ <input type="hidden" name="post_type" value="<?php echo RTB_BOOKING_POST_TYPE; ?>" />
98
+ <input type="hidden" name="page" value="rtb-bookings">
99
+
100
+ <div class="rtb-primary-controls clearfix">
101
+ <div class="rtb-views">
102
+ <?php $this->bookings_table->views(); ?>
103
+ </div>
104
+ <?php $this->bookings_table->advanced_filters(); ?>
105
+ </div>
106
+
107
+ <?php $this->bookings_table->display(); ?>
108
+ </form>
109
+ <?php do_action( 'rtb_bookings_table_btm' ); ?>
110
+ </div>
111
+
112
+ <?php
113
+ }
114
+
115
+ /**
116
+ * Print button for configuring columns
117
+ *
118
+ * @param string pos Position of this tablenav: top|btm
119
+ * @since 0.1
120
+ */
121
+ public function print_columns_config_button( $pos ) {
122
+ if ( $pos != 'top' ) {
123
+ return;
124
+ }
125
+ ?>
126
+
127
+ <div class="alignleft actions rtb-actions">
128
+ <a href="#" class="button rtb-columns-button">
129
+ <span class="dashicons dashicons-admin-settings"></span>
130
+ <?php esc_html_e( 'Columns', 'restaurant-reservations' ); ?>
131
+ </a>
132
+ </div>
133
+
134
+ <?php
135
+ }
136
+
137
+ /**
138
+ * Print the modal containers
139
+ *
140
+ * New/edit bookings, send email, configure columns, errors.
141
+ *
142
+ * @since 0.0.1
143
+ */
144
+ public function print_modals() {
145
+
146
+ global $rtb_controller;
147
+ ?>
148
+
149
+ <!-- Restaurant Reservations add/edit booking modal -->
150
+ <div id="rtb-booking-modal" class="rtb-admin-modal">
151
+ <div class="rtb-booking-form rtb-container">
152
+ <form method="POST">
153
+ <input type="hidden" name="action" value="admin_booking_request">
154
+ <input type="hidden" name="ID" value="">
155
+
156
+ <?php
157
+ /**
158
+ * The generated fields are wrapped in a div so we can
159
+ * replace its contents with an HTML blob passed back from
160
+ * an Ajax request. This way the field data and error
161
+ * messages are always populated from the same server-side
162
+ * code.
163
+ */
164
+ ?>
165
+ <div id="rtb-booking-form-fields">
166
+ <?php echo $this->print_booking_form_fields(); ?>
167
+ </div>
168
+
169
+ <button type="submit" class="button button-primary">
170
+ <?php _e( 'Add Booking', 'restaurant-reservations' ); ?>
171
+ </button>
172
+ <a href="#" class="button" id="rtb-cancel-booking-modal">
173
+ <?php _e( 'Cancel', 'restaurant-reservations' ); ?>
174
+ </a>
175
+ <div class="action-status">
176
+ <span class="spinner loading"></span>
177
+ <span class="dashicons dashicons-no-alt error"></span>
178
+ <span class="dashicons dashicons-yes success"></span>
179
+ </div>
180
+ </form>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- Restaurant Reservations send email modal -->
185
+ <div id="rtb-email-modal" class="rtb-admin-modal">
186
+ <div class="rtb-email-form rtb-container">
187
+ <form method="POST">
188
+ <input type="hidden" name="action" value="admin_send_email">
189
+ <input type="hidden" name="ID" value="">
190
+ <input type="hidden" name="name" value="">
191
+ <input type="hidden" name="email" value="">
192
+
193
+ <fieldset>
194
+ <legend><?php _e( 'Send Email', 'retaurant-reservations' ); ?></legend>
195
+
196
+ <div class="to">
197
+ <label for="rtb-email-to"><?php _ex( 'To', 'Label next to the email address to which an email will be sent', 'restaurant-reservations' ); ?></label>
198
+ <span class="rtb-email-to"></span>
199
+ </div>
200
+ <div class="subject">
201
+ <label for="rtb-email-subject"><?php _e( 'Subject', 'restaurant-reservations' ); ?></label>
202
+ <input type="text" name="rtb-email-subject" placeholder="<?php echo $rtb_controller->settings->get_setting( 'subject-admin-notice'); ?>">
203
+ </div>
204
+ <div class="message">
205
+ <label for="rtb-email-message"><?php _e( 'Message', 'restaurant-reservations' ); ?></label>
206
+ <textarea name="rtb-email-message" id="rtb-email-message"></textarea>
207
+ </div>
208
+ </fieldset>
209
+
210
+ <button type="submit" class="button button-primary">
211
+ <?php _e( 'Send Email', 'restaurant-reservations' ); ?>
212
+ </button>
213
+ <a href="#" class="button" id="rtb-cancel-email-modal">
214
+ <?php _e( 'Cancel', 'restaurant-reservations' ); ?>
215
+ </a>
216
+ <div class="action-status">
217
+ <span class="spinner loading"></span>
218
+ <span class="dashicons dashicons-no-alt error"></span>
219
+ <span class="dashicons dashicons-yes success"></span>
220
+ </div>
221
+ </form>
222
+ </div>
223
+ </div>
224
+
225
+ <!-- Restaurant Reservations column configuration modal -->
226
+ <div id="rtb-column-modal" class="rtb-admin-modal">
227
+ <div class="rtb-column-form rtb-container">
228
+ <form method="POST">
229
+ <input type="hidden" name="action" value="admin_column_config">
230
+
231
+ <fieldset>
232
+ <legend><?php esc_html_e( 'Columns', 'restaurant-reservations' ); ?></legend>
233
+ <ul>
234
+ <?php
235
+ $columns = $this->bookings_table->get_all_columns();
236
+ $visible = $this->bookings_table->get_columns();
237
+ foreach( $columns as $column => $label ) :
238
+ // Don't allow these columns to be hidden
239
+ if ( $column == 'cb' || $column == 'details' || $column == 'date' ) {
240
+ continue;
241
+ }
242
+ ?>
243
+ <li>
244
+ <label>
245
+ <input type="checkbox" name="rtb-columns-config" value="<?php esc_attr_e( $column ); ?>"<?php if ( array_key_exists( $column, $visible ) ) : ?> checked<?php endif; ?>>
246
+ <?php esc_html_e( $label ); ?>
247
+ </label>
248
+ </li>
249
+ <?php
250
+ endforeach;
251
+ ?>
252
+ </ul>
253
+ </fieldset>
254
+
255
+ <button type="submit" class="button button-primary">
256
+ <?php _e( 'Update', 'restaurant-reservations' ); ?>
257
+ </button>
258
+ <a href="#" class="button" id="rtb-cancel-column-modal">
259
+ <?php _e( 'Cancel', 'restaurant-reservations' ); ?>
260
+ </a>
261
+ <div class="action-status">
262
+ <span class="spinner loading"></span>
263
+ <span class="dashicons dashicons-no-alt error"></span>
264
+ <span class="dashicons dashicons-yes success"></span>
265
+ </div>
266
+ </form>
267
+ </div>
268
+ </div>
269
+
270
+ <!-- Restaurant Reservations details modal -->
271
+ <div id="rtb-details-modal" class="rtb-admin-modal">
272
+ <div class="rtb-details-form rtb-container">
273
+ <div class="rtb-details-data"></div>
274
+ <a href="#" class="button" id="rtb-cancel-details-modal">
275
+ <?php _e( 'Close', 'restaurant-reservations' ); ?>
276
+ </a>
277
+ </div>
278
+ </div>
279
+
280
+ <!-- Restaurant Reservations ban email/ip modal -->
281
+ <div id="rtb-ban-modal" class="rtb-admin-modal">
282
+ <div class="rtb-ban-form rtb-container">
283
+ <div class="rtb-ban-msg">
284
+ <p class="intro">
285
+ <?php
286
+ printf(
287
+ __( 'Ban future bookings from the email address %s or the IP address %s?', 'restaurant-reservations' ),
288
+ '<span id="rtb-ban-modal-email"></span>',
289
+ '<span id="rtb-ban-modal-ip"></span>'
290
+ );
291
+ ?>
292
+ </p>
293
+ <p>
294
+ <?php
295
+ esc_html_e( 'It is recommended to ban by email address instead of IP. Only ban by IP address to block a malicious user who is using different email addresses to avoid a previous ban.', 'restaurant-reservations' );
296
+ ?>
297
+ </p>
298
+ </div>
299
+ <button class="button button-primary" id="rtb-ban-modal-email-btn">Ban Email</button>
300
+ <button class="button button-primary" id="rtb-ban-modal-ip-btn">Ban IP</button>
301
+ <a href="#" id="rtb-cancel-ban-modal" class="button"><?php _e( 'Close', 'restaurant-reservations' ); ?></a>
302
+ <a class="button-link" href="<?php echo esc_url( admin_url( '/admin.php?page=rtb-settings' ) ); ?>" target="_blank">
303
+ <?php esc_html_e( 'View all bans', 'restaurant-reservations' ); ?>
304
+ </a>
305
+ <div class="action-status">
306
+ <span class="spinner loading"></span>
307
+ <span class="dashicons dashicons-no-alt error"></span>
308
+ <span class="dashicons dashicons-yes success"></span>
309
+ </div>
310
+ </div>
311
+ </div>
312
+
313
+ <!-- Restaurant Reservations delete customer modal -->
314
+ <div id="rtb-delete-modal" class="rtb-admin-modal">
315
+ <div class="rtb-delete-form rtb-container">
316
+ <div class="rtb-delete-msg">
317
+ <?php
318
+ printf(
319
+ __( 'Delete all booking records related to email address %s? This action can not be undone.', 'restaurant-reservations' ),
320
+ '<span id="rtb-delete-modal-email"></span>'
321
+ );
322
+ ?>
323
+ </div>
324
+ <div id="rtb-delete-status">
325
+ <span class="rtb-delete-status-total">
326
+ <span id="rtb-delete-status-progress" class="rtb-delete-status-progress"></span>
327
+ </span>
328
+ <div id="rtb-delete-status-deleted"></div>
329
+ </div>
330
+ <button class="button button-primary" id="rtb-delete-modal-btn">Delete Bookings</button>
331
+ <button id="rtb-cancel-delete-modal" class="button"><?php _e( 'Close', 'restaurant-reservations' ); ?></button>
332
+ <div class="action-status">
333
+ <span class="spinner loading"></span>
334
+ <span class="dashicons dashicons-no-alt error"></span>
335
+ <span class="dashicons dashicons-yes success"></span>
336
+ </div>
337
+ </div>
338
+ </div>
339
+
340
+ <!-- Restaurant Reservations error message modal -->
341
+ <div id="rtb-error-modal" class="rtb-admin-modal">
342
+ <div class="rtb-error rtb-container">
343
+ <div class="rtb-error-msg"></div>
344
+ <a href="#" class="button"><?php _e( 'Close', 'restaurant-reservations' ); ?></a>
345
+ </div>
346
+ </div>
347
+
348
+ <?php
349
+ }
350
+
351
+ /**
352
+ * Retrieve booking form fields used in the admin booking modal. These
353
+ * fields are also passed back with ajax requests since they render error
354
+ * messages and populate fields with validated data.
355
+ * @since 0.0.1
356
+ */
357
+ public function print_booking_form_fields() {
358
+
359
+ global $rtb_controller;
360
+
361
+ // Add post status and notification fields to admin booking form
362
+ add_filter( 'rtb_booking_form_fields', array( $this, 'add_admin_fields' ), 20, 2 );
363
+
364
+ // Retrieve the form fields
365
+ $fields = $rtb_controller->settings->get_booking_form_fields( $rtb_controller->request );
366
+
367
+ ob_start();
368
+ ?>
369
+
370
+ <?php foreach( $fields as $fieldset => $contents ) : ?>
371
+ <fieldset class="<?php echo $fieldset; ?>">
372
+ <?php
373
+ foreach( $contents['fields'] as $slug => $field ) {
374
+
375
+ $args = empty( $field['callback_args'] ) ? null : $field['callback_args'];
376
+
377
+ call_user_func( $field['callback'], $slug, $field['title'], $field['request_input'], $args );
378
+ }
379
+ ?>
380
+ </fieldset>
381
+ <?php endforeach;
382
+
383
+ // Remove the admin fields filter
384
+ remove_filter( 'rtb_booking_form_fields', array( $this, 'add_admin_fields' ) );
385
+
386
+ return ob_get_clean();
387
+ }
388
+
389
+ /**
390
+ * Add the post status and notifications fields to the booking form fields
391
+ * @since 1.3
392
+ */
393
+ public function add_admin_fields( $fields, $request ) {
394
+
395
+ if ( !is_admin() || !current_user_can( 'manage_bookings' ) ) {
396
+ return $fields;
397
+ }
398
+
399
+ global $rtb_controller;
400
+
401
+ // Get all valid booking statuses
402
+ $booking_statuses = array();
403
+ foreach( $rtb_controller->cpts->booking_statuses as $status => $args ) {
404
+ $booking_statuses[ $status ] = $args['label'];
405
+ }
406
+
407
+ $fields['admin'] = array(
408
+ 'fields' => array(
409
+ 'post-status' => array(
410
+ 'title' => __( 'Booking Status', 'restaurant-reservations' ),
411
+ 'request_input' => empty( $request->post_status ) ? '' : $request->post_status,
412
+ 'callback' => 'rtb_print_form_select_field',
413
+ 'callback_args' => array(
414
+ 'options' => $booking_statuses,
415
+ )
416
+ ),
417
+ 'notifications' => array(
418
+ 'title' => __( 'Send notifications', 'restaurant-reservations' ),
419
+ 'request_input' => empty( $request->send_notifications ) ? false : $request->send_notifications,
420
+ 'callback' => array( $this, 'print_form_send_notifications_field' ),
421
+ 'callback_args' => array(
422
+ 'description' => array(
423
+ 'prompt' => __( 'Learn more', 'restaurant-reservations' ),
424
+ 'text' => __( "When adding a booking or changing a booking's status with this form, no email notifications will be sent. Check this option if you want to send email notifications.", 'restaurant-reservations' ),
425
+ ),
426
+ ),
427
+ ),
428
+ ),
429
+ );
430
+
431
+ return $fields;
432
+ }
433
+
434
+
435
+ /**
436
+ * Print a field to toggle notifications when adding/editing a booking from
437
+ * the admin
438
+ * @since 1.3
439
+ */
440
+ function print_form_send_notifications_field( $slug, $title, $value, $args ) {
441
+
442
+ $slug = esc_attr( $slug );
443
+ $title = esc_attr( $title );
444
+ $value = (bool) $value;
445
+ $description = empty( $args['description'] ) || empty( $args['description']['prompt'] ) || empty( $args['description']['text'] ) ? null : $args['description'];
446
+ ?>
447
+
448
+ <div class="<?php echo $slug; ?>">
449
+ <?php echo rtb_print_form_error( $slug ); ?>
450
+ <label>
451
+ <input type="checkbox" name="rtb-<?php echo esc_attr( $slug ); ?>" value="1"<?php checked( $value ); ?>>
452
+ <?php echo $title; ?>
453
+ <?php if ( !empty( $description ) ) : ?>
454
+ <a href="#" class="rtb-description-prompt">
455
+ <?php echo $description['prompt']; ?>
456
+ </a>
457
+ <?php endif; ?>
458
+ </label>
459
+ <?php if ( !empty( $description ) ) : ?>
460
+ <div class="rtb-description">
461
+ <?php echo $description['text']; ?>
462
+ </div>
463
+ <?php endif; ?>
464
+ </div>
465
+
466
+ <?php
467
+ }
468
+
469
+ /**
470
+ * Handle ajax requests from the admin bookings area from logged out users
471
+ * @since 1.3
472
+ */
473
+ public function nopriv_ajax() {
474
+
475
+ wp_send_json_error(
476
+ array(
477
+ 'error' => 'loggedout',
478
+ 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( admin_url( 'admin.php?page=rtb-bookings&status=pending' ) ) . '">', '</a>' ),
479
+ )
480
+ );
481
+ }
482
+
483
+ /**
484
+ * Handle ajax requests from the admin bookings area
485
+ * @since 1.3
486
+ */
487
+ public function booking_modal_ajax() {
488
+
489
+ global $rtb_controller;
490
+
491
+ // Authenticate request
492
+ if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
493
+ $this->nopriv_ajax();
494
+ }
495
+
496
+ // Retrieve a booking with a GET request
497
+ if ( !empty( $_GET['booking'] ) && !empty( $_GET['booking']['ID'] ) ) {
498
+
499
+ $id = (int) $_GET['booking']['ID'];
500
+
501
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
502
+ $rtb_controller->request = new rtbBooking();
503
+ $result = $rtb_controller->request->load_post( $id );
504
+
505
+ if ( $result ) {
506
+
507
+ // Don't allow editing of trashed bookings. This wil force
508
+ // appropriate use of the trash status and (hopefully) prevent
509
+ // mistakes in booking management.
510
+ if ( $rtb_controller->request->post_status == 'trash' ) {
511
+ wp_send_json_error(
512
+ array(
513
+ 'error' => 'booking_trashed',
514
+ 'msg' => sprintf( __( 'This booking has been sent to the %sTrash%s where it can not be edited. Set the booking to Pending or Confirmed to edit it.', 'restaurant-reservations' ), '<a href="' . admin_url( 'admin.php?page=rtb-bookings&status=trash' ) . '">', '</a>' ),
515
+ )
516
+ );
517
+ }
518
+
519
+ $rtb_controller->request->prepare_request_data();
520
+ wp_send_json_success(
521
+ array(
522
+ 'booking' => $rtb_controller->request,
523
+ 'fields' => $this->print_booking_form_fields(),
524
+ )
525
+ );
526
+
527
+ } else {
528
+ wp_send_json_error(
529
+ array(
530
+ 'error' => 'booking_not_found',
531
+ 'msg' => __( 'The booking could not be retrieved. Please reload the page and try again.', 'restaurant-reservations' ),
532
+ )
533
+ );
534
+ }
535
+
536
+ // Insert or update a booking with a POST request
537
+ } elseif ( !empty( $_POST['booking'] ) ) {
538
+
539
+ // Set up $_POST object for validation
540
+ foreach( $_POST['booking'] as $field ) {
541
+
542
+ // $field is setup by jQuery's serializeArray(), which will preserve
543
+ // array indicators in field names. So name[] is passed as "name[]"
544
+ // instead of "name". Let's strip out any trailing brackets to match
545
+ // the normal behaviour when receiving $_POST data on the server.
546
+ if ( substr( $field['name'], -2 ) === '[]' ) {
547
+ $name = substr( $field['name'], 0, -2 );
548
+ if ( !isset( $_POST[ $name ] ) ) {
549
+ $_POST[ $name ] = array();
550
+ }
551
+ $_POST[ $name ][] = $field['value'];
552
+ } else {
553
+ $_POST[ $field['name'] ] = $field['value'];
554
+ }
555
+
556
+ }
557
+
558
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
559
+ $rtb_controller->request = new rtbBooking();
560
+
561
+ // Add an ID if we're updating the post
562
+ if ( !empty( $_POST['ID'] ) ) {
563
+ $result = $rtb_controller->request->load_post((int) $_POST['ID']);
564
+ if (!$result) {
565
+ wp_send_json_error(
566
+ array(
567
+ 'error' => 'no_booking_found',
568
+ 'booking' => $rtb_controller->request,
569
+ 'fields' => $this->print_booking_form_fields(),
570
+ )
571
+ );
572
+ }
573
+ }
574
+
575
+ // Disable notifications
576
+ $this->maybe_disable_notifications();
577
+
578
+ $result = $rtb_controller->request->insert_booking($by_admin = true);
579
+
580
+ if ( $result ) {
581
+ wp_send_json_success(
582
+ array(
583
+ 'booking' => $rtb_controller->request,
584
+ )
585
+ );
586
+ } else {
587
+ wp_send_json_error(
588
+ array(
589
+ 'error' => 'invalid_booking_data',
590
+ 'booking' => $rtb_controller->request,
591
+ 'fields' => $this->print_booking_form_fields(),
592
+ )
593
+ );
594
+ }
595
+ }
596
+
597
+ // Fallback to a valid error
598
+ wp_send_json_error();
599
+ }
600
+
601
+ /**
602
+ * Set booking status to trash
603
+ * @since 1.3
604
+ */
605
+ public function trash_booking_ajax() {
606
+
607
+ global $rtb_controller;
608
+
609
+ // Authenticate request
610
+ if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) || empty( $_POST['booking'] ) ) {
611
+ $this->nopriv_ajax();
612
+ }
613
+
614
+ $id = (int) $_POST['booking'];
615
+
616
+ $result = wp_trash_post( $id );
617
+
618
+ if ( $result === false ) {
619
+ wp_send_json_error(
620
+ array(
621
+ 'error' => 'trash_failed',
622
+ 'msg' => __( 'Unable to trash this post. Please try again. If you continue to have trouble, please refresh the page.', 'restaurant-reservations' ),
623
+ )
624
+ );
625
+
626
+ } else {
627
+ wp_send_json_success(
628
+ array(
629
+ 'booking' => $id,
630
+ )
631
+ );
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Handle ajax requests to send an email
637
+ *
638
+ * @since 1.3.1
639
+ */
640
+ public function email_modal_ajax() {
641
+
642
+ global $rtb_controller;
643
+
644
+ // Authenticate request
645
+ if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
646
+ $this->nopriv_ajax();
647
+ }
648
+
649
+ // Set up $_POST object for validation
650
+ foreach( $_POST['email'] as $field ) {
651
+ $_POST[ $field['name'] ] = $field['value'];
652
+ }
653
+
654
+ $ids = ( isset( $_POST['ID'] ) and $_POST['ID'] != '' ) ? explode( ',', $_POST['ID'] ) : array();
655
+ $subject = stripcslashes( sanitize_text_field( $_POST['rtb-email-subject'] ) );
656
+ $message = stripcslashes( wp_kses_post( $_POST['rtb-email-message'] ) );
657
+
658
+ if ( empty( $message ) ) {
659
+ wp_send_json_error(
660
+ array(
661
+ 'error' => 'email_missing_message',
662
+ 'msg' => __( 'Please enter a message before sending the email.', 'restaurant-reservations' ),
663
+ )
664
+ );
665
+ }
666
+
667
+ if ( empty( $ids ) ) {
668
+ wp_send_json_error(
669
+ array(
670
+ 'error' => 'email_missing_data',
671
+ 'msg' => __( 'The email could not be sent because some critical information was missing.', 'restaurant-reservations' ),
672
+ )
673
+ );
674
+ }
675
+
676
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
677
+
678
+ foreach ( $ids as $id ) {
679
+
680
+ $booking = new rtbBooking();
681
+ $booking->load_post( $id );
682
+
683
+ $email = new rtbNotificationEmail( 'admin_email_notice', 'user' );
684
+ $email->subject = empty( $subject ) ? $rtb_controller->settings->get_setting( 'subject-admin-notice' ) : $subject;
685
+ $email->message = $message;
686
+ $email->set_booking( $booking );
687
+ if ( $email->prepare_notification() ) {
688
+ do_action( 'rtb_send_notification_before', $email );
689
+ $email->send_notification();
690
+ do_action( 'rtb_send_notification_after', $email );
691
+ }
692
+
693
+ // Store email in postmeta for log
694
+ $booking->add_log( 'email', $email->subject, $email->message );
695
+ $booking->insert_post_data();
696
+ }
697
+
698
+ wp_send_json_success();
699
+ }
700
+
701
+ /**
702
+ * Handle ajax requests to configure columns
703
+ *
704
+ * @since 1.3.1
705
+ */
706
+ public function column_modal_ajax() {
707
+
708
+ global $rtb_controller;
709
+
710
+ // Authenticate request
711
+ if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
712
+ $this->nopriv_ajax();
713
+ }
714
+
715
+ if ( !isset( $_POST['columns'] ) || !is_array( $_POST['columns'] ) || empty( $_POST['columns'] ) ) {
716
+ wp_send_json_error(
717
+ array(
718
+ 'error' => 'no_columns',
719
+ 'msg' => __( 'You must select at least one column to display.', 'restaurant-reservations' ),
720
+ )
721
+ );
722
+ }
723
+
724
+ $settings = get_option( 'rtb-settings' );
725
+ $settings['bookings-table-columns'] = array_map( 'sanitize_key', $_POST['columns'] );
726
+ update_option( 'rtb-settings', $settings );
727
+
728
+ wp_send_json_success();
729
+ }
730
+
731
+ /**
732
+ * Handle ajax requests to ban by IP or email address
733
+ *
734
+ * @since 1.3.1
735
+ */
736
+ public function ban_modal_ajax() {
737
+
738
+ global $rtb_controller;
739
+
740
+ // Authenticate request
741
+ if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
742
+ $this->nopriv_ajax();
743
+ }
744
+
745
+ // Ban an email address
746
+ if ( isset( $_POST['email'] ) && !empty( $_POST['email'] ) ) {
747
+ $email = trim( sanitize_text_field( $_POST['email'] ) );
748
+ $banned_emails = preg_split( '/\r\n|\r|\n/', (string) $rtb_controller->settings->get_setting( 'ban-emails' ) );
749
+
750
+ if ( !in_array( $email, $banned_emails ) ) {
751
+ $banned_emails[] = $email;
752
+ $rtb_controller->settings->settings['ban-emails'] = join( "\n", $banned_emails );
753
+ update_option( 'rtb-settings', $rtb_controller->settings->settings );
754
+ }
755
+
756
+ wp_send_json_success();
757
+
758
+ // Ban an IP address
759
+ } elseif ( isset( $_POST['ip'] ) && !empty( $_POST['ip'] ) ) {
760
+ $ip = trim( sanitize_text_field( $_POST['ip'] ) );
761
+ $banned_ips = preg_split( '/\r\n|\r|\n/', (string) $rtb_controller->settings->get_setting( 'ban-ips' ) );
762
+
763
+ if ( !in_array( $ip, $banned_ips ) ) {
764
+ $banned_ips[] = $ip;
765
+ $rtb_controller->settings->settings['ban-ips'] = join( "\n", $banned_ips );
766
+ update_option( 'rtb-settings', $rtb_controller->settings->settings );
767
+ }
768
+
769
+ wp_send_json_success();
770
+ }
771
+
772
+ wp_send_json_error(
773
+ array(
774
+ 'error' => 'no_data',
775
+ 'msg' => __( 'No IP or email address could be found for this ban request.', 'restaurant-reservations' ),
776
+ )
777
+ );
778
+ }
779
+
780
+ /**
781
+ * Handle ajax requests to delete bookings by email
782
+ *
783
+ * @since 1.7.7
784
+ */
785
+ public function delete_modal_ajax() {
786
+
787
+ global $rtb_controller;
788
+
789
+ // Authenticate request
790
+ if ( !check_ajax_referer( 'rtb-admin', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
791
+ $this->nopriv_ajax();
792
+ }
793
+
794
+ if ( !empty( $_POST['email'] ) ) {
795
+ $email = sanitize_email( $_POST['email'] );
796
+
797
+ $args = array(
798
+ 'date_range' => null,
799
+ 'posts_per_page' => 100,
800
+ 'paged' => !empty( $_POST['page'] ) ? (int) $_POST['page'] : 1,
801
+ );
802
+
803
+ $query = new rtbQuery( $args, 'delete-by-email' );
804
+ $query->prepare_args();
805
+
806
+ $bookings = $query->get_bookings();
807
+ $deleted = 0;
808
+ foreach( $bookings as $booking ) {
809
+ if ( isset( $booking->email ) && $booking->email === $email) {
810
+ wp_delete_post( $booking->ID, true );
811
+ $deleted++;
812
+ }
813
+ }
814
+
815
+ // Get count of all bookings
816
+ global $wpdb;
817
+ $where = "WHERE p.post_type = '" . RTB_BOOKING_POST_TYPE . "'";
818
+ $query = "SELECT count( * ) AS num_posts
819
+ FROM $wpdb->posts p
820
+ $where
821
+ ";
822
+
823
+ $count = $wpdb->get_results( $query );
824
+ $count = (int) $count[0]->num_posts;
825
+
826
+ wp_send_json_success(array(
827
+ 'processed' => count($bookings),
828
+ 'deleted' => $deleted,
829
+ 'total' => $count,
830
+ ));
831
+ }
832
+
833
+ wp_send_json_error(
834
+ array(
835
+ 'error' => 'no_data',
836
+ 'msg' => __( 'No email address could be found for this delete request.', 'restaurant-reservations' ),
837
+ )
838
+ );
839
+ }
840
+
841
+ /**
842
+ * Register a party as having arrived
843
+ * @since 2.0.0
844
+ */
845
+ public function set_booking_arrived() {
846
+ $booking_id = isset($_POST['booking_id']) ? intval( $_POST['booking_id'] ) : 0;
847
+
848
+ $booking_id = wp_update_post(array(
849
+ 'ID' => $booking_id,
850
+ 'post_status' => 'arrived'
851
+ ) );
852
+
853
+ if ( $booking_id ) {
854
+ wp_send_json_success();
855
+ }
856
+ else {
857
+ wp_send_json_error(
858
+ array(
859
+ 'error' => 'loggedout',
860
+ 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( ) . '">', '</a>' ),
861
+ )
862
+ );
863
+ }
864
+ }
865
+
866
+ /**
867
+ * Validate post status and notification fields
868
+ * @since 1.3
869
+ */
870
+ public function validate_admin_fields( $booking ) {
871
+
872
+ // Only validate in the admin
873
+ if ( !$_POST['action'] || $_POST['action'] !== 'admin_booking_request' ) {
874
+ return;
875
+ }
876
+
877
+ global $rtb_controller;
878
+
879
+ // Disable Notifications
880
+ $booking->send_notifications = empty( $_POST['rtb-notifications'] ) ? false : true;
881
+ }
882
+
883
+ /**
884
+ * Adjust post status when adding/editing a booking from the admin area
885
+ * @since 1.3
886
+ */
887
+ public function insert_booking_data( $args, $booking ) {
888
+
889
+ // Validate user request
890
+ if ( empty( $_POST['action'] ) || $_POST['action'] !== 'admin_booking_request' || !current_user_can( 'manage_bookings' ) ) {
891
+ return $args;
892
+ }
893
+
894
+ if ( !empty( $booking->post_status ) ) {
895
+ $args['post_status'] = $booking->post_status;
896
+ }
897
+
898
+ return $args;
899
+ }
900
+
901
+ /**
902
+ * Maybe disable notifications when adding/editing bookings from the
903
+ * admin booking modal
904
+ * @since 1.3
905
+ */
906
+ public function maybe_disable_notifications() {
907
+
908
+ // Don't disable notifications if they have opted to send them
909
+ if ( !empty( $_POST['rtb-notifications'] ) ) {
910
+ return;
911
+ }
912
+
913
+ // Disable all notifications. This filter is here in case a
914
+ // third-party sets up a notification that they don't want to be
915
+ // disabled even if the user has opted not to send notifications
916
+ // To exempt a notification, hook into the filter and copy it
917
+ // from $rtb_notifications to the empty array.
918
+ global $rtb_controller;
919
+ $rtb_controller->notifications->notifications = apply_filters( 'rtb_admin_disabled_notifications_exemption', array(), $rtb_controller->notifications->notifications );
920
+ }
921
+ }
922
+ } // endif;
includes/AdminPageSettingLicenseKey.class.php CHANGED
@@ -1,422 +1,422 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbAdminPageSettingLicenseKey' ) ) {
5
- /**
6
- * Add a setting to Simple Admin Pages to register and verify an
7
- * EDD Software Licensing key.
8
- *
9
- * This class is modelled on AdminPageSetting.class.php in the
10
- * Simple Admin Pages Library. But it doesn't extend that class
11
- * due to rules within the library about how versions are
12
- * managed.
13
- *
14
- * See: https://github.com/NateWr/simple-admin-pages
15
- *
16
- * @since 1.4.1
17
- */
18
- class rtbAdminPageSettingLicenseKey {
19
-
20
- /**
21
- * Scripts to load for this component
22
- *
23
- * @since 1.4.1
24
- */
25
- public $scripts = array();
26
-
27
- /**
28
- * Styles to load for this component
29
- *
30
- * @since 1.4.1
31
- */
32
- public $styles = array();
33
-
34
- /**
35
- * Product slug on the store
36
- *
37
- * @since 1.4.1
38
- */
39
- public $product;
40
-
41
- /**
42
- * Store URL which manages license data
43
- *
44
- * @since 1.4.1
45
- */
46
- public $store_url;
47
-
48
- /**
49
- * Translateable strings required for this component
50
- *
51
- * @since 1.4.1
52
- */
53
- public $strings = array(
54
- 'active' => null, // __( 'Active', 'textdomain' ),
55
- 'expired' => null, // __( 'Expired', 'textdomain' ),
56
- 'inactive' => null, // __( 'Inactive', 'textdomain' ),
57
- 'expiry' => null, // _x( 'Expiry', 'Label before the expiration date of the license key', textdomain' ),
58
- 'deactivate' => null, // __( 'Deactivate License', 'textdomain' ),
59
- );
60
-
61
- /**
62
- * Initialize the setting
63
- *
64
- * @since 1.4.1
65
- */
66
- public function __construct( $args ) {
67
-
68
- // Parse the values passed
69
- $this->parse_args( $args );
70
-
71
- // Get any existing value
72
- $this->set_value();
73
-
74
- // Set an error if the object is missing necessary data
75
- if ( $this->missing_data() ) {
76
- $this->set_error();
77
- }
78
-
79
- // Process a license activation/deactivation
80
- add_filter( 'admin_init', array( $this, 'process_action' ), 100 );
81
- }
82
-
83
- /**
84
- * Parse the arguments passed in the construction and assign them to
85
- * internal variables.
86
- *
87
- * @since 1.4.1
88
- */
89
- private function parse_args( $args ) {
90
- foreach ( $args as $key => $val ) {
91
- switch ( $key ) {
92
-
93
- case 'id' :
94
- $this->{$key} = esc_attr( $val );
95
-
96
- case 'title' :
97
- $this->{$key} = esc_attr( $val );
98
-
99
- case 'product' :
100
- $this->{$key} = sanitize_key( $val );
101
-
102
- case 'url' :
103
- $this->{$key} = sanitize_text_field( $val );
104
-
105
- default :
106
- $this->{$key} = $val;
107
-
108
- }
109
- }
110
- }
111
-
112
- /**
113
- * Check for missing data when setup.
114
- *
115
- * @since 1.4.1
116
- */
117
- private function missing_data() {
118
-
119
- // Required fields
120
- if ( empty( $this->id ) ) {
121
- $this->set_error(
122
- array(
123
- 'type' => 'missing_data',
124
- 'data' => 'id'
125
- )
126
- );
127
- }
128
- if ( empty( $this->title ) ) {
129
- $this->set_error(
130
- array(
131
- 'type' => 'missing_data',
132
- 'data' => 'title'
133
- )
134
- );
135
- }
136
- }
137
-
138
- /**
139
- * Set a value
140
- *
141
- * @since 1.4.1
142
- */
143
- public function set_value( $val = null ) {
144
-
145
- if ( $val === null ) {
146
- $option_group_value = get_option( $this->page );
147
- $val = isset( $option_group_value[ $this->id ] ) ? $option_group_value[ $this->id ] : '';
148
- }
149
-
150
- $this->value = $this->esc_value( $val );
151
- }
152
-
153
- /**
154
- * Escape the value to display it in text fields and other input fields
155
- *
156
- * @since 1.4.1
157
- */
158
- public function esc_value( $val ) {
159
-
160
- $value = array(
161
- 'api_key' => '',
162
- 'status' => false,
163
- 'expiry' => false,
164
- );
165
-
166
- if ( empty( $val ) || empty( $val['api_key'] ) ) {
167
- return $value;
168
- }
169
-
170
- $value['api_key'] = esc_attr( $val['api_key'] );
171
-
172
- if ( !empty( $val['status'] ) ) {
173
- $value['status'] = esc_attr( $val['status'] );
174
- }
175
-
176
- if ( !empty( $val['expiry'] ) ) {
177
- $value['expiry'] = esc_attr( $val['expiry'] );
178
- }
179
-
180
- return $value;
181
- }
182
-
183
- /**
184
- * Display this setting
185
- *
186
- * @since 1.4.1
187
- */
188
- public function display_setting() {
189
-
190
- // Set a flag for the output
191
- $is_active = $this->value['status'] == 'valid' ? true : false;
192
- $status = empty( $this->value['status'] ) ? 'inactive' : $this->value['status'];
193
- $status_string = !empty( $this->strings[ $status] ) ? $this->strings[ $status ] : __( 'Invalid', 'restaurant-reservations' );
194
-
195
- // Compile activation/deactivation URL
196
- if ( !empty( $this->value['api_key'] ) ) {
197
- $url = add_query_arg(
198
- array(
199
- 'id' => $this->id,
200
- )
201
- );
202
- if ( $is_active ) {
203
- $url = add_query_arg( 'action', 'deactivate', $url );
204
- } else {
205
- $url = add_query_arg( 'action', 'activate', $url );
206
- }
207
- }
208
- ?>
209
-
210
- <div class="rtb-license-setting" data-id="<?php echo esc_attr( $this->id ); ?>">
211
- <input name="<?php echo $this->get_input_name(); ?>[api_key]" type="text" id="<?php echo $this->get_input_name(); ?>[api_key]" value="<?php echo $this->value['api_key']; ?>"<?php echo !empty( $this->placeholder ) ? ' placeholder="' . esc_attr( $this->placeholder ) . '"' : ''; ?> class="regular-text">
212
-
213
- <?php if ( !empty( $this->value['api_key'] ) ) : ?>
214
- <span class="status <?php echo $is_active ? 'valid' : 'inactive'; ?>">
215
- <?php echo $status_string; ?>
216
- </span>
217
-
218
- <a href="<?php echo esc_url( $url ); ?>" class="button">
219
- <?php echo $is_active ? $this->strings['deactivate'] : $this->strings['activate']; ?>
220
- </a>
221
-
222
- <span class="spinner"></span>
223
-
224
- <?php endif;
225
-
226
- $this->display_description();
227
-
228
- ?>
229
-
230
- </div>
231
-
232
- <?php
233
- }
234
-
235
- /**
236
- * Display a description for this setting
237
- *
238
- * @since 1.4.1
239
- */
240
- public function display_description() {
241
-
242
- if ( !empty( $this->description ) ) : ?>
243
-
244
- <p class="description"><?php echo $this->description; ?></p>
245
-
246
- <?php endif;
247
- }
248
-
249
- /**
250
- * Generate an option input field name, using the grouped schema.
251
- *
252
- * @since 1.4.1
253
- */
254
- public function get_input_name() {
255
- return esc_attr( $this->page ) . '[' . esc_attr( $this->id ) . ']';
256
- }
257
-
258
-
259
- /**
260
- * Sanitize the array of text inputs for this setting
261
- *
262
- * @since 1.4.1
263
- */
264
- public function sanitize_callback_wrapper( $values ) {
265
-
266
- $output = array(
267
- 'api_key' => '',
268
- 'status' => false,
269
- 'expiry' => false,
270
- );
271
-
272
- if ( empty( $values ) || empty( $values['api_key'] ) ) {
273
- return $output;
274
- }
275
-
276
- $output['api_key'] = trim( sanitize_text_field( $values['api_key'] ) );
277
-
278
- // Clear status and expiry when a license key has changed
279
- global $rtb_controller;
280
- $old = $rtb_controller->settings->get_setting( $this->id );
281
- if ( empty( $old['api_key'] ) || $old['api_key'] !== $output['api_key'] ) {
282
- return $output;
283
- }
284
-
285
- // Preserve old status values
286
- $output = array_merge( $old, $output );
287
-
288
- return $output;
289
- }
290
-
291
- /**
292
- * Add and register this setting
293
- *
294
- * @since 1.4.1
295
- */
296
- public function add_settings_field( $section_id ) {
297
-
298
- add_settings_field(
299
- $this->id,
300
- $this->title,
301
- array( $this, 'display_setting' ),
302
- $this->tab,
303
- $section_id
304
- );
305
-
306
- }
307
-
308
- /**
309
- * Set an error
310
- *
311
- * @since 1.4.1
312
- */
313
- public function set_error( $error ) {
314
- $this->errors[] = array_merge(
315
- $error,
316
- array(
317
- 'class' => get_class( $this ),
318
- 'id' => $this->id,
319
- 'backtrace' => debug_backtrace()
320
- )
321
- );
322
- }
323
-
324
- /**
325
- * Process a license activation if requested
326
- *
327
- * @since 1.4.1
328
- */
329
- public function process_action() {
330
-
331
- if ( !current_user_can( 'manage_options' ) || empty( $_GET['tab'] ) || $_GET['tab'] !== 'rtb-licenses' || empty( $_GET['action'] ) || empty( $_GET['id'] ) || $_GET['id'] !== $this->id ) {
332
- return;
333
- }
334
-
335
- $params = array();
336
- $params['edd_action'] = $_GET['action'] === 'activate' ? 'activate_license' : 'deactivate_license';
337
- $params['license'] = sanitize_text_field( $this->value['api_key'] );
338
- $params['item_name'] = urlencode( $this->product );
339
-
340
- $response = wp_remote_get( add_query_arg( $params, $this->store_url ), array( 'timeout' => 15, 'sslverify' => false ) );
341
-
342
- if ( is_wp_error( $response ) ) {
343
- $url = remove_query_arg( array( 'id', 'action' ) );
344
- $url = add_query_arg( 'license_result', 'response_wp_error', $url );
345
- header( 'Location: ' . esc_url_raw( $url ) );
346
- }
347
-
348
- $license_data = json_decode( wp_remote_retrieve_body( $response ) );
349
-
350
-
351
- if ( $params['edd_action'] == 'activate' ) {
352
- $result = $this->process_activation_response( $license_data );
353
- } else {
354
- $result = $this->process_activation_response( $license_data );
355
- }
356
-
357
- // Construct a URL to redirect back to the tab
358
- $url = remove_query_arg( array( 'id', 'action' ) );
359
- $url = add_query_arg(
360
- array(
361
- 'license_result' => $result ? 1 : 0,
362
- 'action' => $_GET['action'] == 'activate' ? 'activate' : 'deactivate',
363
- ),
364
- $url
365
- );
366
-
367
- // If the result failed maybe add note on why
368
- if ( !$result && !empty( $license_data->error ) ) {
369
- $url = add_query_arg( 'result_error', $license_data->error, $url );
370
- }
371
-
372
- header( 'Location: ' . esc_url_raw( $url ) );
373
-
374
- }
375
-
376
- /**
377
- * Process the response to an activation request
378
- *
379
- * @since 1.4.1
380
- */
381
- public function process_activation_response( $license_data ) {
382
-
383
- if ( ( !empty( $license_data->error ) && ( $license_data->error == 'missing' || $license_data->error == 'item_name_mismatch' ) ) || $license_data->license == 'invalid' ) {
384
- $this->value['status'] = 'invalid';
385
- $this->value['expiry'] = false;
386
- } else {
387
- $this->value['status'] = $license_data->license;
388
- $this->value['expiry'] = $license_data->expires;
389
- }
390
-
391
- $rtb_settings = get_option( $this->page );
392
- $rtb_settings[ $this->id ] = $this->value;
393
-
394
- update_option( $this->page, $rtb_settings );
395
-
396
- return $license_data->license == 'valid' || $license_data->license == 'deactivated';
397
- }
398
-
399
- /**
400
- * Process the response to an deactivation request
401
- *
402
- * @since 1.4.1
403
- */
404
- public function process_deactivation_response( $license_data ) {
405
-
406
- if ( $license_data->license !== 'deactivated' ) {
407
- return false;
408
- } else {
409
- $this->value['status'] = false;
410
- $this->value['expiry'] = false;
411
- }
412
-
413
- $rtb_settings = get_option( $this->page );
414
- $rtb_settings[ $this->id ] = $this->value;
415
-
416
- update_option( $this->page, $rtb_settings );
417
-
418
- return true;
419
- }
420
-
421
- }
422
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbAdminPageSettingLicenseKey' ) ) {
5
+ /**
6
+ * Add a setting to Simple Admin Pages to register and verify an
7
+ * EDD Software Licensing key.
8
+ *
9
+ * This class is modelled on AdminPageSetting.class.php in the
10
+ * Simple Admin Pages Library. But it doesn't extend that class
11
+ * due to rules within the library about how versions are
12
+ * managed.
13
+ *
14
+ * See: https://github.com/NateWr/simple-admin-pages
15
+ *
16
+ * @since 1.4.1
17
+ */
18
+ class rtbAdminPageSettingLicenseKey {
19
+
20
+ /**
21
+ * Scripts to load for this component
22
+ *
23
+ * @since 1.4.1
24
+ */
25
+ public $scripts = array();
26
+
27
+ /**
28
+ * Styles to load for this component
29
+ *
30
+ * @since 1.4.1
31
+ */
32
+ public $styles = array();
33
+
34
+ /**
35
+ * Product slug on the store
36
+ *
37
+ * @since 1.4.1
38
+ */
39
+ public $product;
40
+
41
+ /**
42
+ * Store URL which manages license data
43
+ *
44
+ * @since 1.4.1
45
+ */
46
+ public $store_url;
47
+
48
+ /**
49
+ * Translateable strings required for this component
50
+ *
51
+ * @since 1.4.1
52
+ */
53
+ public $strings = array(
54
+ 'active' => null, // __( 'Active', 'textdomain' ),
55
+ 'expired' => null, // __( 'Expired', 'textdomain' ),
56
+ 'inactive' => null, // __( 'Inactive', 'textdomain' ),
57
+ 'expiry' => null, // _x( 'Expiry', 'Label before the expiration date of the license key', textdomain' ),
58
+ 'deactivate' => null, // __( 'Deactivate License', 'textdomain' ),
59
+ );
60
+
61
+ /**
62
+ * Initialize the setting
63
+ *
64
+ * @since 1.4.1
65
+ */
66
+ public function __construct( $args ) {
67
+
68
+ // Parse the values passed
69
+ $this->parse_args( $args );
70
+
71
+ // Get any existing value
72
+ $this->set_value();
73
+
74
+ // Set an error if the object is missing necessary data
75
+ if ( $this->missing_data() ) {
76
+ $this->set_error();
77
+ }
78
+
79
+ // Process a license activation/deactivation
80
+ add_filter( 'admin_init', array( $this, 'process_action' ), 100 );
81
+ }
82
+
83
+ /**
84
+ * Parse the arguments passed in the construction and assign them to
85
+ * internal variables.
86
+ *
87
+ * @since 1.4.1
88
+ */
89
+ private function parse_args( $args ) {
90
+ foreach ( $args as $key => $val ) {
91
+ switch ( $key ) {
92
+
93
+ case 'id' :
94
+ $this->{$key} = esc_attr( $val );
95
+
96
+ case 'title' :
97
+ $this->{$key} = esc_attr( $val );
98
+
99
+ case 'product' :
100
+ $this->{$key} = sanitize_key( $val );
101
+
102
+ case 'url' :
103
+ $this->{$key} = sanitize_text_field( $val );
104
+
105
+ default :
106
+ $this->{$key} = $val;
107
+
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Check for missing data when setup.
114
+ *
115
+ * @since 1.4.1
116
+ */
117
+ private function missing_data() {
118
+
119
+ // Required fields
120
+ if ( empty( $this->id ) ) {
121
+ $this->set_error(
122
+ array(
123
+ 'type' => 'missing_data',
124
+ 'data' => 'id'
125
+ )
126
+ );
127
+ }
128
+ if ( empty( $this->title ) ) {
129
+ $this->set_error(
130
+ array(
131
+ 'type' => 'missing_data',
132
+ 'data' => 'title'
133
+ )
134
+ );
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Set a value
140
+ *
141
+ * @since 1.4.1
142
+ */
143
+ public function set_value( $val = null ) {
144
+
145
+ if ( $val === null ) {
146
+ $option_group_value = get_option( $this->page );
147
+ $val = isset( $option_group_value[ $this->id ] ) ? $option_group_value[ $this->id ] : '';
148
+ }
149
+
150
+ $this->value = $this->esc_value( $val );
151
+ }
152
+
153
+ /**
154
+ * Escape the value to display it in text fields and other input fields
155
+ *
156
+ * @since 1.4.1
157
+ */
158
+ public function esc_value( $val ) {
159
+
160
+ $value = array(
161
+ 'api_key' => '',
162
+ 'status' => false,
163
+ 'expiry' => false,
164
+ );
165
+
166
+ if ( empty( $val ) || empty( $val['api_key'] ) ) {
167
+ return $value;
168
+ }
169
+
170
+ $value['api_key'] = esc_attr( $val['api_key'] );
171
+
172
+ if ( !empty( $val['status'] ) ) {
173
+ $value['status'] = esc_attr( $val['status'] );
174
+ }
175
+
176
+ if ( !empty( $val['expiry'] ) ) {
177
+ $value['expiry'] = esc_attr( $val['expiry'] );
178
+ }
179
+
180
+ return $value;
181
+ }
182
+
183
+ /**
184
+ * Display this setting
185
+ *
186
+ * @since 1.4.1
187
+ */
188
+ public function display_setting() {
189
+
190
+ // Set a flag for the output
191
+ $is_active = $this->value['status'] == 'valid' ? true : false;
192
+ $status = empty( $this->value['status'] ) ? 'inactive' : $this->value['status'];
193
+ $status_string = !empty( $this->strings[ $status] ) ? $this->strings[ $status ] : __( 'Invalid', 'restaurant-reservations' );
194
+
195
+ // Compile activation/deactivation URL
196
+ if ( !empty( $this->value['api_key'] ) ) {
197
+ $url = add_query_arg(
198
+ array(
199
+ 'id' => $this->id,
200
+ )
201
+ );
202
+ if ( $is_active ) {
203
+ $url = add_query_arg( 'action', 'deactivate', $url );
204
+ } else {
205
+ $url = add_query_arg( 'action', 'activate', $url );
206
+ }
207
+ }
208
+ ?>
209
+
210
+ <div class="rtb-license-setting" data-id="<?php echo esc_attr( $this->id ); ?>">
211
+ <input name="<?php echo $this->get_input_name(); ?>[api_key]" type="text" id="<?php echo $this->get_input_name(); ?>[api_key]" value="<?php echo $this->value['api_key']; ?>"<?php echo !empty( $this->placeholder ) ? ' placeholder="' . esc_attr( $this->placeholder ) . '"' : ''; ?> class="regular-text">
212
+
213
+ <?php if ( !empty( $this->value['api_key'] ) ) : ?>
214
+ <span class="status <?php echo $is_active ? 'valid' : 'inactive'; ?>">
215
+ <?php echo $status_string; ?>
216
+ </span>
217
+
218
+ <a href="<?php echo esc_url( $url ); ?>" class="button">
219
+ <?php echo $is_active ? $this->strings['deactivate'] : $this->strings['activate']; ?>
220
+ </a>
221
+
222
+ <span class="spinner"></span>
223
+
224
+ <?php endif;
225
+
226
+ $this->display_description();
227
+
228
+ ?>
229
+
230
+ </div>
231
+
232
+ <?php
233
+ }
234
+
235
+ /**
236
+ * Display a description for this setting
237
+ *
238
+ * @since 1.4.1
239
+ */
240
+ public function display_description() {
241
+
242
+ if ( !empty( $this->description ) ) : ?>
243
+
244
+ <p class="description"><?php echo $this->description; ?></p>
245
+
246
+ <?php endif;
247
+ }
248
+
249
+ /**
250
+ * Generate an option input field name, using the grouped schema.
251
+ *
252
+ * @since 1.4.1
253
+ */
254
+ public function get_input_name() {
255
+ return esc_attr( $this->page ) . '[' . esc_attr( $this->id ) . ']';
256
+ }
257
+
258
+
259
+ /**
260
+ * Sanitize the array of text inputs for this setting
261
+ *
262
+ * @since 1.4.1
263
+ */
264
+ public function sanitize_callback_wrapper( $values ) {
265
+
266
+ $output = array(
267
+ 'api_key' => '',
268
+ 'status' => false,
269
+ 'expiry' => false,
270
+ );
271
+
272
+ if ( empty( $values ) || empty( $values['api_key'] ) ) {
273
+ return $output;
274
+ }
275
+
276
+ $output['api_key'] = trim( sanitize_text_field( $values['api_key'] ) );
277
+
278
+ // Clear status and expiry when a license key has changed
279
+ global $rtb_controller;
280
+ $old = $rtb_controller->settings->get_setting( $this->id );
281
+ if ( empty( $old['api_key'] ) || $old['api_key'] !== $output['api_key'] ) {
282
+ return $output;
283
+ }
284
+
285
+ // Preserve old status values
286
+ $output = array_merge( $old, $output );
287
+
288
+ return $output;
289
+ }
290
+
291
+ /**
292
+ * Add and register this setting
293
+ *
294
+ * @since 1.4.1
295
+ */
296
+ public function add_settings_field( $section_id ) {
297
+
298
+ add_settings_field(
299
+ $this->id,
300
+ $this->title,
301
+ array( $this, 'display_setting' ),
302
+ $this->tab,
303
+ $section_id
304
+ );
305
+
306
+ }
307
+
308
+ /**
309
+ * Set an error
310
+ *
311
+ * @since 1.4.1
312
+ */
313
+ public function set_error( $error ) {
314
+ $this->errors[] = array_merge(
315
+ $error,
316
+ array(
317
+ 'class' => get_class( $this ),
318
+ 'id' => $this->id,
319
+ 'backtrace' => debug_backtrace()
320
+ )
321
+ );
322
+ }
323
+
324
+ /**
325
+ * Process a license activation if requested
326
+ *
327
+ * @since 1.4.1
328
+ */
329
+ public function process_action() {
330
+
331
+ if ( !current_user_can( 'manage_options' ) || empty( $_GET['tab'] ) || $_GET['tab'] !== 'rtb-licenses' || empty( $_GET['action'] ) || empty( $_GET['id'] ) || $_GET['id'] !== $this->id ) {
332
+ return;
333
+ }
334
+
335
+ $params = array();
336
+ $params['edd_action'] = $_GET['action'] === 'activate' ? 'activate_license' : 'deactivate_license';
337
+ $params['license'] = sanitize_text_field( $this->value['api_key'] );
338
+ $params['item_name'] = urlencode( $this->product );
339
+
340
+ $response = wp_remote_get( add_query_arg( $params, $this->store_url ), array( 'timeout' => 15, 'sslverify' => false ) );
341
+
342
+ if ( is_wp_error( $response ) ) {
343
+ $url = remove_query_arg( array( 'id', 'action' ) );
344
+ $url = add_query_arg( 'license_result', 'response_wp_error', $url );
345
+ header( 'Location: ' . esc_url_raw( $url ) );
346
+ }
347
+
348
+ $license_data = json_decode( wp_remote_retrieve_body( $response ) );
349
+
350
+
351
+ if ( $params['edd_action'] == 'activate' ) {
352
+ $result = $this->process_activation_response( $license_data );
353
+ } else {
354
+ $result = $this->process_activation_response( $license_data );
355
+ }
356
+
357
+ // Construct a URL to redirect back to the tab
358
+ $url = remove_query_arg( array( 'id', 'action' ) );
359
+ $url = add_query_arg(
360
+ array(
361
+ 'license_result' => $result ? 1 : 0,
362
+ 'action' => $_GET['action'] == 'activate' ? 'activate' : 'deactivate',
363
+ ),
364
+ $url
365
+ );
366
+
367
+ // If the result failed maybe add note on why
368
+ if ( !$result && !empty( $license_data->error ) ) {
369
+ $url = add_query_arg( 'result_error', $license_data->error, $url );
370
+ }
371
+
372
+ header( 'Location: ' . esc_url_raw( $url ) );
373
+
374
+ }
375
+
376
+ /**
377
+ * Process the response to an activation request
378
+ *
379
+ * @since 1.4.1
380
+ */
381
+ public function process_activation_response( $license_data ) {
382
+
383
+ if ( ( !empty( $license_data->error ) && ( $license_data->error == 'missing' || $license_data->error == 'item_name_mismatch' ) ) || $license_data->license == 'invalid' ) {
384
+ $this->value['status'] = 'invalid';
385
+ $this->value['expiry'] = false;
386
+ } else {
387
+ $this->value['status'] = $license_data->license;
388
+ $this->value['expiry'] = $license_data->expires;
389
+ }
390
+
391
+ $rtb_settings = get_option( $this->page );
392
+ $rtb_settings[ $this->id ] = $this->value;
393
+
394
+ update_option( $this->page, $rtb_settings );
395
+
396
+ return $license_data->license == 'valid' || $license_data->license == 'deactivated';
397
+ }
398
+
399
+ /**
400
+ * Process the response to an deactivation request
401
+ *
402
+ * @since 1.4.1
403
+ */
404
+ public function process_deactivation_response( $license_data ) {
405
+
406
+ if ( $license_data->license !== 'deactivated' ) {
407
+ return false;
408
+ } else {
409
+ $this->value['status'] = false;
410
+ $this->value['expiry'] = false;
411
+ }
412
+
413
+ $rtb_settings = get_option( $this->page );
414
+ $rtb_settings[ $this->id ] = $this->value;
415
+
416
+ update_option( $this->page, $rtb_settings );
417
+
418
+ return true;
419
+ }
420
+
421
+ }
422
+ } // endif;
includes/Ajax.class.php CHANGED
@@ -1,878 +1,878 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbAJAX' ) ) {
5
- /**
6
- * Class to handle AJAX date interactions for Restaurant Reservations
7
- *
8
- * @since 2.0.0
9
- */
10
- class rtbAJAX {
11
-
12
- /**
13
- * The location we're getting timeslots for, if specified
14
- * @since 2.3.6
15
- */
16
- public $location;
17
-
18
- /**
19
- * The year of the booking date we're getting timeslots for
20
- * @since 2.0.0
21
- */
22
- public $year;
23
-
24
- /**
25
- * The month of the booking date we're getting timeslots for
26
- * @since 2.0.0
27
- */
28
- public $month;
29
-
30
- /**
31
- * The day of the booking date we're getting timeslots for
32
- * @since 2.0.0
33
- */
34
- public $day;
35
-
36
- /**
37
- * The time of the booking we're getting timeslots for
38
- * @since 2.1.5
39
- */
40
- public $time;
41
-
42
- /**
43
- * The party size we're looking to find valid tables for
44
- * @since 2.1.7
45
- */
46
- public $party;
47
-
48
- public function __construct() {
49
-
50
- add_action( 'wp_ajax_rtb_get_available_time_slots', array( $this, 'get_time_slots' ) );
51
- add_action( 'wp_ajax_nopriv_rtb_get_available_time_slots', array( $this, 'get_time_slots' ) );
52
-
53
- add_action( 'wp_ajax_rtb_find_reservations', array( $this, 'get_reservations' ) );
54
- add_action( 'wp_ajax_nopriv_rtb_find_reservations', array( $this, 'get_reservations' ) );
55
-
56
- add_action( 'wp_ajax_rtb_cancel_reservations', array( $this, 'cancel_reservation' ), 10, 0 );
57
- add_action( 'wp_ajax_nopriv_rtb_cancel_reservations', array( $this, 'cancel_reservation' ), 10, 0 );
58
-
59
- add_action( 'wp_ajax_rtb_get_available_party_size', array( $this, 'get_available_party_size' ) );
60
- add_action( 'wp_ajax_nopriv_rtb_get_available_party_size', array( $this, 'get_available_party_size' ) );
61
-
62
- add_action( 'wp_ajax_rtb_get_available_tables', array( $this, 'get_available_tables' ) );
63
- add_action( 'wp_ajax_nopriv_rtb_get_available_tables', array( $this, 'get_available_tables' ) );
64
- }
65
-
66
- /**
67
- * Get reservations that are associated with the email address that was sent
68
- * @since 2.1.0
69
- */
70
- public function get_reservations() {
71
- global $wpdb, $rtb_controller;
72
-
73
- $email = isset($_POST['booking_email']) ? sanitize_email( $_POST['booking_email'] ) : '';
74
-
75
- if ( ! $email ) {
76
- wp_send_json_error(
77
- array(
78
- 'error' => 'noemail',
79
- 'msg' => __( 'The email you entered is not valid.', 'restaurant-reservations' ),
80
- )
81
- );
82
- }
83
-
84
- $booking_status_lbls = $rtb_controller->cpts->booking_statuses;
85
-
86
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
87
-
88
- $bookings = array();
89
- $booking_ids = $wpdb->get_results(
90
- $wpdb->prepare("
91
- SELECT `post_id` FROM `{$wpdb->postmeta}` WHERE `meta_key` = 'rtb' AND `meta_value` LIKE %s",
92
- '%' . sanitize_email( $email ) . '%'
93
- )
94
- );
95
-
96
- foreach ( $booking_ids as $booking_id ) {
97
- $booking = new rtbBooking();
98
- if ( $booking->load_post( $booking_id->post_id ) ) {
99
- $booking_date = (new DateTime($booking->date, wp_timezone()))->format('U');
100
- if ( in_array($booking->post_status, ['pending', 'payment_pending', 'payment_failed', 'confirmed'] ) and time() < $booking_date ) {
101
- $bookings[] = array(
102
- 'ID' => $booking->ID,
103
- 'email' => $booking->email,
104
- 'datetime' => $booking->format_date( $booking->date ),
105
- 'party' => $booking->party,
106
- 'status' => $booking->post_status,
107
- 'status_lbl' => $booking_status_lbls[$booking->post_status]['label']
108
- );
109
- }
110
- }
111
- }
112
-
113
- if ( ! empty($bookings) ) {
114
- wp_send_json_success(
115
- array(
116
- 'bookings' => $bookings
117
- )
118
- );
119
- }
120
- else {
121
- wp_send_json_error(
122
- array(
123
- 'error' => 'nobookings',
124
- 'msg' => __( 'No bookings were found for the email address you entered.', 'restaurant-reservations' ),
125
- )
126
- );
127
- }
128
-
129
- die();
130
- }
131
-
132
- /**
133
- * Cancel a reservation based on its ID, with the email address used for confirmation
134
- * @since 2.1.0
135
- */
136
- public function cancel_reservation( $ajax = true ) {
137
- global $rtb_controller;
138
-
139
- $cancelled_redirect = $rtb_controller->settings->get_setting( 'cancelled-redirect-page' );
140
-
141
- $booking_id = isset($_REQUEST['booking_id']) ? absint( $_REQUEST['booking_id'] ) : '';
142
- $booking_email = isset($_REQUEST['booking_email']) ? sanitize_email( $_REQUEST['booking_email'] ) : '';
143
-
144
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
145
-
146
- $success = false;
147
-
148
- $booking = new rtbBooking();
149
- if ( $booking->load_post( $booking_id ) ) {
150
- if ( $booking_email == $booking->email ) {
151
- wp_update_post( array( 'ID' => $booking->ID, 'post_status' => 'cancelled' ) );
152
-
153
- $success = true;
154
- }
155
- else {
156
- wp_send_json_error(
157
- array(
158
- 'error' => 'invalidemail',
159
- 'msg' => __( 'No booking matches the information that was sent.', 'restaurant-reservations' ),
160
- )
161
- );
162
- }
163
- }
164
- else {
165
- wp_send_json_error(
166
- array(
167
- 'error' => 'invalidid',
168
- 'msg' => __( 'No booking matches the information that was sent.', 'restaurant-reservations' ),
169
- )
170
- );
171
- }
172
-
173
- if ( $ajax ) {
174
- if ( $success ) {
175
-
176
- $response = array( 'booking_id' => $booking_id );
177
-
178
- if( '' != $cancelled_redirect ) {
179
- $response['cancelled_redirect'] = $cancelled_redirect;
180
- }
181
-
182
- wp_send_json_success( $response );
183
- }
184
- else {
185
- wp_send_json_error(
186
- array(
187
- 'error' => 'unknown',
188
- 'msg' => __( 'Unkown error. Please try again', 'restaurant-reservations' ),
189
- )
190
- );
191
- }
192
- }
193
- else {
194
- $redirect_url = '';
195
-
196
- if( '' != $cancelled_redirect && $success ) {
197
- $redirect_url = $cancelled_redirect;
198
- }
199
- else {
200
- $booking_page_id = $rtb_controller->settings->get_setting( 'booking-page' );
201
- $booking_page_url = get_permalink( $booking_page_id );
202
-
203
- $redirect_url = add_query_arg(
204
- array(
205
- 'bookingCancelled' => $success ? 'success' : 'fail'
206
- ),
207
- $booking_page_url
208
- );
209
- }
210
-
211
- header( 'location:' . $redirect_url );
212
- }
213
- }
214
-
215
- /**
216
- * Get available timeslots when "Max Reservations" or "Max People" is enabled
217
- * @since 2.0.0
218
- */
219
- public function get_time_slots() {
220
- global $rtb_controller;
221
-
222
- $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables' );
223
-
224
- // proessing request for this date
225
- $this->location = ! empty( $_POST['location'] ) ? get_term( intval( $_POST['location'] ) ) : false;
226
-
227
- $this->year = sanitize_text_field( $_POST['year'] );
228
- $this->month = sanitize_text_field( $_POST['month'] );
229
- $this->day = sanitize_text_field( $_POST['day'] );
230
-
231
- $finalize_response = function ( $open_close_pair_list = array() ) {
232
-
233
- $valid_times = [];
234
-
235
- if ( ! empty( $open_close_pair_list ) ) {
236
-
237
- foreach ( $open_close_pair_list as $pair ) {
238
-
239
- $valid_times[] = array(
240
- 'from' => $this->format_pickadate_time( $pair['from'] ),
241
- 'to' => $this->format_pickadate_time( $pair['to'] ),
242
- 'inverted' => true
243
- );
244
- }
245
- }
246
-
247
- echo json_encode( $valid_times );
248
-
249
- die();
250
- };
251
-
252
- // Get opening/closing times for this particular day
253
- $hours = $this->get_opening_hours();
254
-
255
- // If the restaurant is closed that day
256
- // If Enable Max Reservation not set
257
- if ( ! $hours || ! $max_reservations_enabled ) {
258
- $finalize_response( $hours );
259
- }
260
-
261
- $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
262
-
263
- $interval = $rtb_controller->settings->get_setting( 'time-interval' ) * 60;
264
-
265
- $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60;
266
-
267
- $min_party_size = (int) $rtb_controller->settings->get_setting( 'party-size-min' );
268
-
269
- $max_reservations = (int) $rtb_controller->settings->get_setting( 'rtb-max-tables-count', $location_slug );
270
-
271
- $max_people = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
272
-
273
- $all_possible_slots = [];
274
- foreach ( $hours as $pair ) {
275
- $all_possible_slots[] = $pair['from'];
276
- $next = $pair['from'] + $interval;
277
- while ( $next <= $pair['to'] ) {
278
- $all_possible_slots[] = $next;
279
- $next += $interval;
280
- }
281
- }
282
-
283
- // Get all current bookings sorted by date
284
- $args = array(
285
- 'posts_per_page' => -1,
286
- 'date_range' => 'dates',
287
- 'start_date' => $this->year . '-' . $this->month . '-' . $this->day,
288
- 'end_date' => $this->year . '-' . $this->month . '-' . $this->day,
289
- 'post_status' => ['pending', 'payment_pending', 'confirmed', 'arrived']
290
- );
291
-
292
- // If there are multiple locations, a location is selected, and
293
- // max reservations and/or seats has been enabled for this specific location
294
- if ( ! empty( $location_slug ) and ( $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-tables-count', $location_slug ) or $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) ) {
295
-
296
- $tax_query = array(
297
- array(
298
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
299
- 'field' => 'term_id',
300
- 'terms' => $this->location->term_id
301
- )
302
- );
303
-
304
- $args['tax_query'] = $tax_query;
305
- }
306
-
307
- $query = new rtbQuery( $args );
308
- $query->prepare_args();
309
- $bookings = $query->get_bookings();
310
-
311
- // This array holds bookings for all slots by expanding the booking by
312
- // dining block length to help finding the overlapped bookings for time-slots
313
- $all_bookings_by_slots = [];
314
- foreach ( $bookings as $key => $booking ) {
315
- // Convert booking date to seconds from UNIX
316
- $booking_time = strtotime($booking->date);
317
- if( ! array_key_exists( $booking_time, $all_bookings_by_slots ) ) {
318
- $all_bookings_by_slots[$booking_time] = [
319
- 'total_bookings' => 0,
320
- 'total_guest' => 0,
321
- 'overlapped' => false
322
- ];
323
- }
324
- $all_bookings_by_slots[$booking_time]['total_bookings']++;
325
- $all_bookings_by_slots[$booking_time]['total_guest'] += intval( $booking->party );
326
-
327
- /**
328
- * Expanding bookings
329
- * Example: If I have someone booked at 1pm who will be in the restaurant for 120 minutes,
330
- * that means they will be in the restaurant until 3pm. There is another booking at 2pm.
331
- * That means, from 2pm to 3pm, there are already two separate reservations in the restaurant.
332
- */
333
- $end = $booking_time + $dining_block_seconds;
334
- $next = $booking_time + $interval;
335
- while($next < $end) {
336
- if( ! array_key_exists( $next, $all_bookings_by_slots ) ) {
337
- $all_bookings_by_slots[$next] = [
338
- 'total_bookings' => 0,
339
- 'total_guest' => 0,
340
- 'overlapped' => false
341
- ];
342
- }
343
- $all_bookings_by_slots[$next]['overlapped'] = true;
344
- $all_bookings_by_slots[$next]['total_bookings']++;
345
- $all_bookings_by_slots[$next]['total_guest'] += intval( $booking->party );
346
- $next += $interval;
347
- }
348
- }
349
-
350
- $all_blocked_slots = [];
351
-
352
- // Go through all bookings and figure out when we're at or above the
353
- // max reservation or max people and mark that slot as blocked
354
- if ( isset( $max_reservations ) and $max_reservations > 0 ) {
355
- foreach ( $all_bookings_by_slots as $slot => $data ) {
356
- if( $max_reservations <= $data['total_bookings'] ) {
357
- $all_blocked_slots[] = $slot;
358
- }
359
- }
360
- }
361
- else if ( isset( $max_people ) and $max_people > 0 ) {
362
- /**
363
- * min_party_size = 10, max_people = 100, 6 bookings of total 91 guests
364
- * Now, if anybody wants to book for at least 10 people, it is not possible
365
- * because the total will surpass the max_people (100)
366
- * thus reducing min_party_size from max_people
367
- *
368
- * $max_people can be zero when min_party_size is same as max_people
369
- */
370
- $max_people = $max_people - $min_party_size;
371
-
372
- foreach ( $all_bookings_by_slots as $slot => $data ) {
373
- if( $max_people < $data['total_guest'] ) {
374
- $all_blocked_slots[] = $slot;
375
- }
376
- }
377
- }
378
-
379
- // Mark slots unavailable, due to dinning block length
380
- $additional_blocked_slots = [];
381
- foreach ($all_blocked_slots as $slot) {
382
- // blocking before this slot
383
- $begin = $slot - $dining_block_seconds;
384
- /**
385
- * interval 30 minutes, dinning_length 120 minutes, slot 10:00am
386
- * additional blockings before shall be 8:30am,9:00am and 9:30am
387
- * thus skipping 8:00am which is valid
388
- *
389
- * @var unix timestamp
390
- */
391
- $next = $begin + $interval;
392
- while($next < $slot) {
393
- $additional_blocked_slots[] = $next;
394
- $next += $interval;
395
- }
396
-
397
- // block after this slot only when this slot is not overlapped
398
- // Overlapped slots should block only backwards, but not afterward
399
- if( $all_bookings_by_slots[$slot]['overlapped'] ) {
400
- continue;
401
- }
402
-
403
- // blocking after this slot
404
- $end = $slot + $dining_block_seconds;
405
- /**
406
- * interval 30 minutes, dinning_length 120 minutes, slot 10:00am
407
- * additional blockings after shall be 10:30am,11:00am and 1130am
408
- * thus skipping 12:00pm which is valid
409
- *
410
- * @var unix timestamp
411
- */
412
- $next = $slot + $interval;
413
- while($next < $end) {
414
- $additional_blocked_slots[] = $next;
415
- $next += $interval;
416
- }
417
- }
418
-
419
- $all_blocked_slots = array_unique(
420
- array_merge( $all_blocked_slots, $additional_blocked_slots ),
421
- SORT_NUMERIC
422
- );
423
-
424
- sort( $all_blocked_slots, SORT_NUMERIC );
425
-
426
- // remove blocked slots from available slots
427
- $available_slots = array_diff( $all_possible_slots, $all_blocked_slots );
428
- sort( $available_slots, SORT_NUMERIC );
429
-
430
- // consolidating timeslots to timeframes
431
- $timeframe = [];
432
- $available_slots_count = count( $available_slots );
433
- if( 0 < $available_slots_count ) {
434
-
435
- $current_pair = [ 'from' => $available_slots[ 0 ] ];
436
-
437
- for ( $i = 1; $i < $available_slots_count; $i++) {
438
- if( $available_slots[ $i ] - $available_slots[ $i - 1 ] !== $interval ) {
439
- $current_pair[ 'to' ] = $available_slots[ $i - 1 ];
440
- $timeframe[] = $current_pair;
441
-
442
- $current_pair = [ 'from' => $available_slots[ $i ] ];
443
- }
444
- }
445
-
446
- $current_pair[ 'to' ] = $available_slots[ $i - 1 ];
447
- $timeframe[] = $current_pair;
448
- }
449
-
450
- $finalize_response( $timeframe );
451
- }
452
-
453
- public function get_opening_hours() {
454
- global $rtb_controller;
455
-
456
- $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
457
-
458
- $schedule_closed = $rtb_controller->settings->get_setting( 'schedule-closed', $location_slug );
459
- $schedule_closed = is_array( $schedule_closed ) ? $schedule_closed : array();
460
-
461
- $valid_times = array();
462
-
463
- // Check if this date is an exception to the rules
464
- if ( $schedule_closed !== 'undefined' ) {
465
-
466
- foreach ( $schedule_closed as $closing ) {
467
- $time = strtotime( $closing['date'] );
468
-
469
- if ( date( 'Y', $time ) == $this->year &&
470
- date( 'm', $time ) == $this->month &&
471
- date( 'd', $time ) == $this->day
472
- ) {
473
-
474
- // Closed all day
475
- if ( ! isset( $closing['time'] ) || $closing['time'] == 'undefined' ) {
476
- return false;
477
- }
478
-
479
- if ( $closing['time']['start'] !== 'undefined' ) {
480
- $open_time = strtotime( $closing['date'] . ' ' . $closing['time']['start'] );
481
- } else {
482
- $open_time = strtotime( $closing['date'] ); // Start of the day
483
- }
484
-
485
- if ( $closing['time']['end'] !== 'undefined' ) {
486
- $close_time = strtotime( $closing['date'] . ' ' . $closing['time']['end'] );
487
- } else {
488
- $close_time = strtotime( $closing['date'] . ' 23:59:59' ); // End of the day
489
- }
490
-
491
- $open_time = $this->get_earliest_time( $open_time );
492
-
493
- if ( $open_time <= $close_time ) {
494
- $valid_times[] = ['from' => $open_time, 'to' => $close_time];
495
- }
496
- }
497
- }
498
-
499
- // Exit early if this date is an exception
500
- if ( isset( $open_time ) ) {
501
- return $valid_times;
502
- }
503
- }
504
-
505
- $schedule_open = $rtb_controller->settings->get_setting( 'schedule-open', $location_slug );
506
- $schedule_open = is_array( $schedule_open ) ? $schedule_open : array();
507
-
508
- // Get any rules which apply to this weekday
509
- $day_of_week = strtolower(
510
- date( 'l', strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' 1:00:00' ) )
511
- );
512
-
513
- foreach ( $schedule_open as $opening ) {
514
-
515
- if ( $opening['weekdays'] !== 'undefined' ) {
516
-
517
- foreach ( $opening['weekdays'] as $weekday => $value ) {
518
-
519
- if ( $weekday == $day_of_week ) {
520
-
521
- // Closed all day
522
- if ( $opening['time'] == 'undefined' ) {
523
-
524
- return false;
525
- }
526
-
527
- if ( $opening['time']['start'] !== 'undefined' ) {
528
-
529
- $open_time = strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' ' . $opening['time']['start'] );
530
- }
531
- else {
532
-
533
- $open_time = strtotime( $this->year . '-' . $this->month . '-' . $this->day );
534
- }
535
-
536
- if ( $opening['time']['end'] !== 'undefined' ) {
537
-
538
- $close_time = strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' ' . $opening['time']['end'] );
539
- }
540
- else {
541
-
542
- // End of the day
543
- $close_time = strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' 23:59:59' );
544
- }
545
-
546
- $open_time = $this->get_earliest_time( $open_time );
547
-
548
- if ( $open_time <= $close_time ) {
549
-
550
- $valid_times[] = ['from' => $open_time, 'to' => $close_time];
551
- }
552
- }
553
- }
554
- }
555
- }
556
-
557
- // Pass any valid times located
558
- if ( sizeOf( $valid_times ) >= 1 ) {
559
-
560
- return $valid_times;
561
- }
562
-
563
- return false;
564
- }
565
-
566
- public function get_earliest_time( $open_time ) {
567
- global $rtb_controller;
568
-
569
- $interval = $rtb_controller->settings->get_setting( 'time-interval' ) * 60;
570
-
571
- $timezone = wp_timezone();
572
- $offset = $timezone->getOffset( new DateTime );
573
-
574
- // adjust open time with respect to the current time of the day for upcoming timeslots
575
- $current_time = time() + $offset;
576
-
577
- // Only make adjustments for current day selections
578
- if ( date( 'y-m-d', strtotime( "{$this->year}-{$this->month}-{$this->day}" ) ) !== date( 'y-m-d', $current_time) ) {
579
- return $open_time;
580
- }
581
-
582
- $late_bookings = ( is_admin() && current_user_can( 'manage_bookings' ) ) ? '' : $rtb_controller->settings->get_setting( 'late-bookings' );
583
-
584
- if( $current_time > $open_time ) {
585
- while( $current_time > $open_time ) {
586
- $open_time += $interval;
587
- }
588
- }
589
-
590
- // adjust the open time for the Late Bookings option
591
- if ( is_numeric($late_bookings) && $late_bookings % 1 === 0 ) {
592
- $time_calc = time() + $offset + $late_bookings * 60;
593
- while ($time_calc > $open_time) {
594
- $open_time = $open_time + $interval;
595
- }
596
- }
597
-
598
- return $open_time;
599
- }
600
-
601
- /**
602
- * Get number of seats remaining avilable to be booked
603
- * @since 2.1.5
604
- */
605
- public function get_available_party_size() {
606
- global $rtb_controller;
607
-
608
- $this->location = ! empty( $_POST['location'] ) ? get_term( intval( $_POST['location'] ) ) : false;
609
- $this->year = sanitize_text_field( $_POST['year'] );
610
- $this->month = sanitize_text_field( $_POST['month'] );
611
- $this->day = sanitize_text_field( $_POST['day'] );
612
- $this->time = sanitize_text_field( $_POST['time'] );
613
-
614
- $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
615
-
616
- $max_people = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
617
-
618
- $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' )* 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
619
-
620
- // Get opening/closing times for this particular day
621
- $hours = $this->get_opening_hours();
622
-
623
- // If the restaurant is closed that day, return false
624
- if ( ! $hours ) { die(); }
625
-
626
- // If no time is selected, return false
627
- if ( ! $this->time ) { die(); }
628
-
629
- $args = array(
630
- 'posts_per_page' => -1,
631
- 'date_range' => 'dates',
632
- 'start_date' => $this->year . '-' . $this->month . '-' . $this->day,
633
- 'end_date' => $this->year . '-' . $this->month . '-' . $this->day
634
- );
635
-
636
- // If there are multiple locations, a location is selected, and
637
- // max seats has been enabled for this specific location
638
- if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) {
639
-
640
- $tax_query = array(
641
- array(
642
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
643
- 'field' => 'term_id',
644
- 'terms' => $this->location->term_id
645
- )
646
- );
647
-
648
- $args['tax_query'] = $tax_query;
649
- }
650
-
651
- $query = new rtbQuery( $args );
652
- $query->prepare_args();
653
-
654
- // Get all current bookings sorted by date
655
- $bookings = $query->get_bookings();
656
-
657
- $selected_date_time = strtotime($this->year . '-' . $this->month . '-' . $this->day . ' ' . $this->time);
658
- $selected_date_time_start = $selected_date_time - $dining_block_seconds;
659
- $selected_date_time_end = $selected_date_time + $dining_block_seconds;
660
- $party_sizes = [];
661
-
662
- if ($max_people != 'undefined' and $max_people != 0) {
663
-
664
- $max_time_size = 0;
665
- $current_times = array();
666
- $party_sizes = array();
667
-
668
- // Go through all current booking and collect the total party size
669
- foreach ( $bookings as $key => $booking ) {
670
-
671
- // Convert booking date to seconds from UNIX
672
- $booking_time = strtotime($booking->date);
673
-
674
- // Ignore bookings outside of our time range
675
- if ($booking_time < $selected_date_time_start or $booking_time > $selected_date_time_end) { continue; }
676
-
677
- $current_times[] = $booking_time;
678
- $party_sizes[] = (int) $booking->party;
679
-
680
- while ( sizeOf( $current_times ) > 0 and reset( $current_times ) < $booking_time - $dining_block_seconds ) {
681
- //save the time to know when the blocking potentially ends
682
- $removed_time = reset( $current_times );
683
-
684
- // remove the expired time and party size
685
- array_shift( $current_times );
686
- array_shift( $party_sizes );
687
- }
688
-
689
- $max_time_size = max( $max_time_size, array_sum( $party_sizes ) );
690
- }
691
-
692
- $response = (object) array( 'available_spots' => $max_people - $max_time_size);
693
-
694
- echo json_encode($response);
695
-
696
- die();
697
- } else {
698
- return false;
699
- }
700
- }
701
-
702
- /**
703
- * Get tables available to be booked at a specific time and party size
704
- * @since 2.1.7
705
- */
706
- public function get_available_tables() {
707
- global $rtb_controller;
708
-
709
- $tables = $rtb_controller->settings->get_sorted_tables();
710
-
711
- $this->booking_id = isset( $_POST['booking_id'] ) ? intval( $_POST['booking_id'] ) : 0;
712
- $this->year = isset( $_POST['year'] ) ? sanitize_text_field( $_POST['year'] ) : false;
713
- $this->month = isset( $_POST['month'] ) ? sanitize_text_field( $_POST['month'] ) : false;
714
- $this->day = isset( $_POST['day'] ) ? sanitize_text_field( $_POST['day'] ) : false;
715
- $this->time = isset( $_POST['time'] ) ? sanitize_text_field( $_POST['time'] ) : false;
716
- $this->party = isset( $_POST['party'] ) ? sanitize_text_field( $_POST['party'] ) : false;
717
-
718
- /*$this->year = 2020;
719
- $this->month = 06;
720
- $this->day = 12;
721
- $this->time = '02:15 PM';
722
- $this->party = 12;*/
723
-
724
- if ( ! isset( $this->year ) or ! isset( $this->month ) or ! isset( $this->day ) or ! isset( $this->time ) ) { return false; }
725
-
726
- $datetime = strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' ' . $this->time );
727
-
728
- $valid_tables = rtb_get_valid_tables( $datetime );
729
-
730
- if ( $this->booking_id ) {
731
-
732
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
733
-
734
- $current_booking = new rtbBooking();
735
- $current_booking->load_post( $this->booking_id );
736
-
737
- if ( $current_booking->table ) { $valid_tables = array_merge( $valid_tables, $current_booking->table ); }
738
- }
739
-
740
- if ( isset( $this->party ) ) {
741
-
742
- $possible_combinations = array();
743
- foreach ( $valid_tables as $valid_table ) {
744
-
745
- // If the party size is between the min and max for the table, great
746
- if ( $tables[ $valid_table ]->min_people <= $this->party and $tables[ $valid_table ]->max_people >= $this->party ) {
747
-
748
- $possible_combinations[] = $valid_table;
749
- }
750
- // If the party is above the minimum for the table, look to see if combinations could work
751
- elseif ( $tables[ $valid_table ]->min_people <= $this->party ) {
752
-
753
- $combination = $this->get_combinations_chain( $tables, $valid_tables, $valid_table, $tables[ $valid_table ]->max_people, $this->party );
754
-
755
- if ( $combination ) {
756
- $possible_combinations[] = $combination;
757
- }
758
- }
759
-
760
- $return_tables = $this->format_tables( $possible_combinations );
761
- }
762
- }
763
- else {
764
- $return_tables = $this->format_tables( $valid_tables );
765
- }
766
-
767
- $selected_table = ( isset( $current_booking ) and $current_booking->table ) ? implode(',', $current_booking->table ) : -1;
768
-
769
- $response = (object) array( 'available_tables' => $return_tables, 'selected_table' => $selected_table );
770
-
771
- echo json_encode($response);
772
-
773
- die();
774
- }
775
-
776
- /**
777
- * Recursively go through table combinations to find one that has enough seats
778
- * @since 2.1.7
779
- */
780
- public function get_combinations_chain(
781
- $tables,
782
- $valid_tables,
783
- $current_table,
784
- $current_size,
785
- $needed_size
786
- ) {
787
- $table_chain[] = $current_table;
788
-
789
- // No combination specified
790
- if ( ! $tables[ $current_table ]->combinations ) {
791
- return false;
792
- }
793
-
794
- $possible_tables = explode( ',', $tables[ $current_table ]->combinations );
795
-
796
- foreach ( $possible_tables as $possible_table ) {
797
-
798
- // If the table has already been booked, continue
799
- if ( !in_array( $possible_table, $valid_tables) ) {
800
- continue;
801
- }
802
-
803
- // If the table can hold the group on its own, continue
804
- if ( $tables[ $possible_table ]->max_people >= $needed_size ) {
805
- continue;
806
- }
807
-
808
- $current_size += $tables[ $possible_table ]->max_people;
809
- $table_chain[] = $possible_table;
810
-
811
- if ( $current_size >= $needed_size ) {
812
- return implode(',', $table_chain);
813
- }
814
- }
815
-
816
- //no viable combination found
817
- return false;
818
- }
819
-
820
- /**
821
- * Format the tables available to be booked as number(s)_string => human_value pairs
822
- * @since 2.1.7
823
- */
824
- public function format_tables ( $table_numbers ) {
825
- global $rtb_controller;
826
-
827
- $formatted_tables = array();
828
-
829
- $tables = json_decode( html_entity_decode( $rtb_controller->settings->get_setting( 'rtb-tables' ) ) );
830
- $tables = is_array( $tables ) ? $tables : array();
831
-
832
- foreach ( $table_numbers as $table_number ) {
833
-
834
- $table_parts = explode( ',', $table_number );
835
-
836
- $table_values = array(
837
- 'numbers' => '',
838
- 'min_people' => 0,
839
- 'max_people' => 0
840
- );
841
-
842
- foreach ( $tables as $table ) {
843
- if ( in_array($table->number, $table_parts) ) {
844
- $table_values['numbers'] .= ( strlen( $table_values['numbers'] ) ? ', ' : '' ) . $table->number;
845
- $table_values['min_people'] += $table->min_people;
846
- $table_values['max_people'] += $table->max_people;
847
-
848
- if ( ! isset( $section_name ) ) { $section_name = $this->get_section_name( $table->section ); }
849
- }
850
- }
851
-
852
- $formatted_tables[ $table_values['numbers'] ] = $table_values['numbers'] . ' - ' . $section_name . ' (min. ' . $table_values['min_people'] . '/max. ' . $table_values['max_people'] . ')';
853
-
854
- unset( $section_name );
855
- }
856
-
857
- return $formatted_tables;
858
- }
859
-
860
- public function get_section_name( $section_id ) {
861
- global $rtb_controller;
862
-
863
- $sections = json_decode( html_entity_decode( $rtb_controller->settings->get_setting( 'rtb-table-sections' ) ) );
864
- $sections = is_array( $sections ) ? $sections : array();
865
-
866
- foreach ( $sections as $section ) {
867
-
868
- if ( $section->section_id == $section_id ) { return $section->name; }
869
- }
870
-
871
- return false;
872
- }
873
-
874
- public function format_pickadate_time( $time ) {
875
- return array( date( 'G', $time ), date( 'i', $time ) );
876
- }
877
- }
878
  }
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbAJAX' ) ) {
5
+ /**
6
+ * Class to handle AJAX date interactions for Restaurant Reservations
7
+ *
8
+ * @since 2.0.0
9
+ */
10
+ class rtbAJAX {
11
+
12
+ /**
13
+ * The location we're getting timeslots for, if specified
14
+ * @since 2.3.6
15
+ */
16
+ public $location;
17
+
18
+ /**
19
+ * The year of the booking date we're getting timeslots for
20
+ * @since 2.0.0
21
+ */
22
+ public $year;
23
+
24
+ /**
25
+ * The month of the booking date we're getting timeslots for
26
+ * @since 2.0.0
27
+ */
28
+ public $month;
29
+
30
+ /**
31
+ * The day of the booking date we're getting timeslots for
32
+ * @since 2.0.0
33
+ */
34
+ public $day;
35
+
36
+ /**
37
+ * The time of the booking we're getting timeslots for
38
+ * @since 2.1.5
39
+ */
40
+ public $time;
41
+
42
+ /**
43
+ * The party size we're looking to find valid tables for
44
+ * @since 2.1.7
45
+ */
46
+ public $party;
47
+
48
+ public function __construct() {
49
+
50
+ add_action( 'wp_ajax_rtb_get_available_time_slots', array( $this, 'get_time_slots' ) );
51
+ add_action( 'wp_ajax_nopriv_rtb_get_available_time_slots', array( $this, 'get_time_slots' ) );
52
+
53
+ add_action( 'wp_ajax_rtb_find_reservations', array( $this, 'get_reservations' ) );
54
+ add_action( 'wp_ajax_nopriv_rtb_find_reservations', array( $this, 'get_reservations' ) );
55
+
56
+ add_action( 'wp_ajax_rtb_cancel_reservations', array( $this, 'cancel_reservation' ), 10, 0 );
57
+ add_action( 'wp_ajax_nopriv_rtb_cancel_reservations', array( $this, 'cancel_reservation' ), 10, 0 );
58
+
59
+ add_action( 'wp_ajax_rtb_get_available_party_size', array( $this, 'get_available_party_size' ) );
60
+ add_action( 'wp_ajax_nopriv_rtb_get_available_party_size', array( $this, 'get_available_party_size' ) );
61
+
62
+ add_action( 'wp_ajax_rtb_get_available_tables', array( $this, 'get_available_tables' ) );
63
+ add_action( 'wp_ajax_nopriv_rtb_get_available_tables', array( $this, 'get_available_tables' ) );
64
+ }
65
+
66
+ /**
67
+ * Get reservations that are associated with the email address that was sent
68
+ * @since 2.1.0
69
+ */
70
+ public function get_reservations() {
71
+ global $wpdb, $rtb_controller;
72
+
73
+ $email = isset($_POST['booking_email']) ? sanitize_email( $_POST['booking_email'] ) : '';
74
+
75
+ if ( ! $email ) {
76
+ wp_send_json_error(
77
+ array(
78
+ 'error' => 'noemail',
79
+ 'msg' => __( 'The email you entered is not valid.', 'restaurant-reservations' ),
80
+ )
81
+ );
82
+ }
83
+
84
+ $booking_status_lbls = $rtb_controller->cpts->booking_statuses;
85
+
86
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
87
+
88
+ $bookings = array();
89
+ $booking_ids = $wpdb->get_results(
90
+ $wpdb->prepare("
91
+ SELECT `post_id` FROM `{$wpdb->postmeta}` WHERE `meta_key` = 'rtb' AND `meta_value` LIKE %s",
92
+ '%' . sanitize_email( $email ) . '%'
93
+ )
94
+ );
95
+
96
+ foreach ( $booking_ids as $booking_id ) {
97
+ $booking = new rtbBooking();
98
+ if ( $booking->load_post( $booking_id->post_id ) ) {
99
+ $booking_date = (new DateTime($booking->date, wp_timezone()))->format('U');
100
+ if ( in_array($booking->post_status, ['pending', 'payment_pending', 'payment_failed', 'confirmed'] ) and time() < $booking_date ) {
101
+ $bookings[] = array(
102
+ 'ID' => $booking->ID,
103
+ 'email' => $booking->email,
104
+ 'datetime' => $booking->format_date( $booking->date ),
105
+ 'party' => $booking->party,
106
+ 'status' => $booking->post_status,
107
+ 'status_lbl' => $booking_status_lbls[$booking->post_status]['label']
108
+ );
109
+ }
110
+ }
111
+ }
112
+
113
+ if ( ! empty($bookings) ) {
114
+ wp_send_json_success(
115
+ array(
116
+ 'bookings' => $bookings
117
+ )
118
+ );
119
+ }
120
+ else {
121
+ wp_send_json_error(
122
+ array(
123
+ 'error' => 'nobookings',
124
+ 'msg' => __( 'No bookings were found for the email address you entered.', 'restaurant-reservations' ),
125
+ )
126
+ );
127
+ }
128
+
129
+ die();
130
+ }
131
+
132
+ /**
133
+ * Cancel a reservation based on its ID, with the email address used for confirmation
134
+ * @since 2.1.0
135
+ */
136
+ public function cancel_reservation( $ajax = true ) {
137
+ global $rtb_controller;
138
+
139
+ $cancelled_redirect = $rtb_controller->settings->get_setting( 'cancelled-redirect-page' );
140
+
141
+ $booking_id = isset($_REQUEST['booking_id']) ? absint( $_REQUEST['booking_id'] ) : '';
142
+ $booking_email = isset($_REQUEST['booking_email']) ? sanitize_email( $_REQUEST['booking_email'] ) : '';
143
+
144
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
145
+
146
+ $success = false;
147
+
148
+ $booking = new rtbBooking();
149
+ if ( $booking->load_post( $booking_id ) ) {
150
+ if ( $booking_email == $booking->email ) {
151
+ wp_update_post( array( 'ID' => $booking->ID, 'post_status' => 'cancelled' ) );
152
+
153
+ $success = true;
154
+ }
155
+ else {
156
+ wp_send_json_error(
157
+ array(
158
+ 'error' => 'invalidemail',
159
+ 'msg' => __( 'No booking matches the information that was sent.', 'restaurant-reservations' ),
160
+ )
161
+ );
162
+ }
163
+ }
164
+ else {
165
+ wp_send_json_error(
166
+ array(
167
+ 'error' => 'invalidid',
168
+ 'msg' => __( 'No booking matches the information that was sent.', 'restaurant-reservations' ),
169
+ )
170
+ );
171
+ }
172
+
173
+ if ( $ajax ) {
174
+ if ( $success ) {
175
+
176
+ $response = array( 'booking_id' => $booking_id );
177
+
178
+ if( '' != $cancelled_redirect ) {
179
+ $response['cancelled_redirect'] = $cancelled_redirect;
180
+ }
181
+
182
+ wp_send_json_success( $response );
183
+ }
184
+ else {
185
+ wp_send_json_error(
186
+ array(
187
+ 'error' => 'unknown',
188
+ 'msg' => __( 'Unkown error. Please try again', 'restaurant-reservations' ),
189
+ )
190
+ );
191
+ }
192
+ }
193
+ else {
194
+ $redirect_url = '';
195
+
196
+ if( '' != $cancelled_redirect && $success ) {
197
+ $redirect_url = $cancelled_redirect;
198
+ }
199
+ else {
200
+ $booking_page_id = $rtb_controller->settings->get_setting( 'booking-page' );
201
+ $booking_page_url = get_permalink( $booking_page_id );
202
+
203
+ $redirect_url = add_query_arg(
204
+ array(
205
+ 'bookingCancelled' => $success ? 'success' : 'fail'
206
+ ),
207
+ $booking_page_url
208
+ );
209
+ }
210
+
211
+ header( 'location:' . $redirect_url );
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Get available timeslots when "Max Reservations" or "Max People" is enabled
217
+ * @since 2.0.0
218
+ */
219
+ public function get_time_slots() {
220
+ global $rtb_controller;
221
+
222
+ $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables' );
223
+
224
+ // proessing request for this date
225
+ $this->location = ! empty( $_POST['location'] ) ? get_term( intval( $_POST['location'] ) ) : false;
226
+
227
+ $this->year = sanitize_text_field( $_POST['year'] );
228
+ $this->month = sanitize_text_field( $_POST['month'] );
229
+ $this->day = sanitize_text_field( $_POST['day'] );
230
+
231
+ $finalize_response = function ( $open_close_pair_list = array() ) {
232
+
233
+ $valid_times = [];
234
+
235
+ if ( ! empty( $open_close_pair_list ) ) {
236
+
237
+ foreach ( $open_close_pair_list as $pair ) {
238
+
239
+ $valid_times[] = array(
240
+ 'from' => $this->format_pickadate_time( $pair['from'] ),
241
+ 'to' => $this->format_pickadate_time( $pair['to'] ),
242
+ 'inverted' => true
243
+ );
244
+ }
245
+ }
246
+
247
+ echo json_encode( $valid_times );
248
+
249
+ die();
250
+ };
251
+
252
+ // Get opening/closing times for this particular day
253
+ $hours = $this->get_opening_hours();
254
+
255
+ // If the restaurant is closed that day
256
+ // If Enable Max Reservation not set
257
+ if ( ! $hours || ! $max_reservations_enabled ) {
258
+ $finalize_response( $hours );
259
+ }
260
+
261
+ $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
262
+
263
+ $interval = $rtb_controller->settings->get_setting( 'time-interval' ) * 60;
264
+
265
+ $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60;
266
+
267
+ $min_party_size = (int) $rtb_controller->settings->get_setting( 'party-size-min' );
268
+
269
+ $max_reservations = (int) $rtb_controller->settings->get_setting( 'rtb-max-tables-count', $location_slug );
270
+
271
+ $max_people = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
272
+
273
+ $all_possible_slots = [];
274
+ foreach ( $hours as $pair ) {
275
+ $all_possible_slots[] = $pair['from'];
276
+ $next = $pair['from'] + $interval;
277
+ while ( $next <= $pair['to'] ) {
278
+ $all_possible_slots[] = $next;
279
+ $next += $interval;
280
+ }
281
+ }
282
+
283
+ // Get all current bookings sorted by date
284
+ $args = array(
285
+ 'posts_per_page' => -1,
286
+ 'date_range' => 'dates',
287
+ 'start_date' => $this->year . '-' . $this->month . '-' . $this->day,
288
+ 'end_date' => $this->year . '-' . $this->month . '-' . $this->day,
289
+ 'post_status' => ['pending', 'payment_pending', 'confirmed', 'arrived']
290
+ );
291
+
292
+ // If there are multiple locations, a location is selected, and
293
+ // max reservations and/or seats has been enabled for this specific location
294
+ if ( ! empty( $location_slug ) and ( $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-tables-count', $location_slug ) or $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) ) {
295
+
296
+ $tax_query = array(
297
+ array(
298
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
299
+ 'field' => 'term_id',
300
+ 'terms' => $this->location->term_id
301
+ )
302
+ );
303
+
304
+ $args['tax_query'] = $tax_query;
305
+ }
306
+
307
+ $query = new rtbQuery( $args );
308
+ $query->prepare_args();
309
+ $bookings = $query->get_bookings();
310
+
311
+ // This array holds bookings for all slots by expanding the booking by
312
+ // dining block length to help finding the overlapped bookings for time-slots
313
+ $all_bookings_by_slots = [];
314
+ foreach ( $bookings as $key => $booking ) {
315
+ // Convert booking date to seconds from UNIX
316
+ $booking_time = strtotime($booking->date);
317
+ if( ! array_key_exists( $booking_time, $all_bookings_by_slots ) ) {
318
+ $all_bookings_by_slots[$booking_time] = [
319
+ 'total_bookings' => 0,
320
+ 'total_guest' => 0,
321
+ 'overlapped' => false
322
+ ];
323
+ }
324
+ $all_bookings_by_slots[$booking_time]['total_bookings']++;
325
+ $all_bookings_by_slots[$booking_time]['total_guest'] += intval( $booking->party );
326
+
327
+ /**
328
+ * Expanding bookings
329
+ * Example: If I have someone booked at 1pm who will be in the restaurant for 120 minutes,
330
+ * that means they will be in the restaurant until 3pm. There is another booking at 2pm.
331
+ * That means, from 2pm to 3pm, there are already two separate reservations in the restaurant.
332
+ */
333
+ $end = $booking_time + $dining_block_seconds;
334
+ $next = $booking_time + $interval;
335
+ while($next < $end) {
336
+ if( ! array_key_exists( $next, $all_bookings_by_slots ) ) {
337
+ $all_bookings_by_slots[$next] = [
338
+ 'total_bookings' => 0,
339
+ 'total_guest' => 0,
340
+ 'overlapped' => false
341
+ ];
342
+ }
343
+ $all_bookings_by_slots[$next]['overlapped'] = true;
344
+ $all_bookings_by_slots[$next]['total_bookings']++;
345
+ $all_bookings_by_slots[$next]['total_guest'] += intval( $booking->party );
346
+ $next += $interval;
347
+ }
348
+ }
349
+
350
+ $all_blocked_slots = [];
351
+
352
+ // Go through all bookings and figure out when we're at or above the
353
+ // max reservation or max people and mark that slot as blocked
354
+ if ( isset( $max_reservations ) and $max_reservations > 0 ) {
355
+ foreach ( $all_bookings_by_slots as $slot => $data ) {
356
+ if( $max_reservations <= $data['total_bookings'] ) {
357
+ $all_blocked_slots[] = $slot;
358
+ }
359
+ }
360
+ }
361
+ else if ( isset( $max_people ) and $max_people > 0 ) {
362
+ /**
363
+ * min_party_size = 10, max_people = 100, 6 bookings of total 91 guests
364
+ * Now, if anybody wants to book for at least 10 people, it is not possible
365
+ * because the total will surpass the max_people (100)
366
+ * thus reducing min_party_size from max_people
367
+ *
368
+ * $max_people can be zero when min_party_size is same as max_people
369
+ */
370
+ $max_people = $max_people - $min_party_size;
371
+
372
+ foreach ( $all_bookings_by_slots as $slot => $data ) {
373
+ if( $max_people < $data['total_guest'] ) {
374
+ $all_blocked_slots[] = $slot;
375
+ }
376
+ }
377
+ }
378
+
379
+ // Mark slots unavailable, due to dinning block length
380
+ $additional_blocked_slots = [];
381
+ foreach ($all_blocked_slots as $slot) {
382
+ // blocking before this slot
383
+ $begin = $slot - $dining_block_seconds;
384
+ /**
385
+ * interval 30 minutes, dinning_length 120 minutes, slot 10:00am
386
+ * additional blockings before shall be 8:30am,9:00am and 9:30am
387
+ * thus skipping 8:00am which is valid
388
+ *
389
+ * @var unix timestamp
390
+ */
391
+ $next = $begin + $interval;
392
+ while($next < $slot) {
393
+ $additional_blocked_slots[] = $next;
394
+ $next += $interval;
395
+ }
396
+
397
+ // block after this slot only when this slot is not overlapped
398
+ // Overlapped slots should block only backwards, but not afterward
399
+ if( $all_bookings_by_slots[$slot]['overlapped'] ) {
400
+ continue;
401
+ }
402
+
403
+ // blocking after this slot
404
+ $end = $slot + $dining_block_seconds;
405
+ /**
406
+ * interval 30 minutes, dinning_length 120 minutes, slot 10:00am
407
+ * additional blockings after shall be 10:30am,11:00am and 1130am
408
+ * thus skipping 12:00pm which is valid
409
+ *
410
+ * @var unix timestamp
411
+ */
412
+ $next = $slot + $interval;
413
+ while($next < $end) {
414
+ $additional_blocked_slots[] = $next;
415
+ $next += $interval;
416
+ }
417
+ }
418
+
419
+ $all_blocked_slots = array_unique(
420
+ array_merge( $all_blocked_slots, $additional_blocked_slots ),
421
+ SORT_NUMERIC
422
+ );
423
+
424
+ sort( $all_blocked_slots, SORT_NUMERIC );
425
+
426
+ // remove blocked slots from available slots
427
+ $available_slots = array_diff( $all_possible_slots, $all_blocked_slots );
428
+ sort( $available_slots, SORT_NUMERIC );
429
+
430
+ // consolidating timeslots to timeframes
431
+ $timeframe = [];
432
+ $available_slots_count = count( $available_slots );
433
+ if( 0 < $available_slots_count ) {
434
+
435
+ $current_pair = [ 'from' => $available_slots[ 0 ] ];
436
+
437
+ for ( $i = 1; $i < $available_slots_count; $i++) {
438
+ if( $available_slots[ $i ] - $available_slots[ $i - 1 ] !== $interval ) {
439
+ $current_pair[ 'to' ] = $available_slots[ $i - 1 ];
440
+ $timeframe[] = $current_pair;
441
+
442
+ $current_pair = [ 'from' => $available_slots[ $i ] ];
443
+ }
444
+ }
445
+
446
+ $current_pair[ 'to' ] = $available_slots[ $i - 1 ];
447
+ $timeframe[] = $current_pair;
448
+ }
449
+
450
+ $finalize_response( $timeframe );
451
+ }
452
+
453
+ public function get_opening_hours() {
454
+ global $rtb_controller;
455
+
456
+ $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
457
+
458
+ $schedule_closed = $rtb_controller->settings->get_setting( 'schedule-closed', $location_slug );
459
+ $schedule_closed = is_array( $schedule_closed ) ? $schedule_closed : array();
460
+
461
+ $valid_times = array();
462
+
463
+ // Check if this date is an exception to the rules
464
+ if ( $schedule_closed !== 'undefined' ) {
465
+
466
+ foreach ( $schedule_closed as $closing ) {
467
+ $time = strtotime( $closing['date'] );
468
+
469
+ if ( date( 'Y', $time ) == $this->year &&
470
+ date( 'm', $time ) == $this->month &&
471
+ date( 'd', $time ) == $this->day
472
+ ) {
473
+
474
+ // Closed all day
475
+ if ( ! isset( $closing['time'] ) || $closing['time'] == 'undefined' ) {
476
+ return false;
477
+ }
478
+
479
+ if ( $closing['time']['start'] !== 'undefined' ) {
480
+ $open_time = strtotime( $closing['date'] . ' ' . $closing['time']['start'] );
481
+ } else {
482
+ $open_time = strtotime( $closing['date'] ); // Start of the day
483
+ }
484
+
485
+ if ( $closing['time']['end'] !== 'undefined' ) {
486
+ $close_time = strtotime( $closing['date'] . ' ' . $closing['time']['end'] );
487
+ } else {
488
+ $close_time = strtotime( $closing['date'] . ' 23:59:59' ); // End of the day
489
+ }
490
+
491
+ $open_time = $this->get_earliest_time( $open_time );
492
+
493
+ if ( $open_time <= $close_time ) {
494
+ $valid_times[] = ['from' => $open_time, 'to' => $close_time];
495
+ }
496
+ }
497
+ }
498
+
499
+ // Exit early if this date is an exception
500
+ if ( isset( $open_time ) ) {
501
+ return $valid_times;
502
+ }
503
+ }
504
+
505
+ $schedule_open = $rtb_controller->settings->get_setting( 'schedule-open', $location_slug );
506
+ $schedule_open = is_array( $schedule_open ) ? $schedule_open : array();
507
+
508
+ // Get any rules which apply to this weekday
509
+ $day_of_week = strtolower(
510
+ date( 'l', strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' 1:00:00' ) )
511
+ );
512
+
513
+ foreach ( $schedule_open as $opening ) {
514
+
515
+ if ( $opening['weekdays'] !== 'undefined' ) {
516
+
517
+ foreach ( $opening['weekdays'] as $weekday => $value ) {
518
+
519
+ if ( $weekday == $day_of_week ) {
520
+
521
+ // Closed all day
522
+ if ( $opening['time'] == 'undefined' ) {
523
+
524
+ return false;
525
+ }
526
+
527
+ if ( $opening['time']['start'] !== 'undefined' ) {
528
+
529
+ $open_time = strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' ' . $opening['time']['start'] );
530
+ }
531
+ else {
532
+
533
+ $open_time = strtotime( $this->year . '-' . $this->month . '-' . $this->day );
534
+ }
535
+
536
+ if ( $opening['time']['end'] !== 'undefined' ) {
537
+
538
+ $close_time = strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' ' . $opening['time']['end'] );
539
+ }
540
+ else {
541
+
542
+ // End of the day
543
+ $close_time = strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' 23:59:59' );
544
+ }
545
+
546
+ $open_time = $this->get_earliest_time( $open_time );
547
+
548
+ if ( $open_time <= $close_time ) {
549
+
550
+ $valid_times[] = ['from' => $open_time, 'to' => $close_time];
551
+ }
552
+ }
553
+ }
554
+ }
555
+ }
556
+
557
+ // Pass any valid times located
558
+ if ( sizeOf( $valid_times ) >= 1 ) {
559
+
560
+ return $valid_times;
561
+ }
562
+
563
+ return false;
564
+ }
565
+
566
+ public function get_earliest_time( $open_time ) {
567
+ global $rtb_controller;
568
+
569
+ $interval = $rtb_controller->settings->get_setting( 'time-interval' ) * 60;
570
+
571
+ $timezone = wp_timezone();
572
+ $offset = $timezone->getOffset( new DateTime );
573
+
574
+ // adjust open time with respect to the current time of the day for upcoming timeslots
575
+ $current_time = time() + $offset;
576
+
577
+ // Only make adjustments for current day selections
578
+ if ( date( 'y-m-d', strtotime( "{$this->year}-{$this->month}-{$this->day}" ) ) !== date( 'y-m-d', $current_time) ) {
579
+ return $open_time;
580
+ }
581
+
582
+ $late_bookings = ( is_admin() && current_user_can( 'manage_bookings' ) ) ? '' : $rtb_controller->settings->get_setting( 'late-bookings' );
583
+
584
+ if( $current_time > $open_time ) {
585
+ while( $current_time > $open_time ) {
586
+ $open_time += $interval;
587
+ }
588
+ }
589
+
590
+ // adjust the open time for the Late Bookings option
591
+ if ( is_numeric($late_bookings) && $late_bookings % 1 === 0 ) {
592
+ $time_calc = time() + $offset + $late_bookings * 60;
593
+ while ($time_calc > $open_time) {
594
+ $open_time = $open_time + $interval;
595
+ }
596
+ }
597
+
598
+ return $open_time;
599
+ }
600
+
601
+ /**
602
+ * Get number of seats remaining avilable to be booked
603
+ * @since 2.1.5
604
+ */
605
+ public function get_available_party_size() {
606
+ global $rtb_controller;
607
+
608
+ $this->location = ! empty( $_POST['location'] ) ? get_term( intval( $_POST['location'] ) ) : false;
609
+ $this->year = sanitize_text_field( $_POST['year'] );
610
+ $this->month = sanitize_text_field( $_POST['month'] );
611
+ $this->day = sanitize_text_field( $_POST['day'] );
612
+ $this->time = sanitize_text_field( $_POST['time'] );
613
+
614
+ $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
615
+
616
+ $max_people = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
617
+
618
+ $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' )* 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
619
+
620
+ // Get opening/closing times for this particular day
621
+ $hours = $this->get_opening_hours();
622
+
623
+ // If the restaurant is closed that day, return false
624
+ if ( ! $hours ) { die(); }
625
+
626
+ // If no time is selected, return false
627
+ if ( ! $this->time ) { die(); }
628
+
629
+ $args = array(
630
+ 'posts_per_page' => -1,
631
+ 'date_range' => 'dates',
632
+ 'start_date' => $this->year . '-' . $this->month . '-' . $this->day,
633
+ 'end_date' => $this->year . '-' . $this->month . '-' . $this->day
634
+ );
635
+
636
+ // If there are multiple locations, a location is selected, and
637
+ // max seats has been enabled for this specific location
638
+ if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) {
639
+
640
+ $tax_query = array(
641
+ array(
642
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
643
+ 'field' => 'term_id',
644
+ 'terms' => $this->location->term_id
645
+ )
646
+ );
647
+
648
+ $args['tax_query'] = $tax_query;
649
+ }
650
+
651
+ $query = new rtbQuery( $args );
652
+ $query->prepare_args();
653
+
654
+ // Get all current bookings sorted by date
655
+ $bookings = $query->get_bookings();
656
+
657
+ $selected_date_time = strtotime($this->year . '-' . $this->month . '-' . $this->day . ' ' . $this->time);
658
+ $selected_date_time_start = $selected_date_time - $dining_block_seconds;
659
+ $selected_date_time_end = $selected_date_time + $dining_block_seconds;
660
+ $party_sizes = [];
661
+
662
+ if ($max_people != 'undefined' and $max_people != 0) {
663
+
664
+ $max_time_size = 0;
665
+ $current_times = array();
666
+ $party_sizes = array();
667
+
668
+ // Go through all current booking and collect the total party size
669
+ foreach ( $bookings as $key => $booking ) {
670
+
671
+ // Convert booking date to seconds from UNIX
672
+ $booking_time = strtotime($booking->date);
673
+
674
+ // Ignore bookings outside of our time range
675
+ if ($booking_time < $selected_date_time_start or $booking_time > $selected_date_time_end) { continue; }
676
+
677
+ $current_times[] = $booking_time;
678
+ $party_sizes[] = (int) $booking->party;
679
+
680
+ while ( sizeOf( $current_times ) > 0 and reset( $current_times ) < $booking_time - $dining_block_seconds ) {
681
+ //save the time to know when the blocking potentially ends
682
+ $removed_time = reset( $current_times );
683
+
684
+ // remove the expired time and party size
685
+ array_shift( $current_times );
686
+ array_shift( $party_sizes );
687
+ }
688
+
689
+ $max_time_size = max( $max_time_size, array_sum( $party_sizes ) );
690
+ }
691
+
692
+ $response = (object) array( 'available_spots' => $max_people - $max_time_size);
693
+
694
+ echo json_encode($response);
695
+
696
+ die();
697
+ } else {
698
+ return false;
699
+ }
700
+ }
701
+
702
+ /**
703
+ * Get tables available to be booked at a specific time and party size
704
+ * @since 2.1.7
705
+ */
706
+ public function get_available_tables() {
707
+ global $rtb_controller;
708
+
709
+ $tables = $rtb_controller->settings->get_sorted_tables();
710
+
711
+ $this->booking_id = isset( $_POST['booking_id'] ) ? intval( $_POST['booking_id'] ) : 0;
712
+ $this->year = isset( $_POST['year'] ) ? sanitize_text_field( $_POST['year'] ) : false;
713
+ $this->month = isset( $_POST['month'] ) ? sanitize_text_field( $_POST['month'] ) : false;
714
+ $this->day = isset( $_POST['day'] ) ? sanitize_text_field( $_POST['day'] ) : false;
715
+ $this->time = isset( $_POST['time'] ) ? sanitize_text_field( $_POST['time'] ) : false;
716
+ $this->party = isset( $_POST['party'] ) ? sanitize_text_field( $_POST['party'] ) : false;
717
+
718
+ /*$this->year = 2020;
719
+ $this->month = 06;
720
+ $this->day = 12;
721
+ $this->time = '02:15 PM';
722
+ $this->party = 12;*/
723
+
724
+ if ( ! isset( $this->year ) or ! isset( $this->month ) or ! isset( $this->day ) or ! isset( $this->time ) ) { return false; }
725
+
726
+ $datetime = strtotime( $this->year . '-' . $this->month . '-' . $this->day . ' ' . $this->time );
727
+
728
+ $valid_tables = rtb_get_valid_tables( $datetime );
729
+
730
+ if ( $this->booking_id ) {
731
+
732
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
733
+
734
+ $current_booking = new rtbBooking();
735
+ $current_booking->load_post( $this->booking_id );
736
+
737
+ if ( $current_booking->table ) { $valid_tables = array_merge( $valid_tables, $current_booking->table ); }
738
+ }
739
+
740
+ if ( isset( $this->party ) ) {
741
+
742
+ $possible_combinations = array();
743
+ foreach ( $valid_tables as $valid_table ) {
744
+
745
+ // If the party size is between the min and max for the table, great
746
+ if ( $tables[ $valid_table ]->min_people <= $this->party and $tables[ $valid_table ]->max_people >= $this->party ) {
747
+
748
+ $possible_combinations[] = $valid_table;
749
+ }
750
+ // If the party is above the minimum for the table, look to see if combinations could work
751
+ elseif ( $tables[ $valid_table ]->min_people <= $this->party ) {
752
+
753
+ $combination = $this->get_combinations_chain( $tables, $valid_tables, $valid_table, $tables[ $valid_table ]->max_people, $this->party );
754
+
755
+ if ( $combination ) {
756
+ $possible_combinations[] = $combination;
757
+ }
758
+ }
759
+
760
+ $return_tables = $this->format_tables( $possible_combinations );
761
+ }
762
+ }
763
+ else {
764
+ $return_tables = $this->format_tables( $valid_tables );
765
+ }
766
+
767
+ $selected_table = ( isset( $current_booking ) and $current_booking->table ) ? implode(',', $current_booking->table ) : -1;
768
+
769
+ $response = (object) array( 'available_tables' => $return_tables, 'selected_table' => $selected_table );
770
+
771
+ echo json_encode($response);
772
+
773
+ die();
774
+ }
775
+
776
+ /**
777
+ * Recursively go through table combinations to find one that has enough seats
778
+ * @since 2.1.7
779
+ */
780
+ public function get_combinations_chain(
781
+ $tables,
782
+ $valid_tables,
783
+ $current_table,
784
+ $current_size,
785
+ $needed_size
786
+ ) {
787
+ $table_chain[] = $current_table;
788
+
789
+ // No combination specified
790
+ if ( ! $tables[ $current_table ]->combinations ) {
791
+ return false;
792
+ }
793
+
794
+ $possible_tables = explode( ',', $tables[ $current_table ]->combinations );
795
+
796
+ foreach ( $possible_tables as $possible_table ) {
797
+
798
+ // If the table has already been booked, continue
799
+ if ( !in_array( $possible_table, $valid_tables) ) {
800
+ continue;
801
+ }
802
+
803
+ // If the table can hold the group on its own, continue
804
+ if ( $tables[ $possible_table ]->max_people >= $needed_size ) {
805
+ continue;
806
+ }
807
+
808
+ $current_size += $tables[ $possible_table ]->max_people;
809
+ $table_chain[] = $possible_table;
810
+
811
+ if ( $current_size >= $needed_size ) {
812
+ return implode(',', $table_chain);
813
+ }
814
+ }
815
+
816
+ //no viable combination found
817
+ return false;
818
+ }
819
+
820
+ /**
821
+ * Format the tables available to be booked as number(s)_string => human_value pairs
822
+ * @since 2.1.7
823
+ */
824
+ public function format_tables ( $table_numbers ) {
825
+ global $rtb_controller;
826
+
827
+ $formatted_tables = array();
828
+
829
+ $tables = json_decode( html_entity_decode( $rtb_controller->settings->get_setting( 'rtb-tables' ) ) );
830
+ $tables = is_array( $tables ) ? $tables : array();
831
+
832
+ foreach ( $table_numbers as $table_number ) {
833
+
834
+ $table_parts = explode( ',', $table_number );
835
+
836
+ $table_values = array(
837
+ 'numbers' => '',
838
+ 'min_people' => 0,
839
+ 'max_people' => 0
840
+ );
841
+
842
+ foreach ( $tables as $table ) {
843
+ if ( in_array($table->number, $table_parts) ) {
844
+ $table_values['numbers'] .= ( strlen( $table_values['numbers'] ) ? ', ' : '' ) . $table->number;
845
+ $table_values['min_people'] += $table->min_people;
846
+ $table_values['max_people'] += $table->max_people;
847
+
848
+ if ( ! isset( $section_name ) ) { $section_name = $this->get_section_name( $table->section ); }
849
+ }
850
+ }
851
+
852
+ $formatted_tables[ $table_values['numbers'] ] = $table_values['numbers'] . ' - ' . $section_name . ' (min. ' . $table_values['min_people'] . '/max. ' . $table_values['max_people'] . ')';
853
+
854
+ unset( $section_name );
855
+ }
856
+
857
+ return $formatted_tables;
858
+ }
859
+
860
+ public function get_section_name( $section_id ) {
861
+ global $rtb_controller;
862
+
863
+ $sections = json_decode( html_entity_decode( $rtb_controller->settings->get_setting( 'rtb-table-sections' ) ) );
864
+ $sections = is_array( $sections ) ? $sections : array();
865
+
866
+ foreach ( $sections as $section ) {
867
+
868
+ if ( $section->section_id == $section_id ) { return $section->name; }
869
+ }
870
+
871
+ return false;
872
+ }
873
+
874
+ public function format_pickadate_time( $time ) {
875
+ return array( date( 'G', $time ), date( 'i', $time ) );
876
+ }
877
+ }
878
  }
includes/Blocks.class.php CHANGED
@@ -1,99 +1,99 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbBlocks' ) ) {
5
- /**
6
- * Class to create, edit and display blocks for the Gutenberg editor
7
- *
8
- * @since 0.0.1
9
- */
10
- class rtbBlocks {
11
-
12
- /**
13
- * Add hooks
14
- */
15
- public function __construct() {
16
-
17
- add_action( 'init', array( $this, 'register' ) );
18
-
19
- add_filter( 'block_categories_all', array( $this, 'add_block_category' ) );
20
- }
21
-
22
- /**
23
- * Register blocks
24
- */
25
- public function register() {
26
-
27
- if ( !function_exists( 'register_block_type' ) ) {
28
- return;
29
- }
30
-
31
- global $rtb_controller;
32
-
33
- $rtb_controller->register_assets();
34
-
35
- wp_register_script(
36
- 'restaurant-reservations-blocks',
37
- RTB_PLUGIN_URL . '/assets/js/blocks.build.js',
38
- array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' )
39
- );
40
-
41
- register_block_type( 'restaurant-reservations/booking-form', array(
42
- 'editor_script' => 'restaurant-reservations-blocks',
43
- 'editor_style' => 'rtb-booking-form',
44
- 'render_callback' => 'rtb_print_booking_form',
45
- 'attributes' => array(
46
- 'location' => array(
47
- 'type' => 'number',
48
- 'default' => 0,
49
- ),
50
- ),
51
- ) );
52
-
53
- add_action( 'admin_init', array( $this, 'register_admin' ) );
54
- }
55
-
56
- /**
57
- * Register admin-only assets for block handling
58
- */
59
- public function register_admin() {
60
-
61
- global $rtb_controller;
62
-
63
- $locations_enabled = !!$rtb_controller->locations->post_type;
64
-
65
- $location_options = array( array( 'value' => 0, 'label' => __('Ask the customer to select a location', 'restaurant-reservations' ) ) );
66
- if ($locations_enabled) {
67
- $locations = $rtb_controller->locations->get_location_options();
68
- foreach ( $locations as $id => $name ) {
69
- $location_options[] = array( 'value' => $id, 'label' => $name);
70
- }
71
- }
72
-
73
- wp_add_inline_script(
74
- 'restaurant-reservations-blocks',
75
- sprintf(
76
- 'var rtb_blocks = %s;',
77
- json_encode( array(
78
- 'locationsEnabled' => $locations_enabled,
79
- 'locations' => $location_options,
80
- ) )
81
- ),
82
- 'before'
83
- );
84
- }
85
-
86
- /**
87
- * Create a new category of blocks to hold our block
88
- */
89
- public function add_block_category( $categories ) {
90
-
91
- $categories[] = array(
92
- 'slug' => 'rtb-blocks',
93
- 'title' => __( 'Five Star Restaurant Reservations', 'restaurant-reservations' ),
94
- );
95
-
96
- return $categories;
97
- }
98
- }
99
- } // endif
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbBlocks' ) ) {
5
+ /**
6
+ * Class to create, edit and display blocks for the Gutenberg editor
7
+ *
8
+ * @since 0.0.1
9
+ */
10
+ class rtbBlocks {
11
+
12
+ /**
13
+ * Add hooks
14
+ */
15
+ public function __construct() {
16
+
17
+ add_action( 'init', array( $this, 'register' ) );
18
+
19
+ add_filter( 'block_categories_all', array( $this, 'add_block_category' ) );
20
+ }
21
+
22
+ /**
23
+ * Register blocks
24
+ */
25
+ public function register() {
26
+
27
+ if ( !function_exists( 'register_block_type' ) ) {
28
+ return;
29
+ }
30
+
31
+ global $rtb_controller;
32
+
33
+ $rtb_controller->register_assets();
34
+
35
+ wp_register_script(
36
+ 'restaurant-reservations-blocks',
37
+ RTB_PLUGIN_URL . '/assets/js/blocks.build.js',
38
+ array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' )
39
+ );
40
+
41
+ register_block_type( 'restaurant-reservations/booking-form', array(
42
+ 'editor_script' => 'restaurant-reservations-blocks',
43
+ 'editor_style' => 'rtb-booking-form',
44
+ 'render_callback' => 'rtb_print_booking_form',
45
+ 'attributes' => array(
46
+ 'location' => array(
47
+ 'type' => 'number',
48
+ 'default' => 0,
49
+ ),
50
+ ),
51
+ ) );
52
+
53
+ add_action( 'admin_init', array( $this, 'register_admin' ) );
54
+ }
55
+
56
+ /**
57
+ * Register admin-only assets for block handling
58
+ */
59
+ public function register_admin() {
60
+
61
+ global $rtb_controller;
62
+
63
+ $locations_enabled = !!$rtb_controller->locations->post_type;
64
+
65
+ $location_options = array( array( 'value' => 0, 'label' => __('Ask the customer to select a location', 'restaurant-reservations' ) ) );
66
+ if ($locations_enabled) {
67
+ $locations = $rtb_controller->locations->get_location_options();
68
+ foreach ( $locations as $id => $name ) {
69
+ $location_options[] = array( 'value' => $id, 'label' => $name);
70
+ }
71
+ }
72
+
73
+ wp_add_inline_script(
74
+ 'restaurant-reservations-blocks',
75
+ sprintf(
76
+ 'var rtb_blocks = %s;',
77
+ json_encode( array(
78
+ 'locationsEnabled' => $locations_enabled,
79
+ 'locations' => $location_options,
80
+ ) )
81
+ ),
82
+ 'before'
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Create a new category of blocks to hold our block
88
+ */
89
+ public function add_block_category( $categories ) {
90
+
91
+ $categories[] = array(
92
+ 'slug' => 'rtb-blocks',
93
+ 'title' => __( 'Five Star Restaurant Reservations', 'restaurant-reservations' ),
94
+ );
95
+
96
+ return $categories;
97
+ }
98
+ }
99
+ } // endif
includes/Booking.class.php CHANGED
@@ -1,1365 +1,1365 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbBooking' ) ) {
5
- /**
6
- * Class to handle a booking for Restaurant Table Bookings
7
- *
8
- * @since 0.0.1
9
- */
10
- class rtbBooking {
11
-
12
- /**
13
- * Whether or not this request has been processed. Used to prevent
14
- * duplicate forms on one page from processing a booking form more than
15
- * once.
16
- * @since 0.0.1
17
- */
18
- public $request_processed = false;
19
-
20
- /**
21
- * Whether or not this request was successfully saved to the database.
22
- * @since 0.0.1
23
- */
24
- public $request_inserted = false;
25
-
26
- /**
27
- * Raw input, clone of $_POST
28
- * @var array
29
- * @since 2.3.0
30
- */
31
- public $raw_input = array();
32
-
33
- public function __construct() {}
34
-
35
- /**
36
- * Load the booking information from a WP_Post object or an ID
37
- *
38
- * @uses load_wp_post()
39
- * @since 0.0.1
40
- */
41
- public function load_post( $post ) {
42
-
43
- if ( is_int( $post ) || is_string( $post ) ) {
44
- $post = get_post( $post );
45
- }
46
-
47
- if ( get_class( $post ) == 'WP_Post' && $post->post_type == RTB_BOOKING_POST_TYPE ) {
48
- $this->load_wp_post( $post );
49
- return true;
50
- } else {
51
- return false;
52
- }
53
-
54
- }
55
-
56
- /**
57
- * Load data from WP post object and retrieve metadata
58
- *
59
- * @uses load_post_metadata()
60
- * @since 0.0.1
61
- */
62
- public function load_wp_post( $post ) {
63
-
64
- // Store post for access to other data if needed by extensions
65
- $this->post = $post;
66
-
67
- $this->ID = $post->ID;
68
- $this->name = $post->post_title;
69
- $this->date = $post->post_date;
70
- $this->message = $post->post_content;
71
- $this->post_status = $post->post_status;
72
-
73
- $this->load_post_metadata();
74
-
75
- do_action( 'rtb_booking_load_post_data', $this, $post );
76
- }
77
-
78
- /**
79
- * Store metadata for post
80
- * @since 0.0.1
81
- */
82
- public function load_post_metadata() {
83
-
84
- $meta_defaults = array(
85
- 'party' => '',
86
- 'email' => '',
87
- 'phone' => '',
88
- 'date_submission' => '',
89
- 'logs' => array(),
90
- 'ip' => '',
91
- 'consent_acquired' => '',
92
- 'deposit' => '0',
93
- 'table' => array(),
94
- 'payment_failure_message' => '',
95
- 'receipt_id' => '',
96
- 'reminder_sent' => false,
97
- 'late_arrival_sent' => false,
98
- );
99
-
100
- $meta_defaults = apply_filters( 'rtb_booking_metadata_defaults', $meta_defaults );
101
-
102
- if ( is_array( $meta = get_post_meta( $this->ID, 'rtb', true ) ) ) {
103
- $meta = array_merge( $meta_defaults, get_post_meta( $this->ID, 'rtb', true ) );
104
- } else {
105
- $meta = $meta_defaults;
106
- }
107
-
108
- $this->party = $meta['party'];
109
- $this->email = $meta['email'];
110
- $this->phone = $meta['phone'];
111
- $this->date_submission = $meta['date_submission'];
112
- $this->logs = $meta['logs'];
113
- $this->ip = $meta['ip'];
114
- $this->consent_acquired = $meta['consent_acquired'];
115
- $this->deposit = $meta['deposit'];
116
- $this->table = $meta['table'];
117
- $this->payment_failure_message = $meta['payment_failure_message'];
118
- $this->receipt_id = $meta['receipt_id'];
119
- $this->late_arrival_sent = $meta['late_arrival_sent'];
120
- $this->reminder_sent = $meta['reminder_sent'];
121
- }
122
-
123
- /**
124
- * Prepare booking data loaded from the database for display in a booking
125
- * form as request fields. This is used, eg, for splitting datetime values
126
- * into date and time fields.
127
- * @since 1.3
128
- */
129
- public function prepare_request_data() {
130
-
131
- // Split $date to $request_date and $request_time
132
- if ( empty( $this->request_date ) || empty( $this->request_time ) ) {
133
- $date = new DateTime( $this->date );
134
- $this->request_date = $date->format( 'Y/m/d' );
135
- $this->request_time = $date->format( 'h:i A' );
136
- }
137
- }
138
-
139
- /**
140
- * Format date
141
- * @since 0.0.1
142
- */
143
- public function format_date( $date ) {
144
- $date = mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $date);
145
- return apply_filters( 'get_the_date', $date );
146
- }
147
-
148
- /**
149
- * Format a timestamp into a human-readable date
150
- *
151
- * @since 1.7.1
152
- */
153
- public function format_timestamp( $timestamp ) {
154
- $time = date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp );
155
- return $time;
156
- }
157
-
158
- /**
159
- * Calculates the deposit required for a reservation, if any
160
- *
161
- * @since 2.1.0
162
- */
163
- public function calculate_deposit() {
164
- global $rtb_controller;
165
-
166
- $deposit = $rtb_controller->settings->get_setting( 'rtb-deposit-amount' );
167
-
168
- if ( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ) == 'size_based' ) {
169
-
170
- $deposit = $this->party < $rtb_controller->settings->get_setting( 'rtb-deposit-min-party-size' ) ? 0 : $deposit;
171
- }
172
-
173
- if ( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ) == 'time_based' ) {
174
-
175
- $deposit = empty( $this->is_time_based_deposit_applicable() ) ? 0 : $deposit;
176
- }
177
-
178
- if ( $rtb_controller->settings->get_setting( 'rtb-deposit-type' ) == 'guest' ) { $deposit = $deposit * $this->party; }
179
-
180
- return $deposit;
181
- }
182
-
183
-
184
- /**
185
- * Insert a new booking submission into the database
186
- *
187
- * Validates the data, adds it to the database and executes notifications
188
- * @since 0.0.1
189
- */
190
- public function insert_booking($by_admin = false) {
191
-
192
- // Check if this request has already been processed. If multiple forms
193
- // exist on the same page, this prevents a single submission from
194
- // being added twice.
195
- if ( $this->request_processed === true ) {
196
- return true;
197
- }
198
-
199
- $this->request_processed = true;
200
-
201
- if ( empty( $this->ID ) ) {
202
- $action = 'insert';
203
- } else {
204
- $action = 'update';
205
- }
206
-
207
- $this->validate_submission($action, $by_admin);
208
- if ( $this->is_valid_submission() === false ) {
209
- return false;
210
- }
211
-
212
- if ( $this->insert_post_data() === false ) {
213
- return false;
214
- } else {
215
- $this->request_inserted = true;
216
- }
217
-
218
- do_action( 'rtb_' . $action . '_booking', $this );
219
-
220
- return true;
221
- }
222
-
223
- /**
224
- * Validate submission data. Expects to find data in $_POST.
225
- *
226
- * ************************** NOTE **************************
227
- * This function also create and assign all the required member variable with
228
- * the acurate values which will be insreted in the DB. One special member,
229
- * raw_input of type array holds the exact copy of $_POST
230
- *
231
- * Example:
232
- * class a {
233
- * public function a() {
234
- * $this->name = 'John Doe';
235
- * }
236
- * public function b() {
237
- * echo $this->name;
238
- * }
239
- * }
240
- *
241
- * $a = new a();
242
- *
243
- * var_dump($a);
244
- * object(a)#1 (0) {
245
- * }
246
- *
247
- * $a->a();
248
- *
249
- * var_dump($a);
250
- * object(a)#1 (1) {
251
- * ["name"]=>
252
- * string(8) "John Doe"
253
- * }
254
- *
255
- * $a->b();
256
- * John Doe
257
- *
258
- * @since 0.0.1
259
- */
260
- public function validate_submission($action = null, $by_admin = false) {
261
-
262
- global $rtb_controller;
263
-
264
- $this->validation_errors = array();
265
- /**
266
- * Raw, unprocessed value so that it can be used to preselect the form
267
- * field values, eg. table
268
- */
269
- $this->raw_input = $_POST;
270
-
271
- do_action( 'rtb_pre_validate_booking_submission', $this );
272
-
273
- // Date
274
- $date = empty( $_POST['rtb-date'] ) ? false : stripslashes_deep( $_POST['rtb-date'] );
275
- if ( $date === false ) {
276
- $this->validation_errors[] = array(
277
- 'field' => 'date',
278
- 'error_msg' => 'Booking request missing date',
279
- 'message' => __( 'Please enter the date you would like to book.', 'restaurant-reservations' ),
280
- );
281
-
282
- } else {
283
- try {
284
- $date = new DateTime( stripslashes_deep( $_POST['rtb-date'] ) );
285
- } catch ( Exception $e ) {
286
- $this->validation_errors[] = array(
287
- 'field' => 'date',
288
- 'error_msg' => $e->getMessage(),
289
- 'message' => __( 'The date you entered is not valid. Please select from one of the dates in the calendar.', 'restaurant-reservations' ),
290
- );
291
- }
292
- }
293
-
294
- // Time
295
- $time = empty( $_POST['rtb-time'] ) ? false : stripslashes_deep( $_POST['rtb-time'] );
296
- if ( $time === false ) {
297
- $this->validation_errors[] = array(
298
- 'field' => 'time',
299
- 'error_msg' => 'Booking request missing time',
300
- 'message' => __( 'Please enter the time you would like to book.', 'restaurant-reservations' ),
301
- );
302
-
303
- } else {
304
- try {
305
- $time = new DateTime( stripslashes_deep( $_POST['rtb-time'] ) );
306
- } catch ( Exception $e ) {
307
- $this->validation_errors[] = array(
308
- 'field' => 'time',
309
- 'error_msg' => $e->getMessage(),
310
- 'message' => __( 'The time you entered is not valid. Please select from one of the times provided.', 'restaurant-reservations' ),
311
- );
312
- }
313
- }
314
-
315
- // Check against valid open dates/times
316
- if ( is_object( $time ) && is_object( $date ) ) {
317
-
318
- $request = new DateTime( $date->format( 'Y-m-d' ) . ' ' . $time->format( 'H:i:s' ) );
319
-
320
- // Exempt Bookings Managers from the early and late bookings restrictions
321
- if ( !current_user_can( 'manage_bookings' ) ) {
322
-
323
- $early_bookings = $rtb_controller->settings->get_setting( 'early-bookings' );
324
- if ( !empty( $early_bookings ) ) {
325
- $uppar_bound = ( new DateTime( 'now' ) )->setTime( 23, 59 );
326
- $uppar_bound->add( new DateInterval( "P{$early_bookings}D" ) );
327
-
328
- if ( $request > $uppar_bound ) {
329
- $this->validation_errors[] = array(
330
- 'field' => 'time',
331
- 'error_msg' => 'Booking request too far in the future',
332
- 'message' => sprintf( __( 'Sorry, bookings can not be made more than %s days in advance.', 'restaurant-reservations' ), $early_bookings ),
333
- );
334
- }
335
- }
336
-
337
- $late_bookings = $rtb_controller->settings->get_setting( 'late-bookings' );
338
- if ( empty( $late_bookings ) ) {
339
- if ( $request->format( 'U' ) < current_time( 'timestamp' ) ) {
340
- $this->validation_errors[] = array(
341
- 'field' => 'time',
342
- 'error_msg' => 'Booking request in the past',
343
- 'message' => __( 'Sorry, bookings can not be made in the past.', 'restaurant-reservations' ),
344
- );
345
- }
346
-
347
- } elseif ( $late_bookings === 'same_day' ) {
348
- if ( $request->format( 'Y-m-d' ) == current_time( 'Y-m-d' ) ) {
349
- $this->validation_errors[] = array(
350
- 'field' => 'time',
351
- 'error_msg' => 'Booking request made on same day',
352
- 'message' => __( 'Sorry, bookings can not be made for the same day.', 'restaurant-reservations' ),
353
- );
354
- }
355
-
356
- } else {
357
- $late_bookings_seconds = $late_bookings * 60; // Late bookings allowance in seconds
358
- if ( $request->format( 'U' ) < ( current_time( 'timestamp' ) + $late_bookings_seconds ) ) {
359
- if ( $late_bookings >= 1440 ) {
360
- $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s days in advance.', 'restaurant-reservations' ), $late_bookings / 1440 );
361
- } elseif ( $late_bookings >= 60 ) {
362
- $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s hours in advance.', 'restaurant-reservations' ), $late_bookings / 60 );
363
- } else {
364
- $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s minutes in advance.', 'restaurant-reservations' ), $late_bookings );
365
- }
366
- $this->validation_errors[] = array(
367
- 'field' => 'time',
368
- 'error_msg' => 'Booking request made too close to the reserved time',
369
- 'message' => $late_bookings_message,
370
- );
371
- }
372
- }
373
- }
374
-
375
- // Check against scheduling exception rules
376
- $exceptions = $rtb_controller->settings->get_setting( 'schedule-closed' );
377
- if ( empty( $this->validation_errors ) && !empty( $exceptions ) && !current_user_can( 'manage_bookings' ) ) {
378
- $exception_is_active = false;
379
- $datetime_is_valid = false;
380
- foreach( $exceptions as $exception ) {
381
- $excp_date = new DateTime( $exception['date'] );
382
- if ( $excp_date->format( 'Y-m-d' ) == $request->format( 'Y-m-d' ) ) {
383
- $exception_is_active = true;
384
-
385
- // Closed all day
386
- if ( empty( $exception['time'] ) ) {
387
- continue;
388
- }
389
-
390
- $excp_start_time = empty( $exception['time']['start'] ) ? $request : new DateTime( $exception['date'] . ' ' . $exception['time']['start'] );
391
- $excp_end_time = empty( $exception['time']['end'] ) ? $request : new DateTime( $exception['date'] . ' ' . $exception['time']['end'] );
392
-
393
- if ( $request->format( 'U' ) >= $excp_start_time->format( 'U' ) && $request->format( 'U' ) <= $excp_end_time->format( 'U' ) ) {
394
- $datetime_is_valid = true;
395
- break;
396
- }
397
- }
398
- }
399
-
400
- if ( $exception_is_active && !$datetime_is_valid ) {
401
- $this->validation_errors[] = array(
402
- 'field' => 'date',
403
- 'error_msg' => 'Booking request made on invalid date or time in an exception rule',
404
- 'message' => __( 'Sorry, no bookings are being accepted then.', 'restaurant-reservations' ),
405
- );
406
- }
407
- }
408
-
409
- // Check against weekly scheduling rules
410
- $rules = $rtb_controller->settings->get_setting( 'schedule-open' );
411
- if ( empty( $exception_is_active ) && empty( $this->validation_errors ) && !empty( $rules ) && !current_user_can( 'manage_bookings' ) ) {
412
- $request_weekday = strtolower( $request->format( 'l' ) );
413
- $time_is_valid = null;
414
- $day_is_valid = null;
415
- foreach( $rules as $rule ) {
416
-
417
- if ( !empty( $rule['weekdays'][ $request_weekday ] ) ) {
418
- $day_is_valid = true;
419
-
420
- if ( empty( $rule['time'] ) ) {
421
- $time_is_valid = true; // Days with no time values are open all day
422
- break;
423
- }
424
-
425
- $too_early = true;
426
- $too_late = true;
427
-
428
- // Too early
429
- if ( !empty( $rule['time']['start'] ) ) {
430
- $rule_start_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['start'] );
431
- if ( $rule_start_time->format( 'U' ) <= $request->format( 'U' ) ) {
432
- $too_early = false;
433
- }
434
- }
435
-
436
- // Too late
437
- if ( !empty( $rule['time']['end'] ) ) {
438
- $rule_end_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['end'] );
439
- if ( $rule_end_time->format( 'U' ) >= $request->format( 'U' ) ) {
440
- $too_late = false;
441
- }
442
- }
443
-
444
- // Valid time found
445
- if ( $too_early === false && $too_late === false) {
446
- $time_is_valid = true;
447
- break;
448
- }
449
- }
450
- }
451
-
452
- if ( !$day_is_valid ) {
453
- $this->validation_errors[] = array(
454
- 'field' => 'date',
455
- 'error_msg' => 'Booking request made on an invalid date',
456
- 'message' => __( 'Sorry, no bookings are being accepted on that date.', 'restaurant-reservations' ),
457
- );
458
- } elseif ( !$time_is_valid ) {
459
- $this->validation_errors[] = array(
460
- 'field' => 'time',
461
- 'error_msg' => 'Booking request made at an invalid time',
462
- 'message' => __( 'Sorry, no bookings are being accepted at that time.', 'restaurant-reservations' ),
463
- );
464
- }
465
- }
466
-
467
- // Accept the date if it has passed validation
468
- if ( empty( $this->validation_errors ) ) {
469
- $this->date = $request->format( 'Y-m-d H:i:s' );
470
- }
471
- }
472
-
473
- // Save requested date/time values in case they need to be
474
- // printed in the form again
475
- $this->request_date = empty( $_POST['rtb-date'] ) ? '' : stripslashes_deep( $_POST['rtb-date'] );
476
- $this->request_time = empty( $_POST['rtb-time'] ) ? '' : stripslashes_deep( $_POST['rtb-time'] );
477
-
478
- // Name
479
- $this->name = empty( $_POST['rtb-name'] ) ? '' : wp_strip_all_tags( sanitize_text_field( stripslashes_deep( $_POST['rtb-name'] ) ), true ); // @todo should I limit length?
480
- if ( empty( $this->name ) ) {
481
- $this->validation_errors[] = array(
482
- 'field' => 'name',
483
- 'post_variable' => $this->name,
484
- 'message' => __( 'Please enter a name for this booking.', 'restaurant-reservations' ),
485
- );
486
- }
487
-
488
- // Party
489
- $this->party = empty( $_POST['rtb-party'] ) ? '' : absint( $_POST['rtb-party'] );
490
- if ( empty( $this->party ) ) {
491
- $this->validation_errors[] = array(
492
- 'field' => 'party',
493
- 'post_variable' => $this->party,
494
- 'message' => __( 'Please let us know how many people will be in your party.', 'restaurant-reservations' ),
495
- );
496
-
497
- // Check party size
498
- } else {
499
- $party_size = $rtb_controller->settings->get_setting( 'party-size' );
500
- if ( ! empty( $party_size ) && $party_size < $this->party ) {
501
- $this->validation_errors[] = array(
502
- 'field' => 'party',
503
- 'post_variable' => $this->party,
504
- 'message' => sprintf( __( 'We only accept bookings for parties of up to %d people.', 'restaurant-reservations' ), $party_size ),
505
- );
506
- }
507
- $party_size_min = $rtb_controller->settings->get_setting( 'party-size-min' );
508
- if ( ! empty( $party_size_min ) && $party_size_min > $this->party ) {
509
- $this->validation_errors[] = array(
510
- 'field' => 'party',
511
- 'post_variable' => $this->party,
512
- 'message' => sprintf( __( 'We only accept bookings for parties of more than %d people.', 'restaurant-reservations' ), $party_size_min ),
513
- );
514
- }
515
- }
516
-
517
- // Email
518
- $this->email = empty( $_POST['rtb-email'] ) ? '' : sanitize_text_field( stripslashes_deep( $_POST['rtb-email'] ) ); // @todo email validation? send notification back to form on bad email address.
519
- if ( empty( $this->email ) ) {
520
- $this->validation_errors[] = array(
521
- 'field' => 'email',
522
- 'post_variable' => $this->email,
523
- 'message' => __( 'Please enter an email address so we can confirm your booking.', 'restaurant-reservations' ),
524
- );
525
- } elseif ( !is_email( $this->email ) && apply_filters( 'rtb_require_valid_email', true ) ) {
526
- $this->validation_errors[] = array(
527
- 'field' => 'email',
528
- 'post_variable' => $this->email,
529
- 'message' => __( 'Please enter a valid email address so we can confirm your booking.', 'restaurant-reservations' ),
530
- );
531
- }
532
-
533
- // Phone
534
- $this->phone = empty( $_POST['rtb-phone'] ) ? '' : sanitize_text_field( stripslashes_deep( $_POST['rtb-phone'] ) );
535
- $phone_required = $rtb_controller->settings->get_setting( 'require-phone' );
536
- if ( $phone_required && empty( $this->phone ) ) {
537
- $this->validation_errors[] = array(
538
- 'field' => 'phone',
539
- 'post_variable' => $this->phone,
540
- 'message' => __( 'Please provide a phone number so we can confirm your booking.', 'restaurant-reservations' ),
541
- );
542
- }
543
-
544
- // Table
545
- $table = empty( $_POST['rtb-table'] ) ? array() : explode( ',', sanitize_text_field( stripslashes_deep( $_POST['rtb-table'] ) ) );
546
- $this->table = is_array( $table ) ? array_map( 'sanitize_text_field', $table ) : array();
547
-
548
- $table_required = $rtb_controller->settings->get_setting( 'require-table' );
549
- if ( $table_required && empty( $this->table ) ) {
550
- $this->validation_errors[] = array(
551
- 'field' => 'table',
552
- 'post_variable' => $this->table,
553
- 'message' => __( 'Please select a table for your booking.', 'restaurant-reservations' ),
554
- );
555
- }
556
-
557
- // check whether there is a time conflict for a particular table
558
- $valid_table = $this->table ? $this->is_valid_table() : true;
559
- if ( ! $valid_table ) {
560
- $this->validation_errors[] = array(
561
- 'field' => 'table',
562
- 'post_variable' => $this->table,
563
- 'message' => __( 'Please select a valid table for your booking.', 'restaurant-reservations' ),
564
- );
565
- }
566
-
567
- // reCAPTCHA
568
- if ( $rtb_controller->settings->get_setting( 'enable-captcha' ) && !is_admin() ) {
569
- if ( ! isset($_POST['g-recaptcha-response']) ) {
570
- $this->validation_errors[] = array(
571
- 'field' => 'recaptcha',
572
- 'error_msg' => 'No reCAPTCHA code',
573
- 'message' => __( 'Please fill out the reCAPTCHA box before submitting.', 'restaurant-reservations' ),
574
- );
575
- }
576
- else {
577
- $secret_key = $rtb_controller->settings->get_setting( 'captcha-secret-key' );
578
- $captcha = $_POST['g-recaptcha-response'];
579
-
580
- $url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . urlencode($secret_key) . '&response=' . urlencode($captcha);
581
- $json_response = file_get_contents( $url );
582
- $response = json_decode( $json_response );
583
-
584
- $reCaptcha_error = false;
585
- if(json_last_error() != JSON_ERROR_NONE) {
586
- $response = new stdClass();
587
- $response->success = false;
588
- $reCaptcha_error = true;
589
- if(defined('WP_DEBUG') && WP_DEBUG) {
590
- error_log('RTB reCAPTCHA error. Raw respose: '.print_r([$json_response], true));
591
- }
592
- }
593
-
594
- if ( ! $response->success ) {
595
- $message = __( 'Please fill out the reCAPTCHA box again and re-submit.', 'restaurant-reservations' );
596
- if($reCaptcha_error) {
597
- $message .= __( ' If you encounter reCAPTCHA error multiple times, please contact us.', 'restaurant-reservations' );
598
- }
599
- $this->validation_errors[] = array(
600
- 'field' => 'recaptcha',
601
- 'error_msg' => 'Invalid reCAPTCHA code',
602
- 'message' => $message,
603
- );
604
- }
605
- }
606
- }
607
-
608
- // Message
609
- $this->message = empty( $_POST['rtb-message'] ) ? '' : nl2br( wp_kses_post( stripslashes_deep( $_POST['rtb-message'] ) ) );
610
-
611
- // Post Status (define a default post status if none passed)
612
- $this->determine_status();
613
-
614
- // Consent
615
- $require_consent = $rtb_controller->settings->get_setting( 'require-consent' );
616
- $consent_statement = $rtb_controller->settings->get_setting( 'consent-statement' );
617
- if ( $require_consent && $consent_statement ) {
618
- // Don't change consent status once initial consent has been collected
619
- if ( empty( $this->consent_acquired ) ) {
620
- $this->consent_acquired = !empty( $_POST['rtb-consent-statement'] );
621
- }
622
- }
623
-
624
- // Check if any required fields are empty
625
- $required_fields = $rtb_controller->settings->get_required_fields();
626
- foreach( $required_fields as $slug => $field ) {
627
- if ( !$this->field_has_error( $slug ) && $this->is_field_empty( $slug ) ) {
628
- $this->validation_errors[] = array(
629
- 'field' => $slug,
630
- 'post_variable' => '',
631
- 'message' => __( 'Please complete this field to request a booking.', 'restaurant-reservations' ),
632
- );
633
- }
634
- }
635
-
636
- // Check if the email or IP is banned
637
- if ( !current_user_can( 'manage_bookings' ) ) {
638
- $ip = $_SERVER['REMOTE_ADDR'];
639
- if ( !$this->is_valid_ip( $ip ) || !$this->is_valid_email( $this->email ) ) {
640
- $this->validation_errors[] = array(
641
- 'field' => 'date',
642
- 'post_variable' => $ip,
643
- 'message' => __( 'Your booking has been rejected. Please call us if you would like to make a booking.', 'restaurant-reservations' ),
644
- );
645
- } elseif ( empty( $this->ip ) and ! $rtb_controller->settings->get_setting( 'disable-ip-capture' ) ) {
646
- $this->ip = sanitize_text_field( $ip );
647
- }
648
- } elseif ( empty( $this->ip ) and ! $rtb_controller->settings->get_setting( 'disable-ip-capture' ) ) {
649
- $this->ip = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
650
- }
651
-
652
- // Check to make sure that the maximum number of reservations has not already been made
653
- if ( ! $this->is_under_max_reservations() ){
654
- $this->validation_errors[] = array(
655
- 'field' => 'time',
656
- 'error_msg' => 'maximum reservations exceeded',
657
- 'message' => __( 'The maximum number of reservations for that timeslot has been reached. Please select a different timeslot.', 'restaurant-reservations' ),
658
- );
659
- }
660
-
661
- // Check to make sure that the maximum number of seats has not already been made
662
- if ( ! $this->is_under_max_seats() ){
663
- $this->validation_errors[] = array(
664
- 'field' => 'time',
665
- 'error_msg' => 'maximum seats exceeded',
666
- 'message' => __( 'With your party, the maximum number of seats for that timeslot would be exceeded. Please select a different timeslot or reduce your party size.', 'restaurant-reservations' ),
667
- );
668
- }
669
-
670
- // Check if there is a booking already made with the exact same information, to prevent double bookings on refresh
671
- if ( (!$by_admin || $by_admin && $action !== 'update') && $this->is_duplicate_booking() ) {
672
- $this->validation_errors[] = array(
673
- 'field' => 'date',
674
- 'error_msg' => 'duplicate booking',
675
- 'message' => __( 'Your booking and personal information exactly matches another booking. If this was not caused by refreshing the page, please call us to make a booking.', 'restaurant-reservations' ),
676
- );
677
- }
678
-
679
- do_action( 'rtb_validate_booking_submission', $this );
680
-
681
- }
682
-
683
- /**
684
- * Check if submission is valid
685
- *
686
- * @since 0.0.1
687
- */
688
- public function is_valid_submission() {
689
-
690
- if ( !count( $this->validation_errors ) ) {
691
- return true;
692
- }
693
-
694
- return false;
695
- }
696
-
697
- /**
698
- * Check if a field already has an error attached to it
699
- *
700
- * @field string Field slug
701
- * @since 1.3
702
- */
703
- public function field_has_error( $field_slug ) {
704
-
705
- foreach( $this->validation_errors as $error ) {
706
- if ( $error['field'] == $field_slug ) {
707
- return true;
708
- }
709
- }
710
-
711
- return false;
712
- }
713
-
714
- /**
715
- * Check if a field is missing
716
- *
717
- * Checks for empty strings and arrays, but accepts '0'
718
- * @since 0.1
719
- */
720
- public function is_field_empty( $slug ) {
721
-
722
- $input = isset( $_POST['rtb-' . $slug ] ) ? $_POST['rtb-' . $slug] : '';
723
-
724
- if ( ( is_string( $input ) && trim( $input ) == '' ) ||
725
- ( is_array( $input ) && empty( $input ) ) ) {
726
- return true;
727
- }
728
-
729
- return false;
730
- }
731
-
732
- /**
733
- * Check if an IP address has been banned
734
- *
735
- * @param string $ip
736
- * @return bool
737
- * @since 1.7
738
- */
739
- public function is_valid_ip( $ip = null ) {
740
-
741
- if ( is_null( $ip ) ) {
742
- $ip = isset( $this->ip ) ? $this->ip : null;
743
- if ( is_null( $ip ) ) {
744
- return false;
745
- }
746
- }
747
-
748
- global $rtb_controller;
749
-
750
- $banned_ips = array_filter( explode( "\n", $rtb_controller->settings->get_setting( 'ban-ips' ) ) );
751
-
752
- foreach( $banned_ips as $banned_ip ) {
753
- if ( $ip == trim( $banned_ip ) ) {
754
- return false;
755
- }
756
- }
757
-
758
- return true;
759
- }
760
-
761
- /**
762
- * Check if an email address has been banned
763
- *
764
- * @param string $email
765
- * @return bool
766
- * @since 1.7
767
- */
768
- public function is_valid_email( $email = null ) {
769
-
770
- if ( is_null( $email ) ) {
771
- $email = isset( $this->email ) ? $this->email : null;
772
- if ( is_null( $email ) ) {
773
- return false;
774
- }
775
- }
776
-
777
- global $rtb_controller;
778
-
779
- $banned_emails = array_filter( explode( "\n", $rtb_controller->settings->get_setting( 'ban-emails' ) ) );
780
-
781
- foreach( $banned_emails as $banned_email ) {
782
- if ( $email == trim( $banned_email ) ) {
783
- return false;
784
- }
785
- }
786
-
787
- return true;
788
- }
789
-
790
- /**
791
- * Check if a table(s) is valid (not already taken during a specific timeslot)
792
- *
793
- * @return bool
794
- * @since 2.1.7
795
- */
796
- public function is_valid_table() {
797
- global $rtb_controller;
798
-
799
- if ( ! $this->table or ! is_array( $this->table ) ) { return false; }
800
-
801
- $valid_tables = rtb_get_valid_tables( $this->date );
802
-
803
- if ( isset( $this->ID ) ) {
804
-
805
- $post_meta = get_post_meta( $this->ID, 'rtb', true );
806
-
807
- if ( isset( $post_meta['table'] ) and is_array( $post_meta['table'] ) ) { $valid_tables = array_merge( $valid_tables, $post_meta['table'] ); }
808
- }
809
-
810
- return $this->table == array_intersect( $this->table, $valid_tables );
811
- }
812
-
813
- /**
814
- * Check if this booking would put the restaurant over the maximum, if set
815
- *
816
- * @return bool
817
- * @since 2.1.20
818
- */
819
- public function is_under_max_reservations() {
820
- global $rtb_controller;
821
-
822
- $location = ( ! empty( $this->location ) and term_exists( $this->location ) ) ? get_term( $this->location ) : false;
823
- $location_slug = ! empty( $location ) ? $location->slug : false;
824
-
825
- $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $location_slug );
826
-
827
- if ( ! $max_reservations_enabled ) { return true; }
828
-
829
- $max_reservations = (int) $rtb_controller->settings->get_setting( 'rtb-max-tables-count', $location_slug );
830
-
831
- if ( $max_reservations == 'undefined' or ! $max_reservations ) { return true; }
832
-
833
- $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
834
-
835
- $after_time = strtotime($this->date) - $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
836
- $before_time = strtotime($this->date) + $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
837
-
838
- $args = array(
839
- 'posts_per_page' => -1,
840
- 'post_status' => array( 'pending', 'payment_pending', 'confirmed', 'arrived' ),
841
- 'date_query' => array(
842
- 'before' => date( 'c', $before_time ),
843
- 'after' => date( 'c', $after_time )
844
- )
845
- );
846
-
847
- // If there are multiple locations, a location is selected, and
848
- // max seats has been enabled for this specific location
849
- if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-tables-count', $location_slug ) ) {
850
-
851
- $tax_query = array(
852
- array(
853
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
854
- 'field' => 'term_id',
855
- 'terms' => $location->term_id
856
- )
857
- );
858
-
859
- $args['tax_query'] = $tax_query;
860
- }
861
-
862
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
863
- $query = new rtbQuery( $args );
864
- $query->prepare_args();
865
-
866
- $times = array();
867
- foreach ( $query->get_bookings() as $booking ) {
868
-
869
- if ( isset( $this->ID ) and $booking->ID == $this->ID ) { continue; }
870
-
871
- $times[] = strtotime( $booking->date );
872
- }
873
-
874
- sort( $times );
875
-
876
- $current_times = array();
877
- foreach ( $times as $time ) {
878
-
879
- $current_times[] = $time;
880
-
881
- if ( reset( $current_times ) < ( $time - $dining_block_seconds ) ) { array_shift( $current_times ); }
882
-
883
- // Check if we go above the max confirmation number
884
- if ( sizeOf( $current_times ) + 1 > $max_reservations ) { return false; }
885
- }
886
-
887
- return true;
888
- }
889
-
890
- /**
891
- * Check if this booking would put the restaurant over the maximum number of people, if set
892
- *
893
- * @return bool
894
- * @since 2.1.20
895
- */
896
- public function is_under_max_seats() {
897
- global $rtb_controller;
898
-
899
- $location = ( ! empty( $this->location ) and term_exists( $this->location ) ) ? get_term( $this->location ) : false;
900
- $location_slug = ! empty( $location ) ? $location->slug : false;
901
-
902
- $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $location_slug );
903
-
904
- if ( ! $max_reservations_enabled ) { return true; }
905
-
906
- $max_seats = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
907
-
908
- if ( $max_seats == 'undefined' or ! $max_seats ) { return true; }
909
- if ( $this->party > $max_seats ) { return false; }
910
-
911
- $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
912
-
913
- $after_time = strtotime($this->date) - $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
914
- $before_time = strtotime($this->date) + $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
915
-
916
- $args = array(
917
- 'posts_per_page' => -1,
918
- 'post_status' => array( 'pending', 'payment_pending', 'confirmed', 'arrived' ),
919
- 'date_query' => array(
920
- 'before' => date( 'c', $before_time ),
921
- 'after' => date( 'c', $after_time )
922
- )
923
- );
924
-
925
- // If there are multiple locations, a location is selected, and
926
- // max seats has been enabled for this specific location
927
- if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) {
928
-
929
- $tax_query = array(
930
- array(
931
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
932
- 'field' => 'term_id',
933
- 'terms' => $location->term_id
934
- )
935
- );
936
-
937
- $args['tax_query'] = $tax_query;
938
- }
939
-
940
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
941
- $query = new rtbQuery( $args );
942
-
943
- $times = array();
944
- foreach ( $query->get_bookings() as $booking ) {
945
-
946
- if ( isset( $this->ID ) and $booking->ID == $this->ID ) { continue; }
947
-
948
- $booking_time = strtotime( $booking->date );
949
- if ( isset( $times[$booking_time] ) ) { $times[$booking_time] += intval( $booking->party ); }
950
- else { $times[$booking_time] = (int) $booking->party; }
951
- }
952
-
953
- ksort( $times );
954
-
955
- $current_seats = array();
956
- foreach ( $times as $time => $seats ) {
957
-
958
- $current_seats[$time] = $seats;
959
-
960
- reset( $current_seats );
961
-
962
- if ( key ( $current_seats ) < $time - $dining_block_seconds ) { array_shift( $current_seats ); }
963
-
964
- // Check if adding the current party puts us above the max confirmation number
965
- if ( array_sum( $current_seats ) + $this->party > $max_seats ) { return false; }
966
- }
967
-
968
- return true;
969
-
970
- }
971
-
972
- /**
973
- * Check if the information in a booking exactly matches another booking
974
- *
975
- * @return bool
976
- * @since 2.1.20
977
- */
978
- public function is_duplicate_booking() {
979
- global $wpdb, $rtb_controller;
980
-
981
- if( 0 < count($this->validation_errors) ) {
982
- /**
983
- * Do not run this check if there is an error already
984
- * There could abe a moment when someminfo could be missing, which is required
985
- * for this qurey to function.
986
- */
987
- return null;
988
- }
989
-
990
- $valid_status = ['confirmed', 'pending'];
991
-
992
- // This is an intermediate status when payment is pending
993
- if ( $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
994
- $valid_status = array_merge($valid_status, ['payment_pending']);
995
- }
996
-
997
- $args = array_merge(
998
- array(
999
- RTB_BOOKING_POST_TYPE,
1000
- $this->date,
1001
- $this->name
1002
- ),
1003
- $valid_status
1004
- );
1005
-
1006
- $status_placeholder = implode( ',', array_fill( 0, count( $valid_status ), '%s' ) );
1007
-
1008
- $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type=%s AND post_date=%s AND post_title=%s AND post_status IN ({$status_placeholder})";
1009
-
1010
- if ( isset( $this->ID ) ) {
1011
- $sql .= ' AND ID!=%d';
1012
- $args[] = $this->ID;
1013
- }
1014
-
1015
- $booking_result = $wpdb->get_row( $wpdb->prepare( $sql, $args ) );
1016
-
1017
- if ( $booking_result ) {
1018
- $meta = get_post_meta( $booking_result->ID, 'rtb', true );
1019
- $meta = is_array( $meta ) ? $meta : array();
1020
-
1021
- if ( $this->party == $meta['party'] and $this->email == $meta['email'] and $this->phone == $meta['phone'] ) {
1022
-
1023
- return true;
1024
- }
1025
- }
1026
-
1027
- return false;
1028
- }
1029
-
1030
- /**
1031
- * Check whether the number of reservations occurring at the same time is below the threshold
1032
- * where reservations get automatically confirmed
1033
- *
1034
- * @since 2.0.0
1035
- */
1036
- public function is_time_based_deposit_applicable() {
1037
- global $rtb_controller;
1038
-
1039
- $deposit_applicable = is_array( $rtb_controller->settings->get_setting( 'rtb-deposit-schedule' ) ) ? $rtb_controller->settings->get_setting( 'rtb-deposit-schedule' ) : array();
1040
-
1041
- // Get any rules which apply to this weekday
1042
- if ( $deposit_applicable != 'undefined' ) {
1043
-
1044
- $date_object = new DateTime( $this->date );
1045
-
1046
- $time = $date_object->format( 'U' );
1047
-
1048
- $day_of_week = strtolower( $date_object->format( 'l' ) );
1049
-
1050
- foreach ( $deposit_applicable as $applicable ) {
1051
-
1052
- if ( $applicable['weekdays'] !== 'undefined' ) {
1053
-
1054
- foreach ( $applicable['weekdays'] as $weekday => $value ) {
1055
-
1056
- if ( $weekday == $day_of_week ) {
1057
-
1058
- // Closed all day
1059
- if ( $applicable['time'] == 'undefined' ) {
1060
-
1061
- return true;
1062
- }
1063
-
1064
- if ( $applicable['time']['start'] !== 'undefined' ) {
1065
-
1066
- $applicable_start_time = strtotime( $date_object->format( 'Y-m-d' ) . ' ' . $applicable['time']['start'] );
1067
- }
1068
- else {
1069
-
1070
- $applicable_start_time = strtotime( $date_object->format( 'Y-m-d' ) );
1071
- }
1072
-
1073
- if ( $applicable['time']['end'] !== 'undefined' ) {
1074
-
1075
- $applicable_end_time = strtotime( $date_object->format( 'Y-m-d' ) . ' ' . $applicable['time']['end'] );
1076
- }
1077
- else {
1078
- // End of the day
1079
- $applicable_end_time = strtotime( $date_object->format( 'Y-m-d' ) . ' 23:59:59' );
1080
- }
1081
-
1082
- if ( $time > $applicable_start_time and $time < $applicable_end_time ) {
1083
-
1084
- return true;
1085
- }
1086
- }
1087
- }
1088
- }
1089
- }
1090
- }
1091
-
1092
- return false;
1093
- }
1094
-
1095
- /**
1096
- * Check whether the number of reservations occurring at the same time is below the threshold
1097
- * where reservations get automatically confirmed
1098
- *
1099
- * @since 2.0.0
1100
- */
1101
- public function under_max_confirm_reservations() {
1102
- global $rtb_controller;
1103
-
1104
- $max_reservations = (int) $rtb_controller->settings->get_setting( 'auto-confirm-max-reservations' );
1105
-
1106
- if ( $max_reservations == 'undefined' or $max_reservations <= 1 ) { return false; }
1107
-
1108
- $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
1109
-
1110
- $after_time = strtotime($this->date) - $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
1111
- $before_time = strtotime($this->date) + $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
1112
-
1113
- $args = array(
1114
- 'posts_per_page' => -1,
1115
- 'post_status' => ['confirmed', 'arrived'],
1116
- 'date_query' => array(
1117
- 'before' => date( 'c', $before_time ),
1118
- 'after' => date( 'c', $after_time )
1119
- )
1120
- );
1121
-
1122
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1123
- $query = new rtbQuery( $args );
1124
- $query->prepare_args();
1125
-
1126
- $times = array();
1127
- foreach ( $query->get_bookings() as $booking ) {
1128
- $times[] = strtotime( $booking->date );
1129
- }
1130
-
1131
- sort( $times );
1132
-
1133
- $auto_confirm = true;
1134
- $current_times = array();
1135
- foreach ( $times as $time ) {
1136
- $current_times[] = $time;
1137
-
1138
- if ( reset( $current_times ) < ($time - $dining_block_seconds) ) { array_shift( $current_times ); }
1139
-
1140
- // Check if we've reached 1 below the max confirmation number, since adding the current booking will put us at the threshold
1141
- if ( sizeOf( $current_times ) + 1 >= $max_reservations ) { $auto_confirm = false; break; }
1142
- }
1143
-
1144
- return $auto_confirm;
1145
- }
1146
-
1147
- /**
1148
- * Check whether the number of seats occurring at the same time is below the threshold
1149
- * where reservations get automatically confirmed
1150
- *
1151
- * @since 2.0.0
1152
- */
1153
- public function under_max_confirm_seats() {
1154
- global $rtb_controller;
1155
-
1156
- $max_seats = (int) $rtb_controller->settings->get_setting( 'auto-confirm-max-seats' );
1157
-
1158
- if ( $max_seats == 'undefined' or $max_seats < 2 or $this->party >= $max_seats ) { return false; }
1159
-
1160
- $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
1161
-
1162
- $after_time = strtotime($this->date) - $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
1163
- $before_time = strtotime($this->date) + $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
1164
-
1165
- $args = array(
1166
- 'posts_per_page' => -1,
1167
- 'post_status' => ['confirmed', 'arrived'],
1168
- 'date_query' => array(
1169
- 'before' => date( 'c', $before_time ),
1170
- 'after' => date( 'c', $after_time )
1171
- )
1172
- );
1173
-
1174
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1175
- $query = new rtbQuery( $args );
1176
-
1177
- $times = array();
1178
- foreach ( $query->get_bookings() as $booking ) {
1179
- $booking_time = strtotime( $booking->date );
1180
- if ( isset( $times[$booking_time] ) ) { $times[$booking_time] += $booking->party; }
1181
- else { $times[$booking_time] = $booking->party; }
1182
- }
1183
-
1184
- ksort( $times );
1185
-
1186
- $auto_confirm = true;
1187
- $current_seats = array();
1188
- foreach ( $times as $time => $seats ) {
1189
- $current_seats[$time] = $seats;
1190
-
1191
- reset( $current_seats );
1192
-
1193
- if ( key ( $current_seats ) < $time - $dining_block_seconds ) { array_shift( $current_seats ); }
1194
-
1195
- // Check if adding the current party puts us at or above the max confirmation number
1196
- if ( array_sum( $current_seats ) + $this->party >= $max_seats ) { $auto_confirm = false; break; }
1197
- }
1198
-
1199
- return $auto_confirm;
1200
- }
1201
-
1202
- /**
1203
- * Determine what status a booking should have
1204
- *
1205
- * @since 2.1.0
1206
- */
1207
- public function determine_status( $payment_made = false ) {
1208
- global $rtb_controller;
1209
-
1210
- if ( !empty( $_POST['rtb-post-status'] ) && array_key_exists( $_POST['rtb-post-status'], $rtb_controller->cpts->booking_statuses ) ) {
1211
- $post_status = sanitize_text_field( stripslashes_deep( $_POST['rtb-post-status'] ) );
1212
- } elseif ( $rtb_controller->settings->get_setting( 'require-deposit' ) and ! $payment_made ) {
1213
- $post_status = 'payment_pending';
1214
- } elseif ( $this->party < $rtb_controller->settings->get_setting( 'auto-confirm-max-party-size' ) ) {
1215
- $post_status = 'confirmed';
1216
- } elseif ($rtb_controller->settings->get_setting( 'auto-confirm-max-reservations' ) and $this->under_max_confirm_reservations() ) {
1217
- $post_status = 'confirmed';
1218
- } elseif ( $rtb_controller->settings->get_setting( 'auto-confirm-max-seats' ) and $this->under_max_confirm_seats() ) {
1219
- $post_status = 'confirmed';
1220
- } else {
1221
- $post_status = 'pending';
1222
- }
1223
-
1224
- $this->post_status = apply_filters( 'rtb_determine_booking_status', $post_status, $this );
1225
- }
1226
-
1227
- /**
1228
- * Add a log entry to the booking
1229
- *
1230
- * @since 1.3.1
1231
- */
1232
- public function add_log( $type, $title, $message = '', $datetime = null ) {
1233
-
1234
- if ( empty( $datetime ) ) {
1235
- $datetime = date( 'Y-m-d H:i:s');
1236
- }
1237
-
1238
- if ( empty( $this->logs ) ) {
1239
- $this->logs = array();
1240
- }
1241
-
1242
- array_push( $this->logs, array( $type, $title, $message, $datetime ) );
1243
- }
1244
-
1245
- /**
1246
- * Insert post data for a new booking or update a booking
1247
- * @since 0.0.1
1248
- */
1249
- public function insert_post_data() {
1250
-
1251
- $args = array(
1252
- 'post_type' => RTB_BOOKING_POST_TYPE,
1253
- 'post_title' => $this->name,
1254
- 'post_content' => $this->message,
1255
- 'post_date' => $this->date,
1256
- 'post_date_gmt' => get_gmt_from_date( $this->date ), // fix for post_date_gmt not being set for some bookings
1257
- 'post_status' => $this->post_status,
1258
- );
1259
-
1260
- if ( !empty( $this->ID ) ) {
1261
- $args['ID'] = $this->ID;
1262
- }
1263
-
1264
- $args = apply_filters( 'rtb_insert_booking_data', $args, $this );
1265
-
1266
- // When updating a booking, we need to update the metadata first, so that
1267
- // notifications hooked to the status changes go out with the new metadata.
1268
- // If we're inserting a new booking, we have to insert it before we can
1269
- // add metadata, and the default notifications don't fire until it's all done.
1270
- if ( !empty( $this->ID ) ) {
1271
- $this->insert_post_meta();
1272
- $id = wp_insert_post( $args );
1273
- } else {
1274
- $id = wp_insert_post( $args );
1275
- if ( $id && !is_wp_error( $id ) ) {
1276
- $this->ID = $id;
1277
- $this->insert_post_meta();
1278
- }
1279
- }
1280
-
1281
- return !is_wp_error( $id ) && $id !== false;
1282
- }
1283
-
1284
- /**
1285
- * Insert the post metadata for a new booking or when updating a booking
1286
- * @since 1.7.7
1287
- */
1288
- public function insert_post_meta() {
1289
-
1290
- $meta = array(
1291
- 'party' => $this->party,
1292
- 'email' => $this->email,
1293
- 'phone' => $this->phone,
1294
- );
1295
-
1296
- if ( !empty( $this->ip ) ) {
1297
- $meta['ip'] = $this->ip;
1298
- }
1299
-
1300
- if ( empty( $this->date_submission ) ) {
1301
- $meta['date_submission'] = current_time( 'timestamp' );
1302
- } else {
1303
- $meta['date_submission'] = $this->date_submission;
1304
- }
1305
-
1306
- if ( !empty( $this->consent_acquired ) ) {
1307
- $meta['consent_acquired'] = $this->consent_acquired;
1308
- }
1309
-
1310
- if ( !empty( $this->logs ) ) {
1311
- $meta['logs'] = $this->logs;
1312
- }
1313
-
1314
- if ( !empty( $this->table ) ) {
1315
- $meta['table'] = $this->table;
1316
- }
1317
-
1318
- if ( !empty( $this->deposit ) ) {
1319
- $meta['deposit'] = $this->deposit;
1320
- }
1321
-
1322
- if ( !empty( $this->payment_failure_message ) ) {
1323
- $meta['payment_failure_message'] = $this->payment_failure_message;
1324
- }
1325
-
1326
- if ( !empty( $this->receipt_id ) ) {
1327
- $meta['receipt_id'] = $this->receipt_id;
1328
- }
1329
-
1330
- if ( !empty( $this->reminder_sent ) ) {
1331
- $meta['reminder_sent'] = $this->reminder_sent;
1332
- }
1333
-
1334
- if ( !empty( $this->late_arrival_sent ) ) {
1335
- $meta['late_arrival_sent'] = $this->late_arrival_sent;
1336
- }
1337
-
1338
- $meta = apply_filters( 'rtb_insert_booking_metadata', $meta, $this );
1339
-
1340
- return update_post_meta( $this->ID, 'rtb', $meta );
1341
- }
1342
-
1343
- public function payment_paid()
1344
- {
1345
- if( isset( $this->ID ) ) {
1346
- $this->determine_status( true );
1347
-
1348
- $this->insert_post_data();
1349
-
1350
- do_action( 'rtb_booking_paid', $this );
1351
- }
1352
- }
1353
-
1354
- public function payment_failed( $message = '' )
1355
- {
1356
- $this->post_status = 'payment_failed';
1357
- $this->payment_failure_message = $message;
1358
-
1359
- $this->insert_post_data();
1360
-
1361
- do_action( 'rtb_booking_paid', $this );
1362
- }
1363
-
1364
- }
1365
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbBooking' ) ) {
5
+ /**
6
+ * Class to handle a booking for Restaurant Table Bookings
7
+ *
8
+ * @since 0.0.1
9
+ */
10
+ class rtbBooking {
11
+
12
+ /**
13
+ * Whether or not this request has been processed. Used to prevent
14
+ * duplicate forms on one page from processing a booking form more than
15
+ * once.
16
+ * @since 0.0.1
17
+ */
18
+ public $request_processed = false;
19
+
20
+ /**
21
+ * Whether or not this request was successfully saved to the database.
22
+ * @since 0.0.1
23
+ */
24
+ public $request_inserted = false;
25
+
26
+ /**
27
+ * Raw input, clone of $_POST
28
+ * @var array
29
+ * @since 2.3.0
30
+ */
31
+ public $raw_input = array();
32
+
33
+ public function __construct() {}
34
+
35
+ /**
36
+ * Load the booking information from a WP_Post object or an ID
37
+ *
38
+ * @uses load_wp_post()
39
+ * @since 0.0.1
40
+ */
41
+ public function load_post( $post ) {
42
+
43
+ if ( is_int( $post ) || is_string( $post ) ) {
44
+ $post = get_post( $post );
45
+ }
46
+
47
+ if ( get_class( $post ) == 'WP_Post' && $post->post_type == RTB_BOOKING_POST_TYPE ) {
48
+ $this->load_wp_post( $post );
49
+ return true;
50
+ } else {
51
+ return false;
52
+ }
53
+
54
+ }
55
+
56
+ /**
57
+ * Load data from WP post object and retrieve metadata
58
+ *
59
+ * @uses load_post_metadata()
60
+ * @since 0.0.1
61
+ */
62
+ public function load_wp_post( $post ) {
63
+
64
+ // Store post for access to other data if needed by extensions
65
+ $this->post = $post;
66
+
67
+ $this->ID = $post->ID;
68
+ $this->name = $post->post_title;
69
+ $this->date = $post->post_date;
70
+ $this->message = $post->post_content;
71
+ $this->post_status = $post->post_status;
72
+
73
+ $this->load_post_metadata();
74
+
75
+ do_action( 'rtb_booking_load_post_data', $this, $post );
76
+ }
77
+
78
+ /**
79
+ * Store metadata for post
80
+ * @since 0.0.1
81
+ */
82
+ public function load_post_metadata() {
83
+
84
+ $meta_defaults = array(
85
+ 'party' => '',
86
+ 'email' => '',
87
+ 'phone' => '',
88
+ 'date_submission' => '',
89
+ 'logs' => array(),
90
+ 'ip' => '',
91
+ 'consent_acquired' => '',
92
+ 'deposit' => '0',
93
+ 'table' => array(),
94
+ 'payment_failure_message' => '',
95
+ 'receipt_id' => '',
96
+ 'reminder_sent' => false,
97
+ 'late_arrival_sent' => false,
98
+ );
99
+
100
+ $meta_defaults = apply_filters( 'rtb_booking_metadata_defaults', $meta_defaults );
101
+
102
+ if ( is_array( $meta = get_post_meta( $this->ID, 'rtb', true ) ) ) {
103
+ $meta = array_merge( $meta_defaults, get_post_meta( $this->ID, 'rtb', true ) );
104
+ } else {
105
+ $meta = $meta_defaults;
106
+ }
107
+
108
+ $this->party = $meta['party'];
109
+ $this->email = $meta['email'];
110
+ $this->phone = $meta['phone'];
111
+ $this->date_submission = $meta['date_submission'];
112
+ $this->logs = $meta['logs'];
113
+ $this->ip = $meta['ip'];
114
+ $this->consent_acquired = $meta['consent_acquired'];
115
+ $this->deposit = $meta['deposit'];
116
+ $this->table = $meta['table'];
117
+ $this->payment_failure_message = $meta['payment_failure_message'];
118
+ $this->receipt_id = $meta['receipt_id'];
119
+ $this->late_arrival_sent = $meta['late_arrival_sent'];
120
+ $this->reminder_sent = $meta['reminder_sent'];
121
+ }
122
+
123
+ /**
124
+ * Prepare booking data loaded from the database for display in a booking
125
+ * form as request fields. This is used, eg, for splitting datetime values
126
+ * into date and time fields.
127
+ * @since 1.3
128
+ */
129
+ public function prepare_request_data() {
130
+
131
+ // Split $date to $request_date and $request_time
132
+ if ( empty( $this->request_date ) || empty( $this->request_time ) ) {
133
+ $date = new DateTime( $this->date );
134
+ $this->request_date = $date->format( 'Y/m/d' );
135
+ $this->request_time = $date->format( 'h:i A' );
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Format date
141
+ * @since 0.0.1
142
+ */
143
+ public function format_date( $date ) {
144
+ $date = mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $date);
145
+ return apply_filters( 'get_the_date', $date );
146
+ }
147
+
148
+ /**
149
+ * Format a timestamp into a human-readable date
150
+ *
151
+ * @since 1.7.1
152
+ */
153
+ public function format_timestamp( $timestamp ) {
154
+ $time = date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp );
155
+ return $time;
156
+ }
157
+
158
+ /**
159
+ * Calculates the deposit required for a reservation, if any
160
+ *
161
+ * @since 2.1.0
162
+ */
163
+ public function calculate_deposit() {
164
+ global $rtb_controller;
165
+
166
+ $deposit = $rtb_controller->settings->get_setting( 'rtb-deposit-amount' );
167
+
168
+ if ( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ) == 'size_based' ) {
169
+
170
+ $deposit = $this->party < $rtb_controller->settings->get_setting( 'rtb-deposit-min-party-size' ) ? 0 : $deposit;
171
+ }
172
+
173
+ if ( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ) == 'time_based' ) {
174
+
175
+ $deposit = empty( $this->is_time_based_deposit_applicable() ) ? 0 : $deposit;
176
+ }
177
+
178
+ if ( $rtb_controller->settings->get_setting( 'rtb-deposit-type' ) == 'guest' ) { $deposit = $deposit * $this->party; }
179
+
180
+ return $deposit;
181
+ }
182
+
183
+
184
+ /**
185
+ * Insert a new booking submission into the database
186
+ *
187
+ * Validates the data, adds it to the database and executes notifications
188
+ * @since 0.0.1
189
+ */
190
+ public function insert_booking($by_admin = false) {
191
+
192
+ // Check if this request has already been processed. If multiple forms
193
+ // exist on the same page, this prevents a single submission from
194
+ // being added twice.
195
+ if ( $this->request_processed === true ) {
196
+ return true;
197
+ }
198
+
199
+ $this->request_processed = true;
200
+
201
+ if ( empty( $this->ID ) ) {
202
+ $action = 'insert';
203
+ } else {
204
+ $action = 'update';
205
+ }
206
+
207
+ $this->validate_submission($action, $by_admin);
208
+ if ( $this->is_valid_submission() === false ) {
209
+ return false;
210
+ }
211
+
212
+ if ( $this->insert_post_data() === false ) {
213
+ return false;
214
+ } else {
215
+ $this->request_inserted = true;
216
+ }
217
+
218
+ do_action( 'rtb_' . $action . '_booking', $this );
219
+
220
+ return true;
221
+ }
222
+
223
+ /**
224
+ * Validate submission data. Expects to find data in $_POST.
225
+ *
226
+ * ************************** NOTE **************************
227
+ * This function also create and assign all the required member variable with
228
+ * the acurate values which will be insreted in the DB. One special member,
229
+ * raw_input of type array holds the exact copy of $_POST
230
+ *
231
+ * Example:
232
+ * class a {
233
+ * public function a() {
234
+ * $this->name = 'John Doe';
235
+ * }
236
+ * public function b() {
237
+ * echo $this->name;
238
+ * }
239
+ * }
240
+ *
241
+ * $a = new a();
242
+ *
243
+ * var_dump($a);
244
+ * object(a)#1 (0) {
245
+ * }
246
+ *
247
+ * $a->a();
248
+ *
249
+ * var_dump($a);
250
+ * object(a)#1 (1) {
251
+ * ["name"]=>
252
+ * string(8) "John Doe"
253
+ * }
254
+ *
255
+ * $a->b();
256
+ * John Doe
257
+ *
258
+ * @since 0.0.1
259
+ */
260
+ public function validate_submission($action = null, $by_admin = false) {
261
+
262
+ global $rtb_controller;
263
+
264
+ $this->validation_errors = array();
265
+ /**
266
+ * Raw, unprocessed value so that it can be used to preselect the form
267
+ * field values, eg. table
268
+ */
269
+ $this->raw_input = $_POST;
270
+
271
+ do_action( 'rtb_pre_validate_booking_submission', $this );
272
+
273
+ // Date
274
+ $date = empty( $_POST['rtb-date'] ) ? false : stripslashes_deep( $_POST['rtb-date'] );
275
+ if ( $date === false ) {
276
+ $this->validation_errors[] = array(
277
+ 'field' => 'date',
278
+ 'error_msg' => 'Booking request missing date',
279
+ 'message' => __( 'Please enter the date you would like to book.', 'restaurant-reservations' ),
280
+ );
281
+
282
+ } else {
283
+ try {
284
+ $date = new DateTime( stripslashes_deep( $_POST['rtb-date'] ) );
285
+ } catch ( Exception $e ) {
286
+ $this->validation_errors[] = array(
287
+ 'field' => 'date',
288
+ 'error_msg' => $e->getMessage(),
289
+ 'message' => __( 'The date you entered is not valid. Please select from one of the dates in the calendar.', 'restaurant-reservations' ),
290
+ );
291
+ }
292
+ }
293
+
294
+ // Time
295
+ $time = empty( $_POST['rtb-time'] ) ? false : stripslashes_deep( $_POST['rtb-time'] );
296
+ if ( $time === false ) {
297
+ $this->validation_errors[] = array(
298
+ 'field' => 'time',
299
+ 'error_msg' => 'Booking request missing time',
300
+ 'message' => __( 'Please enter the time you would like to book.', 'restaurant-reservations' ),
301
+ );
302
+
303
+ } else {
304
+ try {
305
+ $time = new DateTime( stripslashes_deep( $_POST['rtb-time'] ) );
306
+ } catch ( Exception $e ) {
307
+ $this->validation_errors[] = array(
308
+ 'field' => 'time',
309
+ 'error_msg' => $e->getMessage(),
310
+ 'message' => __( 'The time you entered is not valid. Please select from one of the times provided.', 'restaurant-reservations' ),
311
+ );
312
+ }
313
+ }
314
+
315
+ // Check against valid open dates/times
316
+ if ( is_object( $time ) && is_object( $date ) ) {
317
+
318
+ $request = new DateTime( $date->format( 'Y-m-d' ) . ' ' . $time->format( 'H:i:s' ) );
319
+
320
+ // Exempt Bookings Managers from the early and late bookings restrictions
321
+ if ( !current_user_can( 'manage_bookings' ) ) {
322
+
323
+ $early_bookings = $rtb_controller->settings->get_setting( 'early-bookings' );
324
+ if ( !empty( $early_bookings ) && is_numeric( $early_bookings ) ) {
325
+ $uppar_bound = ( new DateTime( 'now' ) )->setTime( 23, 59 );
326
+ $uppar_bound->add( new DateInterval( "P{$early_bookings}D" ) );
327
+
328
+ if ( $request > $uppar_bound ) {
329
+ $this->validation_errors[] = array(
330
+ 'field' => 'time',
331
+ 'error_msg' => 'Booking request too far in the future',
332
+ 'message' => sprintf( __( 'Sorry, bookings can not be made more than %s days in advance.', 'restaurant-reservations' ), $early_bookings ),
333
+ );
334
+ }
335
+ }
336
+
337
+ $late_bookings = $rtb_controller->settings->get_setting( 'late-bookings' );
338
+ if ( empty( $late_bookings ) ) {
339
+ if ( $request->format( 'U' ) < current_time( 'timestamp' ) ) {
340
+ $this->validation_errors[] = array(
341
+ 'field' => 'time',
342
+ 'error_msg' => 'Booking request in the past',
343
+ 'message' => __( 'Sorry, bookings can not be made in the past.', 'restaurant-reservations' ),
344
+ );
345
+ }
346
+
347
+ } elseif ( $late_bookings === 'same_day' ) {
348
+ if ( $request->format( 'Y-m-d' ) == current_time( 'Y-m-d' ) ) {
349
+ $this->validation_errors[] = array(
350
+ 'field' => 'time',
351
+ 'error_msg' => 'Booking request made on same day',
352
+ 'message' => __( 'Sorry, bookings can not be made for the same day.', 'restaurant-reservations' ),
353
+ );
354
+ }
355
+
356
+ } elseif( is_numeric( $late_bookings ) ) {
357
+ $late_bookings_seconds = $late_bookings * 60; // Late bookings allowance in seconds
358
+ if ( $request->format( 'U' ) < ( current_time( 'timestamp' ) + $late_bookings_seconds ) ) {
359
+ if ( $late_bookings >= 1440 ) {
360
+ $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s days in advance.', 'restaurant-reservations' ), $late_bookings / 1440 );
361
+ } elseif ( $late_bookings >= 60 ) {
362
+ $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s hours in advance.', 'restaurant-reservations' ), $late_bookings / 60 );
363
+ } else {
364
+ $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s minutes in advance.', 'restaurant-reservations' ), $late_bookings );
365
+ }
366
+ $this->validation_errors[] = array(
367
+ 'field' => 'time',
368
+ 'error_msg' => 'Booking request made too close to the reserved time',
369
+ 'message' => $late_bookings_message,
370
+ );
371
+ }
372
+ }
373
+ }
374
+
375
+ // Check against scheduling exception rules
376
+ $exceptions = $rtb_controller->settings->get_setting( 'schedule-closed' );
377
+ if ( empty( $this->validation_errors ) && !empty( $exceptions ) && !current_user_can( 'manage_bookings' ) ) {
378
+ $exception_is_active = false;
379
+ $datetime_is_valid = false;
380
+ foreach( $exceptions as $exception ) {
381
+ $excp_date = new DateTime( $exception['date'] );
382
+ if ( $excp_date->format( 'Y-m-d' ) == $request->format( 'Y-m-d' ) ) {
383
+ $exception_is_active = true;
384
+
385
+ // Closed all day
386
+ if ( empty( $exception['time'] ) ) {
387
+ continue;
388
+ }
389
+
390
+ $excp_start_time = empty( $exception['time']['start'] ) ? $request : new DateTime( $exception['date'] . ' ' . $exception['time']['start'] );
391
+ $excp_end_time = empty( $exception['time']['end'] ) ? $request : new DateTime( $exception['date'] . ' ' . $exception['time']['end'] );
392
+
393
+ if ( $request->format( 'U' ) >= $excp_start_time->format( 'U' ) && $request->format( 'U' ) <= $excp_end_time->format( 'U' ) ) {
394
+ $datetime_is_valid = true;
395
+ break;
396
+ }
397
+ }
398
+ }
399
+
400
+ if ( $exception_is_active && !$datetime_is_valid ) {
401
+ $this->validation_errors[] = array(
402
+ 'field' => 'date',
403
+ 'error_msg' => 'Booking request made on invalid date or time in an exception rule',
404
+ 'message' => __( 'Sorry, no bookings are being accepted then.', 'restaurant-reservations' ),
405
+ );
406
+ }
407
+ }
408
+
409
+ // Check against weekly scheduling rules
410
+ $rules = $rtb_controller->settings->get_setting( 'schedule-open' );
411
+ if ( empty( $exception_is_active ) && empty( $this->validation_errors ) && !empty( $rules ) && !current_user_can( 'manage_bookings' ) ) {
412
+ $request_weekday = strtolower( $request->format( 'l' ) );
413
+ $time_is_valid = null;
414
+ $day_is_valid = null;
415
+ foreach( $rules as $rule ) {
416
+
417
+ if ( !empty( $rule['weekdays'][ $request_weekday ] ) ) {
418
+ $day_is_valid = true;
419
+
420
+ if ( empty( $rule['time'] ) ) {
421
+ $time_is_valid = true; // Days with no time values are open all day
422
+ break;
423
+ }
424
+
425
+ $too_early = true;
426
+ $too_late = true;
427
+
428
+ // Too early
429
+ if ( !empty( $rule['time']['start'] ) ) {
430
+ $rule_start_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['start'] );
431
+ if ( $rule_start_time->format( 'U' ) <= $request->format( 'U' ) ) {
432
+ $too_early = false;
433
+ }
434
+ }
435
+
436
+ // Too late
437
+ if ( !empty( $rule['time']['end'] ) ) {
438
+ $rule_end_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['end'] );
439
+ if ( $rule_end_time->format( 'U' ) >= $request->format( 'U' ) ) {
440
+ $too_late = false;
441
+ }
442
+ }
443
+
444
+ // Valid time found
445
+ if ( $too_early === false && $too_late === false) {
446
+ $time_is_valid = true;
447
+ break;
448
+ }
449
+ }
450
+ }
451
+
452
+ if ( !$day_is_valid ) {
453
+ $this->validation_errors[] = array(
454
+ 'field' => 'date',
455
+ 'error_msg' => 'Booking request made on an invalid date',
456
+ 'message' => __( 'Sorry, no bookings are being accepted on that date.', 'restaurant-reservations' ),
457
+ );
458
+ } elseif ( !$time_is_valid ) {
459
+ $this->validation_errors[] = array(
460
+ 'field' => 'time',
461
+ 'error_msg' => 'Booking request made at an invalid time',
462
+ 'message' => __( 'Sorry, no bookings are being accepted at that time.', 'restaurant-reservations' ),
463
+ );
464
+ }
465
+ }
466
+
467
+ // Accept the date if it has passed validation
468
+ if ( empty( $this->validation_errors ) ) {
469
+ $this->date = $request->format( 'Y-m-d H:i:s' );
470
+ }
471
+ }
472
+
473
+ // Save requested date/time values in case they need to be
474
+ // printed in the form again
475
+ $this->request_date = empty( $_POST['rtb-date'] ) ? '' : stripslashes_deep( $_POST['rtb-date'] );
476
+ $this->request_time = empty( $_POST['rtb-time'] ) ? '' : stripslashes_deep( $_POST['rtb-time'] );
477
+
478
+ // Name
479
+ $this->name = empty( $_POST['rtb-name'] ) ? '' : wp_strip_all_tags( sanitize_text_field( stripslashes_deep( $_POST['rtb-name'] ) ), true ); // @todo should I limit length?
480
+ if ( empty( $this->name ) ) {
481
+ $this->validation_errors[] = array(
482
+ 'field' => 'name',
483
+ 'post_variable' => $this->name,
484
+ 'message' => __( 'Please enter a name for this booking.', 'restaurant-reservations' ),
485
+ );
486
+ }
487
+
488
+ // Party
489
+ $this->party = empty( $_POST['rtb-party'] ) ? '' : absint( $_POST['rtb-party'] );
490
+ if ( empty( $this->party ) ) {
491
+ $this->validation_errors[] = array(
492
+ 'field' => 'party',
493
+ 'post_variable' => $this->party,
494
+ 'message' => __( 'Please let us know how many people will be in your party.', 'restaurant-reservations' ),
495
+ );
496
+
497
+ // Check party size
498
+ } else {
499
+ $party_size = $rtb_controller->settings->get_setting( 'party-size' );
500
+ if ( ! empty( $party_size ) && $party_size < $this->party ) {
501
+ $this->validation_errors[] = array(
502
+ 'field' => 'party',
503
+ 'post_variable' => $this->party,
504
+ 'message' => sprintf( __( 'We only accept bookings for parties of up to %d people.', 'restaurant-reservations' ), $party_size ),
505
+ );
506
+ }
507
+ $party_size_min = $rtb_controller->settings->get_setting( 'party-size-min' );
508
+ if ( ! empty( $party_size_min ) && $party_size_min > $this->party ) {
509
+ $this->validation_errors[] = array(
510
+ 'field' => 'party',
511
+ 'post_variable' => $this->party,
512
+ 'message' => sprintf( __( 'We only accept bookings for parties of more than %d people.', 'restaurant-reservations' ), $party_size_min ),
513
+ );
514
+ }
515
+ }
516
+
517
+ // Email
518
+ $this->email = empty( $_POST['rtb-email'] ) ? '' : sanitize_text_field( stripslashes_deep( $_POST['rtb-email'] ) ); // @todo email validation? send notification back to form on bad email address.
519
+ if ( empty( $this->email ) ) {
520
+ $this->validation_errors[] = array(
521
+ 'field' => 'email',
522
+ 'post_variable' => $this->email,
523
+ 'message' => __( 'Please enter an email address so we can confirm your booking.', 'restaurant-reservations' ),
524
+ );
525
+ } elseif ( !is_email( $this->email ) && apply_filters( 'rtb_require_valid_email', true ) ) {
526
+ $this->validation_errors[] = array(
527
+ 'field' => 'email',
528
+ 'post_variable' => $this->email,
529
+ 'message' => __( 'Please enter a valid email address so we can confirm your booking.', 'restaurant-reservations' ),
530
+ );
531
+ }
532
+
533
+ // Phone
534
+ $this->phone = empty( $_POST['rtb-phone'] ) ? '' : sanitize_text_field( stripslashes_deep( $_POST['rtb-phone'] ) );
535
+ $phone_required = $rtb_controller->settings->get_setting( 'require-phone' );
536
+ if ( $phone_required && empty( $this->phone ) ) {
537
+ $this->validation_errors[] = array(
538
+ 'field' => 'phone',
539
+ 'post_variable' => $this->phone,
540
+ 'message' => __( 'Please provide a phone number so we can confirm your booking.', 'restaurant-reservations' ),
541
+ );
542
+ }
543
+
544
+ // Table
545
+ $table = empty( $_POST['rtb-table'] ) ? array() : explode( ',', sanitize_text_field( stripslashes_deep( $_POST['rtb-table'] ) ) );
546
+ $this->table = is_array( $table ) ? array_map( 'sanitize_text_field', $table ) : array();
547
+
548
+ $table_required = $rtb_controller->settings->get_setting( 'require-table' );
549
+ if ( $table_required && empty( $this->table ) ) {
550
+ $this->validation_errors[] = array(
551
+ 'field' => 'table',
552
+ 'post_variable' => $this->table,
553
+ 'message' => __( 'Please select a table for your booking.', 'restaurant-reservations' ),
554
+ );
555
+ }
556
+
557
+ // check whether there is a time conflict for a particular table
558
+ $valid_table = $this->table ? $this->is_valid_table() : true;
559
+ if ( ! $valid_table ) {
560
+ $this->validation_errors[] = array(
561
+ 'field' => 'table',
562
+ 'post_variable' => $this->table,
563
+ 'message' => __( 'Please select a valid table for your booking.', 'restaurant-reservations' ),
564
+ );
565
+ }
566
+
567
+ // reCAPTCHA
568
+ if ( $rtb_controller->settings->get_setting( 'enable-captcha' ) && !is_admin() ) {
569
+ if ( ! isset($_POST['g-recaptcha-response']) ) {
570
+ $this->validation_errors[] = array(
571
+ 'field' => 'recaptcha',
572
+ 'error_msg' => 'No reCAPTCHA code',
573
+ 'message' => __( 'Please fill out the reCAPTCHA box before submitting.', 'restaurant-reservations' ),
574
+ );
575
+ }
576
+ else {
577
+ $secret_key = $rtb_controller->settings->get_setting( 'captcha-secret-key' );
578
+ $captcha = $_POST['g-recaptcha-response'];
579
+
580
+ $url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . urlencode($secret_key) . '&response=' . urlencode($captcha);
581
+ $json_response = file_get_contents( $url );
582
+ $response = json_decode( $json_response );
583
+
584
+ $reCaptcha_error = false;
585
+ if(json_last_error() != JSON_ERROR_NONE) {
586
+ $response = new stdClass();
587
+ $response->success = false;
588
+ $reCaptcha_error = true;
589
+ if(defined('WP_DEBUG') && WP_DEBUG) {
590
+ error_log('RTB reCAPTCHA error. Raw respose: '.print_r([$json_response], true));
591
+ }
592
+ }
593
+
594
+ if ( ! $response->success ) {
595
+ $message = __( 'Please fill out the reCAPTCHA box again and re-submit.', 'restaurant-reservations' );
596
+ if($reCaptcha_error) {
597
+ $message .= __( ' If you encounter reCAPTCHA error multiple times, please contact us.', 'restaurant-reservations' );
598
+ }
599
+ $this->validation_errors[] = array(
600
+ 'field' => 'recaptcha',
601
+ 'error_msg' => 'Invalid reCAPTCHA code',
602
+ 'message' => $message,
603
+ );
604
+ }
605
+ }
606
+ }
607
+
608
+ // Message
609
+ $this->message = empty( $_POST['rtb-message'] ) ? '' : nl2br( wp_kses_post( stripslashes_deep( $_POST['rtb-message'] ) ) );
610
+
611
+ // Post Status (define a default post status if none passed)
612
+ $this->determine_status();
613
+
614
+ // Consent
615
+ $require_consent = $rtb_controller->settings->get_setting( 'require-consent' );
616
+ $consent_statement = $rtb_controller->settings->get_setting( 'consent-statement' );
617
+ if ( $require_consent && $consent_statement ) {
618
+ // Don't change consent status once initial consent has been collected
619
+ if ( empty( $this->consent_acquired ) ) {
620
+ $this->consent_acquired = !empty( $_POST['rtb-consent-statement'] );
621
+ }
622
+ }
623
+
624
+ // Check if any required fields are empty
625
+ $required_fields = $rtb_controller->settings->get_required_fields();
626
+ foreach( $required_fields as $slug => $field ) {
627
+ if ( !$this->field_has_error( $slug ) && $this->is_field_empty( $slug ) ) {
628
+ $this->validation_errors[] = array(
629
+ 'field' => $slug,
630
+ 'post_variable' => '',
631
+ 'message' => __( 'Please complete this field to request a booking.', 'restaurant-reservations' ),
632
+ );
633
+ }
634
+ }
635
+
636
+ // Check if the email or IP is banned
637
+ if ( !current_user_can( 'manage_bookings' ) ) {
638
+ $ip = $_SERVER['REMOTE_ADDR'];
639
+ if ( !$this->is_valid_ip( $ip ) || !$this->is_valid_email( $this->email ) ) {
640
+ $this->validation_errors[] = array(
641
+ 'field' => 'date',
642
+ 'post_variable' => $ip,
643
+ 'message' => __( 'Your booking has been rejected. Please call us if you would like to make a booking.', 'restaurant-reservations' ),
644
+ );
645
+ } elseif ( empty( $this->ip ) and ! $rtb_controller->settings->get_setting( 'disable-ip-capture' ) ) {
646
+ $this->ip = sanitize_text_field( $ip );
647
+ }
648
+ } elseif ( empty( $this->ip ) and ! $rtb_controller->settings->get_setting( 'disable-ip-capture' ) ) {
649
+ $this->ip = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
650
+ }
651
+
652
+ // Check to make sure that the maximum number of reservations has not already been made
653
+ if ( ! $this->is_under_max_reservations() ){
654
+ $this->validation_errors[] = array(
655
+ 'field' => 'time',
656
+ 'error_msg' => 'maximum reservations exceeded',
657
+ 'message' => __( 'The maximum number of reservations for that timeslot has been reached. Please select a different timeslot.', 'restaurant-reservations' ),
658
+ );
659
+ }
660
+
661
+ // Check to make sure that the maximum number of seats has not already been made
662
+ if ( ! $this->is_under_max_seats() ){
663
+ $this->validation_errors[] = array(
664
+ 'field' => 'time',
665
+ 'error_msg' => 'maximum seats exceeded',
666
+ 'message' => __( 'With your party, the maximum number of seats for that timeslot would be exceeded. Please select a different timeslot or reduce your party size.', 'restaurant-reservations' ),
667
+ );
668
+ }
669
+
670
+ // Check if there is a booking already made with the exact same information, to prevent double bookings on refresh
671
+ if ( (!$by_admin || $by_admin && $action !== 'update') && $this->is_duplicate_booking() ) {
672
+ $this->validation_errors[] = array(
673
+ 'field' => 'date',
674
+ 'error_msg' => 'duplicate booking',
675
+ 'message' => __( 'Your booking and personal information exactly matches another booking. If this was not caused by refreshing the page, please call us to make a booking.', 'restaurant-reservations' ),
676
+ );
677
+ }
678
+
679
+ do_action( 'rtb_validate_booking_submission', $this );
680
+
681
+ }
682
+
683
+ /**
684
+ * Check if submission is valid
685
+ *
686
+ * @since 0.0.1
687
+ */
688
+ public function is_valid_submission() {
689
+
690
+ if ( !count( $this->validation_errors ) ) {
691
+ return true;
692
+ }
693
+
694
+ return false;
695
+ }
696
+
697
+ /**
698
+ * Check if a field already has an error attached to it
699
+ *
700
+ * @field string Field slug
701
+ * @since 1.3
702
+ */
703
+ public function field_has_error( $field_slug ) {
704
+
705
+ foreach( $this->validation_errors as $error ) {
706
+ if ( $error['field'] == $field_slug ) {
707
+ return true;
708
+ }
709
+ }
710
+
711
+ return false;
712
+ }
713
+
714
+ /**
715
+ * Check if a field is missing
716
+ *
717
+ * Checks for empty strings and arrays, but accepts '0'
718
+ * @since 0.1
719
+ */
720
+ public function is_field_empty( $slug ) {
721
+
722
+ $input = isset( $_POST['rtb-' . $slug ] ) ? $_POST['rtb-' . $slug] : '';
723
+
724
+ if ( ( is_string( $input ) && trim( $input ) == '' ) ||
725
+ ( is_array( $input ) && empty( $input ) ) ) {
726
+ return true;
727
+ }
728
+
729
+ return false;
730
+ }
731
+
732
+ /**
733
+ * Check if an IP address has been banned
734
+ *
735
+ * @param string $ip
736
+ * @return bool
737
+ * @since 1.7
738
+ */
739
+ public function is_valid_ip( $ip = null ) {
740
+
741
+ if ( is_null( $ip ) ) {
742
+ $ip = isset( $this->ip ) ? $this->ip : null;
743
+ if ( is_null( $ip ) ) {
744
+ return false;
745
+ }
746
+ }
747
+
748
+ global $rtb_controller;
749
+
750
+ $banned_ips = array_filter( explode( "\n", $rtb_controller->settings->get_setting( 'ban-ips' ) ) );
751
+
752
+ foreach( $banned_ips as $banned_ip ) {
753
+ if ( $ip == trim( $banned_ip ) ) {
754
+ return false;
755
+ }
756
+ }
757
+
758
+ return true;
759
+ }
760
+
761
+ /**
762
+ * Check if an email address has been banned
763
+ *
764
+ * @param string $email
765
+ * @return bool
766
+ * @since 1.7
767
+ */
768
+ public function is_valid_email( $email = null ) {
769
+
770
+ if ( is_null( $email ) ) {
771
+ $email = isset( $this->email ) ? $this->email : null;
772
+ if ( is_null( $email ) ) {
773
+ return false;
774
+ }
775
+ }
776
+
777
+ global $rtb_controller;
778
+
779
+ $banned_emails = array_filter( explode( "\n", $rtb_controller->settings->get_setting( 'ban-emails' ) ) );
780
+
781
+ foreach( $banned_emails as $banned_email ) {
782
+ if ( $email == trim( $banned_email ) ) {
783
+ return false;
784
+ }
785
+ }
786
+
787
+ return true;
788
+ }
789
+
790
+ /**
791
+ * Check if a table(s) is valid (not already taken during a specific timeslot)
792
+ *
793
+ * @return bool
794
+ * @since 2.1.7
795
+ */
796
+ public function is_valid_table() {
797
+ global $rtb_controller;
798
+
799
+ if ( ! $this->table or ! is_array( $this->table ) ) { return false; }
800
+
801
+ $valid_tables = rtb_get_valid_tables( $this->date );
802
+
803
+ if ( isset( $this->ID ) ) {
804
+
805
+ $post_meta = get_post_meta( $this->ID, 'rtb', true );
806
+
807
+ if ( isset( $post_meta['table'] ) and is_array( $post_meta['table'] ) ) { $valid_tables = array_merge( $valid_tables, $post_meta['table'] ); }
808
+ }
809
+
810
+ return $this->table == array_intersect( $this->table, $valid_tables );
811
+ }
812
+
813
+ /**
814
+ * Check if this booking would put the restaurant over the maximum, if set
815
+ *
816
+ * @return bool
817
+ * @since 2.1.20
818
+ */
819
+ public function is_under_max_reservations() {
820
+ global $rtb_controller;
821
+
822
+ $location = ( ! empty( $this->location ) and term_exists( $this->location ) ) ? get_term( $this->location ) : false;
823
+ $location_slug = ! empty( $location ) ? $location->slug : false;
824
+
825
+ $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $location_slug );
826
+
827
+ if ( ! $max_reservations_enabled ) { return true; }
828
+
829
+ $max_reservations = (int) $rtb_controller->settings->get_setting( 'rtb-max-tables-count', $location_slug );
830
+
831
+ if ( $max_reservations == 'undefined' or ! $max_reservations ) { return true; }
832
+
833
+ $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
834
+
835
+ $after_time = strtotime($this->date) - $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
836
+ $before_time = strtotime($this->date) + $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
837
+
838
+ $args = array(
839
+ 'posts_per_page' => -1,
840
+ 'post_status' => array( 'pending', 'payment_pending', 'confirmed', 'arrived' ),
841
+ 'date_query' => array(
842
+ 'before' => date( 'c', $before_time ),
843
+ 'after' => date( 'c', $after_time )
844
+ )
845
+ );
846
+
847
+ // If there are multiple locations, a location is selected, and
848
+ // max seats has been enabled for this specific location
849
+ if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-tables-count', $location_slug ) ) {
850
+
851
+ $tax_query = array(
852
+ array(
853
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
854
+ 'field' => 'term_id',
855
+ 'terms' => $location->term_id
856
+ )
857
+ );
858
+
859
+ $args['tax_query'] = $tax_query;
860
+ }
861
+
862
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
863
+ $query = new rtbQuery( $args );
864
+ $query->prepare_args();
865
+
866
+ $times = array();
867
+ foreach ( $query->get_bookings() as $booking ) {
868
+
869
+ if ( isset( $this->ID ) and $booking->ID == $this->ID ) { continue; }
870
+
871
+ $times[] = strtotime( $booking->date );
872
+ }
873
+
874
+ sort( $times );
875
+
876
+ $current_times = array();
877
+ foreach ( $times as $time ) {
878
+
879
+ $current_times[] = $time;
880
+
881
+ if ( reset( $current_times ) < ( $time - $dining_block_seconds ) ) { array_shift( $current_times ); }
882
+
883
+ // Check if we go above the max confirmation number
884
+ if ( sizeOf( $current_times ) + 1 > $max_reservations ) { return false; }
885
+ }
886
+
887
+ return true;
888
+ }
889
+
890
+ /**
891
+ * Check if this booking would put the restaurant over the maximum number of people, if set
892
+ *
893
+ * @return bool
894
+ * @since 2.1.20
895
+ */
896
+ public function is_under_max_seats() {
897
+ global $rtb_controller;
898
+
899
+ $location = ( ! empty( $this->location ) and term_exists( $this->location ) ) ? get_term( $this->location ) : false;
900
+ $location_slug = ! empty( $location ) ? $location->slug : false;
901
+
902
+ $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $location_slug );
903
+
904
+ if ( ! $max_reservations_enabled ) { return true; }
905
+
906
+ $max_seats = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
907
+
908
+ if ( $max_seats == 'undefined' or ! $max_seats ) { return true; }
909
+ if ( $this->party > $max_seats ) { return false; }
910
+
911
+ $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
912
+
913
+ $after_time = strtotime($this->date) - $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
914
+ $before_time = strtotime($this->date) + $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
915
+
916
+ $args = array(
917
+ 'posts_per_page' => -1,
918
+ 'post_status' => array( 'pending', 'payment_pending', 'confirmed', 'arrived' ),
919
+ 'date_query' => array(
920
+ 'before' => date( 'c', $before_time ),
921
+ 'after' => date( 'c', $after_time )
922
+ )
923
+ );
924
+
925
+ // If there are multiple locations, a location is selected, and
926
+ // max seats has been enabled for this specific location
927
+ if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) {
928
+
929
+ $tax_query = array(
930
+ array(
931
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
932
+ 'field' => 'term_id',
933
+ 'terms' => $location->term_id
934
+ )
935
+ );
936
+
937
+ $args['tax_query'] = $tax_query;
938
+ }
939
+
940
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
941
+ $query = new rtbQuery( $args );
942
+
943
+ $times = array();
944
+ foreach ( $query->get_bookings() as $booking ) {
945
+
946
+ if ( isset( $this->ID ) and $booking->ID == $this->ID ) { continue; }
947
+
948
+ $booking_time = strtotime( $booking->date );
949
+ if ( isset( $times[$booking_time] ) ) { $times[$booking_time] += intval( $booking->party ); }
950
+ else { $times[$booking_time] = (int) $booking->party; }
951
+ }
952
+
953
+ ksort( $times );
954
+
955
+ $current_seats = array();
956
+ foreach ( $times as $time => $seats ) {
957
+
958
+ $current_seats[$time] = $seats;
959
+
960
+ reset( $current_seats );
961
+
962
+ if ( key ( $current_seats ) < $time - $dining_block_seconds ) { array_shift( $current_seats ); }
963
+
964
+ // Check if adding the current party puts us above the max confirmation number
965
+ if ( array_sum( $current_seats ) + $this->party > $max_seats ) { return false; }
966
+ }
967
+
968
+ return true;
969
+
970
+ }
971
+
972
+ /**
973
+ * Check if the information in a booking exactly matches another booking
974
+ *
975
+ * @return bool
976
+ * @since 2.1.20
977
+ */
978
+ public function is_duplicate_booking() {
979
+ global $wpdb, $rtb_controller;
980
+
981
+ if( 0 < count($this->validation_errors) ) {
982
+ /**
983
+ * Do not run this check if there is an error already
984
+ * There could abe a moment when someminfo could be missing, which is required
985
+ * for this qurey to function.
986
+ */
987
+ return null;
988
+ }
989
+
990
+ $valid_status = ['confirmed', 'pending'];
991
+
992
+ // This is an intermediate status when payment is pending
993
+ if ( $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
994
+ $valid_status = array_merge($valid_status, ['payment_pending']);
995
+ }
996
+
997
+ $args = array_merge(
998
+ array(
999
+ RTB_BOOKING_POST_TYPE,
1000
+ $this->date,
1001
+ $this->name
1002
+ ),
1003
+ $valid_status
1004
+ );
1005
+
1006
+ $status_placeholder = implode( ',', array_fill( 0, count( $valid_status ), '%s' ) );
1007
+
1008
+ $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type=%s AND post_date=%s AND post_title=%s AND post_status IN ({$status_placeholder})";
1009
+
1010
+ if ( isset( $this->ID ) ) {
1011
+ $sql .= ' AND ID!=%d';
1012
+ $args[] = $this->ID;
1013
+ }
1014
+
1015
+ $booking_result = $wpdb->get_row( $wpdb->prepare( $sql, $args ) );
1016
+
1017
+ if ( $booking_result ) {
1018
+ $meta = get_post_meta( $booking_result->ID, 'rtb', true );
1019
+ $meta = is_array( $meta ) ? $meta : array();
1020
+
1021
+ if ( $this->party == $meta['party'] and $this->email == $meta['email'] and $this->phone == $meta['phone'] ) {
1022
+
1023
+ return true;
1024
+ }
1025
+ }
1026
+
1027
+ return false;
1028
+ }
1029
+
1030
+ /**
1031
+ * Check whether the number of reservations occurring at the same time is below the threshold
1032
+ * where reservations get automatically confirmed
1033
+ *
1034
+ * @since 2.0.0
1035
+ */
1036
+ public function is_time_based_deposit_applicable() {
1037
+ global $rtb_controller;
1038
+
1039
+ $deposit_applicable = is_array( $rtb_controller->settings->get_setting( 'rtb-deposit-schedule' ) ) ? $rtb_controller->settings->get_setting( 'rtb-deposit-schedule' ) : array();
1040
+
1041
+ // Get any rules which apply to this weekday
1042
+ if ( $deposit_applicable != 'undefined' ) {
1043
+
1044
+ $date_object = new DateTime( $this->date );
1045
+
1046
+ $time = $date_object->format( 'U' );
1047
+
1048
+ $day_of_week = strtolower( $date_object->format( 'l' ) );
1049
+
1050
+ foreach ( $deposit_applicable as $applicable ) {
1051
+
1052
+ if ( $applicable['weekdays'] !== 'undefined' ) {
1053
+
1054
+ foreach ( $applicable['weekdays'] as $weekday => $value ) {
1055
+
1056
+ if ( $weekday == $day_of_week ) {
1057
+
1058
+ // Closed all day
1059
+ if ( $applicable['time'] == 'undefined' ) {
1060
+
1061
+ return true;
1062
+ }
1063
+
1064
+ if ( $applicable['time']['start'] !== 'undefined' ) {
1065
+
1066
+ $applicable_start_time = strtotime( $date_object->format( 'Y-m-d' ) . ' ' . $applicable['time']['start'] );
1067
+ }
1068
+ else {
1069
+
1070
+ $applicable_start_time = strtotime( $date_object->format( 'Y-m-d' ) );
1071
+ }
1072
+
1073
+ if ( $applicable['time']['end'] !== 'undefined' ) {
1074
+
1075
+ $applicable_end_time = strtotime( $date_object->format( 'Y-m-d' ) . ' ' . $applicable['time']['end'] );
1076
+ }
1077
+ else {
1078
+ // End of the day
1079
+ $applicable_end_time = strtotime( $date_object->format( 'Y-m-d' ) . ' 23:59:59' );
1080
+ }
1081
+
1082
+ if ( $time > $applicable_start_time and $time < $applicable_end_time ) {
1083
+
1084
+ return true;
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+ }
1091
+
1092
+ return false;
1093
+ }
1094
+
1095
+ /**
1096
+ * Check whether the number of reservations occurring at the same time is below the threshold
1097
+ * where reservations get automatically confirmed
1098
+ *
1099
+ * @since 2.0.0
1100
+ */
1101
+ public function under_max_confirm_reservations() {
1102
+ global $rtb_controller;
1103
+
1104
+ $max_reservations = (int) $rtb_controller->settings->get_setting( 'auto-confirm-max-reservations' );
1105
+
1106
+ if ( $max_reservations == 'undefined' or $max_reservations <= 1 ) { return false; }
1107
+
1108
+ $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
1109
+
1110
+ $after_time = strtotime($this->date) - $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
1111
+ $before_time = strtotime($this->date) + $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
1112
+
1113
+ $args = array(
1114
+ 'posts_per_page' => -1,
1115
+ 'post_status' => ['confirmed', 'arrived'],
1116
+ 'date_query' => array(
1117
+ 'before' => date( 'c', $before_time ),
1118
+ 'after' => date( 'c', $after_time )
1119
+ )
1120
+ );
1121
+
1122
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1123
+ $query = new rtbQuery( $args );
1124
+ $query->prepare_args();
1125
+
1126
+ $times = array();
1127
+ foreach ( $query->get_bookings() as $booking ) {
1128
+ $times[] = strtotime( $booking->date );
1129
+ }
1130
+
1131
+ sort( $times );
1132
+
1133
+ $auto_confirm = true;
1134
+ $current_times = array();
1135
+ foreach ( $times as $time ) {
1136
+ $current_times[] = $time;
1137
+
1138
+ if ( reset( $current_times ) < ($time - $dining_block_seconds) ) { array_shift( $current_times ); }
1139
+
1140
+ // Check if we've reached 1 below the max confirmation number, since adding the current booking will put us at the threshold
1141
+ if ( sizeOf( $current_times ) + 1 >= $max_reservations ) { $auto_confirm = false; break; }
1142
+ }
1143
+
1144
+ return $auto_confirm;
1145
+ }
1146
+
1147
+ /**
1148
+ * Check whether the number of seats occurring at the same time is below the threshold
1149
+ * where reservations get automatically confirmed
1150
+ *
1151
+ * @since 2.0.0
1152
+ */
1153
+ public function under_max_confirm_seats() {
1154
+ global $rtb_controller;
1155
+
1156
+ $max_seats = (int) $rtb_controller->settings->get_setting( 'auto-confirm-max-seats' );
1157
+
1158
+ if ( $max_seats == 'undefined' or $max_seats < 2 or $this->party >= $max_seats ) { return false; }
1159
+
1160
+ $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60 - 1; // Take 1 second off, to avoid bookings that start or end exactly at the beginning of a booking block
1161
+
1162
+ $after_time = strtotime($this->date) - $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
1163
+ $before_time = strtotime($this->date) + $dining_block_seconds - (3600 * get_option( 'gmt_offset' ) );
1164
+
1165
+ $args = array(
1166
+ 'posts_per_page' => -1,
1167
+ 'post_status' => ['confirmed', 'arrived'],
1168
+ 'date_query' => array(
1169
+ 'before' => date( 'c', $before_time ),
1170
+ 'after' => date( 'c', $after_time )
1171
+ )
1172
+ );
1173
+
1174
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1175
+ $query = new rtbQuery( $args );
1176
+
1177
+ $times = array();
1178
+ foreach ( $query->get_bookings() as $booking ) {
1179
+ $booking_time = strtotime( $booking->date );
1180
+ if ( isset( $times[$booking_time] ) ) { $times[$booking_time] += $booking->party; }
1181
+ else { $times[$booking_time] = $booking->party; }
1182
+ }
1183
+
1184
+ ksort( $times );
1185
+
1186
+ $auto_confirm = true;
1187
+ $current_seats = array();
1188
+ foreach ( $times as $time => $seats ) {
1189
+ $current_seats[$time] = $seats;
1190
+
1191
+ reset( $current_seats );
1192
+
1193
+ if ( key ( $current_seats ) < $time - $dining_block_seconds ) { array_shift( $current_seats ); }
1194
+
1195
+ // Check if adding the current party puts us at or above the max confirmation number
1196
+ if ( array_sum( $current_seats ) + $this->party >= $max_seats ) { $auto_confirm = false; break; }
1197
+ }
1198
+
1199
+ return $auto_confirm;
1200
+ }
1201
+
1202
+ /**
1203
+ * Determine what status a booking should have
1204
+ *
1205
+ * @since 2.1.0
1206
+ */
1207
+ public function determine_status( $payment_made = false ) {
1208
+ global $rtb_controller;
1209
+
1210
+ if ( !empty( $_POST['rtb-post-status'] ) && array_key_exists( $_POST['rtb-post-status'], $rtb_controller->cpts->booking_statuses ) ) {
1211
+ $post_status = sanitize_text_field( stripslashes_deep( $_POST['rtb-post-status'] ) );
1212
+ } elseif ( $rtb_controller->settings->get_setting( 'require-deposit' ) and ! $payment_made ) {
1213
+ $post_status = 'payment_pending';
1214
+ } elseif ( $this->party < $rtb_controller->settings->get_setting( 'auto-confirm-max-party-size' ) ) {
1215
+ $post_status = 'confirmed';
1216
+ } elseif ($rtb_controller->settings->get_setting( 'auto-confirm-max-reservations' ) and $this->under_max_confirm_reservations() ) {
1217
+ $post_status = 'confirmed';
1218
+ } elseif ( $rtb_controller->settings->get_setting( 'auto-confirm-max-seats' ) and $this->under_max_confirm_seats() ) {
1219
+ $post_status = 'confirmed';
1220
+ } else {
1221
+ $post_status = 'pending';
1222
+ }
1223
+
1224
+ $this->post_status = apply_filters( 'rtb_determine_booking_status', $post_status, $this );
1225
+ }
1226
+
1227
+ /**
1228
+ * Add a log entry to the booking
1229
+ *
1230
+ * @since 1.3.1
1231
+ */
1232
+ public function add_log( $type, $title, $message = '', $datetime = null ) {
1233
+
1234
+ if ( empty( $datetime ) ) {
1235
+ $datetime = date( 'Y-m-d H:i:s');
1236
+ }
1237
+
1238
+ if ( empty( $this->logs ) ) {
1239
+ $this->logs = array();
1240
+ }
1241
+
1242
+ array_push( $this->logs, array( $type, $title, $message, $datetime ) );
1243
+ }
1244
+
1245
+ /**
1246
+ * Insert post data for a new booking or update a booking
1247
+ * @since 0.0.1
1248
+ */
1249
+ public function insert_post_data() {
1250
+
1251
+ $args = array(
1252
+ 'post_type' => RTB_BOOKING_POST_TYPE,
1253
+ 'post_title' => $this->name,
1254
+ 'post_content' => $this->message,
1255
+ 'post_date' => $this->date,
1256
+ 'post_date_gmt' => get_gmt_from_date( $this->date ), // fix for post_date_gmt not being set for some bookings
1257
+ 'post_status' => $this->post_status,
1258
+ );
1259
+
1260
+ if ( !empty( $this->ID ) ) {
1261
+ $args['ID'] = $this->ID;
1262
+ }
1263
+
1264
+ $args = apply_filters( 'rtb_insert_booking_data', $args, $this );
1265
+
1266
+ // When updating a booking, we need to update the metadata first, so that
1267
+ // notifications hooked to the status changes go out with the new metadata.
1268
+ // If we're inserting a new booking, we have to insert it before we can
1269
+ // add metadata, and the default notifications don't fire until it's all done.
1270
+ if ( !empty( $this->ID ) ) {
1271
+ $this->insert_post_meta();
1272
+ $id = wp_insert_post( $args );
1273
+ } else {
1274
+ $id = wp_insert_post( $args );
1275
+ if ( $id && !is_wp_error( $id ) ) {
1276
+ $this->ID = $id;
1277
+ $this->insert_post_meta();
1278
+ }
1279
+ }
1280
+
1281
+ return !is_wp_error( $id ) && $id !== false;
1282
+ }
1283
+
1284
+ /**
1285
+ * Insert the post metadata for a new booking or when updating a booking
1286
+ * @since 1.7.7
1287
+ */
1288
+ public function insert_post_meta() {
1289
+
1290
+ $meta = array(
1291
+ 'party' => $this->party,
1292
+ 'email' => $this->email,
1293
+ 'phone' => $this->phone,
1294
+ );
1295
+
1296
+ if ( !empty( $this->ip ) ) {
1297
+ $meta['ip'] = $this->ip;
1298
+ }
1299
+
1300
+ if ( empty( $this->date_submission ) ) {
1301
+ $meta['date_submission'] = current_time( 'timestamp' );
1302
+ } else {
1303
+ $meta['date_submission'] = $this->date_submission;
1304
+ }
1305
+
1306
+ if ( !empty( $this->consent_acquired ) ) {
1307
+ $meta['consent_acquired'] = $this->consent_acquired;
1308
+ }
1309
+
1310
+ if ( !empty( $this->logs ) ) {
1311
+ $meta['logs'] = $this->logs;
1312
+ }
1313
+
1314
+ if ( !empty( $this->table ) ) {
1315
+ $meta['table'] = $this->table;
1316
+ }
1317
+
1318
+ if ( !empty( $this->deposit ) ) {
1319
+ $meta['deposit'] = $this->deposit;
1320
+ }
1321
+
1322
+ if ( !empty( $this->payment_failure_message ) ) {
1323
+ $meta['payment_failure_message'] = $this->payment_failure_message;
1324
+ }
1325
+
1326
+ if ( !empty( $this->receipt_id ) ) {
1327
+ $meta['receipt_id'] = $this->receipt_id;
1328
+ }
1329
+
1330
+ if ( !empty( $this->reminder_sent ) ) {
1331
+ $meta['reminder_sent'] = $this->reminder_sent;
1332
+ }
1333
+
1334
+ if ( !empty( $this->late_arrival_sent ) ) {
1335
+ $meta['late_arrival_sent'] = $this->late_arrival_sent;
1336
+ }
1337
+
1338
+ $meta = apply_filters( 'rtb_insert_booking_metadata', $meta, $this );
1339
+
1340
+ return update_post_meta( $this->ID, 'rtb', $meta );
1341
+ }
1342
+
1343
+ public function payment_paid()
1344
+ {
1345
+ if( isset( $this->ID ) ) {
1346
+ $this->determine_status( true );
1347
+
1348
+ $this->insert_post_data();
1349
+
1350
+ do_action( 'rtb_booking_paid', $this );
1351
+ }
1352
+ }
1353
+
1354
+ public function payment_failed( $message = '' )
1355
+ {
1356
+ $this->post_status = 'payment_failed';
1357
+ $this->payment_failure_message = $message;
1358
+
1359
+ $this->insert_post_data();
1360
+
1361
+ do_action( 'rtb_booking_paid', $this );
1362
+ }
1363
+
1364
+ }
1365
+ } // endif;
includes/Compatibility.class.php CHANGED
@@ -1,191 +1,191 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbCompatibility' ) ) {
5
- /**
6
- * Class to handle backwards compatibilty and cross-plugin compatibility issues
7
- * for Restaurant Reservations
8
- *
9
- * @since 1.3
10
- */
11
- class rtbCompatibility {
12
-
13
- /**
14
- * Set up hooks
15
- */
16
- public function __construct() {
17
-
18
- // Preserve defined constants in case anyone relied on them
19
- // to check if the plugin was active
20
- define( 'RTB_TEXTDOMAIN', 'rtbdomain' );
21
- define( 'RTB_LOAD_FRONTEND_ASSETS', apply_filters( 'rtb-load-frontend-assets', true ) );
22
-
23
- // Load a .mo file for an old textdomain if one exists
24
- add_filter( 'load_textdomain_mofile', array( $this, 'load_old_textdomain' ), 10, 2 );
25
-
26
- // Run a filter deprecrated in 1.4.3
27
- add_filter( 'rtb_bookings_table_views_date_range', array( $this, 'rtn_bookings_table_views_schedule' ) );
28
-
29
- add_action( 'admin_init', array($this, 'check_rtb_version' ) );
30
-
31
- // Make sure custom fields don't completely disappear in 1.5, should no longer be needed
32
- //add_action( 'admin_init', array( $this, 'maybe_bridge_cffrtb_to_1_5' ) );
33
-
34
- }
35
-
36
- /**
37
- * Load a .mo file for an old textdomain if one exists
38
- *
39
- * In versions prior to 1.3, the textdomain did not match the plugin
40
- * slug. This had to be changed to comply with upcoming changes to
41
- * how translations are managed in the .org repo. This function
42
- * checks to see if an old translation file exists and loads it if
43
- * it does, so that people don't lose their translations.
44
- *
45
- * Old textdomain: rtbdomain
46
- */
47
- public function load_old_textdomain( $mofile, $textdomain ) {
48
-
49
- if ( $textdomain === 'restaurant-reservations' && 0 === strpos( $mofile, WP_LANG_DIR . '/plugins/' ) && !file_exists( $mofile ) ) {
50
- $mofile = dirname( $mofile ) . DIRECTORY_SEPARATOR . str_replace( $textdomain, 'rtbdomain', basename( $mofile ) );
51
- }
52
-
53
- return $mofile;
54
- }
55
-
56
- /**
57
- * Run a filter on the admin bookings page display views that was
58
- * deprecrated in v1.4.3
59
- *
60
- * @since 1.4.3
61
- */
62
- public function rtn_bookings_table_views_schedule( $views ) {
63
- return apply_filters( 'rtn_bookings_table_views_schedule', $views );
64
- }
65
-
66
- public function check_rtb_version() {
67
- global $rtb_controller;
68
-
69
- if ( get_option( 'rtb-version' ) != RTB_VERSION ) {
70
- $rtb_controller->permissions->set_permissions();
71
-
72
- update_option( 'rtb-version', RTB_VERSION );
73
- }
74
- }
75
-
76
- /**
77
- * Check whether or not we need to run some compatibiilty code for older
78
- * versions of the Custom Fields addon.
79
- *
80
- * @since 0.1 // THIS FUNCTION SHOULD NO LONGER BE NECESSARY AFTER MERGING CODE BASES
81
- */
82
- /*public function maybe_bridge_cffrtb_to_1_5() {
83
- if ( !function_exists( 'cffrtbInit' ) || !function_exists( 'get_plugin_data' ) ) {
84
- return;
85
- }
86
-
87
- $cffrtb = get_plugin_data( cffrtbInit::$plugin_dir . '/custom-fields-for-rtb.php', false );
88
- if ( !isset( $cffrtb['Version'] ) || $cffrtb['Version'] >= 1.2 ) {
89
- return;
90
- }
91
-
92
- add_filter( 'rtb_bookings_table_column_details', array( $this, 'add_cffrtb_fields_to_details' ), 11, 2 );
93
- }*/
94
-
95
- /**
96
- * Add custom fields output to details column
97
- *
98
- * This function eases the transition to v1.5 for users of the Custom Fields
99
- * addon. Some of the details around how the custom field data is added to
100
- * the bookings table was changed. If the user is using an outdated version
101
- * of Custom Fields, this function will ensure the data gets dropped into
102
- * the new details column. There is still some wonky behavior: the custom
103
- * fields appear as potential columns but adding them won't do anything.
104
- * But this will at least ensure that the data doesn't disappear and users
105
- * can then update to get the full functionality.
106
- *
107
- * @since 1.5 // THIS FUNCTION SHOULD NO LONGER BE NECESSARY AFTER MERGING CODE BASES
108
- */
109
- /*public function add_cffrtb_fields_to_details( $details, $booking ) {
110
-
111
- if ( !isset( $booking->custom_fields ) ) {
112
- return $details;
113
- }
114
-
115
- $fields = cffrtbInit()->fields->get_booking_fields_display_array( $booking );
116
- foreach( $fields as $field ) {
117
- $details[] = array(
118
- 'label' => $field['title'],
119
- 'value' => $field['display_val'],
120
- );
121
- }
122
-
123
- return $details;
124
- }*/
125
- }
126
- } // endif
127
-
128
- /**
129
- * This adds a function missing in PHP versions less than 5.3 which is used to
130
- * properly format non-standard Latin characters in the name portion of an
131
- * email's Reply-To headers. The name variable is passed through this function
132
- * before being added to the headers.
133
- *
134
- * If it detects that the function already exists, it will do nothing.
135
- *
136
- * From: http://php.net/manual/en/function.quoted-printable-encode.php#115840#
137
- */
138
- if ( !function_exists( 'quoted_printable_encode' ) ) {
139
- function quoted_printable_encode($str) {
140
- $php_qprint_maxl = 75;
141
- $lp = 0;
142
- $ret = '';
143
- $hex = "0123456789ABCDEF";
144
- $length = strlen($str);
145
- $str_index = 0;
146
-
147
- while ($length--) {
148
- if ((($c = $str[$str_index++]) == "\015") && ($str[$str_index] == "\012") && $length > 0) {
149
- $ret .= "\015";
150
- $ret .= $str[$str_index++];
151
- $length--;
152
- $lp = 0;
153
- } else {
154
- if (ctype_cntrl($c)
155
- || (ord($c) == 0x7f)
156
- || (ord($c) & 0x80)
157
- || ($c == '=')
158
- || (($c == ' ') && ($str[$str_index] == "\015")))
159
- {
160
- if (($lp += 3) > $php_qprint_maxl)
161
- {
162
- $ret .= '=';
163
- $ret .= "\015";
164
- $ret .= "\012";
165
- $lp = 3;
166
- }
167
- $ret .= '=';
168
- $ret .= $hex[ord($c) >> 4];
169
- $ret .= $hex[ord($c) & 0xf];
170
- }
171
- else
172
- {
173
- if ((++$lp) > $php_qprint_maxl)
174
- {
175
- $ret .= '=';
176
- $ret .= "\015";
177
- $ret .= "\012";
178
- $lp = 1;
179
- }
180
- $ret .= $c;
181
- if($lp == 1 && $c == '.') {
182
- $ret .= '.';
183
- $lp++;
184
- }
185
- }
186
- }
187
- }
188
-
189
- return $ret;
190
- }
191
- } // endif
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbCompatibility' ) ) {
5
+ /**
6
+ * Class to handle backwards compatibilty and cross-plugin compatibility issues
7
+ * for Restaurant Reservations
8
+ *
9
+ * @since 1.3
10
+ */
11
+ class rtbCompatibility {
12
+
13
+ /**
14
+ * Set up hooks
15
+ */
16
+ public function __construct() {
17
+
18
+ // Preserve defined constants in case anyone relied on them
19
+ // to check if the plugin was active
20
+ define( 'RTB_TEXTDOMAIN', 'rtbdomain' );
21
+ define( 'RTB_LOAD_FRONTEND_ASSETS', apply_filters( 'rtb-load-frontend-assets', true ) );
22
+
23
+ // Load a .mo file for an old textdomain if one exists
24
+ add_filter( 'load_textdomain_mofile', array( $this, 'load_old_textdomain' ), 10, 2 );
25
+
26
+ // Run a filter deprecrated in 1.4.3
27
+ add_filter( 'rtb_bookings_table_views_date_range', array( $this, 'rtn_bookings_table_views_schedule' ) );
28
+
29
+ add_action( 'admin_init', array($this, 'check_rtb_version' ) );
30
+
31
+ // Make sure custom fields don't completely disappear in 1.5, should no longer be needed
32
+ //add_action( 'admin_init', array( $this, 'maybe_bridge_cffrtb_to_1_5' ) );
33
+
34
+ }
35
+
36
+ /**
37
+ * Load a .mo file for an old textdomain if one exists
38
+ *
39
+ * In versions prior to 1.3, the textdomain did not match the plugin
40
+ * slug. This had to be changed to comply with upcoming changes to
41
+ * how translations are managed in the .org repo. This function
42
+ * checks to see if an old translation file exists and loads it if
43
+ * it does, so that people don't lose their translations.
44
+ *
45
+ * Old textdomain: rtbdomain
46
+ */
47
+ public function load_old_textdomain( $mofile, $textdomain ) {
48
+
49
+ if ( $textdomain === 'restaurant-reservations' && 0 === strpos( $mofile, WP_LANG_DIR . '/plugins/' ) && !file_exists( $mofile ) ) {
50
+ $mofile = dirname( $mofile ) . DIRECTORY_SEPARATOR . str_replace( $textdomain, 'rtbdomain', basename( $mofile ) );
51
+ }
52
+
53
+ return $mofile;
54
+ }
55
+
56
+ /**
57
+ * Run a filter on the admin bookings page display views that was
58
+ * deprecrated in v1.4.3
59
+ *
60
+ * @since 1.4.3
61
+ */
62
+ public function rtn_bookings_table_views_schedule( $views ) {
63
+ return apply_filters( 'rtn_bookings_table_views_schedule', $views );
64
+ }
65
+
66
+ public function check_rtb_version() {
67
+ global $rtb_controller;
68
+
69
+ if ( get_option( 'rtb-version' ) != RTB_VERSION ) {
70
+ $rtb_controller->permissions->set_permissions();
71
+
72
+ update_option( 'rtb-version', RTB_VERSION );
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Check whether or not we need to run some compatibiilty code for older
78
+ * versions of the Custom Fields addon.
79
+ *
80
+ * @since 0.1 // THIS FUNCTION SHOULD NO LONGER BE NECESSARY AFTER MERGING CODE BASES
81
+ */
82
+ /*public function maybe_bridge_cffrtb_to_1_5() {
83
+ if ( !function_exists( 'cffrtbInit' ) || !function_exists( 'get_plugin_data' ) ) {
84
+ return;
85
+ }
86
+
87
+ $cffrtb = get_plugin_data( cffrtbInit::$plugin_dir . '/custom-fields-for-rtb.php', false );
88
+ if ( !isset( $cffrtb['Version'] ) || $cffrtb['Version'] >= 1.2 ) {
89
+ return;
90
+ }
91
+
92
+ add_filter( 'rtb_bookings_table_column_details', array( $this, 'add_cffrtb_fields_to_details' ), 11, 2 );
93
+ }*/
94
+
95
+ /**
96
+ * Add custom fields output to details column
97
+ *
98
+ * This function eases the transition to v1.5 for users of the Custom Fields
99
+ * addon. Some of the details around how the custom field data is added to
100
+ * the bookings table was changed. If the user is using an outdated version
101
+ * of Custom Fields, this function will ensure the data gets dropped into
102
+ * the new details column. There is still some wonky behavior: the custom
103
+ * fields appear as potential columns but adding them won't do anything.
104
+ * But this will at least ensure that the data doesn't disappear and users
105
+ * can then update to get the full functionality.
106
+ *
107
+ * @since 1.5 // THIS FUNCTION SHOULD NO LONGER BE NECESSARY AFTER MERGING CODE BASES
108
+ */
109
+ /*public function add_cffrtb_fields_to_details( $details, $booking ) {
110
+
111
+ if ( !isset( $booking->custom_fields ) ) {
112
+ return $details;
113
+ }
114
+
115
+ $fields = cffrtbInit()->fields->get_booking_fields_display_array( $booking );
116
+ foreach( $fields as $field ) {
117
+ $details[] = array(
118
+ 'label' => $field['title'],
119
+ 'value' => $field['display_val'],
120
+ );
121
+ }
122
+
123
+ return $details;
124
+ }*/
125
+ }
126
+ } // endif
127
+
128
+ /**
129
+ * This adds a function missing in PHP versions less than 5.3 which is used to
130
+ * properly format non-standard Latin characters in the name portion of an
131
+ * email's Reply-To headers. The name variable is passed through this function
132
+ * before being added to the headers.
133
+ *
134
+ * If it detects that the function already exists, it will do nothing.
135
+ *
136
+ * From: http://php.net/manual/en/function.quoted-printable-encode.php#115840#
137
+ */
138
+ if ( !function_exists( 'quoted_printable_encode' ) ) {
139
+ function quoted_printable_encode($str) {
140
+ $php_qprint_maxl = 75;
141
+ $lp = 0;
142
+ $ret = '';
143
+ $hex = "0123456789ABCDEF";
144
+ $length = strlen($str);
145
+ $str_index = 0;
146
+
147
+ while ($length--) {
148
+ if ((($c = $str[$str_index++]) == "\015") && ($str[$str_index] == "\012") && $length > 0) {
149
+ $ret .= "\015";
150
+ $ret .= $str[$str_index++];
151
+ $length--;
152
+ $lp = 0;
153
+ } else {
154
+ if (ctype_cntrl($c)
155
+ || (ord($c) == 0x7f)
156
+ || (ord($c) & 0x80)
157
+ || ($c == '=')
158
+ || (($c == ' ') && ($str[$str_index] == "\015")))
159
+ {
160
+ if (($lp += 3) > $php_qprint_maxl)
161
+ {
162
+ $ret .= '=';
163
+ $ret .= "\015";
164
+ $ret .= "\012";
165
+ $lp = 3;
166
+ }
167
+ $ret .= '=';
168
+ $ret .= $hex[ord($c) >> 4];
169
+ $ret .= $hex[ord($c) & 0xf];
170
+ }
171
+ else
172
+ {
173
+ if ((++$lp) > $php_qprint_maxl)
174
+ {
175
+ $ret .= '=';
176
+ $ret .= "\015";
177
+ $ret .= "\012";
178
+ $lp = 1;
179
+ }
180
+ $ret .= $c;
181
+ if($lp == 1 && $c == '.') {
182
+ $ret .= '.';
183
+ $lp++;
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ return $ret;
190
+ }
191
+ } // endif
includes/Cron.class.php CHANGED
@@ -1,252 +1,252 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbCron' ) ) {
5
- /**
6
- * This class handles scheduling of cron jobs for different notifications
7
- * such as reservation reminders or when customers are late for their reservations
8
- *
9
- * @since 2.0.0
10
- */
11
- class rtbCron {
12
-
13
- /**
14
- * Adds the necessary filter and action calls
15
- * @since 2.0.0
16
- */
17
- public function __construct() {
18
- add_filter( 'cron_schedules', array($this, 'add_cron_interval') );
19
-
20
- add_action( 'rtb_cron_jobs', array($this, 'handle_late_arrivals_task') );
21
- add_action( 'rtb_cron_jobs', array($this, 'handle_reminder_task') );
22
-
23
- // if ( isset($_GET['debug']) ) { add_action('admin_init', array($this, 'handle_reminder_task') ); } // Used for testing
24
- }
25
-
26
- /**
27
- * Adds in 10 minute cron interval
28
- *
29
- * @var array $schedules
30
- * @since 2.0.0
31
- */
32
- public function add_cron_interval( $schedules ) {
33
- $schedules['ten_minutes'] = array(
34
- 'interval' => 600,
35
- 'display' => esc_html__( 'Every Ten Minutes' )
36
- );
37
-
38
- return $schedules;
39
- }
40
-
41
- /**
42
- * Creates a scheduled action called by wp_cron every 10 minutes
43
- * The class hooks into those calls for reminders and late arrivals
44
- *
45
- * @since 2.0.0
46
- */
47
- public function schedule_events() {
48
- if (! wp_next_scheduled ( 'rtb_cron_jobs' )) {
49
- wp_schedule_event( time(), 'ten_minutes', 'rtb_cron_jobs' );
50
- }
51
- }
52
-
53
- /**
54
- * Clears the rtb_cron_job hook so that it's no longer called after the plugin is deactivated
55
- *
56
- * @since 2.0.0
57
- */
58
- public function unschedule_events() {
59
- wp_clear_scheduled_hook( 'rtb_cron_jobs' );
60
- }
61
-
62
- /**
63
- * Handles the late arrival event when called by wp_scheduler
64
- *
65
- * @since 2.0.0
66
- */
67
- public function handle_late_arrivals_task() {
68
- global $rtb_controller;
69
-
70
- if ( empty( $rtb_controller->settings->get_setting( 'time-late-user' ) ) ) { return; }
71
-
72
- require_once( RTB_PLUGIN_DIR . '/includes/Notification.class.php' );
73
- require_once( RTB_PLUGIN_DIR . '/includes/Notification.Email.class.php' );
74
- require_once( RTB_PLUGIN_DIR . '/includes/Notification.SMS.class.php' );
75
-
76
- $bookings = $this->get_late_arrival_posts();
77
-
78
- foreach ($bookings as $booking) {
79
-
80
- if ( ! $booking->late_arrival_sent ) {
81
- if ( $rtb_controller->settings->get_setting( 'late-notification-format' ) == 'text' ) {
82
- $notification = new rtbNotificationSMS( 'late_user', 'user' );
83
- }
84
- else {
85
- $notification = new rtbNotificationEmail( 'late_user', 'user' );
86
- }
87
-
88
- $notification->set_booking($booking);
89
-
90
- $notification->prepare_notification();
91
-
92
- do_action( 'rtb_send_notification_before', $notification );
93
- $sent = $notification->send_notification();
94
- do_action( 'rtb_send_notification_after', $notification );
95
-
96
- if ( $sent ) {
97
- $booking->late_arrival_sent = true;
98
- $booking->insert_post_meta();
99
- }
100
- }
101
- }
102
-
103
- wp_reset_postdata();
104
- }
105
-
106
- /**
107
- * Handles the notification reminders event when called by wp_scheduler
108
- *
109
- * @since 2.0.0
110
- */
111
- public function handle_reminder_task() {
112
- global $rtb_controller;
113
-
114
- if ( empty( $rtb_controller->settings->get_setting( 'time-reminder-user' ) ) ) { return; }
115
-
116
- require_once( RTB_PLUGIN_DIR . '/includes/Notification.class.php' );
117
- require_once( RTB_PLUGIN_DIR . '/includes/Notification.Email.class.php' );
118
- require_once( RTB_PLUGIN_DIR . '/includes/Notification.SMS.class.php' );
119
-
120
- $bookings = $this->get_reminder_posts();
121
-
122
- foreach ($bookings as $booking) {
123
-
124
- if ( ! $booking->reminder_sent ) {
125
- if ( $rtb_controller->settings->get_setting( 'reminder-notification-format' ) == 'text' ) {
126
- $notification = new rtbNotificationSMS( 'reminder', 'user' );
127
- }
128
- else {
129
- $notification = new rtbNotificationEmail( 'reminder', 'user' );
130
- }
131
-
132
- $notification->set_booking($booking);
133
-
134
- $notification->prepare_notification();
135
-
136
- do_action( 'rtb_send_notification_before', $notification );
137
- $sent = $notification->send_notification();
138
- do_action( 'rtb_send_notification_after', $notification );
139
-
140
- if ( $sent ) {
141
- $booking->reminder_sent = true;
142
- $booking->insert_post_meta();
143
- }
144
- }
145
- }
146
-
147
- wp_reset_postdata();
148
- }
149
-
150
- /**
151
- * Gets the bookings that might need reminders sent to them
152
- *
153
- * @since 2.0.0
154
- */
155
- public function get_late_arrival_posts() {
156
- global $rtb_controller;
157
-
158
- $time_interval = $this->get_time_interval( 'time-late-user' );
159
-
160
- $after_datetime = new DateTime( 'now', wp_timezone() );
161
- $before_datetime = new DateTime( 'now', wp_timezone() );
162
-
163
- $after_datetime->setTimestamp( time() - ( $time_interval + 3600 ) );
164
- $before_datetime->setTimestamp( time() - $time_interval );
165
-
166
- $args = array(
167
- 'post_status' => 'confirmed,',
168
- 'posts_per_page' => -1,
169
- 'date_query' => array(
170
- 'before' => $before_datetime->format( 'Y-m-d H:i:s' ),
171
- 'after' => $after_datetime->format( 'Y-m-d H:i:s' ),
172
- 'column' => 'post_date'
173
- )
174
- );
175
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
176
- $query = new rtbQuery( $args );
177
-
178
- $query->prepare_args();
179
-
180
- return $query->get_bookings();
181
- }
182
-
183
- /**
184
- * Gets the bookings that might need reminders sent to them
185
- *
186
- * @since 2.0.0
187
- */
188
- public function get_reminder_posts() {
189
- global $rtb_controller;
190
-
191
- $time_interval = $this->get_time_interval( 'time-reminder-user' );
192
- $time_interval = new DateInterval( "PT{$time_interval}S" );
193
-
194
- $reminder_time_window_start = new DateTime( 'now', wp_timezone() );
195
- $one_hour = new DateInterval( "PT1H" );
196
- $reminder_time_window_start->sub( $one_hour );
197
-
198
- $reminder_time_window_end = new DateTime( 'now', wp_timezone() );
199
- $reminder_time_window_end->add( $time_interval );
200
-
201
- $args = array(
202
- 'post_status' => 'confirmed,',
203
- 'post_count' => -1,
204
- 'date_query' => array(
205
- 'after' => $reminder_time_window_start->format( 'Y-m-d H:i:s' ),
206
- 'before' => $reminder_time_window_end->format( 'Y-m-d H:i:s' ),
207
- 'column' => 'post_date'
208
- )
209
- );
210
-
211
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
212
- $query = new rtbQuery( $args );
213
-
214
- $query->prepare_args();
215
-
216
- return $query->get_bookings();
217
- }
218
-
219
- /**
220
- * Converts a time unit and interval into its value in seconds
221
- *
222
- * @since 2.0.0
223
- */
224
- public function get_time_interval( $setting ) {
225
- global $rtb_controller;
226
-
227
- $late_arrival_time = $rtb_controller->settings->get_setting( $setting );
228
-
229
- $count = intval( substr( $late_arrival_time, 0, strpos( $late_arrival_time, "_" ) ) );
230
- $unit = substr( $late_arrival_time, strpos( $late_arrival_time, "_" ) + 1 );
231
-
232
- switch ($unit) {
233
- case 'days':
234
- $multiplier = 24*3600;
235
- break;
236
- case 'hours':
237
- $multiplier = 3600;
238
- break;
239
- case 'minutes':
240
- $multiplier = 60;
241
- break;
242
-
243
- default:
244
- $multiplier = 1;
245
- break;
246
- }
247
-
248
- return $count * $multiplier;
249
- }
250
-
251
- }
252
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbCron' ) ) {
5
+ /**
6
+ * This class handles scheduling of cron jobs for different notifications
7
+ * such as reservation reminders or when customers are late for their reservations
8
+ *
9
+ * @since 2.0.0
10
+ */
11
+ class rtbCron {
12
+
13
+ /**
14
+ * Adds the necessary filter and action calls
15
+ * @since 2.0.0
16
+ */
17
+ public function __construct() {
18
+ add_filter( 'cron_schedules', array($this, 'add_cron_interval') );
19
+
20
+ add_action( 'rtb_cron_jobs', array($this, 'handle_late_arrivals_task') );
21
+ add_action( 'rtb_cron_jobs', array($this, 'handle_reminder_task') );
22
+
23
+ // if ( isset($_GET['debug']) ) { add_action('admin_init', array($this, 'handle_reminder_task') ); } // Used for testing
24
+ }
25
+
26
+ /**
27
+ * Adds in 10 minute cron interval
28
+ *
29
+ * @var array $schedules
30
+ * @since 2.0.0
31
+ */
32
+ public function add_cron_interval( $schedules ) {
33
+ $schedules['ten_minutes'] = array(
34
+ 'interval' => 600,
35
+ 'display' => esc_html__( 'Every Ten Minutes' )
36
+ );
37
+
38
+ return $schedules;
39
+ }
40
+
41
+ /**
42
+ * Creates a scheduled action called by wp_cron every 10 minutes
43
+ * The class hooks into those calls for reminders and late arrivals
44
+ *
45
+ * @since 2.0.0
46
+ */
47
+ public function schedule_events() {
48
+ if (! wp_next_scheduled ( 'rtb_cron_jobs' )) {
49
+ wp_schedule_event( time(), 'ten_minutes', 'rtb_cron_jobs' );
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Clears the rtb_cron_job hook so that it's no longer called after the plugin is deactivated
55
+ *
56
+ * @since 2.0.0
57
+ */
58
+ public function unschedule_events() {
59
+ wp_clear_scheduled_hook( 'rtb_cron_jobs' );
60
+ }
61
+
62
+ /**
63
+ * Handles the late arrival event when called by wp_scheduler
64
+ *
65
+ * @since 2.0.0
66
+ */
67
+ public function handle_late_arrivals_task() {
68
+ global $rtb_controller;
69
+
70
+ if ( empty( $rtb_controller->settings->get_setting( 'time-late-user' ) ) ) { return; }
71
+
72
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.class.php' );
73
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.Email.class.php' );
74
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.SMS.class.php' );
75
+
76
+ $bookings = $this->get_late_arrival_posts();
77
+
78
+ foreach ($bookings as $booking) {
79
+
80
+ if ( ! $booking->late_arrival_sent ) {
81
+ if ( $rtb_controller->settings->get_setting( 'late-notification-format' ) == 'text' ) {
82
+ $notification = new rtbNotificationSMS( 'late_user', 'user' );
83
+ }
84
+ else {
85
+ $notification = new rtbNotificationEmail( 'late_user', 'user' );
86
+ }
87
+
88
+ $notification->set_booking($booking);
89
+
90
+ $notification->prepare_notification();
91
+
92
+ do_action( 'rtb_send_notification_before', $notification );
93
+ $sent = $notification->send_notification();
94
+ do_action( 'rtb_send_notification_after', $notification );
95
+
96
+ if ( $sent ) {
97
+ $booking->late_arrival_sent = true;
98
+ $booking->insert_post_meta();
99
+ }
100
+ }
101
+ }
102
+
103
+ wp_reset_postdata();
104
+ }
105
+
106
+ /**
107
+ * Handles the notification reminders event when called by wp_scheduler
108
+ *
109
+ * @since 2.0.0
110
+ */
111
+ public function handle_reminder_task() {
112
+ global $rtb_controller;
113
+
114
+ if ( empty( $rtb_controller->settings->get_setting( 'time-reminder-user' ) ) ) { return; }
115
+
116
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.class.php' );
117
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.Email.class.php' );
118
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.SMS.class.php' );
119
+
120
+ $bookings = $this->get_reminder_posts();
121
+
122
+ foreach ($bookings as $booking) {
123
+
124
+ if ( ! $booking->reminder_sent ) {
125
+ if ( $rtb_controller->settings->get_setting( 'reminder-notification-format' ) == 'text' ) {
126
+ $notification = new rtbNotificationSMS( 'reminder', 'user' );
127
+ }
128
+ else {
129
+ $notification = new rtbNotificationEmail( 'reminder', 'user' );
130
+ }
131
+
132
+ $notification->set_booking($booking);
133
+
134
+ $notification->prepare_notification();
135
+
136
+ do_action( 'rtb_send_notification_before', $notification );
137
+ $sent = $notification->send_notification();
138
+ do_action( 'rtb_send_notification_after', $notification );
139
+
140
+ if ( $sent ) {
141
+ $booking->reminder_sent = true;
142
+ $booking->insert_post_meta();
143
+ }
144
+ }
145
+ }
146
+
147
+ wp_reset_postdata();
148
+ }
149
+
150
+ /**
151
+ * Gets the bookings that might need reminders sent to them
152
+ *
153
+ * @since 2.0.0
154
+ */
155
+ public function get_late_arrival_posts() {
156
+ global $rtb_controller;
157
+
158
+ $time_interval = $this->get_time_interval( 'time-late-user' );
159
+
160
+ $after_datetime = new DateTime( 'now', wp_timezone() );
161
+ $before_datetime = new DateTime( 'now', wp_timezone() );
162
+
163
+ $after_datetime->setTimestamp( time() - ( $time_interval + 3600 ) );
164
+ $before_datetime->setTimestamp( time() - $time_interval );
165
+
166
+ $args = array(
167
+ 'post_status' => 'confirmed,',
168
+ 'posts_per_page' => -1,
169
+ 'date_query' => array(
170
+ 'before' => $before_datetime->format( 'Y-m-d H:i:s' ),
171
+ 'after' => $after_datetime->format( 'Y-m-d H:i:s' ),
172
+ 'column' => 'post_date'
173
+ )
174
+ );
175
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
176
+ $query = new rtbQuery( $args );
177
+
178
+ $query->prepare_args();
179
+
180
+ return $query->get_bookings();
181
+ }
182
+
183
+ /**
184
+ * Gets the bookings that might need reminders sent to them
185
+ *
186
+ * @since 2.0.0
187
+ */
188
+ public function get_reminder_posts() {
189
+ global $rtb_controller;
190
+
191
+ $time_interval = $this->get_time_interval( 'time-reminder-user' );
192
+ $time_interval = new DateInterval( "PT{$time_interval}S" );
193
+
194
+ $reminder_time_window_start = new DateTime( 'now', wp_timezone() );
195
+ $one_hour = new DateInterval( "PT1H" );
196
+ $reminder_time_window_start->sub( $one_hour );
197
+
198
+ $reminder_time_window_end = new DateTime( 'now', wp_timezone() );
199
+ $reminder_time_window_end->add( $time_interval );
200
+
201
+ $args = array(
202
+ 'post_status' => 'confirmed,',
203
+ 'post_count' => -1,
204
+ 'date_query' => array(
205
+ 'after' => $reminder_time_window_start->format( 'Y-m-d H:i:s' ),
206
+ 'before' => $reminder_time_window_end->format( 'Y-m-d H:i:s' ),
207
+ 'column' => 'post_date'
208
+ )
209
+ );
210
+
211
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
212
+ $query = new rtbQuery( $args );
213
+
214
+ $query->prepare_args();
215
+
216
+ return $query->get_bookings();
217
+ }
218
+
219
+ /**
220
+ * Converts a time unit and interval into its value in seconds
221
+ *
222
+ * @since 2.0.0
223
+ */
224
+ public function get_time_interval( $setting ) {
225
+ global $rtb_controller;
226
+
227
+ $late_arrival_time = $rtb_controller->settings->get_setting( $setting );
228
+
229
+ $count = intval( substr( $late_arrival_time, 0, strpos( $late_arrival_time, "_" ) ) );
230
+ $unit = substr( $late_arrival_time, strpos( $late_arrival_time, "_" ) + 1 );
231
+
232
+ switch ($unit) {
233
+ case 'days':
234
+ $multiplier = 24*3600;
235
+ break;
236
+ case 'hours':
237
+ $multiplier = 3600;
238
+ break;
239
+ case 'minutes':
240
+ $multiplier = 60;
241
+ break;
242
+
243
+ default:
244
+ $multiplier = 1;
245
+ break;
246
+ }
247
+
248
+ return $count * $multiplier;
249
+ }
250
+
251
+ }
252
+ } // endif;
includes/CustomFields.class.php CHANGED
@@ -1,193 +1,193 @@
1
- <?php
2
- /**
3
- * A class that handles the main custom field functions
4
- */
5
- if ( ! defined( 'ABSPATH' ) )
6
- exit;
7
-
8
- if ( !class_exists( 'rtbCustomFields' ) ) {
9
- class rtbCustomFields {
10
-
11
- /**
12
- * Option name for storing modified default fields
13
- *
14
- * @since 0.1
15
- */
16
- public $modified_option_key;
17
-
18
- /**
19
- * Common string tacked onto the end of error messages
20
- *
21
- * @since 0.1
22
- */
23
- public $common_error_msg;
24
-
25
- /**
26
- * Initialize the plugin and register hooks
27
- *
28
- * @since 0.1
29
- */
30
- public function __construct() {
31
-
32
- // Option key where information about default fields that have
33
- // been modified and disabled is stored in the database
34
- $this->modified_option_key = apply_filters( 'cffrtb_modified_fields_option_key', 'cffrtb_modified_fields' );
35
-
36
- // Common string tacked onto the end of error messages
37
- $this->common_error_msg = sprintf( _x( 'Please try again. If the problem persists, you may need to refresh the page. If that does not solve the problem, please %scontact support%s for help.', 'A common phrase added to the end of error messages', 'custom-fields-for-rtb' ), '<a href="http://fivestarplugins.com/contact-us/">', '</a>' );
38
-
39
- // Validate user input for custom fields
40
- add_action( 'rtb_validate_booking_submission', array( $this, 'validate_custom_fields_input' ) );
41
-
42
- // Filter required phone setting when phone field is disabled
43
- add_filter( 'rtb-setting-require-phone', array( $this, 'never_require_phone' ) );
44
-
45
- // Insert/load custom field input with booking metadata
46
- add_filter( 'rtb_insert_booking_metadata', array( $this, 'insert_booking_metadata' ), 10, 2 );
47
- add_action( 'rtb_booking_load_post_data', array( $this, 'load_booking_meta_data' ), 10, 2 );
48
-
49
- // Print custom fields in notification template tags
50
- add_filter( 'rtb_notification_template_tags', array( $this, 'add_notification_template_tags' ), 10, 2 );
51
- add_filter( 'rtb_notification_template_tag_descriptions', array( $this, 'add_notification_template_tag_descriptions' ) );
52
-
53
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
54
- }
55
-
56
- public function enqueue_scripts() {
57
- $currentScreen = get_current_screen();
58
- if ( $currentScreen->id == 'bookings_page_cffrtb-editor' ) {
59
- wp_enqueue_style( 'rtb-admin-css', RTB_PLUGIN_URL . '/assets/css/admin.css', array(), RTB_VERSION );
60
- wp_enqueue_script( 'rtb-admin-js', RTB_PLUGIN_URL . '/assets/js/admin.js', array( 'jquery' ), RTB_VERSION, true );
61
- }
62
- }
63
-
64
- /**
65
- * Validate user input for custom fields
66
- *
67
- * @since 0.1
68
- */
69
- public function validate_custom_fields_input( $booking ) {
70
-
71
- $fields = rtb_get_custom_fields();
72
-
73
- if ( !count( $fields ) ) {
74
- return;
75
- }
76
-
77
- foreach( $fields as $field ) {
78
- $validation = $field->validate_input( $booking );
79
- }
80
- }
81
-
82
- /**
83
- * Add custom fields to metadata when a booking is saved
84
- *
85
- * @since 0.1
86
- */
87
- public function insert_booking_metadata( $meta, $booking ) {
88
-
89
- if ( empty( $booking->custom_fields ) ) {
90
- return $meta;
91
- }
92
-
93
- if ( !is_array( $meta ) ) {
94
- $meta = array();
95
- }
96
-
97
- if ( !isset( $meta['custom_fields'] ) ) {
98
- $meta['custom_fields'] = array();
99
- }
100
-
101
- $meta['custom_fields'] = $booking->custom_fields;
102
-
103
- return $meta;
104
- }
105
-
106
- /**
107
- * Add custom fields to metadata when booking is loaded
108
- *
109
- * @since 0.1
110
- */
111
- public function load_booking_meta_data( $booking, $post ) {
112
-
113
- $meta = get_post_meta( $booking->ID, 'rtb', true );
114
-
115
- if ( empty( $meta['custom_fields'] ) ) {
116
- return;
117
- }
118
-
119
- $booking->custom_fields = $meta['custom_fields'];
120
- }
121
-
122
- /**
123
- * Add custom fields as notification template tags
124
- *
125
- * @since 0.1
126
- */
127
- public function add_notification_template_tags( $tags, $notification ) {
128
- global $rtb_controller;
129
-
130
- $fields = rtb_get_custom_fields();
131
-
132
- $cf = isset( $notification->booking->custom_fields ) ? $notification->booking->custom_fields : array();
133
- $checkbox_icon = apply_filters( 'cffrtb_checkbox_icon_notification', '', $notification );
134
-
135
- foreach( $fields as $field ) {
136
-
137
- if ( $field->type == 'fieldset' ) {
138
- continue;
139
- }
140
-
141
- if ( isset( $cf[ $field->slug ] ) ) {
142
- $display_val = apply_filters( 'cffrtb_display_value_notification', $rtb_controller->fields->get_display_value( $cf[ $field->slug ], $field, $checkbox_icon ), $cf[ $field->slug ], $field, $notification );
143
- } else {
144
- $display_val = '';
145
- }
146
- $tags[ '{cf-' . esc_attr( $field->slug ) . '}' ] = $display_val;
147
- }
148
-
149
- return $tags;
150
- }
151
-
152
- /**
153
- * Add custom field notification template tag descriptions
154
- *
155
- * @since 0.1
156
- */
157
- public function add_notification_template_tag_descriptions( $tags ) {
158
-
159
- $fields = rtb_get_custom_fields();
160
-
161
- foreach( $fields as $field ) {
162
-
163
- if ( $field->type == 'fieldset' ) {
164
- continue;
165
- }
166
-
167
- $tags[ '{cf-' . esc_attr( $field->slug ) . '}' ] = esc_html( $field->title );
168
- }
169
-
170
- return $tags;
171
- }
172
-
173
- /**
174
- * Override the required phone setting when the phone field has been
175
- * disabled.
176
- *
177
- * @param string $value The value of the setting
178
- * @since 1.2.3
179
- */
180
- public function never_require_phone( $value ) {
181
- global $rtb_controller;
182
-
183
- $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
184
-
185
- if ( $modified && isset( $modified['phone'] ) && !empty( $modified['phone']['disabled'] ) ) {
186
- return '';
187
- }
188
-
189
- return $value;
190
- }
191
-
192
- }
193
- } // endif;
1
+ <?php
2
+ /**
3
+ * A class that handles the main custom field functions
4
+ */
5
+ if ( ! defined( 'ABSPATH' ) )
6
+ exit;
7
+
8
+ if ( !class_exists( 'rtbCustomFields' ) ) {
9
+ class rtbCustomFields {
10
+
11
+ /**
12
+ * Option name for storing modified default fields
13
+ *
14
+ * @since 0.1
15
+ */
16
+ public $modified_option_key;
17
+
18
+ /**
19
+ * Common string tacked onto the end of error messages
20
+ *
21
+ * @since 0.1
22
+ */
23
+ public $common_error_msg;
24
+
25
+ /**
26
+ * Initialize the plugin and register hooks
27
+ *
28
+ * @since 0.1
29
+ */
30
+ public function __construct() {
31
+
32
+ // Option key where information about default fields that have
33
+ // been modified and disabled is stored in the database
34
+ $this->modified_option_key = apply_filters( 'cffrtb_modified_fields_option_key', 'cffrtb_modified_fields' );
35
+
36
+ // Common string tacked onto the end of error messages
37
+ $this->common_error_msg = sprintf( _x( 'Please try again. If the problem persists, you may need to refresh the page. If that does not solve the problem, please %scontact support%s for help.', 'A common phrase added to the end of error messages', 'custom-fields-for-rtb' ), '<a href="http://fivestarplugins.com/contact-us/">', '</a>' );
38
+
39
+ // Validate user input for custom fields
40
+ add_action( 'rtb_validate_booking_submission', array( $this, 'validate_custom_fields_input' ) );
41
+
42
+ // Filter required phone setting when phone field is disabled
43
+ add_filter( 'rtb-setting-require-phone', array( $this, 'never_require_phone' ) );
44
+
45
+ // Insert/load custom field input with booking metadata
46
+ add_filter( 'rtb_insert_booking_metadata', array( $this, 'insert_booking_metadata' ), 10, 2 );
47
+ add_action( 'rtb_booking_load_post_data', array( $this, 'load_booking_meta_data' ), 10, 2 );
48
+
49
+ // Print custom fields in notification template tags
50
+ add_filter( 'rtb_notification_template_tags', array( $this, 'add_notification_template_tags' ), 10, 2 );
51
+ add_filter( 'rtb_notification_template_tag_descriptions', array( $this, 'add_notification_template_tag_descriptions' ) );
52
+
53
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
54
+ }
55
+
56
+ public function enqueue_scripts() {
57
+ $currentScreen = get_current_screen();
58
+ if ( $currentScreen->id == 'bookings_page_cffrtb-editor' ) {
59
+ wp_enqueue_style( 'rtb-admin-css', RTB_PLUGIN_URL . '/assets/css/admin.css', array(), RTB_VERSION );
60
+ wp_enqueue_script( 'rtb-admin-js', RTB_PLUGIN_URL . '/assets/js/admin.js', array( 'jquery' ), RTB_VERSION, true );
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Validate user input for custom fields
66
+ *
67
+ * @since 0.1
68
+ */
69
+ public function validate_custom_fields_input( $booking ) {
70
+
71
+ $fields = rtb_get_custom_fields();
72
+
73
+ if ( !count( $fields ) ) {
74
+ return;
75
+ }
76
+
77
+ foreach( $fields as $field ) {
78
+ $validation = $field->validate_input( $booking );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Add custom fields to metadata when a booking is saved
84
+ *
85
+ * @since 0.1
86
+ */
87
+ public function insert_booking_metadata( $meta, $booking ) {
88
+
89
+ if ( empty( $booking->custom_fields ) ) {
90
+ return $meta;
91
+ }
92
+
93
+ if ( !is_array( $meta ) ) {
94
+ $meta = array();
95
+ }
96
+
97
+ if ( !isset( $meta['custom_fields'] ) ) {
98
+ $meta['custom_fields'] = array();
99
+ }
100
+
101
+ $meta['custom_fields'] = $booking->custom_fields;
102
+
103
+ return $meta;
104
+ }
105
+
106
+ /**
107
+ * Add custom fields to metadata when booking is loaded
108
+ *
109
+ * @since 0.1
110
+ */
111
+ public function load_booking_meta_data( $booking, $post ) {
112
+
113
+ $meta = get_post_meta( $booking->ID, 'rtb', true );
114
+
115
+ if ( empty( $meta['custom_fields'] ) ) {
116
+ return;
117
+ }
118
+
119
+ $booking->custom_fields = $meta['custom_fields'];
120
+ }
121
+
122
+ /**
123
+ * Add custom fields as notification template tags
124
+ *
125
+ * @since 0.1
126
+ */
127
+ public function add_notification_template_tags( $tags, $notification ) {
128
+ global $rtb_controller;
129
+
130
+ $fields = rtb_get_custom_fields();
131
+
132
+ $cf = isset( $notification->booking->custom_fields ) ? $notification->booking->custom_fields : array();
133
+ $checkbox_icon = apply_filters( 'cffrtb_checkbox_icon_notification', '', $notification );
134
+
135
+ foreach( $fields as $field ) {
136
+
137
+ if ( $field->type == 'fieldset' ) {
138
+ continue;
139
+ }
140
+
141
+ if ( isset( $cf[ $field->slug ] ) ) {
142
+ $display_val = apply_filters( 'cffrtb_display_value_notification', $rtb_controller->fields->get_display_value( $cf[ $field->slug ], $field, $checkbox_icon ), $cf[ $field->slug ], $field, $notification );
143
+ } else {
144
+ $display_val = '';
145
+ }
146
+ $tags[ '{cf-' . esc_attr( $field->slug ) . '}' ] = $display_val;
147
+ }
148
+
149
+ return $tags;
150
+ }
151
+
152
+ /**
153
+ * Add custom field notification template tag descriptions
154
+ *
155
+ * @since 0.1
156
+ */
157
+ public function add_notification_template_tag_descriptions( $tags ) {
158
+
159
+ $fields = rtb_get_custom_fields();
160
+
161
+ foreach( $fields as $field ) {
162
+
163
+ if ( $field->type == 'fieldset' ) {
164
+ continue;
165
+ }
166
+
167
+ $tags[ '{cf-' . esc_attr( $field->slug ) . '}' ] = esc_html( $field->title );
168
+ }
169
+
170
+ return $tags;
171
+ }
172
+
173
+ /**
174
+ * Override the required phone setting when the phone field has been
175
+ * disabled.
176
+ *
177
+ * @param string $value The value of the setting
178
+ * @since 1.2.3
179
+ */
180
+ public function never_require_phone( $value ) {
181
+ global $rtb_controller;
182
+
183
+ $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
184
+
185
+ if ( $modified && isset( $modified['phone'] ) && !empty( $modified['phone']['disabled'] ) ) {
186
+ return '';
187
+ }
188
+
189
+ return $value;
190
+ }
191
+
192
+ }
193
+ } // endif;
includes/CustomPostTypes.class.php CHANGED
@@ -1,444 +1,445 @@
1
- <?php
2
- /**
3
- * Class to handle all custom post type definitions for Restaurant Reservations
4
- */
5
-
6
- if ( !defined( 'ABSPATH' ) )
7
- exit;
8
-
9
- if ( !class_exists( 'rtbCustomPostTypes' ) ) {
10
- class rtbCustomPostTypes {
11
-
12
- // Array of valid post statuses
13
- // @sa set_booking_statuses()
14
- public $booking_statuses = array();
15
-
16
- // Cached select fields for booking statuses
17
- public $status_select_html = array();
18
-
19
- public function __construct() {
20
-
21
- // Call when plugin is initialized on every page load
22
- add_action( 'init', array( $this, 'load_cpts' ) );
23
-
24
- // Set up $booking_statuses array and register new post statuses
25
- add_action( 'init', array( $this, 'set_booking_statuses' ) );
26
- add_filter( 'rtb_post_statuses_args' , array( $this, 'add_arrived_status' ) );
27
- add_filter( 'rtb_post_statuses_args' , array( $this, 'add_cancelled_status' ) );
28
- add_filter( 'rtb_post_statuses_args' , array( $this, 'add_payment_failed_status' ) );
29
-
30
- // Display the count of pending bookings
31
- add_action( 'admin_footer', array( $this, 'show_pending_count' ) );
32
-
33
- // Maintain the count of pending bookings
34
- add_action( 'rtb_insert_booking', array( $this, 'update_pending_count' ) );
35
- add_action( 'rtb_update_booking', array( $this, 'update_pending_count' ) );
36
- add_action( 'transition_post_status', array( $this, 'maybe_update_pending_count' ), 999, 3 );
37
-
38
- }
39
-
40
- /**
41
- * Initialize custom post types
42
- * @since 0.1
43
- */
44
- public function load_cpts() {
45
- global $rtb_controller;
46
-
47
- // Define the booking custom post type
48
- $args = array(
49
- 'labels' => array(
50
- 'name' => __( 'Bookings', 'restaurant-reservations' ),
51
- 'singular_name' => __( 'Booking', 'restaurant-reservations' ),
52
- 'menu_name' => __( 'Bookings', 'restaurant-reservations' ),
53
- 'name_admin_bar' => __( 'Bookings', 'restaurant-reservations' ),
54
- 'add_new' => __( 'Add New', 'restaurant-reservations' ),
55
- 'add_new_item' => __( 'Add New Booking', 'restaurant-reservations' ),
56
- 'edit_item' => __( 'Edit Booking', 'restaurant-reservations' ),
57
- 'new_item' => __( 'New Booking', 'restaurant-reservations' ),
58
- 'view_item' => __( 'View Booking', 'restaurant-reservations' ),
59
- 'search_items' => __( 'Search Bookings', 'restaurant-reservations' ),
60
- 'not_found' => __( 'No bookings found', 'restaurant-reservations' ),
61
- 'not_found_in_trash' => __( 'No bookings found in trash', 'restaurant-reservations' ),
62
- 'all_items' => __( 'All Bookings', 'restaurant-reservations' ),
63
- ),
64
- 'menu_icon' => 'dashicons-calendar',
65
- 'public' => false,
66
- 'supports' => array(
67
- 'title',
68
- 'revisions'
69
- )
70
- );
71
-
72
- // Create filter so addons can modify the arguments
73
- $args = apply_filters( 'rtb_booking_args', $args );
74
-
75
- // Add an action so addons can hook in before the post type is registered
76
- do_action( 'rtb_booking_pre_register' );
77
-
78
- // Register the post type
79
- register_post_type( RTB_BOOKING_POST_TYPE, $args );
80
-
81
- // Add an action so addons can hook in after the post type is registered
82
- do_action( 'rtb_booking_post_register' );
83
-
84
- if ( $rtb_controller->permissions->check_permission( 'custom_fields' ) ) {
85
- // Define the field custom post type
86
- $args = array(
87
- 'labels' => array(
88
- 'name' => __( 'Field', 'custom-fields-for-rtb' ),
89
- 'singular_name' => __( 'Field', 'custom-fields-for-rtb' ),
90
- 'menu_name' => __( 'Fields', 'custom-fields-for-rtb' ),
91
- 'name_admin_bar' => __( 'Fields', 'custom-fields-for-rtb' ),
92
- 'add_new' => __( 'Add Field', 'custom-fields-for-rtb' ),
93
- 'add_new_item' => __( 'Add New Field', 'custom-fields-for-rtb' ),
94
- 'edit_item' => __( 'Edit Field', 'custom-fields-for-rtb' ),
95
- 'new_item' => __( 'New Field', 'custom-fields-for-rtb' ),
96
- 'view_item' => __( 'View Field', 'custom-fields-for-rtb' ),
97
- 'search_items' => __( 'Search Fields', 'custom-fields-for-rtb' ),
98
- 'not_found' => __( 'No fields found', 'custom-fields-for-rtb' ),
99
- 'not_found_in_trash' => __( 'No fields found in trash', 'custom-fields-for-rtb' ),
100
- 'all_items' => __( 'All Fields', 'custom-fields-for-rtb' ),
101
- ),
102
- 'public' => false
103
- );
104
-
105
- $args = apply_filters( 'cffrtb_field_post_type_args', $args );
106
-
107
- register_post_type( 'cffrtb_field', $args );
108
- }
109
- }
110
-
111
- /**
112
- * Set an array of valid booking statuses and register any custom statuses
113
- * @since 0.0.1
114
- */
115
- public function set_booking_statuses() {
116
-
117
- $this->booking_statuses['pending'] = array(
118
- 'label' => _x( 'Pending', 'Booking status when it is pending review', 'restaurant-reservations' ),
119
- 'default' => true, // Whether or not this status is part of WP Core
120
- 'user_selectable' => true, // Whether or not a user can set a booking to this status
121
- );
122
-
123
- $this->booking_statuses['confirmed'] = array (
124
- 'label' => _x( 'Confirmed', 'Booking status for a confirmed booking', 'restaurant-reservations' ),
125
- 'default' => false, // Whether or not this status is part of WP Core
126
- 'user_selectable' => true, // Whether or not a user can set a booking to this status
127
- 'public' => false,
128
- 'exclude_from_search' => true,
129
- 'show_in_admin_all_list' => true,
130
- 'show_in_admin_status_list' => true,
131
- 'label_count' => _n_noop( 'Confirmed <span class="count">(%s)</span>', 'Confirmed <span class="count">(%s)</span>', 'restaurant-reservations' ),
132
- );
133
-
134
- $this->booking_statuses['closed'] = array(
135
- 'label' => _x( 'Closed', 'Booking status for a closed booking', 'restaurant-reservations' ),
136
- 'default' => false, // Whether or not this status is part of WP Core
137
- 'user_selectable' => true, // Whether or not a user can set a booking to this status
138
- 'public' => false,
139
- 'exclude_from_search' => true,
140
- 'show_in_admin_all_list' => true,
141
- 'show_in_admin_status_list' => true,
142
- 'label_count' => _n_noop( 'Closed <span class="count">(%s)</span>', 'Closed <span class="count">(%s)</span>', 'restaurant-reservations' )
143
- );
144
-
145
- // Let addons hook in to add/edit/remove post statuses
146
- $this->booking_statuses = apply_filters( 'rtb_post_statuses_args', $this->booking_statuses );
147
-
148
- // Register the custom post statuses
149
- foreach ( $this->booking_statuses as $status => $args ) {
150
- if ( $args['default'] === false ) {
151
- register_post_status( $status, $args );
152
- }
153
- }
154
-
155
- }
156
-
157
-
158
- /**
159
- * @since 2.1.0
160
- * Adds in a "Cancelled" status if the option to allow guest to cancel
161
- * their reservation has been toggled on.
162
- */
163
- public function add_cancelled_status( $booking_statuses = array() ) {
164
- global $rtb_controller;
165
-
166
- if ( $rtb_controller->settings->get_setting( 'allow-cancellations' ) ) {
167
- $booking_statuses['cancelled'] = array(
168
- 'label' => _x( 'Cancelled', 'The guest has cancelled their reservation themselves.', 'restaurant-reservations' ),
169
- 'default' => false, // Whether or not this status is part of WP Core
170
- 'user_selectable' => false, // Whether or not a user can set a booking to this status
171
- 'public' => false,
172
- 'exclude_from_search' => true,
173
- 'show_in_admin_all_list' => true,
174
- 'show_in_admin_status_list' => true,
175
- 'label_count' => _n_noop( 'Cancelled <span class="count">(%s)</span>', 'Cancelled <span class="count">(%s)</span>', 'restaurant-reservations' )
176
- );
177
- }
178
-
179
- return $booking_statuses;
180
- }
181
-
182
-
183
- /**
184
- * @since 2.0.0
185
- * Adds in an "Arrived" status if the option to check guests in on arrival
186
- * has been toggled on.
187
- */
188
- public function add_arrived_status( $booking_statuses = array() ) {
189
- global $rtb_controller;
190
-
191
- if ( $rtb_controller->settings->get_setting( 'view-bookings-arrivals' ) ) {
192
- $booking_statuses['arrived'] = array(
193
- 'label' => _x( 'Arrived', 'The guests have arrived for their reservation', 'restaurant-reservations' ),
194
- 'default' => false, // Whether or not this status is part of WP Core
195
- 'user_selectable' => true, // Whether or not a user can set a booking to this status
196
- 'public' => false,
197
- 'exclude_from_search' => true,
198
- 'show_in_admin_all_list' => true,
199
- 'show_in_admin_status_list' => true,
200
- 'label_count' => _n_noop( 'Arrived <span class="count">(%s)</span>', 'Arrived <span class="count">(%s)</span>', 'restaurant-reservations' )
201
- );
202
- }
203
-
204
- return $booking_statuses;
205
- }
206
-
207
- /**
208
- * @since 2.1.9
209
- * Adds in a "Payment Failed" status if the option to require deposits when
210
- * booking a reservation has been toggled on.
211
- */
212
- public function add_payment_failed_status( $booking_statuses = array() ) {
213
- global $rtb_controller;
214
-
215
- if ( $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
216
- $booking_statuses['payment_failed'] = array(
217
- 'label' => _x( 'Payment Failed', 'The guest has tried to make a payment but it was declined.', 'restaurant-reservations' ),
218
- 'default' => false, // Whether or not this status is part of WP Core
219
- 'user_selectable' => false, // Whether or not a user can set a booking to this status
220
- 'public' => false,
221
- 'exclude_from_search' => true,
222
- 'show_in_admin_all_list' => true,
223
- 'show_in_admin_status_list' => true,
224
- 'label_count' => _n_noop( 'Payment Failed <span class="count">(%s)</span>', 'Payment Failed <span class="count">(%s)</span>', 'restaurant-reservations' )
225
- );
226
-
227
- // This is an intermediate status when payment is pending
228
- $booking_statuses['payment_pending'] = array(
229
- 'label' => _x( 'Payment Pending', 'The guest has booked but payment is pending', 'restaurant-reservations' ),
230
- 'default' => false, // Whether or not this status is part of WP Core
231
- 'user_selectable' => false, // Whether or not a user can set a booking to this status
232
- 'public' => false,
233
- 'exclude_from_search' => true,
234
- 'show_in_admin_all_list' => true,
235
- 'show_in_admin_status_list' => true,
236
- 'label_count' => _n_noop( 'Payment Pending <span class="count">(%s)</span>', 'Payment Pending <span class="count">(%s)</span>', 'restaurant-reservations' )
237
- );
238
- }
239
-
240
- return $booking_statuses;
241
- }
242
-
243
- /**
244
- * Print an HTML element to select a booking status
245
- * @since 0.0.1
246
- * @note This is no longer used in the bookings table, but it could be
247
- * useful in the future, so leave it in for now (0.0.1) until the plugin is
248
- * more fleshed out.
249
- */
250
- public function print_booking_status_select( $current = false ) {
251
-
252
- if ( $current === false ) {
253
- $current = 'none';
254
- }
255
-
256
- // Output stored select field if available
257
- if ( !empty( $this->status_select_html[$current] ) ) {
258
- return $this->status_select_html[$current];
259
- }
260
-
261
- ob_start();
262
- ?>
263
-
264
- <select name="rtb-select-status">
265
- <?php foreach ( $this->booking_statuses as $status => $args ) : ?>
266
- <?php if ( $args['user_selectable'] === true ) : ?>
267
- <option value="<?php echo esc_attr( $status ); ?>"<?php echo $status == $current ? ' selected="selected"' : ''; ?>><?php echo esc_attr( $args['label'] ); ?></option>
268
- <?php endif; ?>
269
- <?php endforeach; ?>
270
- </select>
271
-
272
- <?php
273
- $output = ob_get_clean();
274
-
275
- // Store output so we don't need to loop for every row
276
- $this->status_select_html[$current] = $output;
277
-
278
- return $output;
279
-
280
- }
281
-
282
- /**
283
- * Delete a booking request (or send to trash)
284
- *
285
- * @since 0.0.1
286
- */
287
- public function delete_booking( $id ) {
288
-
289
- $id = absint( $id );
290
- if ( !current_user_can( 'manage_bookings' ) ) {
291
- return false;
292
- }
293
-
294
- $booking = get_post( $id );
295
-
296
- if ( !$this->is_valid_booking_post_object( $booking ) ) {
297
- return false;
298
- }
299
-
300
- // If we're already looking at trashed posts, delete it for good.
301
- // Otherwise, just send it to trash.
302
- if ( !empty( $_GET['status'] ) && $_GET['status'] == 'trash' ) {
303
- $screen = get_current_screen();
304
- if ( $screen->base == 'toplevel_page_rtb-bookings' ) {
305
- $result = wp_delete_post( $id, true );
306
- }
307
- } else {
308
- $result = wp_trash_post( $id );
309
- }
310
-
311
- if ( $result === false ) {
312
- return false;
313
- } else {
314
- return true;
315
- }
316
- }
317
-
318
- /**
319
- * Update a booking status.
320
- * @since 0.0.1
321
- */
322
- function update_booking_status( $id, $status ) {
323
-
324
- $id = absint( $id );
325
- if ( !current_user_can( 'manage_bookings' ) ) {
326
- return false;
327
- }
328
-
329
- if ( !$this->is_valid_booking_status( $status ) ) {
330
- return false;
331
- }
332
-
333
- $booking = get_post( $id );
334
-
335
- if ( !$this->is_valid_booking_post_object( $booking ) ) {
336
- return false;
337
- }
338
-
339
- if ( $booking->post_status === $status ) {
340
- return null;
341
- }
342
-
343
- $result = wp_update_post(
344
- array(
345
- 'ID' => $id,
346
- 'post_status' => $status,
347
- 'edit_date' => current_time( 'mysql' ),
348
- )
349
- );
350
-
351
- return $result ? true : false;
352
- }
353
-
354
- /**
355
- * Check if status is valid for bookings
356
- * @since 0.0.1
357
- */
358
- public function is_valid_booking_status( $status ) {
359
- return isset( $this->booking_statuses[$status] ) ? true : false;
360
- }
361
-
362
- /**
363
- * Check if booking is a valid Post object with the correct post type
364
- * @since 0.0.1
365
- */
366
- public function is_valid_booking_post_object( $booking ) {
367
- return !is_wp_error( $booking ) && is_object( $booking ) && $booking->post_type == RTB_BOOKING_POST_TYPE;
368
- }
369
-
370
- /**
371
- * Show the count of upcoming pending bookings in admin nav menu
372
- *
373
- * This is hooked to admin_footer to ensure that any actions on the page
374
- * which might effect booking statuses have already fired, such as the
375
- * bulk actions on the bookings page, which are processed after the nav
376
- * menu has been loaded.
377
- *
378
- * @since 1.7.5
379
- */
380
- public function show_pending_count() {
381
-
382
- global $rtb_controller;
383
- $rtb_controller->cpts->update_pending_count();
384
- $pending_count = get_option( 'rtb_pending_count', 0 );
385
-
386
- if ( !$pending_count ) {
387
- return;
388
- }
389
-
390
- $pending_bubble = ' <span class="update-plugins count-' . (int) $pending_count . '">' .
391
- '<span class="plugin-count" aria-hidden="true">' . (int) $pending_count . '</span></span>';
392
-
393
- ?>
394
-
395
- <script type="text/javascript">
396
- jQuery(document).ready(function ($) {
397
- $( '#toplevel_page_rtb-bookings > a .wp-menu-name' ).append( '<?php echo $pending_bubble; ?>' );
398
- });
399
- </script>
400
-
401
- <?php
402
- }
403
-
404
- /**
405
- * Update the count of upcoming pending bookings
406
- *
407
- * This is hooked to fire whenever a booking is added or updated. But it can
408
- * also be called directly once to set the initial option.
409
- *
410
- * @param rtbBooking $booking Optional. The booking being added or updated.
411
- * @since 1.7.5
412
- */
413
- public function update_pending_count( $booking = null ) {
414
-
415
- global $wpdb;
416
- $current_date_time = date( 'Y-m-d H:i:s', current_time( 'timestamp' ) - HOUR_IN_SECONDS );
417
- $count = $wpdb->get_var(
418
- $wpdb->prepare(
419
- "SELECT COUNT(ID) FROM {$wpdb->prefix}posts WHERE post_type=%s AND post_status='pending' AND post_date>=%s;",
420
- RTB_BOOKING_POST_TYPE,
421
- $current_date_time
422
- )
423
- );
424
-
425
- update_option( 'rtb_pending_count', (int) $count );
426
- }
427
-
428
- /**
429
- * Update the count of upcoming pending bookings whenever a booking status
430
- * is modified
431
- *
432
- * @param string $new_status The status being transitioned to
433
- * @param string $old_status The status the post used to have
434
- * @param WP_Post $post The post being transitioned
435
- * @since 1.7.5
436
- */
437
- public function maybe_update_pending_count( $new_status, $old_status, $post ) {
438
- if ( $post->post_type === RTB_BOOKING_POST_TYPE ) {
439
- $this->update_pending_count();
440
- }
441
- }
442
-
443
- }
444
- } // endif;
 
1
+ <?php
2
+ /**
3
+ * Class to handle all custom post type definitions for Restaurant Reservations
4
+ */
5
+
6
+ if ( !defined( 'ABSPATH' ) )
7
+ exit;
8
+
9
+ if ( !class_exists( 'rtbCustomPostTypes' ) ) {
10
+ class rtbCustomPostTypes {
11
+
12
+ // Array of valid post statuses
13
+ // @sa set_booking_statuses()
14
+ public $booking_statuses = array();
15
+
16
+ // Cached select fields for booking statuses
17
+ public $status_select_html = array();
18
+
19
+ public function __construct() {
20
+
21
+ // Call when plugin is initialized on every page load
22
+ add_action( 'init', array( $this, 'load_cpts' ) );
23
+
24
+ // Set up $booking_statuses array and register new post statuses
25
+ add_action( 'init', array( $this, 'set_booking_statuses' ) );
26
+ add_filter( 'rtb_post_statuses_args' , array( $this, 'add_arrived_status' ) );
27
+ add_filter( 'rtb_post_statuses_args' , array( $this, 'add_cancelled_status' ) );
28
+ add_filter( 'rtb_post_statuses_args' , array( $this, 'add_payment_failed_status' ) );
29
+
30
+ // Display the count of pending bookings
31
+ add_action( 'admin_footer', array( $this, 'show_pending_count' ) );
32
+
33
+ // Maintain the count of pending bookings
34
+ add_action( 'rtb_insert_booking', array( $this, 'update_pending_count' ) );
35
+ add_action( 'rtb_update_booking', array( $this, 'update_pending_count' ) );
36
+ add_action( 'transition_post_status', array( $this, 'maybe_update_pending_count' ), 999, 3 );
37
+
38
+ }
39
+
40
+ /**
41
+ * Initialize custom post types
42
+ * @since 0.1
43
+ */
44
+ public function load_cpts() {
45
+ global $rtb_controller;
46
+
47
+ // Define the booking custom post type
48
+ $args = array(
49
+ 'labels' => array(
50
+ 'name' => __( 'Bookings', 'restaurant-reservations' ),
51
+ 'singular_name' => __( 'Booking', 'restaurant-reservations' ),
52
+ 'menu_name' => __( 'Bookings', 'restaurant-reservations' ),
53
+ 'name_admin_bar' => __( 'Bookings', 'restaurant-reservations' ),
54
+ 'add_new' => __( 'Add New', 'restaurant-reservations' ),
55
+ 'add_new_item' => __( 'Add New Booking', 'restaurant-reservations' ),
56
+ 'edit_item' => __( 'Edit Booking', 'restaurant-reservations' ),
57
+ 'new_item' => __( 'New Booking', 'restaurant-reservations' ),
58
+ 'view_item' => __( 'View Booking', 'restaurant-reservations' ),
59
+ 'search_items' => __( 'Search Bookings', 'restaurant-reservations' ),
60
+ 'not_found' => __( 'No bookings found', 'restaurant-reservations' ),
61
+ 'not_found_in_trash' => __( 'No bookings found in trash', 'restaurant-reservations' ),
62
+ 'all_items' => __( 'All Bookings', 'restaurant-reservations' ),
63
+ ),
64
+ 'menu_icon' => 'dashicons-calendar',
65
+ 'public' => false,
66
+ 'supports' => array(
67
+ 'title',
68
+ 'revisions'
69
+ )
70
+ );
71
+
72
+ // Create filter so addons can modify the arguments
73
+ $args = apply_filters( 'rtb_booking_args', $args );
74
+
75
+ // Add an action so addons can hook in before the post type is registered
76
+ do_action( 'rtb_booking_pre_register' );
77
+
78
+ // Register the post type
79
+ register_post_type( RTB_BOOKING_POST_TYPE, $args );
80
+
81
+ // Add an action so addons can hook in after the post type is registered
82
+ do_action( 'rtb_booking_post_register' );
83
+
84
+ if ( $rtb_controller->permissions->check_permission( 'custom_fields' ) ) {
85
+ // Define the field custom post type
86
+ $args = array(
87
+ 'labels' => array(
88
+ 'name' => __( 'Field', 'custom-fields-for-rtb' ),
89
+ 'singular_name' => __( 'Field', 'custom-fields-for-rtb' ),
90
+ 'menu_name' => __( 'Fields', 'custom-fields-for-rtb' ),
91
+ 'name_admin_bar' => __( 'Fields', 'custom-fields-for-rtb' ),
92
+ 'add_new' => __( 'Add Field', 'custom-fields-for-rtb' ),
93
+ 'add_new_item' => __( 'Add New Field', 'custom-fields-for-rtb' ),
94
+ 'edit_item' => __( 'Edit Field', 'custom-fields-for-rtb' ),
95
+ 'new_item' => __( 'New Field', 'custom-fields-for-rtb' ),
96
+ 'view_item' => __( 'View Field', 'custom-fields-for-rtb' ),
97
+ 'search_items' => __( 'Search Fields', 'custom-fields-for-rtb' ),
98
+ 'not_found' => __( 'No fields found', 'custom-fields-for-rtb' ),
99
+ 'not_found_in_trash' => __( 'No fields found in trash', 'custom-fields-for-rtb' ),
100
+ 'all_items' => __( 'All Fields', 'custom-fields-for-rtb' ),
101
+ ),
102
+ 'public' => false
103
+ );
104
+
105
+ $args = apply_filters( 'cffrtb_field_post_type_args', $args );
106
+
107
+ register_post_type( 'cffrtb_field', $args );
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Set an array of valid booking statuses and register any custom statuses
113
+ * @since 0.0.1
114
+ */
115
+ public function set_booking_statuses() {
116
+
117
+ $this->booking_statuses['pending'] = array(
118
+ 'label' => _x( 'Pending', 'Booking status when it is pending review', 'restaurant-reservations' ),
119
+ 'default' => true, // Whether or not this status is part of WP Core
120
+ 'user_selectable' => true, // Whether or not a user can set a booking to this status
121
+ 'label_count' => _n_noop( 'Pending <span class="count">(%s)</span>', 'Pending <span class="count">(%s)</span>'),
122
+ );
123
+
124
+ $this->booking_statuses['confirmed'] = array (
125
+ 'label' => _x( 'Confirmed', 'Booking status for a confirmed booking', 'restaurant-reservations' ),
126
+ 'default' => false, // Whether or not this status is part of WP Core
127
+ 'user_selectable' => true, // Whether or not a user can set a booking to this status
128
+ 'public' => false,
129
+ 'exclude_from_search' => true,
130
+ 'show_in_admin_all_list' => true,
131
+ 'show_in_admin_status_list' => true,
132
+ 'label_count' => _n_noop( 'Confirmed <span class="count">(%s)</span>', 'Confirmed <span class="count">(%s)</span>', 'restaurant-reservations' ),
133
+ );
134
+
135
+ $this->booking_statuses['closed'] = array(
136
+ 'label' => _x( 'Closed', 'Booking status for a closed booking', 'restaurant-reservations' ),
137
+ 'default' => false, // Whether or not this status is part of WP Core
138
+ 'user_selectable' => true, // Whether or not a user can set a booking to this status
139
+ 'public' => false,
140
+ 'exclude_from_search' => true,
141
+ 'show_in_admin_all_list' => true,
142
+ 'show_in_admin_status_list' => true,
143
+ 'label_count' => _n_noop( 'Closed <span class="count">(%s)</span>', 'Closed <span class="count">(%s)</span>', 'restaurant-reservations' )
144
+ );
145
+
146
+ // Let addons hook in to add/edit/remove post statuses
147
+ $this->booking_statuses = apply_filters( 'rtb_post_statuses_args', $this->booking_statuses );
148
+
149
+ // Register the custom post statuses
150
+ foreach ( $this->booking_statuses as $status => $args ) {
151
+ if ( $args['default'] === false ) {
152
+ register_post_status( $status, $args );
153
+ }
154
+ }
155
+
156
+ }
157
+
158
+
159
+ /**
160
+ * @since 2.1.0
161
+ * Adds in a "Cancelled" status if the option to allow guest to cancel
162
+ * their reservation has been toggled on.
163
+ */
164
+ public function add_cancelled_status( $booking_statuses = array() ) {
165
+ global $rtb_controller;
166
+
167
+ if ( $rtb_controller->settings->get_setting( 'allow-cancellations' ) ) {
168
+ $booking_statuses['cancelled'] = array(
169
+ 'label' => _x( 'Cancelled', 'The guest has cancelled their reservation themselves.', 'restaurant-reservations' ),
170
+ 'default' => false, // Whether or not this status is part of WP Core
171
+ 'user_selectable' => false, // Whether or not a user can set a booking to this status
172
+ 'public' => false,
173
+ 'exclude_from_search' => true,
174
+ 'show_in_admin_all_list' => true,
175
+ 'show_in_admin_status_list' => true,
176
+ 'label_count' => _n_noop( 'Cancelled <span class="count">(%s)</span>', 'Cancelled <span class="count">(%s)</span>', 'restaurant-reservations' )
177
+ );
178
+ }
179
+
180
+ return $booking_statuses;
181
+ }
182
+
183
+
184
+ /**
185
+ * @since 2.0.0
186
+ * Adds in an "Arrived" status if the option to check guests in on arrival
187
+ * has been toggled on.
188
+ */
189
+ public function add_arrived_status( $booking_statuses = array() ) {
190
+ global $rtb_controller;
191
+
192
+ if ( $rtb_controller->settings->get_setting( 'view-bookings-arrivals' ) ) {
193
+ $booking_statuses['arrived'] = array(
194
+ 'label' => _x( 'Arrived', 'The guests have arrived for their reservation', 'restaurant-reservations' ),
195
+ 'default' => false, // Whether or not this status is part of WP Core
196
+ 'user_selectable' => true, // Whether or not a user can set a booking to this status
197
+ 'public' => false,
198
+ 'exclude_from_search' => true,
199
+ 'show_in_admin_all_list' => true,
200
+ 'show_in_admin_status_list' => true,
201
+ 'label_count' => _n_noop( 'Arrived <span class="count">(%s)</span>', 'Arrived <span class="count">(%s)</span>', 'restaurant-reservations' )
202
+ );
203
+ }
204
+
205
+ return $booking_statuses;
206
+ }
207
+
208
+ /**
209
+ * @since 2.1.9
210
+ * Adds in a "Payment Failed" status if the option to require deposits when
211
+ * booking a reservation has been toggled on.
212
+ */
213
+ public function add_payment_failed_status( $booking_statuses = array() ) {
214
+ global $rtb_controller;
215
+
216
+ if ( $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
217
+ $booking_statuses['payment_failed'] = array(
218
+ 'label' => _x( 'Payment Failed', 'The guest has tried to make a payment but it was declined.', 'restaurant-reservations' ),
219
+ 'default' => false, // Whether or not this status is part of WP Core
220
+ 'user_selectable' => false, // Whether or not a user can set a booking to this status
221
+ 'public' => false,
222
+ 'exclude_from_search' => true,
223
+ 'show_in_admin_all_list' => true,
224
+ 'show_in_admin_status_list' => true,
225
+ 'label_count' => _n_noop( 'Payment Failed <span class="count">(%s)</span>', 'Payment Failed <span class="count">(%s)</span>', 'restaurant-reservations' )
226
+ );
227
+
228
+ // This is an intermediate status when payment is pending
229
+ $booking_statuses['payment_pending'] = array(
230
+ 'label' => _x( 'Payment Pending', 'The guest has booked but payment is pending', 'restaurant-reservations' ),
231
+ 'default' => false, // Whether or not this status is part of WP Core
232
+ 'user_selectable' => false, // Whether or not a user can set a booking to this status
233
+ 'public' => false,
234
+ 'exclude_from_search' => true,
235
+ 'show_in_admin_all_list' => true,
236
+ 'show_in_admin_status_list' => true,
237
+ 'label_count' => _n_noop( 'Payment Pending <span class="count">(%s)</span>', 'Payment Pending <span class="count">(%s)</span>', 'restaurant-reservations' )
238
+ );
239
+ }
240
+
241
+ return $booking_statuses;
242
+ }
243
+
244
+ /**
245
+ * Print an HTML element to select a booking status
246
+ * @since 0.0.1
247
+ * @note This is no longer used in the bookings table, but it could be
248
+ * useful in the future, so leave it in for now (0.0.1) until the plugin is
249
+ * more fleshed out.
250
+ */
251
+ public function print_booking_status_select( $current = false ) {
252
+
253
+ if ( $current === false ) {
254
+ $current = 'none';
255
+ }
256
+
257
+ // Output stored select field if available
258
+ if ( !empty( $this->status_select_html[$current] ) ) {
259
+ return $this->status_select_html[$current];
260
+ }
261
+
262
+ ob_start();
263
+ ?>
264
+
265
+ <select name="rtb-select-status">
266
+ <?php foreach ( $this->booking_statuses as $status => $args ) : ?>
267
+ <?php if ( $args['user_selectable'] === true ) : ?>
268
+ <option value="<?php echo esc_attr( $status ); ?>"<?php echo $status == $current ? ' selected="selected"' : ''; ?>><?php echo esc_attr( $args['label'] ); ?></option>
269
+ <?php endif; ?>
270
+ <?php endforeach; ?>
271
+ </select>
272
+
273
+ <?php
274
+ $output = ob_get_clean();
275
+
276
+ // Store output so we don't need to loop for every row
277
+ $this->status_select_html[$current] = $output;
278
+
279
+ return $output;
280
+
281
+ }
282
+
283
+ /**
284
+ * Delete a booking request (or send to trash)
285
+ *
286
+ * @since 0.0.1
287
+ */
288
+ public function delete_booking( $id ) {
289
+
290
+ $id = absint( $id );
291
+ if ( !current_user_can( 'manage_bookings' ) ) {
292
+ return false;
293
+ }
294
+
295
+ $booking = get_post( $id );
296
+
297
+ if ( !$this->is_valid_booking_post_object( $booking ) ) {
298
+ return false;
299
+ }
300
+
301
+ // If we're already looking at trashed posts, delete it for good.
302
+ // Otherwise, just send it to trash.
303
+ if ( !empty( $_GET['status'] ) && $_GET['status'] == 'trash' ) {
304
+ $screen = get_current_screen();
305
+ if ( $screen->base == 'toplevel_page_rtb-bookings' ) {
306
+ $result = wp_delete_post( $id, true );
307
+ }
308
+ } else {
309
+ $result = wp_trash_post( $id );
310
+ }
311
+
312
+ if ( $result === false ) {
313
+ return false;
314
+ } else {
315
+ return true;
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Update a booking status.
321
+ * @since 0.0.1
322
+ */
323
+ function update_booking_status( $id, $status ) {
324
+
325
+ $id = absint( $id );
326
+ if ( !current_user_can( 'manage_bookings' ) ) {
327
+ return false;
328
+ }
329
+
330
+ if ( !$this->is_valid_booking_status( $status ) ) {
331
+ return false;
332
+ }
333
+
334
+ $booking = get_post( $id );
335
+
336
+ if ( !$this->is_valid_booking_post_object( $booking ) ) {
337
+ return false;
338
+ }
339
+
340
+ if ( $booking->post_status === $status ) {
341
+ return null;
342
+ }
343
+
344
+ $result = wp_update_post(
345
+ array(
346
+ 'ID' => $id,
347
+ 'post_status' => $status,
348
+ 'edit_date' => current_time( 'mysql' ),
349
+ )
350
+ );
351
+
352
+ return $result ? true : false;
353
+ }
354
+
355
+ /**
356
+ * Check if status is valid for bookings
357
+ * @since 0.0.1
358
+ */
359
+ public function is_valid_booking_status( $status ) {
360
+ return isset( $this->booking_statuses[$status] ) ? true : false;
361
+ }
362
+
363
+ /**
364
+ * Check if booking is a valid Post object with the correct post type
365
+ * @since 0.0.1
366
+ */
367
+ public function is_valid_booking_post_object( $booking ) {
368
+ return !is_wp_error( $booking ) && is_object( $booking ) && $booking->post_type == RTB_BOOKING_POST_TYPE;
369
+ }
370
+
371
+ /**
372
+ * Show the count of upcoming pending bookings in admin nav menu
373
+ *
374
+ * This is hooked to admin_footer to ensure that any actions on the page
375
+ * which might effect booking statuses have already fired, such as the
376
+ * bulk actions on the bookings page, which are processed after the nav
377
+ * menu has been loaded.
378
+ *
379
+ * @since 1.7.5
380
+ */
381
+ public function show_pending_count() {
382
+
383
+ global $rtb_controller;
384
+ $rtb_controller->cpts->update_pending_count();
385
+ $pending_count = get_option( 'rtb_pending_count', 0 );
386
+
387
+ if ( !$pending_count ) {
388
+ return;
389
+ }
390
+
391
+ $pending_bubble = ' <span class="update-plugins count-' . (int) $pending_count . '">' .
392
+ '<span class="plugin-count" aria-hidden="true">' . (int) $pending_count . '</span></span>';
393
+
394
+ ?>
395
+
396
+ <script type="text/javascript">
397
+ jQuery(document).ready(function ($) {
398
+ $( '#toplevel_page_rtb-bookings > a .wp-menu-name' ).append( '<?php echo $pending_bubble; ?>' );
399
+ });
400
+ </script>
401
+
402
+ <?php
403
+ }
404
+
405
+ /**
406
+ * Update the count of upcoming pending bookings
407
+ *
408
+ * This is hooked to fire whenever a booking is added or updated. But it can
409
+ * also be called directly once to set the initial option.
410
+ *
411
+ * @param rtbBooking $booking Optional. The booking being added or updated.
412
+ * @since 1.7.5
413
+ */
414
+ public function update_pending_count( $booking = null ) {
415
+
416
+ global $wpdb;
417
+ $current_date_time = date( 'Y-m-d H:i:s', current_time( 'timestamp' ) - HOUR_IN_SECONDS );
418
+ $count = $wpdb->get_var(
419
+ $wpdb->prepare(
420
+ "SELECT COUNT(ID) FROM {$wpdb->prefix}posts WHERE post_type=%s AND post_status='pending' AND post_date>=%s;",
421
+ RTB_BOOKING_POST_TYPE,
422
+ $current_date_time
423
+ )
424
+ );
425
+
426
+ update_option( 'rtb_pending_count', (int) $count );
427
+ }
428
+
429
+ /**
430
+ * Update the count of upcoming pending bookings whenever a booking status
431
+ * is modified
432
+ *
433
+ * @param string $new_status The status being transitioned to
434
+ * @param string $old_status The status the post used to have
435
+ * @param WP_Post $post The post being transitioned
436
+ * @since 1.7.5
437
+ */
438
+ public function maybe_update_pending_count( $new_status, $old_status, $post ) {
439
+ if ( $post->post_type === RTB_BOOKING_POST_TYPE ) {
440
+ $this->update_pending_count();
441
+ }
442
+ }
443
+
444
+ }
445
+ } // endif;
includes/DeactivationSurvey.class.php CHANGED
@@ -1,84 +1,84 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbDeactivationSurvey' ) ) {
5
- /**
6
- * Class to handle plugin deactivation survey
7
- *
8
- * @since 2.0.15
9
- */
10
- class rtbDeactivationSurvey {
11
-
12
- public function __construct() {
13
- add_action( 'current_screen', array( $this, 'maybe_add_survey' ) );
14
- }
15
-
16
- public function maybe_add_survey() {
17
- if ( in_array( get_current_screen()->id, array( 'plugins', 'plugins-network' ), true) ) {
18
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_deactivation_scripts') );
19
- add_action( 'admin_footer', array( $this, 'add_deactivation_html') );
20
- }
21
- }
22
-
23
- public function enqueue_deactivation_scripts() {
24
- wp_enqueue_style( 'rtb-deactivation-css', RTB_PLUGIN_URL . '/assets/css/plugin-deactivation.css' );
25
- wp_enqueue_script( 'rtb-deactivation-js', RTB_PLUGIN_URL . '/assets/js/plugin-deactivation.js', array( 'jquery' ) );
26
-
27
- wp_localize_script( 'rtb-deactivation-js', 'rtb_deactivation_data', array( 'site_url' => site_url() ) );
28
- }
29
-
30
- public function add_deactivation_html() {
31
-
32
- $install_time = get_option( 'rtb-installation-time' );
33
-
34
- $options = array(
35
- 1 => array(
36
- 'title' => esc_html__( 'I no longer need the plugin', 'restaurant-reservations' ),
37
- ),
38
- 2 => array(
39
- 'title' => esc_html__( 'I\'m switching to a different plugin', 'restaurant-reservations' ),
40
- 'details' => esc_html__( 'Please share which plugin', 'restaurant-reservations' ),
41
- ),
42
- 3 => array(
43
- 'title' => esc_html__( 'I couldn\'t get the plugin to work', 'restaurant-reservations' ),
44
- 'details' => esc_html__( 'Please share what wasn\'t working', 'restaurant-reservations' ),
45
- ),
46
- 4 => array(
47
- 'title' => esc_html__( 'It\'s a temporary deactivation', 'restaurant-reservations' ),
48
- ),
49
- 5 => array(
50
- 'title' => esc_html__( 'Other', 'restaurant-reservations' ),
51
- 'details' => esc_html__( 'Please share the reason', 'restaurant-reservations' ),
52
- ),
53
- );
54
- ?>
55
- <div class="rtb-deactivate-survey-modal" id="rtb-deactivate-survey-restaurant-reservations">
56
- <div class="rtb-deactivate-survey-wrap">
57
- <form class="rtb-deactivate-survey" method="post" data-installtime="<?php echo $install_time; ?>">
58
- <span class="rtb-deactivate-survey-title"><span class="dashicons dashicons-testimonial"></span><?php echo ' ' . __( 'Quick Feedback', 'restaurant-reservations' ); ?></span>
59
- <span class="rtb-deactivate-survey-desc"><?php echo __('If you have a moment, please share why you are deactivating Five-Star Restaurant Reservations:', 'restaurant-reservations' ); ?></span>
60
- <div class="rtb-deactivate-survey-options">
61
- <?php foreach ( $options as $id => $option ) : ?>
62
- <div class="rtb-deactivate-survey-option">
63
- <label for="rtb-deactivate-survey-option-restaurant-reservations-<?php echo $id; ?>" class="rtb-deactivate-survey-option-label">
64
- <input id="rtb-deactivate-survey-option-restaurant-reservations-<?php echo $id; ?>" class="rtb-deactivate-survey-option-input" type="radio" name="code" value="<?php echo $id; ?>" />
65
- <span class="rtb-deactivate-survey-option-reason"><?php echo $option['title']; ?></span>
66
- </label>
67
- <?php if ( ! empty( $option['details'] ) ) : ?>
68
- <input class="rtb-deactivate-survey-option-details" type="text" placeholder="<?php echo $option['details']; ?>" />
69
- <?php endif; ?>
70
- </div>
71
- <?php endforeach; ?>
72
- </div>
73
- <div class="rtb-deactivate-survey-footer">
74
- <button type="submit" class="rtb-deactivate-survey-submit button button-primary button-large"><?php _e('Submit and Deactivate', 'restaurant-reservations' ); ?></button>
75
- <a href="#" class="rtb-deactivate-survey-deactivate"><?php _e('Skip and Deactivate', 'restaurant-reservations' ); ?></a>
76
- </div>
77
- </form>
78
- </div>
79
- </div>
80
- <?php
81
- }
82
- }
83
-
84
  }
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbDeactivationSurvey' ) ) {
5
+ /**
6
+ * Class to handle plugin deactivation survey
7
+ *
8
+ * @since 2.0.15
9
+ */
10
+ class rtbDeactivationSurvey {
11
+
12
+ public function __construct() {
13
+ add_action( 'current_screen', array( $this, 'maybe_add_survey' ) );
14
+ }
15
+
16
+ public function maybe_add_survey() {
17
+ if ( in_array( get_current_screen()->id, array( 'plugins', 'plugins-network' ), true) ) {
18
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_deactivation_scripts') );
19
+ add_action( 'admin_footer', array( $this, 'add_deactivation_html') );
20
+ }
21
+ }
22
+
23
+ public function enqueue_deactivation_scripts() {
24
+ wp_enqueue_style( 'rtb-deactivation-css', RTB_PLUGIN_URL . '/assets/css/plugin-deactivation.css' );
25
+ wp_enqueue_script( 'rtb-deactivation-js', RTB_PLUGIN_URL . '/assets/js/plugin-deactivation.js', array( 'jquery' ) );
26
+
27
+ wp_localize_script( 'rtb-deactivation-js', 'rtb_deactivation_data', array( 'site_url' => site_url() ) );
28
+ }
29
+
30
+ public function add_deactivation_html() {
31
+
32
+ $install_time = get_option( 'rtb-installation-time' );
33
+
34
+ $options = array(
35
+ 1 => array(
36
+ 'title' => esc_html__( 'I no longer need the plugin', 'restaurant-reservations' ),
37
+ ),
38
+ 2 => array(
39
+ 'title' => esc_html__( 'I\'m switching to a different plugin', 'restaurant-reservations' ),
40
+ 'details' => esc_html__( 'Please share which plugin', 'restaurant-reservations' ),
41
+ ),
42
+ 3 => array(
43
+ 'title' => esc_html__( 'I couldn\'t get the plugin to work', 'restaurant-reservations' ),
44
+ 'details' => esc_html__( 'Please share what wasn\'t working', 'restaurant-reservations' ),
45
+ ),
46
+ 4 => array(
47
+ 'title' => esc_html__( 'It\'s a temporary deactivation', 'restaurant-reservations' ),
48
+ ),
49
+ 5 => array(
50
+ 'title' => esc_html__( 'Other', 'restaurant-reservations' ),
51
+ 'details' => esc_html__( 'Please share the reason', 'restaurant-reservations' ),
52
+ ),
53
+ );
54
+ ?>
55
+ <div class="rtb-deactivate-survey-modal" id="rtb-deactivate-survey-restaurant-reservations">
56
+ <div class="rtb-deactivate-survey-wrap">
57
+ <form class="rtb-deactivate-survey" method="post" data-installtime="<?php echo $install_time; ?>">
58
+ <span class="rtb-deactivate-survey-title"><span class="dashicons dashicons-testimonial"></span><?php echo ' ' . __( 'Quick Feedback', 'restaurant-reservations' ); ?></span>
59
+ <span class="rtb-deactivate-survey-desc"><?php echo __('If you have a moment, please share why you are deactivating Five-Star Restaurant Reservations:', 'restaurant-reservations' ); ?></span>
60
+ <div class="rtb-deactivate-survey-options">
61
+ <?php foreach ( $options as $id => $option ) : ?>
62
+ <div class="rtb-deactivate-survey-option">
63
+ <label for="rtb-deactivate-survey-option-restaurant-reservations-<?php echo $id; ?>" class="rtb-deactivate-survey-option-label">
64
+ <input id="rtb-deactivate-survey-option-restaurant-reservations-<?php echo $id; ?>" class="rtb-deactivate-survey-option-input" type="radio" name="code" value="<?php echo $id; ?>" />
65
+ <span class="rtb-deactivate-survey-option-reason"><?php echo $option['title']; ?></span>
66
+ </label>
67
+ <?php if ( ! empty( $option['details'] ) ) : ?>
68
+ <input class="rtb-deactivate-survey-option-details" type="text" placeholder="<?php echo $option['details']; ?>" />
69
+ <?php endif; ?>
70
+ </div>
71
+ <?php endforeach; ?>
72
+ </div>
73
+ <div class="rtb-deactivate-survey-footer">
74
+ <button type="submit" class="rtb-deactivate-survey-submit button button-primary button-large"><?php _e('Submit and Deactivate', 'restaurant-reservations' ); ?></button>
75
+ <a href="#" class="rtb-deactivate-survey-deactivate"><?php _e('Skip and Deactivate', 'restaurant-reservations' ); ?></a>
76
+ </div>
77
+ </form>
78
+ </div>
79
+ </div>
80
+ <?php
81
+ }
82
+ }
83
+
84
  }
includes/Export.CSV.class.php CHANGED
@@ -1,376 +1,376 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'ebfrtbExportCSV' ) ) {
5
- /**
6
- * Handle CSV exports
7
- *
8
- * PHP's fputcsv() function is used to stream a
9
- * CSV file to the browser.
10
- *
11
- * @since 0.2
12
- */
13
- class ebfrtbExportCSV extends ebfrtbExport {
14
-
15
- /**
16
- * Arguments for the query used to fetch
17
- * bookings for this export
18
- *
19
- * @since 0.1
20
- */
21
- public $query_args;
22
-
23
- /**
24
- * Insantiate the CSV export
25
- *
26
- * @since 0.1
27
- */
28
- public function __construct( $bookings, $args = array() ) {
29
-
30
- $this->bookings = $bookings;
31
-
32
- // Date range
33
- if ( !empty( $args['query_args'] ) ) {
34
- $this->query_args = $args['query_args'];
35
- }
36
-
37
- // Locations
38
- global $rtb_controller;
39
- if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) {
40
- add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_location_header' ) );
41
- add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_location' ), 10, 2 );
42
- }
43
-
44
- // Privacy consent
45
- if ( $rtb_controller->settings->get_setting( 'require-consent' ) ) {
46
- add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_privacy_header' ) );
47
- add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_privacy' ), 10, 2 );
48
- }
49
-
50
- if ( $rtb_controller->settings->get_setting( 'enable-tables' ) ) {
51
- add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_table_header' ), 9 );
52
- add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_table' ), 9, 2 );
53
- }
54
-
55
- if ( $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
56
- add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_deposit_header' ) );
57
- add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_deposit' ), 10, 2 );
58
- }
59
-
60
- add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_custom_fields_header' ) );
61
- add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_custom_fields' ), 10, 2 );
62
- }
63
-
64
- /**
65
- * Compile an array for the CSV file
66
- *
67
- * @since 0.1
68
- */
69
- public function export() {
70
-
71
- global $rtb_controller;
72
- $date_format = $rtb_controller->settings->get_setting( 'ebfrtb-csv-date-format' );
73
-
74
- // Compile bookings arrayarray headers
75
- $arr = apply_filters( 'ebfrtb_export_csv_booking_headers', array(
76
- array(
77
- 'ID' => __( 'Booking ID', 'restaurant-reservations' ),
78
- 'date' => __( 'Date', 'restaurant-reservations' ),
79
- 'name' => __( 'Name', 'restaurant-reservations' ),
80
- 'party' => __( 'Party', 'restaurant-reservations' ),
81
- 'email' => __( 'Email', 'restaurant-reservations' ),
82
- 'phone' => __( 'Phone', 'restaurant-reservations' ),
83
- 'message' => __( 'Message', 'restaurant-reservations' ),
84
- 'date_submission' => __( 'Date the request was made', 'restaurant-reservations' ),
85
- 'status' => __( 'Booking Status', 'restaurant-reservations' ),
86
- )
87
- ) );
88
-
89
- // Compile bookings array
90
- foreach( $this->bookings as $booking ) {
91
- $arr[] = apply_filters( 'ebfrtb_export_csv_booking', array(
92
- 'ID' => $booking->ID,
93
- 'date' => date_i18n( $date_format, strtotime( $booking->date ) ),
94
- 'name' => $booking->name,
95
- 'party' => $booking->party,
96
- 'email' => $booking->email,
97
- 'phone' => $booking->phone,
98
- 'message' => str_replace( array( "\r\n", "\n", "\r", '<br />', '<br>', '<br/>' ), ' ', $booking->message ),
99
- 'date_submission' => date_i18n( $date_format, $booking->date_submission ),
100
- 'status' => $booking->post_status,
101
- ), $booking );
102
- }
103
-
104
- $this->export = apply_filters( 'ebfrtb_export_csv_bookings', $arr );
105
-
106
- return $this->export;
107
- }
108
-
109
- /**
110
- * Deliver the CSV file to the browser
111
- *
112
- * @since 0.1
113
- */
114
- public function deliver() {
115
-
116
- // Generate the export if it's not been done yet
117
- if ( empty( $this->export ) ) {
118
- $this->export();
119
- }
120
-
121
- $filename = apply_filters( 'ebfrtb_export_csv_filename', sanitize_file_name( $this->get_date_phrase() ) . '.csv' );
122
- $delimiter = apply_filters( 'ebfrtb_export_csv_delimiter', ',' );
123
-
124
- header( 'Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' );
125
- header( 'Content-Description: File Transfer' );
126
- header( 'Content-type: text/csv' );
127
- header( 'Content-Disposition: attachment; filename=' . $filename );
128
- header( 'Expires: 0' );
129
- header( 'Pragma: no-cache' );
130
-
131
- $output = @fopen( 'php://output', 'w' );
132
-
133
- foreach( $this->export as $booking ) {
134
- fputcsv( $output, $booking, $delimiter );
135
- }
136
-
137
- fclose( $output );
138
-
139
- exit();
140
- }
141
-
142
- /**
143
- * Add a location header if locations are active
144
- *
145
- * @param array $headers Key/value of spreadsheet header rows id/label
146
- * @since 1.1
147
- */
148
- public function add_location_header( $headers ) {
149
-
150
- $headers[0] = array_merge(
151
- array( 'location' => __( 'Location', 'restaurant-reservations' ) ),
152
- $headers[0]
153
- );
154
-
155
- return $headers;
156
- }
157
-
158
- /**
159
- * Add location data from a booking to the array for conversion to csv
160
- *
161
- * @param array $arr Assoc array of booking data compiled for conversion to
162
- * csv
163
- * @param rtbBooking $booking Original booking object
164
- * @since 1.1
165
- */
166
- public function add_location( $arr, $booking ) {
167
-
168
- $location = '';
169
- if ( !empty( $booking->location ) ) {
170
- $term = get_term( $booking->location );
171
- if ( is_a( $term, 'WP_Term' ) ) {
172
- $location = $term->name;
173
- }
174
- }
175
-
176
- $arr = array_merge(
177
- array( 'location' => $location ),
178
- $arr
179
- );
180
-
181
- return $arr;
182
- }
183
-
184
- /**
185
- * Add a header for the privacy consent if it's active
186
- *
187
- * @param array $headers Key/value of spreadsheet header rows id/label
188
- * @since 1.1.1
189
- */
190
- public function add_privacy_header( $headers ) {
191
-
192
- $headers[0] = array_merge(
193
- $headers[0],
194
- array( 'consent_acquired' => __( 'Data Privacy Consent', 'restaurant-reservations' ) )
195
- );
196
-
197
- return $headers;
198
- }
199
-
200
- /**
201
- * Add privacy consent collection status to the array for conversion to csv
202
- *
203
- * @param array $arr Assoc array of booking data compiled for conversion to
204
- * csv
205
- * @param rtbBooking $booking Original booking object
206
- * @since 1.1
207
- */
208
- public function add_privacy( $arr, $booking ) {
209
-
210
- $consent_acquired = !empty( $booking->consent_acquired ) ? __( 'Yes', 'restaurant-reservations' ) : __( 'No', 'restaurant-reservations' );
211
- $arr = array_merge(
212
- $arr,
213
- array( 'consent_acquired' => $consent_acquired )
214
- );
215
-
216
- return $arr;
217
- }
218
-
219
- /**
220
- * Add a header for the table(s) feature if it's active
221
- *
222
- * @param array $headers Key/value of spreadsheet header rows id/label
223
- * @since 2.2.0
224
- */
225
- public function add_table_header( $headers ) {
226
-
227
- $headers[0] = array_merge(
228
- $headers[0],
229
- array( 'table' => __( 'Table(s)', 'restaurant-reservations' ) )
230
- );
231
-
232
- return $headers;
233
- }
234
-
235
- /**
236
- * Add the selected table(s) to the array for conversion to csv
237
- *
238
- * @param array $arr Assoc array of booking data compiled for conversion to
239
- * csv
240
- * @param rtbBooking $booking Original booking object
241
- * @since 2.2.0
242
- */
243
- public function add_table( $arr, $booking ) {
244
-
245
- $arr = array_merge(
246
- $arr,
247
- array( 'table' => implode( ',', $booking->table ) )
248
- );
249
-
250
- return $arr;
251
- }
252
-
253
- /**
254
- * Add a header for the payment(s) feature if it's active
255
- *
256
- * @param array $headers Key/value of spreadsheet header rows id/label
257
- * @since 2.2.8
258
- */
259
- public function add_deposit_header( $headers ) {
260
-
261
- $headers[0] = array_merge(
262
- $headers[0],
263
- array(
264
- 'deposit' => __( 'Deposit', 'restaurant-reservations' ),
265
- 'receipt_id' => __( 'Receipt ID', 'restaurant-reservations' )
266
- )
267
- );
268
-
269
- return $headers;
270
- }
271
-
272
- /**
273
- * Add the deposit(s) to the array for conversion to csv
274
- *
275
- * @param array $arr Assoc array of booking data compiled for conversion to
276
- * csv
277
- * @param rtbBooking $booking Original booking object
278
- * @since 2.2.0
279
- */
280
- public function add_deposit( $arr, $booking ) {
281
-
282
- global $rtb_controller;
283
-
284
- $arr = array_merge(
285
- $arr,
286
- array(
287
- 'deposit' => $rtb_controller->settings->get_setting( 'rtb-currency' ) .' '. $booking->deposit,
288
- 'receipt_id' => $booking->receipt_id
289
- )
290
- );
291
-
292
- return $arr;
293
- }
294
-
295
- /**
296
- * Add custom fields to CSV headers
297
- *
298
- * @param array $headers Key/value of spreadsheet header rows id/label
299
- * @since 2.0
300
- */
301
- public function add_custom_fields_header( $headers ) {
302
-
303
- $fields = rtb_get_custom_fields();
304
-
305
- foreach( $fields as $field ) {
306
-
307
- if ( $field->type == 'fieldset' ) {
308
- continue;
309
- }
310
-
311
- $headers[0][ 'cf-' . $field->slug ] = $field->title;
312
- }
313
-
314
- return $headers;
315
- }
316
-
317
- /**
318
- * Add custom fields to CSV headers
319
- *
320
- * @param array $headers Key/value of spreadsheet header rows id/label
321
- * @since 2.0
322
- */
323
- public function add_custom_fields( $row, $booking ) {
324
- global $rtb_controller;
325
-
326
- $fields = rtb_get_custom_fields();
327
-
328
- $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
329
-
330
- foreach( $fields as $field ) {
331
-
332
- if ( $field->type == 'fieldset' ) {
333
- continue;
334
- }
335
-
336
- $val = isset( $cf[ $field->slug ] ) ? $cf[ $field->slug ] : '';
337
- $display_val = apply_filters( 'cffrtb_display_value_csv', $rtb_controller->fields->get_display_value( $val, $field, '', false ), $val, $field, $booking );
338
-
339
- $row[ 'cf-' . $field->slug ] = $display_val;
340
- }
341
-
342
- return $row;
343
- }
344
-
345
- /**
346
- * Add custom fields to CSV data row
347
- *
348
- * @param array $row Assoc array of booking data compiled for conversion to
349
- * csv
350
- * @param rtbBooking $booking Original booking object
351
- * @since 2.0
352
- */
353
- public function cffrtb_ebfrtb_add_csv_row( $row, $booking ) {
354
- global $rtb_controller;
355
-
356
- $fields = rtb_get_custom_fields();
357
-
358
- $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
359
-
360
- foreach( $fields as $field ) {
361
-
362
- if ( $field->type == 'fieldset' ) {
363
- continue;
364
- }
365
-
366
- $val = isset( $cf[ $field->slug ] ) ? $cf[ $field->slug ] : '';
367
- $display_val = apply_filters( 'cffrtb_display_value_csv', $rtb_controller->fields->get_display_value( $val, $field, '', false ), $val, $field, $booking );
368
-
369
- $row[ 'cf-' . $field->slug ] = $display_val;
370
- }
371
-
372
- return $row;
373
- }
374
-
375
- }
376
- } // endif
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'ebfrtbExportCSV' ) ) {
5
+ /**
6
+ * Handle CSV exports
7
+ *
8
+ * PHP's fputcsv() function is used to stream a
9
+ * CSV file to the browser.
10
+ *
11
+ * @since 0.2
12
+ */
13
+ class ebfrtbExportCSV extends ebfrtbExport {
14
+
15
+ /**
16
+ * Arguments for the query used to fetch
17
+ * bookings for this export
18
+ *
19
+ * @since 0.1
20
+ */
21
+ public $query_args;
22
+
23
+ /**
24
+ * Insantiate the CSV export
25
+ *
26
+ * @since 0.1
27
+ */
28
+ public function __construct( $bookings, $args = array() ) {
29
+
30
+ $this->bookings = $bookings;
31
+
32
+ // Date range
33
+ if ( !empty( $args['query_args'] ) ) {
34
+ $this->query_args = $args['query_args'];
35
+ }
36
+
37
+ // Locations
38
+ global $rtb_controller;
39
+ if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) {
40
+ add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_location_header' ) );
41
+ add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_location' ), 10, 2 );
42
+ }
43
+
44
+ // Privacy consent
45
+ if ( $rtb_controller->settings->get_setting( 'require-consent' ) ) {
46
+ add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_privacy_header' ) );
47
+ add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_privacy' ), 10, 2 );
48
+ }
49
+
50
+ if ( $rtb_controller->settings->get_setting( 'enable-tables' ) ) {
51
+ add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_table_header' ), 9 );
52
+ add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_table' ), 9, 2 );
53
+ }
54
+
55
+ if ( $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
56
+ add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_deposit_header' ) );
57
+ add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_deposit' ), 10, 2 );
58
+ }
59
+
60
+ add_filter( 'ebfrtb_export_csv_booking_headers', array( $this, 'add_custom_fields_header' ) );
61
+ add_filter( 'ebfrtb_export_csv_booking', array( $this, 'add_custom_fields' ), 10, 2 );
62
+ }
63
+
64
+ /**
65
+ * Compile an array for the CSV file
66
+ *
67
+ * @since 0.1
68
+ */
69
+ public function export() {
70
+
71
+ global $rtb_controller;
72
+ $date_format = $rtb_controller->settings->get_setting( 'ebfrtb-csv-date-format' );
73
+
74
+ // Compile bookings arrayarray headers
75
+ $arr = apply_filters( 'ebfrtb_export_csv_booking_headers', array(
76
+ array(
77
+ 'ID' => __( 'Booking ID', 'restaurant-reservations' ),
78
+ 'date' => __( 'Date', 'restaurant-reservations' ),
79
+ 'name' => __( 'Name', 'restaurant-reservations' ),
80
+ 'party' => __( 'Party', 'restaurant-reservations' ),
81
+ 'email' => __( 'Email', 'restaurant-reservations' ),
82
+ 'phone' => __( 'Phone', 'restaurant-reservations' ),
83
+ 'message' => __( 'Message', 'restaurant-reservations' ),
84
+ 'date_submission' => __( 'Date the request was made', 'restaurant-reservations' ),
85
+ 'status' => __( 'Booking Status', 'restaurant-reservations' ),
86
+ )
87
+ ) );
88
+
89
+ // Compile bookings array
90
+ foreach( $this->bookings as $booking ) {
91
+ $arr[] = apply_filters( 'ebfrtb_export_csv_booking', array(
92
+ 'ID' => $booking->ID,
93
+ 'date' => date_i18n( $date_format, strtotime( $booking->date ) ),
94
+ 'name' => $booking->name,
95
+ 'party' => $booking->party,
96
+ 'email' => $booking->email,
97
+ 'phone' => $booking->phone,
98
+ 'message' => str_replace( array( "\r\n", "\n", "\r", '<br />', '<br>', '<br/>' ), ' ', $booking->message ),
99
+ 'date_submission' => date_i18n( $date_format, $booking->date_submission ),
100
+ 'status' => $booking->post_status,
101
+ ), $booking );
102
+ }
103
+
104
+ $this->export = apply_filters( 'ebfrtb_export_csv_bookings', $arr );
105
+
106
+ return $this->export;
107
+ }
108
+
109
+ /**
110
+ * Deliver the CSV file to the browser
111
+ *
112
+ * @since 0.1
113
+ */
114
+ public function deliver() {
115
+
116
+ // Generate the export if it's not been done yet
117
+ if ( empty( $this->export ) ) {
118
+ $this->export();
119
+ }
120
+
121
+ $filename = apply_filters( 'ebfrtb_export_csv_filename', sanitize_file_name( $this->get_date_phrase() ) . '.csv' );
122
+ $delimiter = apply_filters( 'ebfrtb_export_csv_delimiter', ',' );
123
+
124
+ header( 'Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' );
125
+ header( 'Content-Description: File Transfer' );
126
+ header( 'Content-type: text/csv' );
127
+ header( 'Content-Disposition: attachment; filename=' . $filename );
128
+ header( 'Expires: 0' );
129
+ header( 'Pragma: no-cache' );
130
+
131
+ $output = @fopen( 'php://output', 'w' );
132
+
133
+ foreach( $this->export as $booking ) {
134
+ fputcsv( $output, $booking, $delimiter );
135
+ }
136
+
137
+ fclose( $output );
138
+
139
+ exit();
140
+ }
141
+
142
+ /**
143
+ * Add a location header if locations are active
144
+ *
145
+ * @param array $headers Key/value of spreadsheet header rows id/label
146
+ * @since 1.1
147
+ */
148
+ public function add_location_header( $headers ) {
149
+
150
+ $headers[0] = array_merge(
151
+ array( 'location' => __( 'Location', 'restaurant-reservations' ) ),
152
+ $headers[0]
153
+ );
154
+
155
+ return $headers;
156
+ }
157
+
158
+ /**
159
+ * Add location data from a booking to the array for conversion to csv
160
+ *
161
+ * @param array $arr Assoc array of booking data compiled for conversion to
162
+ * csv
163
+ * @param rtbBooking $booking Original booking object
164
+ * @since 1.1
165
+ */
166
+ public function add_location( $arr, $booking ) {
167
+
168
+ $location = '';
169
+ if ( !empty( $booking->location ) ) {
170
+ $term = get_term( $booking->location );
171
+ if ( is_a( $term, 'WP_Term' ) ) {
172
+ $location = $term->name;
173
+ }
174
+ }
175
+
176
+ $arr = array_merge(
177
+ array( 'location' => $location ),
178
+ $arr
179
+ );
180
+
181
+ return $arr;
182
+ }
183
+
184
+ /**
185
+ * Add a header for the privacy consent if it's active
186
+ *
187
+ * @param array $headers Key/value of spreadsheet header rows id/label
188
+ * @since 1.1.1
189
+ */
190
+ public function add_privacy_header( $headers ) {
191
+
192
+ $headers[0] = array_merge(
193
+ $headers[0],
194
+ array( 'consent_acquired' => __( 'Data Privacy Consent', 'restaurant-reservations' ) )
195
+ );
196
+
197
+ return $headers;
198
+ }
199
+
200
+ /**
201
+ * Add privacy consent collection status to the array for conversion to csv
202
+ *
203
+ * @param array $arr Assoc array of booking data compiled for conversion to
204
+ * csv
205
+ * @param rtbBooking $booking Original booking object
206
+ * @since 1.1
207
+ */
208
+ public function add_privacy( $arr, $booking ) {
209
+
210
+ $consent_acquired = !empty( $booking->consent_acquired ) ? __( 'Yes', 'restaurant-reservations' ) : __( 'No', 'restaurant-reservations' );
211
+ $arr = array_merge(
212
+ $arr,
213
+ array( 'consent_acquired' => $consent_acquired )
214
+ );
215
+
216
+ return $arr;
217
+ }
218
+
219
+ /**
220
+ * Add a header for the table(s) feature if it's active
221
+ *
222
+ * @param array $headers Key/value of spreadsheet header rows id/label
223
+ * @since 2.2.0
224
+ */
225
+ public function add_table_header( $headers ) {
226
+
227
+ $headers[0] = array_merge(
228
+ $headers[0],
229
+ array( 'table' => __( 'Table(s)', 'restaurant-reservations' ) )
230
+ );
231
+
232
+ return $headers;
233
+ }
234
+
235
+ /**
236
+ * Add the selected table(s) to the array for conversion to csv
237
+ *
238
+ * @param array $arr Assoc array of booking data compiled for conversion to
239
+ * csv
240
+ * @param rtbBooking $booking Original booking object
241
+ * @since 2.2.0
242
+ */
243
+ public function add_table( $arr, $booking ) {
244
+
245
+ $arr = array_merge(
246
+ $arr,
247
+ array( 'table' => implode( ',', $booking->table ) )
248
+ );
249
+
250
+ return $arr;
251
+ }
252
+
253
+ /**
254
+ * Add a header for the payment(s) feature if it's active
255
+ *
256
+ * @param array $headers Key/value of spreadsheet header rows id/label
257
+ * @since 2.2.8
258
+ */
259
+ public function add_deposit_header( $headers ) {
260
+
261
+ $headers[0] = array_merge(
262
+ $headers[0],
263
+ array(
264
+ 'deposit' => __( 'Deposit', 'restaurant-reservations' ),
265
+ 'receipt_id' => __( 'Receipt ID', 'restaurant-reservations' )
266
+ )
267
+ );
268
+
269
+ return $headers;
270
+ }
271
+
272
+ /**
273
+ * Add the deposit(s) to the array for conversion to csv
274
+ *
275
+ * @param array $arr Assoc array of booking data compiled for conversion to
276
+ * csv
277
+ * @param rtbBooking $booking Original booking object
278
+ * @since 2.2.0
279
+ */
280
+ public function add_deposit( $arr, $booking ) {
281
+
282
+ global $rtb_controller;
283
+
284
+ $arr = array_merge(
285
+ $arr,
286
+ array(
287
+ 'deposit' => $rtb_controller->settings->get_setting( 'rtb-currency' ) .' '. $booking->deposit,
288
+ 'receipt_id' => $booking->receipt_id
289
+ )
290
+ );
291
+
292
+ return $arr;
293
+ }
294
+
295
+ /**
296
+ * Add custom fields to CSV headers
297
+ *
298
+ * @param array $headers Key/value of spreadsheet header rows id/label
299
+ * @since 2.0
300
+ */
301
+ public function add_custom_fields_header( $headers ) {
302
+
303
+ $fields = rtb_get_custom_fields();
304
+
305
+ foreach( $fields as $field ) {
306
+
307
+ if ( $field->type == 'fieldset' ) {
308
+ continue;
309
+ }
310
+
311
+ $headers[0][ 'cf-' . $field->slug ] = $field->title;
312
+ }
313
+
314
+ return $headers;
315
+ }
316
+
317
+ /**
318
+ * Add custom fields to CSV headers
319
+ *
320
+ * @param array $headers Key/value of spreadsheet header rows id/label
321
+ * @since 2.0
322
+ */
323
+ public function add_custom_fields( $row, $booking ) {
324
+ global $rtb_controller;
325
+
326
+ $fields = rtb_get_custom_fields();
327
+
328
+ $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
329
+
330
+ foreach( $fields as $field ) {
331
+
332
+ if ( $field->type == 'fieldset' ) {
333
+ continue;
334
+ }
335
+
336
+ $val = isset( $cf[ $field->slug ] ) ? $cf[ $field->slug ] : '';
337
+ $display_val = apply_filters( 'cffrtb_display_value_csv', $rtb_controller->fields->get_display_value( $val, $field, '', false ), $val, $field, $booking );
338
+
339
+ $row[ 'cf-' . $field->slug ] = $display_val;
340
+ }
341
+
342
+ return $row;
343
+ }
344
+
345
+ /**
346
+ * Add custom fields to CSV data row
347
+ *
348
+ * @param array $row Assoc array of booking data compiled for conversion to
349
+ * csv
350
+ * @param rtbBooking $booking Original booking object
351
+ * @since 2.0
352
+ */
353
+ public function cffrtb_ebfrtb_add_csv_row( $row, $booking ) {
354
+ global $rtb_controller;
355
+
356
+ $fields = rtb_get_custom_fields();
357
+
358
+ $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
359
+
360
+ foreach( $fields as $field ) {
361
+
362
+ if ( $field->type == 'fieldset' ) {
363
+ continue;
364
+ }
365
+
366
+ $val = isset( $cf[ $field->slug ] ) ? $cf[ $field->slug ] : '';
367
+ $display_val = apply_filters( 'cffrtb_display_value_csv', $rtb_controller->fields->get_display_value( $val, $field, '', false ), $val, $field, $booking );
368
+
369
+ $row[ 'cf-' . $field->slug ] = $display_val;
370
+ }
371
+
372
+ return $row;
373
+ }
374
+
375
+ }
376
+ } // endif
includes/Export.PDF.class.php CHANGED
@@ -1,330 +1,330 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'ebfrtbExportPDF' ) ) {
5
- /**
6
- * Handle PDF exports
7
- *
8
- * mPDF or TCPDF is used to render the PDFs.
9
- *
10
- * mPDF has better HTML/CSS support, but may not
11
- * be compatible with all servers.
12
- *
13
- * http://www.mpdf1.com/
14
- *
15
- * TCPDF should have better server compatiblity,
16
- * so it is a fallback in case the main renderer
17
- * is not compatible.
18
- *
19
- * http://www.tcpdf.org/
20
- *
21
- * @since 1.4.1
22
- */
23
- class ebfrtbExportPDF extends ebfrtbExport {
24
-
25
- /**
26
- * PDF Rendering Library
27
- *
28
- * @since 0.1
29
- */
30
- public $lib;
31
-
32
- /**
33
- * Paper size to use
34
- *
35
- * @since 0.1
36
- */
37
- public $paper_size;
38
-
39
- /**
40
- * Arguments for the query used to fetch
41
- * bookings for this export
42
- *
43
- * @since 0.1
44
- */
45
- public $query_args;
46
-
47
- /**
48
- * Insantiate the PDF export
49
- *
50
- * @since 0.1
51
- */
52
- public function __construct( $bookings, $args = array() ) {
53
-
54
- $this->bookings = $bookings;
55
-
56
- global $rtb_controller;
57
- $this->lib = !empty( $args['pdf-lib'] ) ? sanitize_key( $args['pdf-lib'] ) : $rtb_controller->settings->get_setting( 'ebfrtb-pdf-lib' );
58
-
59
- // Get paper size
60
- $this->paper_size = !empty( $args['paper-size'] ) ? sanitize_key( $args['paper-size'] ) : $rtb_controller->settings->get_setting( 'ebfrtb-paper-size' );
61
-
62
- // Query arguments
63
- if ( !empty( $args['query_args'] ) ) {
64
- $this->query_args = $args['query_args'];
65
- }
66
-
67
- // Maybe add table hooks
68
- add_filter( 'ebfrtb_mpdf_after_details', array($this, 'add_booking_table_to_pdf' ) );
69
- add_filter( 'ebfrtb_tcpdf_after_details', array($this, 'add_booking_table_to_pdf' ) );
70
-
71
- // Add custom field hooks
72
- add_filter( 'ebfrtb_mpdf_after_details', array($this, 'add_custom_fields_to_mpdf' ) );
73
- add_filter( 'ebfrtb_tcpdf_after_details', array($this, 'add_custom_fields_to_tcpdf' ) );
74
- }
75
-
76
- /**
77
- * Set document information
78
- *
79
- * Sets document meta data like title, creator, etc. Both
80
- * TCPDF and mPDF use the same methods for this.
81
- *
82
- * @since 0.1
83
- */
84
- public function set_doc_info( $pdf ) {
85
-
86
- $pdf->SetCreator( get_bloginfo( 'sitename' ) );
87
- $pdf->SetAuthor( get_bloginfo( 'sitename' ) );
88
- $pdf->SetTitle( sprintf( _x( 'Bookings at %s', 'Title of PDF documents', 'restaurant-reservations' ), get_bloginfo( 'sitename' ) ) );
89
- $pdf->SetSubject( $this->get_date_phrase() );
90
-
91
- return $pdf;
92
- }
93
-
94
- /**
95
- * Compile the PDF file
96
- *
97
- * This routes to the appropriate export method
98
- * depending on the PDF library being used.
99
- *
100
- * @since 0.1
101
- */
102
- public function export() {
103
-
104
- if ( $this->lib === 'tcpdf' ) {
105
- $this->export_tcpdf();
106
-
107
- } elseif ( $this->lib === 'mpdf' ) {
108
- $this->export_mpdf();
109
-
110
- } else {
111
- do_action( 'ebcfrtb_pdf_export_' . $this->lib, $this );
112
- }
113
-
114
- return $this->export;
115
- }
116
-
117
- /**
118
- * Deliver the PDF to the browser
119
- *
120
- * @since 0.1
121
- */
122
- public function deliver() {
123
-
124
- // Generate the export if it's not been done yet
125
- if ( empty( $this->export ) ) {
126
- $this->export();
127
- }
128
-
129
- $filename = apply_filters( 'ebfrtb_export_pdf_filename', sanitize_file_name( $this->get_date_phrase() ) . '.pdf' );
130
-
131
- // Clean any stray errors, warnings or notices that may have been
132
- // printed to the buffer
133
- ob_get_clean();
134
-
135
- if ( $this->lib === 'tcpdf' || $this->lib === 'mpdf' ) {
136
- $this->export->Output( $filename, 'I');
137
- exit();
138
-
139
- } else {
140
- do_action( 'ebcfrtb_pdf_deliver_' . $this->lib, $this );
141
- }
142
-
143
- // Just in case we forget...
144
- wp_die( __( 'An unexpected error occurred and your export request could not be fulfilled.', 'restaurant-reservations' ) );
145
- }
146
-
147
- /**
148
- * Compile PDF file with TCPDF library
149
- *
150
- * @since 0.1
151
- */
152
- public function export_tcpdf() {
153
-
154
- // Load TCPDF library
155
- require_once( RTB_PLUGIN_DIR . '/lib/tcpdf/config/tcpdf_config.php' );
156
- require_once( RTB_PLUGIN_DIR . '/lib/tcpdf/tcpdf.php' );
157
-
158
- // TCPDF uses a different identifier for U.S. Letter format
159
- if ( $this->paper_size === 'LETTER' ) {
160
- $this->paper_size = 'ANSI_A';
161
- }
162
-
163
- $tcpdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, $this->paper_size, true, 'UTF-8', false);
164
- $tcpdf = $this->set_doc_info( $tcpdf );
165
-
166
- // set default header data
167
- $header_title = get_bloginfo( 'sitename' );
168
- if ( !empty( $this->query_args['location'] ) ) {
169
- $term = get_term( $this->query_args['location'] );
170
- if ( is_a( $term, 'WP_Term' ) ) {
171
- $header_title = $term->name;
172
- }
173
- }
174
- $tcpdf->SetHeaderData('', '', $header_title, $this->get_date_phrase(), array( 117, 117, 117 ), array( 117, 117, 117 ) );
175
- $tcpdf->setFooterData(array(0,64,0), array(0,64,128));
176
-
177
- // set header and footer fonts
178
- $tcpdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
179
- $tcpdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
180
-
181
- // set margins
182
- $tcpdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
183
- $tcpdf->SetHeaderMargin(PDF_MARGIN_HEADER);
184
- $tcpdf->SetFooterMargin(PDF_MARGIN_FOOTER);
185
-
186
- // set auto page breaks
187
- $tcpdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
188
-
189
- // set image scale factor
190
- $tcpdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
191
-
192
- // Subset fonts for smaller doc sizes
193
- $tcpdf->setFontSubsetting(true);
194
-
195
- // Add a page
196
- $tcpdf->AddPage();
197
- $tcpdf->SetCellPadding(0);
198
-
199
- // Generate the bookings HTML
200
- ob_start();
201
- $this->include_template( 'tcpdf.php' );
202
- $html = ob_get_clean();
203
-
204
- // Print HTML
205
- $tcpdf->writeHTML($html, false, true, false, false, '');
206
-
207
- $this->export = $tcpdf;
208
-
209
- return $this->export;
210
- }
211
-
212
- /**
213
- *Maybe add the table for a booking to PDF output
214
- *
215
- * @since 2.2
216
- */
217
- public function add_booking_table_to_pdf( $booking ) {
218
- global $rtb_controller;
219
-
220
- if ( $rtb_controller->settings->get_setting( 'enable-tables' ) ) { ?>
221
-
222
- <p class="booking-table">
223
- <span class="label"><?php echo __( 'Table(s)' ) . esc_html_x( ': ', 'Appears after table label in PDF exports', 'custom-fields-for-rtb' ); ?></span>
224
- <?php echo implode( ',', $booking->table ); ?>
225
- </p>
226
-
227
- <?php }
228
- }
229
-
230
- /**
231
- * Add custom fields to TCPDF output
232
- *
233
- * @since 2.0
234
- */
235
- public function add_custom_fields_to_tcpdf( $booking ) {
236
- global $rtb_controller;
237
-
238
- $fields = rtb_get_custom_fields();
239
- $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
240
-
241
- foreach( $fields as $field ) {
242
-
243
- if ( $field->type == 'fieldset' || !isset( $cf[ $field->slug ] ) ) {
244
- continue;
245
- }
246
-
247
- $val = $cf[ $field->slug ];
248
- $display_val = apply_filters( 'cffrtb_display_value_tcpdf', $rtb_controller->fields->get_display_value( $val, $field, '', true ), $val, $field, $booking );
249
-
250
- ?>
251
-
252
- <div class="custom-field <?php echo esc_attr( $field->type . ' ' . $field->slug ); ?>">
253
- <?php echo esc_html( $field->title ) . esc_html_x( ': ', 'Appears after field label in TCPDF exports', 'custom-fields-for-rtb' ); ?>
254
- <?php echo $display_val ?>
255
- </div>
256
-
257
- <?php
258
- }
259
- }
260
-
261
- /**
262
- * Compile PDF file with mPDF library
263
- *
264
- * @since 0.1
265
- */
266
- public function export_mpdf() {
267
-
268
- if ( !extension_loaded( 'mbstring' ) ) {
269
- $rtb_settings_link = '<a href="' . admin_url( 'admin.php?page=rtb-settings&tab=rtb-export' ) . '">' . _x( 'export settings', 'Name of a link to the Export tab on the settings page', 'restaurant-reservations' ) . '</a>';
270
- wp_die( sprintf( __( 'Your server has not loaded the mbstring PHP extension, or has disabled the mbregex PHP extension. mPDF requires both to output a PDF file. Please contact your website host and ask them to enable these PHP extensions. Or switch to the TCPDF library, which has fewer server requirements. You can change the PDF Renderer in the %s.', 'restaurant-reservations' ), $rtb_settings_link ) );
271
- }
272
-
273
- // Load mPDF library
274
- require_once( RTB_PLUGIN_DIR . '/lib/mpdf/vendor/autoload.php' );
275
-
276
- $mpdf = new \Mpdf\Mpdf( [], $this->paper_size, '', '', 15, 15, 25 );
277
- $mpdf = $this->set_doc_info( $mpdf );
278
-
279
- // Support languages automatically
280
- $mpdf->autoScriptToLang = true;
281
- $mpdf->baseScript = 1;
282
- $mpdf->autoVietnamese = true;
283
- $mpdf->autoArabic = true;
284
- $mpdf->autoLangToFont = true;
285
-
286
- // Generate the bookings HTML
287
- ob_start();
288
- $this->include_template( 'mpdf.php' );
289
- $html = ob_get_clean();
290
-
291
- $mpdf->WriteHTML($html);
292
-
293
- $this->export = $mpdf;
294
-
295
- return $this->export;
296
- }
297
-
298
- /**
299
- * Add custom fields to mPDF output
300
- *
301
- * @since 2.0
302
- */
303
- public function add_custom_fields_to_mpdf( $booking ) {
304
- global $rtb_controller;
305
-
306
- $fields = rtb_get_custom_fields();
307
- $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
308
-
309
- foreach( $fields as $field ) {
310
-
311
- if ( $field->type == 'fieldset' || !isset( $cf[ $field->slug ] ) ) {
312
- continue;
313
- }
314
-
315
- $val = $cf[ $field->slug ];
316
- $display_val = apply_filters( 'cffrtb_display_value_mpdf', $rtb_controller->fields->get_display_value( $val, $field, '', true ), $val, $field, $booking );
317
-
318
- ?>
319
-
320
- <p class="custom-field <?php echo esc_attr( $field->type . ' ' . $field->slug ); ?>">
321
- <span class="label"><?php echo esc_html( $field->title ) . esc_html_x( ': ', 'Appears after field label in mPDF exports', 'custom-fields-for-rtb' ); ?></span>
322
- <?php echo $display_val ?>
323
- </p>
324
-
325
- <?php
326
- }
327
- }
328
-
329
- }
330
- } // endif
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'ebfrtbExportPDF' ) ) {
5
+ /**
6
+ * Handle PDF exports
7
+ *
8
+ * mPDF or TCPDF is used to render the PDFs.
9
+ *
10
+ * mPDF has better HTML/CSS support, but may not
11
+ * be compatible with all servers.
12
+ *
13
+ * http://www.mpdf1.com/
14
+ *
15
+ * TCPDF should have better server compatiblity,
16
+ * so it is a fallback in case the main renderer
17
+ * is not compatible.
18
+ *
19
+ * http://www.tcpdf.org/
20
+ *
21
+ * @since 1.4.1
22
+ */
23
+ class ebfrtbExportPDF extends ebfrtbExport {
24
+
25
+ /**
26
+ * PDF Rendering Library
27
+ *
28
+ * @since 0.1
29
+ */
30
+ public $lib;
31
+
32
+ /**
33
+ * Paper size to use
34
+ *
35
+ * @since 0.1
36
+ */
37
+ public $paper_size;
38
+
39
+ /**
40
+ * Arguments for the query used to fetch
41
+ * bookings for this export
42
+ *
43
+ * @since 0.1
44
+ */
45
+ public $query_args;
46
+
47
+ /**
48
+ * Insantiate the PDF export
49
+ *
50
+ * @since 0.1
51
+ */
52
+ public function __construct( $bookings, $args = array() ) {
53
+
54
+ $this->bookings = $bookings;
55
+
56
+ global $rtb_controller;
57
+ $this->lib = !empty( $args['pdf-lib'] ) ? sanitize_key( $args['pdf-lib'] ) : $rtb_controller->settings->get_setting( 'ebfrtb-pdf-lib' );
58
+
59
+ // Get paper size
60
+ $this->paper_size = !empty( $args['paper-size'] ) ? sanitize_key( $args['paper-size'] ) : $rtb_controller->settings->get_setting( 'ebfrtb-paper-size' );
61
+
62
+ // Query arguments
63
+ if ( !empty( $args['query_args'] ) ) {
64
+ $this->query_args = $args['query_args'];
65
+ }
66
+
67
+ // Maybe add table hooks
68
+ add_filter( 'ebfrtb_mpdf_after_details', array($this, 'add_booking_table_to_pdf' ) );
69
+ add_filter( 'ebfrtb_tcpdf_after_details', array($this, 'add_booking_table_to_pdf' ) );
70
+
71
+ // Add custom field hooks
72
+ add_filter( 'ebfrtb_mpdf_after_details', array($this, 'add_custom_fields_to_mpdf' ) );
73
+ add_filter( 'ebfrtb_tcpdf_after_details', array($this, 'add_custom_fields_to_tcpdf' ) );
74
+ }
75
+
76
+ /**
77
+ * Set document information
78
+ *
79
+ * Sets document meta data like title, creator, etc. Both
80
+ * TCPDF and mPDF use the same methods for this.
81
+ *
82
+ * @since 0.1
83
+ */
84
+ public function set_doc_info( $pdf ) {
85
+
86
+ $pdf->SetCreator( get_bloginfo( 'sitename' ) );
87
+ $pdf->SetAuthor( get_bloginfo( 'sitename' ) );
88
+ $pdf->SetTitle( sprintf( _x( 'Bookings at %s', 'Title of PDF documents', 'restaurant-reservations' ), get_bloginfo( 'sitename' ) ) );
89
+ $pdf->SetSubject( $this->get_date_phrase() );
90
+
91
+ return $pdf;
92
+ }
93
+
94
+ /**
95
+ * Compile the PDF file
96
+ *
97
+ * This routes to the appropriate export method
98
+ * depending on the PDF library being used.
99
+ *
100
+ * @since 0.1
101
+ */
102
+ public function export() {
103
+
104
+ if ( $this->lib === 'tcpdf' ) {
105
+ $this->export_tcpdf();
106
+
107
+ } elseif ( $this->lib === 'mpdf' ) {
108
+ $this->export_mpdf();
109
+
110
+ } else {
111
+ do_action( 'ebcfrtb_pdf_export_' . $this->lib, $this );
112
+ }
113
+
114
+ return $this->export;
115
+ }
116
+
117
+ /**
118
+ * Deliver the PDF to the browser
119
+ *
120
+ * @since 0.1
121
+ */
122
+ public function deliver() {
123
+
124
+ // Generate the export if it's not been done yet
125
+ if ( empty( $this->export ) ) {
126
+ $this->export();
127
+ }
128
+
129
+ $filename = apply_filters( 'ebfrtb_export_pdf_filename', sanitize_file_name( $this->get_date_phrase() ) . '.pdf' );
130
+
131
+ // Clean any stray errors, warnings or notices that may have been
132
+ // printed to the buffer
133
+ ob_get_clean();
134
+
135
+ if ( $this->lib === 'tcpdf' || $this->lib === 'mpdf' ) {
136
+ $this->export->Output( $filename, 'I');
137
+ exit();
138
+
139
+ } else {
140
+ do_action( 'ebcfrtb_pdf_deliver_' . $this->lib, $this );
141
+ }
142
+
143
+ // Just in case we forget...
144
+ wp_die( __( 'An unexpected error occurred and your export request could not be fulfilled.', 'restaurant-reservations' ) );
145
+ }
146
+
147
+ /**
148
+ * Compile PDF file with TCPDF library
149
+ *
150
+ * @since 0.1
151
+ */
152
+ public function export_tcpdf() {
153
+
154
+ // Load TCPDF library
155
+ require_once( RTB_PLUGIN_DIR . '/lib/tcpdf/config/tcpdf_config.php' );
156
+ require_once( RTB_PLUGIN_DIR . '/lib/tcpdf/tcpdf.php' );
157
+
158
+ // TCPDF uses a different identifier for U.S. Letter format
159
+ if ( $this->paper_size === 'LETTER' ) {
160
+ $this->paper_size = 'ANSI_A';
161
+ }
162
+
163
+ $tcpdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, $this->paper_size, true, 'UTF-8', false);
164
+ $tcpdf = $this->set_doc_info( $tcpdf );
165
+
166
+ // set default header data
167
+ $header_title = get_bloginfo( 'sitename' );
168
+ if ( !empty( $this->query_args['location'] ) ) {
169
+ $term = get_term( $this->query_args['location'] );
170
+ if ( is_a( $term, 'WP_Term' ) ) {
171
+ $header_title = $term->name;
172
+ }
173
+ }
174
+ $tcpdf->SetHeaderData('', '', $header_title, $this->get_date_phrase(), array( 117, 117, 117 ), array( 117, 117, 117 ) );
175
+ $tcpdf->setFooterData(array(0,64,0), array(0,64,128));
176
+
177
+ // set header and footer fonts
178
+ $tcpdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
179
+ $tcpdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
180
+
181
+ // set margins
182
+ $tcpdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
183
+ $tcpdf->SetHeaderMargin(PDF_MARGIN_HEADER);
184
+ $tcpdf->SetFooterMargin(PDF_MARGIN_FOOTER);
185
+
186
+ // set auto page breaks
187
+ $tcpdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
188
+
189
+ // set image scale factor
190
+ $tcpdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
191
+
192
+ // Subset fonts for smaller doc sizes
193
+ $tcpdf->setFontSubsetting(true);
194
+
195
+ // Add a page
196
+ $tcpdf->AddPage();
197
+ $tcpdf->SetCellPadding(0);
198
+
199
+ // Generate the bookings HTML
200
+ ob_start();
201
+ $this->include_template( 'tcpdf.php' );
202
+ $html = ob_get_clean();
203
+
204
+ // Print HTML
205
+ $tcpdf->writeHTML($html, false, true, false, false, '');
206
+
207
+ $this->export = $tcpdf;
208
+
209
+ return $this->export;
210
+ }
211
+
212
+ /**
213
+ *Maybe add the table for a booking to PDF output
214
+ *
215
+ * @since 2.2
216
+ */
217
+ public function add_booking_table_to_pdf( $booking ) {
218
+ global $rtb_controller;
219
+
220
+ if ( $rtb_controller->settings->get_setting( 'enable-tables' ) ) { ?>
221
+
222
+ <p class="booking-table">
223
+ <span class="label"><?php echo __( 'Table(s)' ) . esc_html_x( ': ', 'Appears after table label in PDF exports', 'custom-fields-for-rtb' ); ?></span>
224
+ <?php echo implode( ',', $booking->table ); ?>
225
+ </p>
226
+
227
+ <?php }
228
+ }
229
+
230
+ /**
231
+ * Add custom fields to TCPDF output
232
+ *
233
+ * @since 2.0
234
+ */
235
+ public function add_custom_fields_to_tcpdf( $booking ) {
236
+ global $rtb_controller;
237
+
238
+ $fields = rtb_get_custom_fields();
239
+ $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
240
+
241
+ foreach( $fields as $field ) {
242
+
243
+ if ( $field->type == 'fieldset' || !isset( $cf[ $field->slug ] ) ) {
244
+ continue;
245
+ }
246
+
247
+ $val = $cf[ $field->slug ];
248
+ $display_val = apply_filters( 'cffrtb_display_value_tcpdf', $rtb_controller->fields->get_display_value( $val, $field, '', true ), $val, $field, $booking );
249
+
250
+ ?>
251
+
252
+ <div class="custom-field <?php echo esc_attr( $field->type . ' ' . $field->slug ); ?>">
253
+ <?php echo esc_html( $field->title ) . esc_html_x( ': ', 'Appears after field label in TCPDF exports', 'custom-fields-for-rtb' ); ?>
254
+ <?php echo $display_val ?>
255
+ </div>
256
+
257
+ <?php
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Compile PDF file with mPDF library
263
+ *
264
+ * @since 0.1
265
+ */
266
+ public function export_mpdf() {
267
+
268
+ if ( !extension_loaded( 'mbstring' ) ) {
269
+ $rtb_settings_link = '<a href="' . admin_url( 'admin.php?page=rtb-settings&tab=rtb-export' ) . '">' . _x( 'export settings', 'Name of a link to the Export tab on the settings page', 'restaurant-reservations' ) . '</a>';
270
+ wp_die( sprintf( __( 'Your server has not loaded the mbstring PHP extension, or has disabled the mbregex PHP extension. mPDF requires both to output a PDF file. Please contact your website host and ask them to enable these PHP extensions. Or switch to the TCPDF library, which has fewer server requirements. You can change the PDF Renderer in the %s.', 'restaurant-reservations' ), $rtb_settings_link ) );
271
+ }
272
+
273
+ // Load mPDF library
274
+ require_once( RTB_PLUGIN_DIR . '/lib/mpdf/vendor/autoload.php' );
275
+
276
+ $mpdf = new \Mpdf\Mpdf( [], $this->paper_size, '', '', 15, 15, 25 );
277
+ $mpdf = $this->set_doc_info( $mpdf );
278
+
279
+ // Support languages automatically
280
+ $mpdf->autoScriptToLang = true;
281
+ $mpdf->baseScript = 1;
282
+ $mpdf->autoVietnamese = true;
283
+ $mpdf->autoArabic = true;
284
+ $mpdf->autoLangToFont = true;
285
+
286
+ // Generate the bookings HTML
287
+ ob_start();
288
+ $this->include_template( 'mpdf.php' );
289
+ $html = ob_get_clean();
290
+
291
+ $mpdf->WriteHTML($html);
292
+
293
+ $this->export = $mpdf;
294
+
295
+ return $this->export;
296
+ }
297
+
298
+ /**
299
+ * Add custom fields to mPDF output
300
+ *
301
+ * @since 2.0
302
+ */
303
+ public function add_custom_fields_to_mpdf( $booking ) {
304
+ global $rtb_controller;
305
+
306
+ $fields = rtb_get_custom_fields();
307
+ $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
308
+
309
+ foreach( $fields as $field ) {
310
+
311
+ if ( $field->type == 'fieldset' || !isset( $cf[ $field->slug ] ) ) {
312
+ continue;
313
+ }
314
+
315
+ $val = $cf[ $field->slug ];
316
+ $display_val = apply_filters( 'cffrtb_display_value_mpdf', $rtb_controller->fields->get_display_value( $val, $field, '', true ), $val, $field, $booking );
317
+
318
+ ?>
319
+
320
+ <p class="custom-field <?php echo esc_attr( $field->type . ' ' . $field->slug ); ?>">
321
+ <span class="label"><?php echo esc_html( $field->title ) . esc_html_x( ': ', 'Appears after field label in mPDF exports', 'custom-fields-for-rtb' ); ?></span>
322
+ <?php echo $display_val ?>
323
+ </p>
324
+
325
+ <?php
326
+ }
327
+ }
328
+
329
+ }
330
+ } // endif
includes/ExportHandler.class.php CHANGED
@@ -1,302 +1,302 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) )
3
- exit;
4
-
5
- if ( !class_exists( 'rtbExportHandler' ) ) {
6
- class rtbExportHandler {
7
-
8
- /**
9
- * Registered exports
10
- *
11
- * @since 0.1
12
- */
13
- public $export_types;
14
-
15
-
16
- /**
17
- * Initialize the class and register hooks
18
- */
19
- public function __construct() {
20
-
21
- // Admin notices
22
- add_action( 'admin_notices', array( $this, 'make_mpdf_dir_writable' ) );
23
-
24
- // Register available exports
25
- add_action( 'admin_init', array( $this, 'register_exports' ) );
26
-
27
- // Load an export file
28
- add_action( 'admin_init', array( $this, 'load_export' ) );
29
-
30
- // Load bookings page and assets
31
- add_action( 'rtb_bookings_table_actions', array( $this, 'print_button' ) );
32
- add_action( 'admin_footer-toplevel_page_rtb-bookings', array( $this, 'print_export_options_modal' ), 9 );
33
-
34
- }
35
-
36
- /**
37
- * Add an admin notice if the font directory for mpdf is not
38
- * writeable
39
- *
40
- * @since 0.1
41
- */
42
- public function make_mpdf_dir_writable() {
43
-
44
- global $rtb_controller;
45
- if ( empty( $rtb_controller ) or ! $rtb_controller->permissions->check_permission( 'export' ) ) {
46
- return;
47
- }
48
-
49
- // Only trigger a warning when using the mpdf library
50
- if ( $rtb_controller->settings->get_setting( 'ebfrtb-pdf-lib' ) !== 'mpdf' ) {
51
- return;
52
- }
53
-
54
-
55
- // No warning needed if the directory is writable
56
- if ( wp_is_writable( RTB_PLUGIN_DIR . '/lib/mpdf/vendor/mpdf/mpdf/tmp/ttfontdata/' ) ) {
57
- return;
58
- }
59
-
60
-
61
- $rtb_settings_link = '<a href="' . admin_url( 'admin.php?page=rtb-settings&tab=rtb-export' ) . '">' . _x( 'export settings', 'Name of a link to the Export tab on the settings page', 'restaurant-reservations' ) . '</a>';
62
- ?>
63
-
64
- <div class="error">
65
- <p>
66
- <?php printf( __( 'Warning from Export Bookings for Restaurant Reservations: The server is not able to write to the font directory for the mPDF generator. Your PDF exports may not work properly until you change the file permissions for the directory /wp-content/plugins/export-for-rtb/lib/mpdf/ttfontdata/. Your web host can help you change the file permissions to be compatible. Or you can switch to the TCPDF renderer in the %s.', 'restaurant-reservations' ), $rtb_settings_link ); ?>
67
- </p>
68
- </div>
69
-
70
- <?php
71
- }
72
-
73
- /**
74
- * Register supported exports
75
- *
76
- * @since 0.1
77
- */
78
- public function register_exports() {
79
-
80
- // Load the export classes
81
- require_once( RTB_PLUGIN_DIR . '/includes/Export.class.php' );
82
- require_once( RTB_PLUGIN_DIR . '/includes/Export.PDF.class.php' );
83
- require_once( RTB_PLUGIN_DIR . '/includes/Export.CSV.class.php' );
84
-
85
- // Array of export types and the class which should be used
86
- // to generate them. All classes should extend ebfrtbExport
87
- // @todo Excel
88
- $this->export_types = apply_filters(
89
- 'ebfrtb_export_types',
90
- array(
91
- 'pdf' => array(
92
- 'label' => __( 'PDF', 'restaurant-reservations' ),
93
- 'class' => 'ebfrtbExportPDF',
94
- ),
95
- 'csv' => array(
96
- 'label' => __( 'Excel/CSV', 'restaurant-reservations' ),
97
- 'class' => 'ebfrtbExportCSV',
98
- ),
99
- )
100
- );
101
-
102
- }
103
-
104
- /**
105
- * Load the requested export
106
- *
107
- * @since 0.1
108
- */
109
- public function load_export() {
110
- global $rtb_controller;
111
-
112
- if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
113
-
114
- if ( !isset( $_GET['action'] ) || $_GET['action'] !== 'ebfrtb-export' ) {
115
- return;
116
- }
117
-
118
- if ( !current_user_can( 'manage_bookings' ) ) {
119
- wp_die( __( 'You do not have the required permissions to export bookings.', 'restaurant-reservations' ) );
120
- }
121
-
122
- if ( isset( $_GET['type'] ) && !empty( $this->export_types[ $_GET['type'] ] ) ) {
123
- $export_class = $this->export_types[ $_GET['type'] ]['class'];
124
- }
125
-
126
- if ( !isset( $export_class ) || !class_exists( $export_class ) ) {
127
- wp_die( __( 'Unable to create export to match your request.', 'restaurant-reservations' ) );
128
- }
129
-
130
- // Prepare query args
131
- $query = new rtbQuery( array(), 'export' );
132
- $query->parse_request_args();
133
- $query->prepare_args();
134
- $query->args['posts_per_page'] = -1;
135
-
136
- // Show an error if they forgot to enter dates
137
- if ( isset( $query->args['date_range'] ) && $query->args['date_range'] == 'dates' && !isset( $query->args['start_date'] ) && empty( $query->args['end_date'] ) ) {
138
- wp_die( __( "You selected a date range but didn't enter a start or end date. Please return and enter a start or end date.", 'restaurant-reservations' ) );
139
- }
140
-
141
- // Retrieve bookings
142
- $bookings = $query->get_bookings();
143
-
144
- if ( empty( $bookings ) ) {
145
- wp_die( __( 'There are no bookings which match your export request.', 'restaurant-reservations' ) );
146
- }
147
-
148
- $export = new $export_class( $bookings, array( 'query_args' => $query->args ) );
149
- $export->deliver(); // calls wp_die()
150
- }
151
-
152
- /**
153
- * Print the export button above and below the table
154
- *
155
- * @since 0.1.0
156
- */
157
- public function print_button( $pos ) {
158
- global $rtb_controller;
159
-
160
- if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
161
-
162
- ?>
163
-
164
- <div class="alignleft actions ebfrtb-actions">
165
- <a href="#" class="button ebfrtb-export-button">
166
- <span class="dashicons dashicons-media-spreadsheet"></span>
167
- <?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?>
168
- </a>
169
- </div>
170
-
171
- <?php
172
- }
173
-
174
- /**
175
- * Print the export options modal in the footer
176
- * of the bookings page
177
- *
178
- * @since 0.1
179
- */
180
- public function print_export_options_modal() {
181
-
182
- global $rtb_controller;
183
-
184
- if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
185
- ?>
186
-
187
- <!-- Export bookings options modal -->
188
- <div id="ebfrtb-options-modal" class="rtb-admin-modal">
189
- <div class="ebfrtb-options-form rtb-container">
190
- <form>
191
-
192
- <div class="title">
193
- <h2>
194
- <?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?>
195
- </h2>
196
- </div>
197
-
198
- <fieldset>
199
- <div class="type">
200
- <label for="type" class="hidden-label">
201
- <?php esc_html_e( 'Type', 'restaurant-reservations' ); ?>
202
- </label>
203
- <select name="type">
204
- <?php foreach( $this->export_types as $type => $export ) : ?>
205
- <option value="<?php echo esc_attr( $type ); ?>"><?php echo $export['label']; ?></option>
206
- <?php endforeach; ?>
207
- </select>
208
- </div>
209
-
210
- <?php if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) : ?>
211
- <div class="location">
212
- <label for="location" class="hidden-label">
213
- <?php esc_html_e( 'Location', 'restaurant-reservations' ); ?>
214
- </label>
215
- <select name="location">
216
- <option value=""><?php esc_html_e( 'All locations', 'restaurant-reservations' ); ?></option>
217
- <?php
218
- $locations = $rtb_controller->locations->get_location_options();
219
- foreach( $locations as $id => $name ) :
220
- ?>
221
- <option value="<?php echo absint( $id ); ?>"><?php echo esc_attr( $name ); ?></option>
222
- <?php
223
- endforeach;
224
- ?>
225
- </select>
226
- </div>
227
- <?php endif; ?>
228
-
229
- <div class="date-range">
230
- <input type="hidden" name="date_range">
231
- <label for="date-range" class="hidden-label">
232
- <?php esc_html_e( 'Date Range', 'restaurant-reservations' ); ?>
233
- </label>
234
- <div class="selector">
235
- <ul class="options">
236
- <li>
237
- <a href="#" data-type="today">
238
- <?php esc_html_e( 'Today', 'restaurant-reservations' ); ?>
239
- </a>
240
- </li>
241
- <li>
242
- <a href="#" data-type="upcoming">
243
- <?php esc_html_e( 'Upcoming', 'restaurant-reservations' ); ?>
244
- </a>
245
- </li>
246
- <li>
247
- <a href="#" data-type="dates">
248
- <?php esc_html_e( 'Date Range', 'restaurant-reservations' ); ?>
249
- </a>
250
- </li>
251
- </ul>
252
- <div class="today">
253
- <?php esc_html_e( "Today's bookings", 'restaurant-reservations' ); ?>
254
- </div>
255
- <div class="upcoming">
256
- <?php esc_html_e( 'All upcoming bookings', 'restaurant-reservations' ); ?>
257
- </div>
258
- <div class="dates">
259
- <div class="date-start">
260
- <label for="ebfrtb-start-date">
261
- <?php esc_html_e( 'Start', 'restaurant-reservations' ); ?>
262
- </label>
263
- <input type="text" name="start_date" id="ebfrtb-start-date">
264
- </div>
265
- <div class="date-end">
266
- <label for="ebfrtb-end-date">
267
- <?php esc_html_e( 'End', 'restaurant-reservations' ); ?>
268
- </label>
269
- <input type="text" name="end_date" id="ebfrtb-end-date">
270
- </div>
271
- </div>
272
- </div>
273
- </div>
274
-
275
- <div class="status">
276
- <?php foreach( $rtb_controller->cpts->booking_statuses as $key => $status ) : ?>
277
- <label>
278
- <input type="checkbox" name="status" value="<?php echo esc_attr( $key ); ?>" <?php checked( $key, 'confirmed' ); ?>>
279
- <?php echo $status['label']; ?>
280
- </label>
281
- <?php endforeach; ?>
282
- </div>
283
-
284
- </fieldset>
285
- <button type="submit" class="button button-primary">
286
- <?php esc_html_e( 'Export', 'restaurant-reservations' ); ?>
287
- </button>
288
- <a href="#" class="button" id="ebfrtb-cancel-export-modal">
289
- <?php esc_html_e( 'Cancel', 'restaurant-reservations' ); ?>
290
- </a>
291
- <a href="<?php echo admin_url( 'admin.php?page=rtb-settings&tab=rtb-export-tab' ); ?>" class="settings">
292
- <?php esc_html_e( 'Settings', 'restaurant-reservations' ); ?>
293
- </a>
294
- </form>
295
- </div>
296
- </div>
297
-
298
- <?php
299
- }
300
-
301
- }
302
- } // endif;
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) )
3
+ exit;
4
+
5
+ if ( !class_exists( 'rtbExportHandler' ) ) {
6
+ class rtbExportHandler {
7
+
8
+ /**
9
+ * Registered exports
10
+ *
11
+ * @since 0.1
12
+ */
13
+ public $export_types;
14
+
15
+
16
+ /**
17
+ * Initialize the class and register hooks
18
+ */
19
+ public function __construct() {
20
+
21
+ // Admin notices
22
+ add_action( 'admin_notices', array( $this, 'make_mpdf_dir_writable' ) );
23
+
24
+ // Register available exports
25
+ add_action( 'admin_init', array( $this, 'register_exports' ) );
26
+
27
+ // Load an export file
28
+ add_action( 'admin_init', array( $this, 'load_export' ) );
29
+
30
+ // Load bookings page and assets
31
+ add_action( 'rtb_bookings_table_actions', array( $this, 'print_button' ) );
32
+ add_action( 'admin_footer-toplevel_page_rtb-bookings', array( $this, 'print_export_options_modal' ), 9 );
33
+
34
+ }
35
+
36
+ /**
37
+ * Add an admin notice if the font directory for mpdf is not
38
+ * writeable
39
+ *
40
+ * @since 0.1
41
+ */
42
+ public function make_mpdf_dir_writable() {
43
+
44
+ global $rtb_controller;
45
+ if ( empty( $rtb_controller ) or ! $rtb_controller->permissions->check_permission( 'export' ) ) {
46
+ return;
47
+ }
48
+
49
+ // Only trigger a warning when using the mpdf library
50
+ if ( $rtb_controller->settings->get_setting( 'ebfrtb-pdf-lib' ) !== 'mpdf' ) {
51
+ return;
52
+ }
53
+
54
+
55
+ // No warning needed if the directory is writable
56
+ if ( wp_is_writable( RTB_PLUGIN_DIR . '/lib/mpdf/vendor/mpdf/mpdf/tmp/ttfontdata/' ) ) {
57
+ return;
58
+ }
59
+
60
+
61
+ $rtb_settings_link = '<a href="' . admin_url( 'admin.php?page=rtb-settings&tab=rtb-export' ) . '">' . _x( 'export settings', 'Name of a link to the Export tab on the settings page', 'restaurant-reservations' ) . '</a>';
62
+ ?>
63
+
64
+ <div class="error">
65
+ <p>
66
+ <?php printf( __( 'Warning from Export Bookings for Restaurant Reservations: The server is not able to write to the font directory for the mPDF generator. Your PDF exports may not work properly until you change the file permissions for the directory /wp-content/plugins/export-for-rtb/lib/mpdf/ttfontdata/. Your web host can help you change the file permissions to be compatible. Or you can switch to the TCPDF renderer in the %s.', 'restaurant-reservations' ), $rtb_settings_link ); ?>
67
+ </p>
68
+ </div>
69
+
70
+ <?php
71
+ }
72
+
73
+ /**
74
+ * Register supported exports
75
+ *
76
+ * @since 0.1
77
+ */
78
+ public function register_exports() {
79
+
80
+ // Load the export classes
81
+ require_once( RTB_PLUGIN_DIR . '/includes/Export.class.php' );
82
+ require_once( RTB_PLUGIN_DIR . '/includes/Export.PDF.class.php' );
83
+ require_once( RTB_PLUGIN_DIR . '/includes/Export.CSV.class.php' );
84
+
85
+ // Array of export types and the class which should be used
86
+ // to generate them. All classes should extend ebfrtbExport
87
+ // @todo Excel
88
+ $this->export_types = apply_filters(
89
+ 'ebfrtb_export_types',
90
+ array(
91
+ 'pdf' => array(
92
+ 'label' => __( 'PDF', 'restaurant-reservations' ),
93
+ 'class' => 'ebfrtbExportPDF',
94
+ ),
95
+ 'csv' => array(
96
+ 'label' => __( 'Excel/CSV', 'restaurant-reservations' ),
97
+ 'class' => 'ebfrtbExportCSV',
98
+ ),
99
+ )
100
+ );
101
+
102
+ }
103
+
104
+ /**
105
+ * Load the requested export
106
+ *
107
+ * @since 0.1
108
+ */
109
+ public function load_export() {
110
+ global $rtb_controller;
111
+
112
+ if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
113
+
114
+ if ( !isset( $_GET['action'] ) || $_GET['action'] !== 'ebfrtb-export' ) {
115
+ return;
116
+ }
117
+
118
+ if ( !current_user_can( 'manage_bookings' ) ) {
119
+ wp_die( __( 'You do not have the required permissions to export bookings.', 'restaurant-reservations' ) );
120
+ }
121
+
122
+ if ( isset( $_GET['type'] ) && !empty( $this->export_types[ $_GET['type'] ] ) ) {
123
+ $export_class = $this->export_types[ $_GET['type'] ]['class'];
124
+ }
125
+
126
+ if ( !isset( $export_class ) || !class_exists( $export_class ) ) {
127
+ wp_die( __( 'Unable to create export to match your request.', 'restaurant-reservations' ) );
128
+ }
129
+
130
+ // Prepare query args
131
+ $query = new rtbQuery( array(), 'export' );
132
+ $query->parse_request_args();
133
+ $query->prepare_args();
134
+ $query->args['posts_per_page'] = -1;
135
+
136
+ // Show an error if they forgot to enter dates
137
+ if ( isset( $query->args['date_range'] ) && $query->args['date_range'] == 'dates' && !isset( $query->args['start_date'] ) && empty( $query->args['end_date'] ) ) {
138
+ wp_die( __( "You selected a date range but didn't enter a start or end date. Please return and enter a start or end date.", 'restaurant-reservations' ) );
139
+ }
140
+
141
+ // Retrieve bookings
142
+ $bookings = $query->get_bookings();
143
+
144
+ if ( empty( $bookings ) ) {
145
+ wp_die( __( 'There are no bookings which match your export request.', 'restaurant-reservations' ) );
146
+ }
147
+
148
+ $export = new $export_class( $bookings, array( 'query_args' => $query->args ) );
149
+ $export->deliver(); // calls wp_die()
150
+ }
151
+
152
+ /**
153
+ * Print the export button above and below the table
154
+ *
155
+ * @since 0.1.0
156
+ */
157
+ public function print_button( $pos ) {
158
+ global $rtb_controller;
159
+
160
+ if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
161
+
162
+ ?>
163
+
164
+ <div class="alignleft actions ebfrtb-actions">
165
+ <a href="#" class="button ebfrtb-export-button">
166
+ <span class="dashicons dashicons-media-spreadsheet"></span>
167
+ <?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?>
168
+ </a>
169
+ </div>
170
+
171
+ <?php
172
+ }
173
+
174
+ /**
175
+ * Print the export options modal in the footer
176
+ * of the bookings page
177
+ *
178
+ * @since 0.1
179
+ */
180
+ public function print_export_options_modal() {
181
+
182
+ global $rtb_controller;
183
+
184
+ if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
185
+ ?>
186
+
187
+ <!-- Export bookings options modal -->
188
+ <div id="ebfrtb-options-modal" class="rtb-admin-modal">
189
+ <div class="ebfrtb-options-form rtb-container">
190
+ <form>
191
+
192
+ <div class="title">
193
+ <h2>
194
+ <?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?>
195
+ </h2>
196
+ </div>
197
+
198
+ <fieldset>
199
+ <div class="type">
200
+ <label for="type" class="hidden-label">
201
+ <?php esc_html_e( 'Type', 'restaurant-reservations' ); ?>
202
+ </label>
203
+ <select name="type">
204
+ <?php foreach( $this->export_types as $type => $export ) : ?>
205
+ <option value="<?php echo esc_attr( $type ); ?>"><?php echo $export['label']; ?></option>
206
+ <?php endforeach; ?>
207
+ </select>
208
+ </div>
209
+
210
+ <?php if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) : ?>
211
+ <div class="location">
212
+ <label for="location" class="hidden-label">
213
+ <?php esc_html_e( 'Location', 'restaurant-reservations' ); ?>
214
+ </label>
215
+ <select name="location">
216
+ <option value=""><?php esc_html_e( 'All locations', 'restaurant-reservations' ); ?></option>
217
+ <?php
218
+ $locations = $rtb_controller->locations->get_location_options();
219
+ foreach( $locations as $id => $name ) :
220
+ ?>
221
+ <option value="<?php echo absint( $id ); ?>"><?php echo esc_attr( $name ); ?></option>
222
+ <?php
223
+ endforeach;
224
+ ?>
225
+ </select>
226
+ </div>
227
+ <?php endif; ?>
228
+
229
+ <div class="date-range">
230
+ <input type="hidden" name="date_range">
231
+ <label for="date-range" class="hidden-label">
232
+ <?php esc_html_e( 'Date Range', 'restaurant-reservations' ); ?>
233
+ </label>
234
+ <div class="selector">
235
+ <ul class="options">
236
+ <li>
237
+ <a href="#" data-type="today">
238
+ <?php esc_html_e( 'Today', 'restaurant-reservations' ); ?>
239
+ </a>
240
+ </li>
241
+ <li>
242
+ <a href="#" data-type="upcoming">
243
+ <?php esc_html_e( 'Upcoming', 'restaurant-reservations' ); ?>
244
+ </a>
245
+ </li>
246
+ <li>
247
+ <a href="#" data-type="dates">
248
+ <?php esc_html_e( 'Date Range', 'restaurant-reservations' ); ?>
249
+ </a>
250
+ </li>
251
+ </ul>
252
+ <div class="today">
253
+ <?php esc_html_e( "Today's bookings", 'restaurant-reservations' ); ?>
254
+ </div>
255
+ <div class="upcoming">
256
+ <?php esc_html_e( 'All upcoming bookings', 'restaurant-reservations' ); ?>
257
+ </div>
258
+ <div class="dates">
259
+ <div class="date-start">
260
+ <label for="ebfrtb-start-date">
261
+ <?php esc_html_e( 'Start', 'restaurant-reservations' ); ?>
262
+ </label>
263
+ <input type="text" name="start_date" id="ebfrtb-start-date">
264
+ </div>
265
+ <div class="date-end">
266
+ <label for="ebfrtb-end-date">
267
+ <?php esc_html_e( 'End', 'restaurant-reservations' ); ?>
268
+ </label>
269
+ <input type="text" name="end_date" id="ebfrtb-end-date">
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+
275
+ <div class="status">
276
+ <?php foreach( $rtb_controller->cpts->booking_statuses as $key => $status ) : ?>
277
+ <label>
278
+ <input type="checkbox" name="status" value="<?php echo esc_attr( $key ); ?>" <?php checked( $key, 'confirmed' ); ?>>
279
+ <?php echo $status['label']; ?>
280
+ </label>
281
+ <?php endforeach; ?>
282
+ </div>
283
+
284
+ </fieldset>
285
+ <button type="submit" class="button button-primary">
286
+ <?php esc_html_e( 'Export', 'restaurant-reservations' ); ?>
287
+ </button>
288
+ <a href="#" class="button" id="ebfrtb-cancel-export-modal">
289
+ <?php esc_html_e( 'Cancel', 'restaurant-reservations' ); ?>
290
+ </a>
291
+ <a href="<?php echo admin_url( 'admin.php?page=rtb-settings&tab=rtb-export-tab' ); ?>" class="settings">
292
+ <?php esc_html_e( 'Settings', 'restaurant-reservations' ); ?>
293
+ </a>
294
+ </form>
295
+ </div>
296
+ </div>
297
+
298
+ <?php
299
+ }
300
+
301
+ }
302
+ } // endif;
includes/Field.class.php CHANGED
@@ -1,608 +1,608 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'cffrtbField' ) ) {
5
- /**
6
- * Field class to add, edit, save, update, validate and process a
7
- * custom field added to the booking form.
8
- *
9
- * @since 0.1
10
- */
11
- class cffrtbField {
12
-
13
- /**
14
- * ID
15
- *
16
- * @since 0.1
17
- */
18
- public $ID;
19
-
20
- /**
21
- * Type
22
- *
23
- * eg - text, options, confirm
24
- *
25
- * @since 0.1
26
- */
27
- public $type;
28
-
29
- /**
30
- * Subtype
31
- *
32
- * eg - text/textarea, select/checkbox/radio, confirm
33
- *
34
- * @since 0.1
35
- */
36
- public $subtype;
37
-
38
- /**
39
- * Label
40
- *
41
- * @since 0.1
42
- */
43
- public $title;
44
-
45
- /**
46
- * Slug
47
- *
48
- * @since 0.1
49
- */
50
- public $slug;
51
-
52
- /**
53
- * Required
54
- *
55
- * @since 0.1
56
- */
57
- public $required;
58
-
59
- /**
60
- * Fieldset
61
- *
62
- * @since 0.1
63
- */
64
- public $fieldset;
65
-
66
- /**
67
- * Order of the field in its fieldset
68
- *
69
- * @since 0.1
70
- */
71
- public $order;
72
-
73
- /**
74
- * Options (used in some field types)
75
- *
76
- * @since 0.1
77
- */
78
- public $options;
79
-
80
- /**
81
- * Post status
82
- *
83
- * @since 0.1
84
- */
85
- public $status;
86
-
87
- /**
88
- * Get it started
89
- *
90
- * @since 0.1
91
- */
92
- public function __construct( $args = array() ) {
93
-
94
- // Load from the db if an ID is passed
95
- if ( !empty( $args['ID'] ) ) {
96
- $args = array_merge( $this->load_from_db( $args['ID'] ), $args );
97
- }
98
-
99
- // Set up the field object
100
- $this->setup_field( $args );
101
-
102
- }
103
-
104
- /**
105
- * Load an object based on the arguments passed
106
- *
107
- * @since 0.1
108
- */
109
- public function load_from_db( $id ) {
110
-
111
- $post = get_post( $id, 'ARRAY_A' );
112
-
113
- $post['title'] = $post['post_title'];
114
- $post['slug'] = $post['post_name'];
115
- $post['order'] = $post['menu_order'];
116
- $post['status'] = $post['post_status'];
117
-
118
- $post_meta = get_post_meta( $id, 'cffrtb', true );
119
-
120
- if( !empty( $post_meta['type'] ) ) { $post['type'] = $post_meta['type']; }
121
- if( !empty( $post_meta['subtype'] ) ) { $post['subtype'] = $post_meta['subtype']; }
122
- if( !empty( $post_meta['required'] ) ) { $post['required'] = $post_meta['required']; }
123
- if( !empty( $post_meta['fieldset'] ) ) { $post['fieldset'] = $post_meta['fieldset']; }
124
- if( !empty( $post_meta['options'] ) ) { $post['options'] = $post_meta['options']; }
125
-
126
- return $post;
127
- }
128
-
129
- /**
130
- * Set up a new field object
131
- *
132
- * @since 0.1
133
- */
134
- public function setup_field( $args ) {
135
- global $rtb_controller;
136
-
137
- // Use defaults for missing args
138
- $args = array_merge( $rtb_controller->fields->get_default_field_values(), $args );
139
-
140
- // Set up the object
141
- $this->ID = empty( $args['ID'] ) ? null : (int) $args['ID'];
142
- $this->type = empty( $args['type'] ) ? $this->get_valid_type( '' ) : $this->get_valid_type( $args['type'] );
143
- $this->subtype = empty( $args['subtype'] ) ? $this->get_valid_subtype( '' ) : $this->get_valid_subtype( $args['subtype'] );
144
- $this->title = empty( $args['title'] ) ? null : sanitize_text_field( $args['title'] );
145
- $this->slug = empty( $args['slug'] ) ? null : sanitize_key( $args['slug'] );
146
- $this->required = empty( $args['required'] ) ? false : (bool) $args['required'];
147
- $this->fieldset = empty( $args['fieldset'] ) ? null : sanitize_key( $args['fieldset'] );
148
- $this->order = empty( $args['order'] ) ? null : (int) $args['order'];
149
- $this->options = empty( $args['options'] ) ? null : $this->get_valid_options( $args['options'] );
150
- $this->status = empty( $args['status'] ) ? 'publish' : sanitize_key( $args['status'] );
151
-
152
- do_action( 'cffrtb_setup_field_object', $this, $args );
153
- }
154
-
155
- /**
156
- * Check if type is valid
157
- *
158
- * @since 0.1
159
- */
160
- public function is_valid_type( $type ) {
161
- global $rtb_controller;
162
-
163
- $types = $rtb_controller->fields->get_valid_field_types();
164
- return !empty( $type ) && array_key_exists( $type, $types );
165
- }
166
-
167
- /**
168
- * Return a valid type. Falls back to default if specified type is
169
- * not valid. Also allows a `fieldset` type to pass
170
- *
171
- * @since 0.1
172
- */
173
- public function get_valid_type( $type ) {
174
- global $rtb_controller;
175
-
176
- if ( $type == 'fieldset' ) {
177
- return $type;
178
- }
179
-
180
- if ( $this->is_valid_type( $type ) ) {
181
- return $type;
182
- }
183
-
184
- $defaults = $rtb_controller->fields->get_default_field_values();
185
- return $defaults['type'];
186
- }
187
-
188
- /**
189
- * Return a valid subtype. Falls back to first subtype of type if
190
- * subtype is not valid.
191
- *
192
- * @since 0.1
193
- */
194
- public function get_valid_subtype( $subtype ) {
195
- global $rtb_controller;
196
-
197
- if( $subtype == 'fieldset' ) {
198
- return $subtype;
199
- }
200
-
201
- $types = $rtb_controller->fields->get_valid_field_types();
202
- if ( array_key_exists( $subtype, $types[ $this->type ]['subtypes'] ) ) {
203
- return $subtype;
204
- }
205
-
206
- reset( $types[ $this->type ][ 'subtypes' ] );
207
-
208
- return key( $types[ $this->type ][ 'subtypes' ] );
209
- }
210
-
211
- /**
212
- * Sanitize options
213
- *
214
- * @since 0.1
215
- */
216
- public function get_valid_options( $options ) {
217
- global $rtb_controller;
218
-
219
- $new_options = array();
220
- foreach( $options as $key => $option ) {
221
-
222
- // Store key for fields that were generated before the change in
223
- // version 1.1, when the key represented the field id
224
- if ( !isset( $option['id'] ) ) {
225
- $option['id'] = $key;
226
- }
227
-
228
- $new_options[] = array(
229
- 'id' => substr( $option['id'], 0, 3 ) == 'new' ? sanitize_text_field( $option['id'] ) : absint( $option['id'] ),
230
- 'value' => isset( $option['value'] ) ? sanitize_text_field( $option['value'] ) : '',
231
- 'disabled' => empty( $option['disabled'] ) ? false : true,
232
- // An order param may not exist if the option was created in an
233
- // earlier version of the plugin
234
- 'order' => isset( $option['order'] ) ? absint( $option['order'] ) : 0,
235
- );
236
- }
237
-
238
- usort( $new_options, array( $rtb_controller->fields, 'sort_by_order' ) );
239
-
240
- return $new_options;
241
- }
242
-
243
- /**
244
- * Prepare an options array for input as `post_meta`
245
- *
246
- * Each option must have a unique, permanent ID. If no ID exists, create a
247
- * unique ID
248
- *
249
- * @since 0.1
250
- */
251
- public function prepare_options_input() {
252
-
253
- // Load existing options
254
- $options = array();
255
- if ( !empty( $this->ID ) ) {
256
- $meta = get_post_meta( $this->ID, 'cffrtb', true );
257
- if ( isset( $meta['options'] ) ) {
258
- $options = $meta['options'];
259
- }
260
- }
261
-
262
- // Disable options that are no longer available
263
- foreach( $options as $i => $option ) {
264
-
265
- // Store key for fields that were generated before the change in
266
- // version 1.1, when the key represented the field id
267
- if ( !isset( $option['id'] ) ) {
268
- $option['id'] = $i;
269
- }
270
-
271
- $exists = false;
272
- foreach( $this->options as $new_option ) {
273
- if ( $new_option['id'] == $option['id'] ) {
274
- $exists = true;
275
- break;
276
- }
277
- }
278
-
279
- if ( !$exists ) {
280
- $options[$i]['disabled'] = true;
281
- }
282
- }
283
-
284
- // Generate new options array with properly assigned IDs for new options
285
- $i = count( $options );
286
- foreach( $this->options as $key => $option ) {
287
- if ( substr( $option['id'], 0, 3 ) == 'new' ) {
288
- $option['id'] = $i;
289
- $i++;
290
- }
291
- $options[$key] = $option;
292
- }
293
-
294
- return $options;
295
- }
296
-
297
- /**
298
- * Check if this field has an id
299
- *
300
- * @since 0.1
301
- */
302
- public function has_id() {
303
- return !empty( $this->ID );
304
- }
305
-
306
- /**
307
- * Check if an option is valid
308
- *
309
- * @since 0.1
310
- */
311
- public function is_valid_option( $value ) {
312
-
313
- if ( !is_array( $this->options ) ) {
314
- return false;
315
- }
316
-
317
- return array_key_exists( $value, $this->options );
318
- }
319
-
320
- /**
321
- * Save the label of this field
322
- *
323
- * @since 0.1
324
- */
325
- public function save_label() {
326
- global $rtb_controller;
327
-
328
- // A default field shouldn't be saved as a custom field post
329
- // type. Instead we need to overwrite the label for the slug.
330
- if ( !$this->has_id() ) {
331
-
332
- if ( empty( $this->slug ) ) {
333
- return array(
334
- false,
335
- array(
336
- 'error' => 'missing_slug',
337
- 'msg' => __( 'No field slug was sent with this request to update a default field.', 'custom-fields-for-rtb' ) . ' ' . $rtb_controller->custom_fields->common_error_msg,
338
- 'data' => array( 'field' => $field, 'post_vars' => $_POST )
339
- )
340
- );
341
- }
342
-
343
- $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
344
-
345
- $new = $modified;
346
-
347
- if ( empty( $new ) ) {
348
- $new = array();
349
- }
350
-
351
- if ( empty( $new[ $this->slug ] ) ) {
352
- $new[ $this->slug ] = array();
353
- }
354
-
355
- $new[ $this->slug ][ 'title'] = $this->title;
356
- $new[ $this->slug ][ 'fieldset'] = $this->fieldset;
357
-
358
- if ( update_option( $rtb_controller->custom_fields->modified_option_key, $new ) ) {
359
- return array(
360
- true,
361
- array(
362
- 'field' => $this
363
- )
364
- );
365
-
366
- } else {
367
-
368
- /**
369
- * update_option() returns false if the new option is
370
- * the same as the old option, so let's check if that's
371
- * why we got a false response and send out a true
372
- * response if there's no error with the save process.
373
- */
374
- $same = true;
375
-
376
- if ( empty( $modified ) || empty( $modified[ $this->slug ] ) ) {
377
- $same = false;
378
- }
379
-
380
- if ( ( isset( $modified[ $this->slug ]['title'] ) && $modified[ $this->slug ]['title'] !== $this->title ) ||
381
- ( !isset( $modified[ $this->slug ]['title'] ) && !empty( $this->title ) ) ) {
382
- $same = false;
383
- }
384
-
385
- if ( empty( $modified[ $this->slug ]['fieldset'] ) || $modified[ $this->slug ]['fieldset'] !== $this->fieldset ) {
386
- $same = false;
387
- }
388
-
389
- if ( $same ) {
390
- return array(
391
- true,
392
- array(
393
- 'field' => $this
394
- )
395
- );
396
-
397
- } else {
398
- return array(
399
- false,
400
- array(
401
- 'error' => 'save_failed',
402
- 'msg' => __( 'An error occurred while updating the label for this default field. Please try again.', 'custom-fields-for-rtb' ),
403
- 'field' => $this,
404
- 'option' => $new,
405
- )
406
- );
407
- }
408
- }
409
-
410
- // Custom field
411
- } else {
412
-
413
- $field = array(
414
- 'ID' => $this->ID,
415
- 'post_title' => $this->title,
416
- );
417
-
418
- if ( wp_update_post( $field ) ) {
419
- return array(
420
- true,
421
- array(
422
- 'title' => get_the_title( $this->ID ),
423
- )
424
- );
425
-
426
- } else {
427
- return array(
428
- false,
429
- array(
430
- 'error' => 'save_failed',
431
- 'msg' => __( 'An error occurred while updating the label for this custom field. Please try again.', 'custom-fields-for-rtb' ),
432
- 'field' => $this,
433
- )
434
- );
435
- }
436
- }
437
- }
438
-
439
- /**
440
- * Save a field
441
- *
442
- * @since 0.1
443
- */
444
- public function save_field() {
445
- global $rtb_controller;
446
-
447
- if ( empty( $this->title ) || empty( $this->type ) || ( $this->type !== 'fieldset' && empty( $this->subtype ) ) ) {
448
- return array(
449
- false,
450
- array(
451
- 'error' => 'missing_data',
452
- 'msg' => __( 'This field could not be saved because required data was missing.', 'custom-fields-for-rtb' ),
453
- 'field' => $this,
454
- )
455
- );
456
- }
457
-
458
- $post = array(
459
- 'post_title' => $this->title,
460
- 'post_type' => 'cffrtb_field',
461
- 'post_status' => $this->status,
462
- );
463
- if ( $this->has_id() ) { $post['ID'] = $this->ID; }
464
- if ( !empty( $this->slug ) ) { $post['post_name'] = $this->slug; }
465
- if ( !empty( $this->order ) ) { $post['menu_order'] = $this->order; }
466
-
467
- $post = apply_filters( 'cffrtb_insert_field_data', $post, $this );
468
-
469
- $id = wp_insert_post( $post );
470
- if ( is_wp_error( $id ) || empty( $id ) ) {
471
- return array(
472
- false,
473
- array(
474
- 'error' => 'save_post_failed',
475
- 'msg' => __( 'An error occurred while saving this field. Please try again. If the problem persists, please refresh the page.', 'custom-fields-for-rtb' ),
476
- 'field' => $this,
477
- 'wp_error' => $id
478
- )
479
- );
480
- }
481
-
482
- $is_new_field = !$this->has_id();
483
-
484
- $this->ID = $id;
485
-
486
- $post_item = get_post( $id );
487
- $this->title = get_the_title( $id );
488
- $this->slug = $post_item->post_name;
489
-
490
- $post_meta = array(
491
- 'type' => $this->type,
492
- );
493
- if ( !empty( $this->subtype ) ) { $post_meta['subtype'] = $this->subtype; }
494
- if ( !empty( $this->required ) ) { $post_meta['required'] = $this->required; }
495
- if ( !empty( $this->fieldset ) ) { $post_meta['fieldset'] = $this->fieldset; }
496
- if ( !empty( $this->options ) && $this->type == 'options' ) { $post_meta['options'] = $this->prepare_options_input(); }
497
-
498
- $post_meta = apply_filters( 'cffrtb_insert_field_metadata', $post_meta, $this );
499
-
500
- update_post_meta( $id, 'cffrtb', $post_meta );
501
-
502
- $field = array(
503
- 'ID' => $this->ID,
504
- 'title' => $this->title,
505
- );
506
-
507
- if ( $this->type == 'fieldset' ) {
508
- $type = 'fieldset';
509
- $field['legend'] = $this->title;
510
- } else {
511
- $type = 'field';
512
- }
513
-
514
- return array(
515
- true,
516
- array(
517
- 'ID' => $this->ID,
518
- 'is_new_field' => $is_new_field,
519
- 'field' => $rtb_controller->editor->print_field( $this->slug, $field, $type ),
520
- 'type' => $this->type,
521
- )
522
- );
523
- }
524
-
525
- /**
526
- * Validate the input for a field
527
- *
528
- * @since 0.1
529
- */
530
- public function validate_input( $booking ) {
531
-
532
- // Don't validate fieldsets
533
- if ( $this->type === 'fieldset' ) {
534
- return;
535
- }
536
-
537
- // Instantiate the custom fields array if it's missing
538
- if ( !isset( $booking->custom_fields ) ) {
539
- $booking->custom_fields = array();
540
- }
541
-
542
- $input = isset( $_POST['rtb-' . $this->slug ] ) ? $_POST['rtb-' . $this->slug] : '';
543
-
544
- // Skip empty fields but do not skip checkboxes.
545
- // required checks are performed by base plugin validation
546
-
547
- $checkbox = 'options' === $this->type && 'checkbox' === $this->subtype;
548
-
549
- if ( ( is_string( $input ) && trim( $input ) == '' ) ||
550
- ( is_array( $input ) && empty( $input ) ) ) {
551
-
552
- // When a checkbox is unselected, it will not override the previously selected
553
- // value because HTML Form will not submit empty checkboxes
554
- if($checkbox) {
555
- $input = [];
556
- }
557
- else {
558
- return;
559
- }
560
- }
561
-
562
- // Option fields
563
- if ( $this->type == 'options' ) {
564
-
565
- if ( !is_array( $input ) ) {
566
- $input = array( $input );
567
- }
568
-
569
- foreach( $input as $input_i ) {
570
- if ( !$this->is_valid_option( $input_i ) ) {
571
- $booking->validation_errors[] = array(
572
- 'field' => $this->slug,
573
- 'post_variable' => $input,
574
- 'message' => __( 'The option you selected is not valid. Please make another choice.', 'custom-fields-for-rtb' )
575
- );
576
-
577
- return;
578
- }
579
- }
580
-
581
- if ( $this->subtype === 'select' || $this->subtype === 'radio' ) {
582
- $val = absint( $input[0] );
583
- if ( isset( $this->options[ $val ] ) ) {
584
- $booking->custom_fields[ $this->slug ] = $val;
585
- }
586
- } elseif ( $this->subtype === 'checkbox' ) {
587
- $val = array_map( 'absint', $input );
588
- $new_val = array();
589
- foreach( $val as $i ) {
590
- if ( isset( $this->options[ $i ] ) ) {
591
- $new_val[] = $i;
592
- }
593
- }
594
- $booking->custom_fields[ $this->slug ] = $new_val;
595
- }
596
-
597
- // Confirm fields (always true if we've reached this stage)
598
- } elseif ( $this->type === 'confirm' ) {
599
- $booking->custom_fields[ $this->slug ] = true;
600
-
601
- // Text fields just need to be sanitized
602
- } elseif ( $this->type === 'text' ) {
603
- $booking->custom_fields[ $this->slug ] = sanitize_text_field( $input );
604
- }
605
- }
606
-
607
- }
608
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'cffrtbField' ) ) {
5
+ /**
6
+ * Field class to add, edit, save, update, validate and process a
7
+ * custom field added to the booking form.
8
+ *
9
+ * @since 0.1
10
+ */
11
+ class cffrtbField {
12
+
13
+ /**
14
+ * ID
15
+ *
16
+ * @since 0.1
17
+ */
18
+ public $ID;
19
+
20
+ /**
21
+ * Type
22
+ *
23
+ * eg - text, options, confirm
24
+ *
25
+ * @since 0.1
26
+ */
27
+ public $type;
28
+
29
+ /**
30
+ * Subtype
31
+ *
32
+ * eg - text/textarea, select/checkbox/radio, confirm
33
+ *
34
+ * @since 0.1
35
+ */
36
+ public $subtype;
37
+
38
+ /**
39
+ * Label
40
+ *
41
+ * @since 0.1
42
+ */
43
+ public $title;
44
+
45
+ /**
46
+ * Slug
47
+ *
48
+ * @since 0.1
49
+ */
50
+ public $slug;
51
+
52
+ /**
53
+ * Required
54
+ *
55
+ * @since 0.1
56
+ */
57
+ public $required;
58
+
59
+ /**
60
+ * Fieldset
61
+ *
62
+ * @since 0.1
63
+ */
64
+ public $fieldset;
65
+
66
+ /**
67
+ * Order of the field in its fieldset
68
+ *
69
+ * @since 0.1
70
+ */
71
+ public $order;
72
+
73
+ /**
74
+ * Options (used in some field types)
75
+ *
76
+ * @since 0.1
77
+ */
78
+ public $options;
79
+
80
+ /**
81
+ * Post status
82
+ *
83
+ * @since 0.1
84
+ */
85
+ public $status;
86
+
87
+ /**
88
+ * Get it started
89
+ *
90
+ * @since 0.1
91
+ */
92
+ public function __construct( $args = array() ) {
93
+
94
+ // Load from the db if an ID is passed
95
+ if ( !empty( $args['ID'] ) ) {
96
+ $args = array_merge( $this->load_from_db( $args['ID'] ), $args );
97
+ }
98
+
99
+ // Set up the field object
100
+ $this->setup_field( $args );
101
+
102
+ }
103
+
104
+ /**
105
+ * Load an object based on the arguments passed
106
+ *
107
+ * @since 0.1
108
+ */
109
+ public function load_from_db( $id ) {
110
+
111
+ $post = get_post( $id, 'ARRAY_A' );
112
+
113
+ $post['title'] = $post['post_title'];
114
+ $post['slug'] = $post['post_name'];
115
+ $post['order'] = $post['menu_order'];
116
+ $post['status'] = $post['post_status'];
117
+
118
+ $post_meta = get_post_meta( $id, 'cffrtb', true );
119
+
120
+ if( !empty( $post_meta['type'] ) ) { $post['type'] = $post_meta['type']; }
121
+ if( !empty( $post_meta['subtype'] ) ) { $post['subtype'] = $post_meta['subtype']; }
122
+ if( !empty( $post_meta['required'] ) ) { $post['required'] = $post_meta['required']; }
123
+ if( !empty( $post_meta['fieldset'] ) ) { $post['fieldset'] = $post_meta['fieldset']; }
124
+ if( !empty( $post_meta['options'] ) ) { $post['options'] = $post_meta['options']; }
125
+
126
+ return $post;
127
+ }
128
+
129
+ /**
130
+ * Set up a new field object
131
+ *
132
+ * @since 0.1
133
+ */
134
+ public function setup_field( $args ) {
135
+ global $rtb_controller;
136
+
137
+ // Use defaults for missing args
138
+ $args = array_merge( $rtb_controller->fields->get_default_field_values(), $args );
139
+
140
+ // Set up the object
141
+ $this->ID = empty( $args['ID'] ) ? null : (int) $args['ID'];
142
+ $this->type = empty( $args['type'] ) ? $this->get_valid_type( '' ) : $this->get_valid_type( $args['type'] );
143
+ $this->subtype = empty( $args['subtype'] ) ? $this->get_valid_subtype( '' ) : $this->get_valid_subtype( $args['subtype'] );
144
+ $this->title = empty( $args['title'] ) ? null : sanitize_text_field( $args['title'] );
145
+ $this->slug = empty( $args['slug'] ) ? null : sanitize_key( $args['slug'] );
146
+ $this->required = empty( $args['required'] ) ? false : (bool) $args['required'];
147
+ $this->fieldset = empty( $args['fieldset'] ) ? null : sanitize_key( $args['fieldset'] );
148
+ $this->order = empty( $args['order'] ) ? null : (int) $args['order'];
149
+ $this->options = empty( $args['options'] ) ? null : $this->get_valid_options( $args['options'] );
150
+ $this->status = empty( $args['status'] ) ? 'publish' : sanitize_key( $args['status'] );
151
+
152
+ do_action( 'cffrtb_setup_field_object', $this, $args );
153
+ }
154
+
155
+ /**
156
+ * Check if type is valid
157
+ *
158
+ * @since 0.1
159
+ */
160
+ public function is_valid_type( $type ) {
161
+ global $rtb_controller;
162
+
163
+ $types = $rtb_controller->fields->get_valid_field_types();
164
+ return !empty( $type ) && array_key_exists( $type, $types );
165
+ }
166
+
167
+ /**
168
+ * Return a valid type. Falls back to default if specified type is
169
+ * not valid. Also allows a `fieldset` type to pass
170
+ *
171
+ * @since 0.1
172
+ */
173
+ public function get_valid_type( $type ) {
174
+ global $rtb_controller;
175
+
176
+ if ( $type == 'fieldset' ) {
177
+ return $type;
178
+ }
179
+
180
+ if ( $this->is_valid_type( $type ) ) {
181
+ return $type;
182
+ }
183
+
184
+ $defaults = $rtb_controller->fields->get_default_field_values();
185
+ return $defaults['type'];
186
+ }
187
+
188
+ /**
189
+ * Return a valid subtype. Falls back to first subtype of type if
190
+ * subtype is not valid.
191
+ *
192
+ * @since 0.1
193
+ */
194
+ public function get_valid_subtype( $subtype ) {
195
+ global $rtb_controller;
196
+
197
+ if( $subtype == 'fieldset' ) {
198
+ return $subtype;
199
+ }
200
+
201
+ $types = $rtb_controller->fields->get_valid_field_types();
202
+ if ( array_key_exists( $subtype, $types[ $this->type ]['subtypes'] ) ) {
203
+ return $subtype;
204
+ }
205
+
206
+ reset( $types[ $this->type ][ 'subtypes' ] );
207
+
208
+ return key( $types[ $this->type ][ 'subtypes' ] );
209
+ }
210
+
211
+ /**
212
+ * Sanitize options
213
+ *
214
+ * @since 0.1
215
+ */
216
+ public function get_valid_options( $options ) {
217
+ global $rtb_controller;
218
+
219
+ $new_options = array();
220
+ foreach( $options as $key => $option ) {
221
+
222
+ // Store key for fields that were generated before the change in
223
+ // version 1.1, when the key represented the field id
224
+ if ( !isset( $option['id'] ) ) {
225
+ $option['id'] = $key;
226
+ }
227
+
228
+ $new_options[] = array(
229
+ 'id' => substr( $option['id'], 0, 3 ) == 'new' ? sanitize_text_field( $option['id'] ) : absint( $option['id'] ),
230
+ 'value' => isset( $option['value'] ) ? sanitize_text_field( $option['value'] ) : '',
231
+ 'disabled' => empty( $option['disabled'] ) ? false : true,
232
+ // An order param may not exist if the option was created in an
233
+ // earlier version of the plugin
234
+ 'order' => isset( $option['order'] ) ? absint( $option['order'] ) : 0,
235
+ );
236
+ }
237
+
238
+ usort( $new_options, array( $rtb_controller->fields, 'sort_by_order' ) );
239
+
240
+ return $new_options;
241
+ }
242
+
243
+ /**
244
+ * Prepare an options array for input as `post_meta`
245
+ *
246
+ * Each option must have a unique, permanent ID. If no ID exists, create a
247
+ * unique ID
248
+ *
249
+ * @since 0.1
250
+ */
251
+ public function prepare_options_input() {
252
+
253
+ // Load existing options
254
+ $options = array();
255
+ if ( !empty( $this->ID ) ) {
256
+ $meta = get_post_meta( $this->ID, 'cffrtb', true );
257
+ if ( isset( $meta['options'] ) ) {
258
+ $options = $meta['options'];
259
+ }
260
+ }
261
+
262
+ // Disable options that are no longer available
263
+ foreach( $options as $i => $option ) {
264
+
265
+ // Store key for fields that were generated before the change in
266
+ // version 1.1, when the key represented the field id
267
+ if ( !isset( $option['id'] ) ) {
268
+ $option['id'] = $i;
269
+ }
270
+
271
+ $exists = false;
272
+ foreach( $this->options as $new_option ) {
273
+ if ( $new_option['id'] == $option['id'] ) {
274
+ $exists = true;
275
+ break;
276
+ }
277
+ }
278
+
279
+ if ( !$exists ) {
280
+ $options[$i]['disabled'] = true;
281
+ }
282
+ }
283
+
284
+ // Generate new options array with properly assigned IDs for new options
285
+ $i = count( $options );
286
+ foreach( $this->options as $key => $option ) {
287
+ if ( substr( $option['id'], 0, 3 ) == 'new' ) {
288
+ $option['id'] = $i;
289
+ $i++;
290
+ }
291
+ $options[$key] = $option;
292
+ }
293
+
294
+ return $options;
295
+ }
296
+
297
+ /**
298
+ * Check if this field has an id
299
+ *
300
+ * @since 0.1
301
+ */
302
+ public function has_id() {
303
+ return !empty( $this->ID );
304
+ }
305
+
306
+ /**
307
+ * Check if an option is valid
308
+ *
309
+ * @since 0.1
310
+ */
311
+ public function is_valid_option( $value ) {
312
+
313
+ if ( !is_array( $this->options ) ) {
314
+ return false;
315
+ }
316
+
317
+ return array_key_exists( $value, $this->options );
318
+ }
319
+
320
+ /**
321
+ * Save the label of this field
322
+ *
323
+ * @since 0.1
324
+ */
325
+ public function save_label() {
326
+ global $rtb_controller;
327
+
328
+ // A default field shouldn't be saved as a custom field post
329
+ // type. Instead we need to overwrite the label for the slug.
330
+ if ( !$this->has_id() ) {
331
+
332
+ if ( empty( $this->slug ) ) {
333
+ return array(
334
+ false,
335
+ array(
336
+ 'error' => 'missing_slug',
337
+ 'msg' => __( 'No field slug was sent with this request to update a default field.', 'custom-fields-for-rtb' ) . ' ' . $rtb_controller->custom_fields->common_error_msg,
338
+ 'data' => array( 'field' => $field, 'post_vars' => $_POST )
339
+ )
340
+ );
341
+ }
342
+
343
+ $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
344
+
345
+ $new = $modified;
346
+
347
+ if ( empty( $new ) ) {
348
+ $new = array();
349
+ }
350
+
351
+ if ( empty( $new[ $this->slug ] ) ) {
352
+ $new[ $this->slug ] = array();
353
+ }
354
+
355
+ $new[ $this->slug ][ 'title'] = $this->title;
356
+ $new[ $this->slug ][ 'fieldset'] = $this->fieldset;
357
+
358
+ if ( update_option( $rtb_controller->custom_fields->modified_option_key, $new ) ) {
359
+ return array(
360
+ true,
361
+ array(
362
+ 'field' => $this
363
+ )
364
+ );
365
+
366
+ } else {
367
+
368
+ /**
369
+ * update_option() returns false if the new option is
370
+ * the same as the old option, so let's check if that's
371
+ * why we got a false response and send out a true
372
+ * response if there's no error with the save process.
373
+ */
374
+ $same = true;
375
+
376
+ if ( empty( $modified ) || empty( $modified[ $this->slug ] ) ) {
377
+ $same = false;
378
+ }
379
+
380
+ if ( ( isset( $modified[ $this->slug ]['title'] ) && $modified[ $this->slug ]['title'] !== $this->title ) ||
381
+ ( !isset( $modified[ $this->slug ]['title'] ) && !empty( $this->title ) ) ) {
382
+ $same = false;
383
+ }
384
+
385
+ if ( empty( $modified[ $this->slug ]['fieldset'] ) || $modified[ $this->slug ]['fieldset'] !== $this->fieldset ) {
386
+ $same = false;
387
+ }
388
+
389
+ if ( $same ) {
390
+ return array(
391
+ true,
392
+ array(
393
+ 'field' => $this
394
+ )
395
+ );
396
+
397
+ } else {
398
+ return array(
399
+ false,
400
+ array(
401
+ 'error' => 'save_failed',
402
+ 'msg' => __( 'An error occurred while updating the label for this default field. Please try again.', 'custom-fields-for-rtb' ),
403
+ 'field' => $this,
404
+ 'option' => $new,
405
+ )
406
+ );
407
+ }
408
+ }
409
+
410
+ // Custom field
411
+ } else {
412
+
413
+ $field = array(
414
+ 'ID' => $this->ID,
415
+ 'post_title' => $this->title,
416
+ );
417
+
418
+ if ( wp_update_post( $field ) ) {
419
+ return array(
420
+ true,
421
+ array(
422
+ 'title' => get_the_title( $this->ID ),
423
+ )
424
+ );
425
+
426
+ } else {
427
+ return array(
428
+ false,
429
+ array(
430
+ 'error' => 'save_failed',
431
+ 'msg' => __( 'An error occurred while updating the label for this custom field. Please try again.', 'custom-fields-for-rtb' ),
432
+ 'field' => $this,
433
+ )
434
+ );
435
+ }
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Save a field
441
+ *
442
+ * @since 0.1
443
+ */
444
+ public function save_field() {
445
+ global $rtb_controller;
446
+
447
+ if ( empty( $this->title ) || empty( $this->type ) || ( $this->type !== 'fieldset' && empty( $this->subtype ) ) ) {
448
+ return array(
449
+ false,
450
+ array(
451
+ 'error' => 'missing_data',
452
+ 'msg' => __( 'This field could not be saved because required data was missing.', 'custom-fields-for-rtb' ),
453
+ 'field' => $this,
454
+ )
455
+ );
456
+ }
457
+
458
+ $post = array(
459
+ 'post_title' => $this->title,
460
+ 'post_type' => 'cffrtb_field',
461
+ 'post_status' => $this->status,
462
+ );
463
+ if ( $this->has_id() ) { $post['ID'] = $this->ID; }
464
+ if ( !empty( $this->slug ) ) { $post['post_name'] = $this->slug; }
465
+ if ( !empty( $this->order ) ) { $post['menu_order'] = $this->order; }
466
+
467
+ $post = apply_filters( 'cffrtb_insert_field_data', $post, $this );
468
+
469
+ $id = wp_insert_post( $post );
470
+ if ( is_wp_error( $id ) || empty( $id ) ) {
471
+ return array(
472
+ false,
473
+ array(
474
+ 'error' => 'save_post_failed',
475
+ 'msg' => __( 'An error occurred while saving this field. Please try again. If the problem persists, please refresh the page.', 'custom-fields-for-rtb' ),
476
+ 'field' => $this,
477
+ 'wp_error' => $id
478
+ )
479
+ );
480
+ }
481
+
482
+ $is_new_field = !$this->has_id();
483
+
484
+ $this->ID = $id;
485
+
486
+ $post_item = get_post( $id );
487
+ $this->title = get_the_title( $id );
488
+ $this->slug = $post_item->post_name;
489
+
490
+ $post_meta = array(
491
+ 'type' => $this->type,
492
+ );
493
+ if ( !empty( $this->subtype ) ) { $post_meta['subtype'] = $this->subtype; }
494
+ if ( !empty( $this->required ) ) { $post_meta['required'] = $this->required; }
495
+ if ( !empty( $this->fieldset ) ) { $post_meta['fieldset'] = $this->fieldset; }
496
+ if ( !empty( $this->options ) && $this->type == 'options' ) { $post_meta['options'] = $this->prepare_options_input(); }
497
+
498
+ $post_meta = apply_filters( 'cffrtb_insert_field_metadata', $post_meta, $this );
499
+
500
+ update_post_meta( $id, 'cffrtb', $post_meta );
501
+
502
+ $field = array(
503
+ 'ID' => $this->ID,
504
+ 'title' => $this->title,
505
+ );
506
+
507
+ if ( $this->type == 'fieldset' ) {
508
+ $type = 'fieldset';
509
+ $field['legend'] = $this->title;
510
+ } else {
511
+ $type = 'field';
512
+ }
513
+
514
+ return array(
515
+ true,
516
+ array(
517
+ 'ID' => $this->ID,
518
+ 'is_new_field' => $is_new_field,
519
+ 'field' => $rtb_controller->editor->print_field( $this->slug, $field, $type ),
520
+ 'type' => $this->type,
521
+ )
522
+ );
523
+ }
524
+
525
+ /**
526
+ * Validate the input for a field
527
+ *
528
+ * @since 0.1
529
+ */
530
+ public function validate_input( $booking ) {
531
+
532
+ // Don't validate fieldsets
533
+ if ( $this->type === 'fieldset' ) {
534
+ return;
535
+ }
536
+
537
+ // Instantiate the custom fields array if it's missing
538
+ if ( !isset( $booking->custom_fields ) ) {
539
+ $booking->custom_fields = array();
540
+ }
541
+
542
+ $input = isset( $_POST['rtb-' . $this->slug ] ) ? $_POST['rtb-' . $this->slug] : '';
543
+
544
+ // Skip empty fields but do not skip checkboxes.
545
+ // required checks are performed by base plugin validation
546
+
547
+ $checkbox = 'options' === $this->type && 'checkbox' === $this->subtype;
548
+
549
+ if ( ( is_string( $input ) && trim( $input ) == '' ) ||
550
+ ( is_array( $input ) && empty( $input ) ) ) {
551
+
552
+ // When a checkbox is unselected, it will not override the previously selected
553
+ // value because HTML Form will not submit empty checkboxes
554
+ if($checkbox) {
555
+ $input = [];
556
+ }
557
+ else {
558
+ return;
559
+ }
560
+ }
561
+
562
+ // Option fields
563
+ if ( $this->type == 'options' ) {
564
+
565
+ if ( !is_array( $input ) ) {
566
+ $input = array( $input );
567
+ }
568
+
569
+ foreach( $input as $input_i ) {
570
+ if ( !$this->is_valid_option( $input_i ) ) {
571
+ $booking->validation_errors[] = array(
572
+ 'field' => $this->slug,
573
+ 'post_variable' => $input,
574
+ 'message' => __( 'The option you selected is not valid. Please make another choice.', 'custom-fields-for-rtb' )
575
+ );
576
+
577
+ return;
578
+ }
579
+ }
580
+
581
+ if ( $this->subtype === 'select' || $this->subtype === 'radio' ) {
582
+ $val = absint( $input[0] );
583
+ if ( isset( $this->options[ $val ] ) ) {
584
+ $booking->custom_fields[ $this->slug ] = $val;
585
+ }
586
+ } elseif ( $this->subtype === 'checkbox' ) {
587
+ $val = array_map( 'absint', $input );
588
+ $new_val = array();
589
+ foreach( $val as $i ) {
590
+ if ( isset( $this->options[ $i ] ) ) {
591
+ $new_val[] = $i;
592
+ }
593
+ }
594
+ $booking->custom_fields[ $this->slug ] = $new_val;
595
+ }
596
+
597
+ // Confirm fields (always true if we've reached this stage)
598
+ } elseif ( $this->type === 'confirm' ) {
599
+ $booking->custom_fields[ $this->slug ] = true;
600
+
601
+ // Text fields just need to be sanitized
602
+ } elseif ( $this->type === 'text' ) {
603
+ $booking->custom_fields[ $this->slug ] = sanitize_text_field( $input );
604
+ }
605
+ }
606
+
607
+ }
608
+ } // endif;
includes/Import.class.php CHANGED
@@ -1,279 +1,279 @@
1
- <?php
2
-
3
- /**
4
- * Class to import bookings from a spreadsheet (not working atm)
5
- */
6
-
7
- if ( !defined( 'ABSPATH' ) )
8
- exit;
9
-
10
- if (!class_exists('ComposerAutoloaderInit4618f5c41cf5e27cc7908556f031e4d4')) {require_once RTB_PLUGIN_DIR . '/lib/PHPSpreadsheet/vendor/autoload.php';}
11
- use PhpOffice\PhpSpreadsheet\Spreadsheet;
12
- class rtbImport {
13
-
14
- /**
15
- * Hook suffix for the page
16
- *
17
- * @since 0.1
18
- */
19
- public $hook_suffix;
20
-
21
- /**
22
- * The success/error message content
23
- *
24
- * @since 0.1
25
- */
26
- public $status;
27
-
28
- /**
29
- * Whether the operation was a success or not
30
- *
31
- * @since 0.1
32
- */
33
- public $message;
34
-
35
- public function __construct() {
36
- add_action( 'admin_menu', array($this, 'register_install_screen' ));
37
-
38
- if ( isset( $_POST['rtbImport'] ) ) { add_action( 'admin_init', array($this, 'import_bookings' )); }
39
-
40
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_import_scripts' ) );
41
- }
42
-
43
- public function register_install_screen() {
44
- $this->hook_suffix = add_submenu_page(
45
- 'rtb-bookings',
46
- _x( 'Import', 'Title of the Imports page', 'restaurant-reservations' ),
47
- _x( 'Import', 'Title of Imports link in the admin menu', 'restaurant-reservations' ),
48
- 'manage_options',
49
- 'rtb-imports',
50
- array( $this, 'display_editor_page' )
51
- );
52
-
53
- // Print the error modal and enqueue assets
54
- add_action( 'load-' . $this->hook_suffix, array( $this, 'enqueue_admin_assets' ) );
55
- }
56
-
57
- public function display_import_screen() {
58
- global $rtb_controller;
59
-
60
- $import_permission = $rtb_controller->permissions->check_permission( 'import' );
61
- ?>
62
- <div class='wrap'>
63
- <h2>Import</h2>
64
- <?php if ( $import_permission ) { ?>
65
- <form method='post' enctype="multipart/form-data">
66
- <p>
67
- <label for="rtb_bookings_spreadsheet"><?php _e("Spreadsheet Containing Bookings", 'restaurant-reservations') ?></label><br />
68
- <input name="rtb_bookings_spreadsheet" type="file" value=""/>
69
- </p>
70
- <input type='submit' name='rtbImport' value='Import Bookings' class='button button-primary' />
71
- </form>
72
- <?php } else { ?>
73
- <div class='rtb-premium-locked'>
74
- <a href="https://www.fivestarplugins.com/license-payment/?Selected=RTB&Quantity=1" target="_blank">Upgrade</a> to the premium version to use this feature
75
- </div>
76
- <?php } ?>
77
- </div>
78
- <?php }
79
-
80
- public function import_bookings() {
81
- global $rtb_controller;
82
-
83
- $fields = $rtb_controller->settings->get_booking_form_fields();
84
-
85
- $update = $this->handle_spreadsheet_upload();
86
-
87
- if ( $update['message_type'] != 'Success' ) :
88
- $this->status = false;
89
- $this->message = $update['message'];
90
-
91
- add_action( 'admin_notices', array( $this, 'display_notice' ) );
92
-
93
- return;
94
- endif;
95
-
96
- $excel_url = RTB_PLUGIN_DIR . '/user-sheets/' . $update['filename'];
97
-
98
- // Build the workbook object out of the uploaded spreadsheet
99
- $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($excel_url);
100
-
101
- // Create a worksheet object out of the product sheet in the workbook
102
- $sheet = $spreadsheet->getActiveSheet();
103
-
104
- $allowable_fields = array();
105
- foreach ($fields as $field) {$allowable_fields[] = $field->name;}
106
- //List of fields that can be accepted via upload
107
- //$allowed_fields = array("ID", "Title", "Description", "Price", "Sections");
108
-
109
-
110
- // Get column names
111
- $highest_column = $sheet->getHighestColumn();
112
- $highest_column_index = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highest_column);
113
- for ($column = 1; $column <= $highest_column_index; $column++) {
114
- /*if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "ID") {$ID_column = $column;}
115
- if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "Title") {$title_column = $column;}
116
- if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "Description") {$description_column = $column;}
117
- if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "Price") {$price_column = $column;}
118
- if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "Sections") {$sections_column = $column;}*/
119
-
120
- foreach ($fields as $key => $field) {
121
- if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == $field->name) {$field->column = $column;}
122
- }
123
- }
124
-
125
-
126
- // Put the spreadsheet data into a multi-dimensional array to facilitate processing
127
- $highest_row = $sheet->getHighestRow();
128
- for ($row = 2; $row <= $highest_row; $row++) {
129
- for ($column = 1; $column <= $highest_column_index; $column++) {
130
- $data[$row][$column] = $sheet->getCellByColumnAndRow($column, $row)->getValue();
131
- }
132
- }
133
-
134
- // Create the query to insert the products one at a time into the database and then run it
135
- foreach ($data as $booking) {
136
- // Create an array of the values that are being inserted for each order,
137
- // edit if it's a current order, otherwise add it
138
- foreach ($booking as $col_index => $value) {
139
- if ($col_index == $ID_column and $ID_column !== null) {$post['ID'] = esc_sql($value);}
140
- if ($col_index == $title_column and $title_column !== null) {$post['post_title'] = esc_sql($value);}
141
- if ($col_index == $description_column and $description_column !== null) {$post['post_content'] = esc_sql($value);}
142
- if ($col_index == $price_column and $price_column !== null) {$post_prices = explode(",", esc_sql($value));}
143
- if (isset($sections_column) and $col_index == $sections_column and $sections_column !== null) {$post_sections = explode(",", esc_sql($value));}
144
- }
145
-
146
- if (!is_array($post_prices)) {$post_prices = array();}
147
- if (!is_array($post_sections)) {$post_sections = array();}
148
-
149
- if ($post['post_title'] == '') {continue;}
150
-
151
- $post['post_status'] = 'publish';
152
- $post['post_type'] = RTB_BOOKING_POST_TYPE;
153
-
154
- if ( isset( $post['ID'] ) and $post['ID'] != '') { $post_id = wp_update_post($post); }
155
- else { $post_id = wp_insert_post($post); }
156
-
157
- if ( $post_id != 0 ) {
158
- foreach ( $post_sections as $section ) {
159
- $menu_section = term_exists($section, 'fdm-menu-section');
160
- if ( $menu_section !== 0 && $menu_section !== null ) { $menu_section_ids[] = (int) $menu_section['term_id']; }
161
- }
162
- if ( isset($menu_section_ids) and is_array($menu_section_ids) ) { wp_set_object_terms($post_id, $menu_section_ids, 'fdm-menu-section'); }
163
-
164
- update_post_meta( $post_id, 'fdm_item_price', implode(",", $post_prices) );
165
-
166
- $field_values = array();
167
- foreach ( $fields as $field ) {
168
- if (isset($field->column) and isset($menu_item[$field->column])) {
169
- $field_values[$field->slug] = esc_sql($menu_item[$field->column]);
170
-
171
- }
172
- }
173
- update_post_meta($post_id, '_fdm_menu_item_custom_fields', $field_values);
174
- }
175
-
176
- unset($post);
177
- unset($post_sections);
178
- unset($menu_section_ids);
179
- unset($post_prices);
180
- unset($field_values);
181
- }
182
-
183
- $this->status = true;
184
- $this->message = __("Reservations added successfully.", 'restaurant-reservations');
185
-
186
- add_action( 'admin_notices', array( $this, 'display_notice' ) );
187
- }
188
-
189
- function handle_spreadsheet_upload() {
190
- /* Test if there is an error with the uploaded spreadsheet and return that error if there is */
191
- if (!empty($_FILES['fdm_menu_items_spreadsheet']['error']))
192
- {
193
- switch($_FILES['fdm_menu_items_spreadsheet']['error'])
194
- {
195
-
196
- case '1':
197
- $error = __('The uploaded file exceeds the upload_max_filesize directive in php.ini', 'restaurant-reservations');
198
- break;
199
- case '2':
200
- $error = __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', 'restaurant-reservations');
201
- break;
202
- case '3':
203
- $error = __('The uploaded file was only partially uploaded', 'restaurant-reservations');
204
- break;
205
- case '4':
206
- $error = __('No file was uploaded.', 'restaurant-reservations');
207
- break;
208
-
209
- case '6':
210
- $error = __('Missing a temporary folder', 'restaurant-reservations');
211
- break;
212
- case '7':
213
- $error = __('Failed to write file to disk', 'restaurant-reservations');
214
- break;
215
- case '8':
216
- $error = __('File upload stopped by extension', 'restaurant-reservations');
217
- break;
218
- case '999':
219
- default:
220
- $error = __('No error code avaiable', 'restaurant-reservations');
221
- }
222
- }
223
- /* Make sure that the file exists */
224
- elseif (empty($_FILES['fdm_menu_items_spreadsheet']['tmp_name']) || $_FILES['fdm_menu_items_spreadsheet']['tmp_name'] == 'none') {
225
- $error = __('No file was uploaded here..', 'restaurant-reservations');
226
- }
227
- /* Move the file and store the URL to pass it onwards*/
228
- /* Check that it is a .xls or .xlsx file */
229
- if(!isset($_FILES['fdm_menu_items_spreadsheet']['name']) or (!preg_match("/\.(xls.?)$/", $_FILES['fdm_menu_items_spreadsheet']['name']) and !preg_match("/\.(csv.?)$/", $_FILES['fdm_menu_items_spreadsheet']['name']))) {
230
- $error = __('File must be .csv, .xls or .xlsx', 'restaurant-reservations');
231
- }
232
- else {
233
- $filename = basename( $_FILES['fdm_menu_items_spreadsheet']['name']);
234
- $filename = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $filename);
235
- $filename = mb_ereg_replace("([\.]{2,})", '', $filename);
236
-
237
- //for security reason, we force to remove all uploaded file
238
- $target_path = RTB_PLUGIN_DIR . "/user-sheets/";
239
-
240
- $target_path = $target_path . $filename;
241
-
242
- if (!move_uploaded_file($_FILES['fdm_menu_items_spreadsheet']['tmp_name'], $target_path)) {
243
- $error .= "There was an error uploading the file, please try again!";
244
- }
245
- else {
246
- $excel_file_name = $filename;
247
- }
248
- }
249
-
250
- /* Pass the data to the appropriate function in Update_Admin_Databases.php to create the products */
251
- if (!isset($error)) {
252
- $update = array("message_type" => "Success", "filename" => $excel_file_name);
253
- }
254
- else {
255
- $update = array("message_type" => "Error", "message" => $error);
256
- }
257
- return $update;
258
- }
259
-
260
- public function enqueue_import_scripts() {
261
- $screen = get_current_screen();
262
- if($screen->id == 'rtb-menu_page_rtb-import'){
263
- wp_enqueue_style( 'rtb-admin-css', RTB_PLUGIN_URL . '/assets/css/admin.css', array(), RTB_VERSION );
264
- wp_enqueue_script( 'rtb-admin-js', RTB_PLUGIN_URL . '/assets/js/admin.js', array( 'jquery' ), RTB_VERSION, true );
265
- }
266
- }
267
-
268
- public function display_notice() {
269
- if ( $this->status ) {
270
- echo "<div class='updated'><p>" . $this->message . "</p></div>";
271
- }
272
- else {
273
- echo "<div class='error'><p>" . $this->message . "</p></div>";
274
- }
275
- }
276
-
277
- }
278
-
279
-
1
+ <?php
2
+
3
+ /**
4
+ * Class to import bookings from a spreadsheet (not working atm)
5
+ */
6
+
7
+ if ( !defined( 'ABSPATH' ) )
8
+ exit;
9
+
10
+ if (!class_exists('ComposerAutoloaderInit4618f5c41cf5e27cc7908556f031e4d4')) {require_once RTB_PLUGIN_DIR . '/lib/PHPSpreadsheet/vendor/autoload.php';}
11
+ use PhpOffice\PhpSpreadsheet\Spreadsheet;
12
+ class rtbImport {
13
+
14
+ /**
15
+ * Hook suffix for the page
16
+ *
17
+ * @since 0.1
18
+ */
19
+ public $hook_suffix;
20
+
21
+ /**
22
+ * The success/error message content
23
+ *
24
+ * @since 0.1
25
+ */
26
+ public $status;
27
+
28
+ /**
29
+ * Whether the operation was a success or not
30
+ *
31
+ * @since 0.1
32
+ */
33
+ public $message;
34
+
35
+ public function __construct() {
36
+ add_action( 'admin_menu', array($this, 'register_install_screen' ));
37
+
38
+ if ( isset( $_POST['rtbImport'] ) ) { add_action( 'admin_init', array($this, 'import_bookings' )); }
39
+
40
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_import_scripts' ) );
41
+ }
42
+
43
+ public function register_install_screen() {
44
+ $this->hook_suffix = add_submenu_page(
45
+ 'rtb-bookings',
46
+ _x( 'Import', 'Title of the Imports page', 'restaurant-reservations' ),
47
+ _x( 'Import', 'Title of Imports link in the admin menu', 'restaurant-reservations' ),
48
+ 'manage_options',
49
+ 'rtb-imports',
50
+ array( $this, 'display_editor_page' )
51
+ );
52
+
53
+ // Print the error modal and enqueue assets
54
+ add_action( 'load-' . $this->hook_suffix, array( $this, 'enqueue_admin_assets' ) );
55
+ }
56
+
57
+ public function display_import_screen() {
58
+ global $rtb_controller;
59
+
60
+ $import_permission = $rtb_controller->permissions->check_permission( 'import' );
61
+ ?>
62
+ <div class='wrap'>
63
+ <h2>Import</h2>
64
+ <?php if ( $import_permission ) { ?>
65
+ <form method='post' enctype="multipart/form-data">
66
+ <p>
67
+ <label for="rtb_bookings_spreadsheet"><?php _e("Spreadsheet Containing Bookings", 'restaurant-reservations') ?></label><br />
68
+ <input name="rtb_bookings_spreadsheet" type="file" value=""/>
69
+ </p>
70
+ <input type='submit' name='rtbImport' value='Import Bookings' class='button button-primary' />
71
+ </form>
72
+ <?php } else { ?>
73
+ <div class='rtb-premium-locked'>
74
+ <a href="https://www.fivestarplugins.com/license-payment/?Selected=RTB&Quantity=1" target="_blank">Upgrade</a> to the premium version to use this feature
75
+ </div>
76
+ <?php } ?>
77
+ </div>
78
+ <?php }
79
+
80
+ public function import_bookings() {
81
+ global $rtb_controller;
82
+
83
+ $fields = $rtb_controller->settings->get_booking_form_fields();
84
+
85
+ $update = $this->handle_spreadsheet_upload();
86
+
87
+ if ( $update['message_type'] != 'Success' ) :
88
+ $this->status = false;
89
+ $this->message = $update['message'];
90
+
91
+ add_action( 'admin_notices', array( $this, 'display_notice' ) );
92
+
93
+ return;
94
+ endif;
95
+
96
+ $excel_url = RTB_PLUGIN_DIR . '/user-sheets/' . $update['filename'];
97
+
98
+ // Build the workbook object out of the uploaded spreadsheet
99
+ $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($excel_url);
100
+
101
+ // Create a worksheet object out of the product sheet in the workbook
102
+ $sheet = $spreadsheet->getActiveSheet();
103
+
104
+ $allowable_fields = array();
105
+ foreach ($fields as $field) {$allowable_fields[] = $field->name;}
106
+ //List of fields that can be accepted via upload
107
+ //$allowed_fields = array("ID", "Title", "Description", "Price", "Sections");
108
+
109
+
110
+ // Get column names
111
+ $highest_column = $sheet->getHighestColumn();
112
+ $highest_column_index = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highest_column);
113
+ for ($column = 1; $column <= $highest_column_index; $column++) {
114
+ /*if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "ID") {$ID_column = $column;}
115
+ if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "Title") {$title_column = $column;}
116
+ if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "Description") {$description_column = $column;}
117
+ if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "Price") {$price_column = $column;}
118
+ if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == "Sections") {$sections_column = $column;}*/
119
+
120
+ foreach ($fields as $key => $field) {
121
+ if (trim($sheet->getCellByColumnAndRow($column, 1)->getValue()) == $field->name) {$field->column = $column;}
122
+ }
123
+ }
124
+
125
+
126
+ // Put the spreadsheet data into a multi-dimensional array to facilitate processing
127
+ $highest_row = $sheet->getHighestRow();
128
+ for ($row = 2; $row <= $highest_row; $row++) {
129
+ for ($column = 1; $column <= $highest_column_index; $column++) {
130
+ $data[$row][$column] = $sheet->getCellByColumnAndRow($column, $row)->getValue();
131
+ }
132
+ }
133
+
134
+ // Create the query to insert the products one at a time into the database and then run it
135
+ foreach ($data as $booking) {
136
+ // Create an array of the values that are being inserted for each order,
137
+ // edit if it's a current order, otherwise add it
138
+ foreach ($booking as $col_index => $value) {
139
+ if ($col_index == $ID_column and $ID_column !== null) {$post['ID'] = esc_sql($value);}
140
+ if ($col_index == $title_column and $title_column !== null) {$post['post_title'] = esc_sql($value);}
141
+ if ($col_index == $description_column and $description_column !== null) {$post['post_content'] = esc_sql($value);}
142
+ if ($col_index == $price_column and $price_column !== null) {$post_prices = explode(",", esc_sql($value));}
143
+ if (isset($sections_column) and $col_index == $sections_column and $sections_column !== null) {$post_sections = explode(",", esc_sql($value));}
144
+ }
145
+
146
+ if (!is_array($post_prices)) {$post_prices = array();}
147
+ if (!is_array($post_sections)) {$post_sections = array();}
148
+
149
+ if ($post['post_title'] == '') {continue;}
150
+
151
+ $post['post_status'] = 'publish';
152
+ $post['post_type'] = RTB_BOOKING_POST_TYPE;
153
+
154
+ if ( isset( $post['ID'] ) and $post['ID'] != '') { $post_id = wp_update_post($post); }
155
+ else { $post_id = wp_insert_post($post); }
156
+
157
+ if ( $post_id != 0 ) {
158
+ foreach ( $post_sections as $section ) {
159
+ $menu_section = term_exists($section, 'fdm-menu-section');
160
+ if ( $menu_section !== 0 && $menu_section !== null ) { $menu_section_ids[] = (int) $menu_section['term_id']; }
161
+ }
162
+ if ( isset($menu_section_ids) and is_array($menu_section_ids) ) { wp_set_object_terms($post_id, $menu_section_ids, 'fdm-menu-section'); }
163
+
164
+ update_post_meta( $post_id, 'fdm_item_price', implode(",", $post_prices) );
165
+
166
+ $field_values = array();
167
+ foreach ( $fields as $field ) {
168
+ if (isset($field->column) and isset($menu_item[$field->column])) {
169
+ $field_values[$field->slug] = esc_sql($menu_item[$field->column]);
170
+
171
+ }
172
+ }
173
+ update_post_meta($post_id, '_fdm_menu_item_custom_fields', $field_values);
174
+ }
175
+
176
+ unset($post);
177
+ unset($post_sections);
178
+ unset($menu_section_ids);
179
+ unset($post_prices);
180
+ unset($field_values);
181
+ }
182
+
183
+ $this->status = true;
184
+ $this->message = __("Reservations added successfully.", 'restaurant-reservations');
185
+
186
+ add_action( 'admin_notices', array( $this, 'display_notice' ) );
187
+ }
188
+
189
+ function handle_spreadsheet_upload() {
190
+ /* Test if there is an error with the uploaded spreadsheet and return that error if there is */
191
+ if (!empty($_FILES['fdm_menu_items_spreadsheet']['error']))
192
+ {
193
+ switch($_FILES['fdm_menu_items_spreadsheet']['error'])
194
+ {
195
+
196
+ case '1':
197
+ $error = __('The uploaded file exceeds the upload_max_filesize directive in php.ini', 'restaurant-reservations');
198
+ break;
199
+ case '2':
200
+ $error = __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', 'restaurant-reservations');
201
+ break;
202
+ case '3':
203
+ $error = __('The uploaded file was only partially uploaded', 'restaurant-reservations');
204
+ break;
205
+ case '4':
206
+ $error = __('No file was uploaded.', 'restaurant-reservations');
207
+ break;
208
+
209
+ case '6':
210
+ $error = __('Missing a temporary folder', 'restaurant-reservations');
211
+ break;
212
+ case '7':
213
+ $error = __('Failed to write file to disk', 'restaurant-reservations');
214
+ break;
215
+ case '8':
216
+ $error = __('File upload stopped by extension', 'restaurant-reservations');
217
+ break;
218
+ case '999':
219
+ default:
220
+ $error = __('No error code avaiable', 'restaurant-reservations');
221
+ }
222
+ }
223
+ /* Make sure that the file exists */
224
+ elseif (empty($_FILES['fdm_menu_items_spreadsheet']['tmp_name']) || $_FILES['fdm_menu_items_spreadsheet']['tmp_name'] == 'none') {
225
+ $error = __('No file was uploaded here..', 'restaurant-reservations');
226
+ }
227
+ /* Move the file and store the URL to pass it onwards*/
228
+ /* Check that it is a .xls or .xlsx file */
229
+ if(!isset($_FILES['fdm_menu_items_spreadsheet']['name']) or (!preg_match("/\.(xls.?)$/", $_FILES['fdm_menu_items_spreadsheet']['name']) and !preg_match("/\.(csv.?)$/", $_FILES['fdm_menu_items_spreadsheet']['name']))) {
230
+ $error = __('File must be .csv, .xls or .xlsx', 'restaurant-reservations');
231
+ }
232
+ else {
233
+ $filename = basename( $_FILES['fdm_menu_items_spreadsheet']['name']);
234
+ $filename = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $filename);
235
+ $filename = mb_ereg_replace("([\.]{2,})", '', $filename);
236
+
237
+ //for security reason, we force to remove all uploaded file
238
+ $target_path = RTB_PLUGIN_DIR . "/user-sheets/";
239
+
240
+ $target_path = $target_path . $filename;
241
+
242
+ if (!move_uploaded_file($_FILES['fdm_menu_items_spreadsheet']['tmp_name'], $target_path)) {
243
+ $error .= "There was an error uploading the file, please try again!";
244
+ }
245
+ else {
246
+ $excel_file_name = $filename;
247
+ }
248
+ }
249
+
250
+ /* Pass the data to the appropriate function in Update_Admin_Databases.php to create the products */
251
+ if (!isset($error)) {
252
+ $update = array("message_type" => "Success", "filename" => $excel_file_name);
253
+ }
254
+ else {
255
+ $update = array("message_type" => "Error", "message" => $error);
256
+ }
257
+ return $update;
258
+ }
259
+
260
+ public function enqueue_import_scripts() {
261
+ $screen = get_current_screen();
262
+ if($screen->id == 'rtb-menu_page_rtb-import'){
263
+ wp_enqueue_style( 'rtb-admin-css', RTB_PLUGIN_URL . '/assets/css/admin.css', array(), RTB_VERSION );
264
+ wp_enqueue_script( 'rtb-admin-js', RTB_PLUGIN_URL . '/assets/js/admin.js', array( 'jquery' ), RTB_VERSION, true );
265
+ }
266
+ }
267
+
268
+ public function display_notice() {
269
+ if ( $this->status ) {
270
+ echo "<div class='updated'><p>" . $this->message . "</p></div>";
271
+ }
272
+ else {
273
+ echo "<div class='error'><p>" . $this->message . "</p></div>";
274
+ }
275
+ }
276
+
277
+ }
278
+
279
+
includes/InstallationWalkthrough.class.php CHANGED
@@ -1,394 +1,394 @@
1
- <?php
2
-
3
- /**
4
- * Class to handle everything related to the walk-through that runs on plugin activation
5
- */
6
-
7
- if ( !defined( 'ABSPATH' ) )
8
- exit;
9
-
10
- class rtbInstallationWalkthrough {
11
-
12
- public function __construct() {
13
- add_action( 'admin_menu', array( $this, 'register_install_screen' ) );
14
- add_action( 'admin_head', array( $this, 'hide_install_screen_menu_item' ) );
15
- add_action( 'admin_init', array( $this, 'redirect' ), 9999 );
16
-
17
- add_action( 'admin_head', array( $this, 'admin_enqueue') );
18
-
19
- add_action( 'wp_ajax_rtb_welcome_add_menu_page', array( $this, 'add_reservations_page' ) );
20
- add_action( 'wp_ajax_rtb_welcome_set_schedule', array( $this, 'set_schedule' ) );
21
- add_action( 'wp_ajax_rtb_welcome_set_options', array( $this, 'set_options' ) );
22
- }
23
-
24
- public function redirect() {
25
- if ( ! get_transient( 'rtb-getting-started' ) )
26
- return;
27
-
28
- delete_transient( 'rtb-getting-started' );
29
-
30
- if ( is_network_admin() || isset( $_GET['activate-multi'] ) )
31
- return;
32
-
33
- $bookings = get_posts( array( 'post_type' => 'rtb-booking', 'post_status' => 'any' ) );
34
- if ( ! empty( $bookings ) ) {
35
- set_transient( 'rtb-admin-install-notice', true, 5 );
36
- return;
37
- }
38
-
39
- wp_safe_redirect( admin_url( 'index.php?page=rtb-getting-started' ) );
40
- exit;
41
- }
42
-
43
- public function register_install_screen() {
44
- add_dashboard_page(
45
- esc_html__( 'Five-Star Restaurant Reservations - Welcome!', 'restaurant-reservations' ),
46
- esc_html__( 'Five-Star Restaurant Reservations - Welcome!', 'restaurant-reservations' ),
47
- 'manage_options',
48
- 'rtb-getting-started',
49
- array($this, 'display_install_screen')
50
- );
51
- }
52
-
53
- public function hide_install_screen_menu_item() {
54
- remove_submenu_page( 'index.php', 'rtb-getting-started' );
55
- }
56
-
57
- public function add_reservations_page() {
58
- $reservations_page = wp_insert_post(array(
59
- 'post_title' => (isset($_POST['reservations_page_title']) ? sanitize_text_field( $_POST['reservations_page_title'] ) : ''),
60
- 'post_content' => '',
61
- 'post_status' => 'publish',
62
- 'post_type' => 'page'
63
- ));
64
-
65
- if ( $reservations_page ) {
66
- $rtb_options = get_option( 'rtb-settings' );
67
- $rtb_options['booking-page'] = $reservations_page;
68
- update_option( 'rtb-settings', $rtb_options );
69
- }
70
-
71
- exit();
72
- }
73
-
74
- public function set_schedule() {
75
- $rtb_options = get_option( 'rtb-settings' );
76
- $rtb_options['schedule-open'] = json_decode( stripslashes( $_POST['schedule_open'] ), true );
77
- update_option( 'rtb-settings', $rtb_options );
78
-
79
- exit();
80
- }
81
-
82
- public function set_options() {
83
- $rtb_options = get_option( 'rtb-settings' );
84
- $rtb_options['party-size-min'] = sanitize_text_field( $_POST['party_size_min'] );
85
- $rtb_options['party-size'] = sanitize_text_field( $_POST['party_size'] );
86
- $rtb_options['early-bookings'] = sanitize_text_field( $_POST['early_bookings'] );
87
- $rtb_options['late-bookings'] = sanitize_text_field( $_POST['late_bookings'] );
88
- $rtb_options['time-interval'] = sanitize_text_field( $_POST['time_interval'] );
89
- update_option( 'rtb-settings', $rtb_options );
90
-
91
- exit();
92
- }
93
-
94
- function admin_enqueue() {
95
-
96
- if ( ! isset( $_GET['page'] ) or $_GET['page'] != 'rtb-getting-started' ) { return; }
97
-
98
- wp_enqueue_style( 'rtb-admin-css', RTB_PLUGIN_URL . '/lib/simple-admin-pages/css/admin.css', array(), RTB_VERSION );
99
- wp_enqueue_style( 'rtb-welcome-screen', RTB_PLUGIN_URL . '/assets/css/admin-rtb-welcome-screen.css', array(), RTB_VERSION );
100
- wp_enqueue_style( 'pickadate-default', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.css', array(), RTB_VERSION );
101
- wp_enqueue_style( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.date.css', array(), RTB_VERSION );
102
- wp_enqueue_style( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.time.css', array(), RTB_VERSION );
103
-
104
- wp_enqueue_script( 'rtb-getting-started', RTB_PLUGIN_URL . '/assets/js/admin-rtb-welcome-screen.js', array('jquery'), RTB_VERSION );
105
- wp_enqueue_script( 'pickadate', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.js', array('jquery'), RTB_VERSION, true );
106
- wp_enqueue_script( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.date.js', array('jquery'), RTB_VERSION, true );
107
- wp_enqueue_script( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.time.js', array('jquery'), RTB_VERSION, true );
108
- wp_enqueue_script( 'pickadate-legacy', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/legacy.js', array('jquery'), RTB_VERSION, true );
109
- wp_enqueue_script( 'sap-scheduler', RTB_PLUGIN_URL . '/lib/simple-admin-pages/js/scheduler.js', array('jquery'), RTB_VERSION, true );
110
-
111
- $sap_scheduler_settings[ 'schedule-open' ] = array(
112
- 'time_interval' => 15,
113
- 'time_format' => 'h:i A',
114
- 'date_format' => 'd mmmm, yyyy',
115
- 'template' => $this->get_template(),
116
- 'weekdays' => array(
117
- 'monday' => 'Mo',
118
- 'tuesday' => 'Tu',
119
- 'wednesday' => 'We',
120
- 'thursday' => 'Th',
121
- 'friday' => 'Fr',
122
- 'saturday' => 'Sa',
123
- 'sunday' => 'Su',
124
- ),
125
- 'weeks' => array(
126
- 'first' => '1st',
127
- 'second' => '2nd',
128
- 'third' => '3rd',
129
- 'fourth' => '4th',
130
- 'last' => 'last',
131
- ),
132
- 'disable_weekdays' => false,
133
- 'disable_weeks' => true,
134
- 'disable_date' => true,
135
- 'disable_time' => false,
136
- 'disable_multiple' => false,
137
- 'summaries' => array(
138
- 'never' => __( 'Never', 'restaurant-reservations' ),
139
- 'weekly_always' => __( 'Every day', 'restaurant-reservations' ),
140
- 'monthly_weekdays' => sprintf( __( '%s on the %s week of the month', 'restaurant-reservations' ), '{days}', '{weeks}' ),
141
- 'monthly_weeks' => sprintf( __( '%s week of the month', 'restaurant-reservations' ), '{weeks}' ),
142
- 'all_day' => __( 'All day', 'restaurant-reservations' ),
143
- 'before' => __( 'Ends at', 'restaurant-reservations' ),
144
- 'after' => __( 'Starts at', 'restaurant-reservations' ),
145
- 'separator' => __( '&mdash', 'restaurant-reservations' ),
146
- ),
147
- );
148
-
149
- // This gets called multiple times, but only the last call is actually
150
- // pushed to the script.
151
- wp_localize_script(
152
- 'sap-scheduler',
153
- 'sap_scheduler',
154
- array(
155
- 'settings' => $sap_scheduler_settings
156
- )
157
- );
158
- }
159
-
160
- public function display_install_screen() { ?>
161
- <div class='rtb-welcome-screen'>
162
- <?php if (!isset($_GET['exclude'])) { ?>
163
- <div class='rtb-welcome-screen-header'>
164
- <h1><?php _e('Welcome to the Five-Star Restaurant Reservations Plugin', 'restaurant-reservations'); ?></h1>
165
- <p><?php _e('Thanks for choosing the Five-Star Restaurant Reservations! The following will help you get started with the setup of your reservations system by creating your reservations page, setting times when bookings are allowed as well as configuring a few key options.', 'restaurant-reservations'); ?></p>
166
- </div>
167
- <?php } ?>
168
- <?php if (!isset($_GET['exclude'])) { ?>
169
- <div class='rtb-welcome-screen-box rtb-welcome-screen-reservations_page rtb-welcome-screen-open' data-screen='reservations_page'>
170
- <h2><?php _e('Add a Reservations Page', 'restaurant-reservations'); ?></h2>
171
- <div class='rtb-welcome-screen-box-content'>
172
- <p><?php _e('You can create a dedicated reservations booking page below, or skip this step and add your reservations to a page you\'ve already created manually.', 'restaurant-reservations'); ?></p>
173
- <div class='rtb-welcome-screen-menu-page'>
174
- <div class='rtb-welcome-screen-add-reservations-page-name rtb-welcome-screen-box-content-divs'><label><?php _e('Page Title:', 'restaurant-reservations'); ?></label><input type='text' value='Reservations' /></div>
175
- <div class='rtb-welcome-screen-add-reservations-page-button' data-nextaction='schedule_open'><?php _e('Create Page', 'restaurant-reservations'); ?></div>
176
- </div>
177
- <div class='rtb-welcome-screen-next-button rtb-welcome-screen-next-button-not-top-margin' data-nextaction='schedule_open'><?php _e('Next Step', 'restaurant-reservations'); ?></div>
178
- <div class='clear'></div>
179
- </div>
180
- </div>
181
- <?php } ?>
182
- <div class='rtb-welcome-screen-box rtb-welcome-screen-schedule_open' data-screen='schedule_open'>
183
- <h2><?php echo (isset($_GET['exclude']) ? '1.' : '2.') . __(' Create Booking Schedule', 'restaurant-reservations'); ?></h2>
184
- <div class='rtb-welcome-screen-box-content'>
185
- <p><?php _e('Choose what times each week your restaurant is available to book reservations.', 'restaurant-reservations'); ?></p>
186
- <div class='rtb-welcome-screen-created-schedule-open'>
187
- <div class="sap-scheduler" id="schedule-open"></div>
188
- <div class="sap-add-scheduler">
189
- <a href="#" class="button">
190
- <?php _e('Add new scheduling rule', 'restaurant-reservations' ); ?>
191
- </a>
192
- </div>
193
- </div>
194
- <div class='rtb-welcome-screen-save-schedule-open-button'><?php _e('Save Schedule', 'restaurant-reservations'); ?></div>
195
- <div class="rtb-welcome-clear"></div>
196
- <div class='rtb-welcome-screen-next-button' data-nextaction='options'><?php _e('Next Step', 'restaurant-reservations'); ?></div>
197
- <div class='rtb-welcome-screen-previous-button' data-previousaction='reservations_page'><?php _e('Previous Step', 'restaurant-reservations'); ?></div>
198
- <div class='clear'></div>
199
- </div>
200
- </div>
201
-
202
- <div class='rtb-welcome-screen-box rtb-welcome-screen-options' data-screen='options'>
203
- <h2><?php echo (isset($_GET['exclude']) ? '2.' : '3.') . __(' Set Key Options', 'restaurant-reservations'); ?></h2>
204
- <div class='rtb-welcome-screen-box-content'>
205
- <p><?php _e('Set a min/max party size for bookings, choose how early and late bookings can be made, and pick the time interval between different booking options.', 'restaurant-reservations'); ?></p>
206
- <div class='rtb-welcome-screen-option'>
207
- <label><?php _e('Min Party Size:', 'restaurant-reservations'); ?></label>
208
- <select name='min-party-size'>
209
- <?php for ( $i = 1; $i <= 100; $i++ ) { ?>
210
- <option value='<?php echo $i; ?>'><?php echo $i; ?></option>
211
- <?php } ?>
212
- </select>
213
- </div>
214
- <div class='rtb-welcome-screen-option'>
215
- <label><?php _e('Max Party Size:', 'restaurant-reservations'); ?></label>
216
- <select name='party-size'>
217
- <option value='0'><?php _e('Any Size', 'restaurant-reservations' ); ?></option>
218
- <?php for ( $i = 1; $i <= 100; $i++ ) { ?>
219
- <option value='<?php echo $i; ?>'><?php echo $i; ?></option>
220
- <?php } ?>
221
- </select>
222
- </div>
223
- <div class='rtb-welcome-screen-option'>
224
- <label><?php _e('Early Bookings:', 'restaurant-reservations'); ?></label>
225
- <select name='early-bookings'>
226
- <option><?php _e('Any Time', 'restaurant-reservations' ); ?></option>
227
- <option value='1'><?php _e('From 1 day in advance', 'restaurant-reservations' ); ?></option>
228
- <option value='7'><?php _e('From 1 week in advance', 'restaurant-reservations' ); ?></option>
229
- <option value='14'><?php _e('From 2 weeks in advance', 'restaurant-reservations' ); ?></option>
230
- <option value='30'><?php _e('From 30 days in advance', 'restaurant-reservations' ); ?></option>
231
- <option value='90'><?php _e('From 90 days in advance', 'restaurant-reservations' ); ?></option>
232
- </select>
233
- </div>
234
- <div class='rtb-welcome-screen-option'>
235
- <label><?php _e('Late Bookings:', 'restaurant-reservations'); ?></label>
236
- <select name='late-bookings'>
237
- <option><?php _e('Up to the last minute', 'restaurant-reservations' ); ?></option>
238
- <option value='15'><?php _e('At least 15 minutes in advance', 'restaurant-reservations' ); ?></option>
239
- <option value='30'><?php _e('At least 30 minutes in advance', 'restaurant-reservations' ); ?></option>
240
- <option value='45'><?php _e('At least 45 minutes in advance', 'restaurant-reservations' ); ?></option>
241
- <option value='60'><?php _e('At least 1 hour in advance', 'restaurant-reservations' ); ?></option>
242
- <option value='240'><?php _e('At least 4 hours in advance', 'restaurant-reservations' ); ?></option>
243
- <option value='1440'><?php _e('At least 24 hours in advance', 'restaurant-reservations' ); ?></option>
244
- <option value='same_day'><?php _e('Block same-day-bookings', 'restaurant-reservations' ); ?></option>
245
- </select>
246
- </div>
247
- <div class='rtb-welcome-screen-option'>
248
- <label><?php _e('Time Interval:', 'restaurant-reservations'); ?></label>
249
- <select name='time-interval'>
250
- <option><?php _e('Every 30 minutes', 'restaurant-reservations' ); ?></option>
251
- <option value='15'><?php _e('Every 15 minutes', 'restaurant-reservations' ); ?></option>
252
- <option value='10'><?php _e('Every 10 minutes', 'restaurant-reservations' ); ?></option>
253
- <option value='5'><?php _e('Every 5 minutes', 'restaurant-reservations' ); ?></option>
254
- </select>
255
- </div>
256
- <div class='rtb-welcome-screen-save-options-button'><?php _e('Save Options', 'restaurant-reservations'); ?></div>
257
- <div class="rtb-welcome-clear"></div>
258
- <div class='rtb-welcome-screen-previous-button' data-previousaction='schedule_open'><?php _e('Previous Step', 'restaurant-reservations'); ?></div>
259
- <div class='rtb-welcome-screen-finish-button'><a href='admin.php?page=rtb-dashboard'><?php _e('Finish', 'restaurant-reservations'); ?></a></div>
260
- <div class='clear'></div>
261
- </div>
262
- </div>
263
-
264
- <div class='rtb-welcome-screen-skip-container'>
265
- <a href='admin.php?page=rtb-dashboard'><div class='rtb-welcome-screen-skip-button'><?php _e('Skip Setup', 'restaurant-reservations'); ?></div></a>
266
- </div>
267
- </div>
268
-
269
- <?php }
270
-
271
- /**
272
- * Retrieve the template for a scheduling rule
273
- * @since 2.0
274
- */
275
- public function get_template( $id = 0, $values = array(), $list = false ) {
276
-
277
- $date_format = 'weekly';
278
- $time_format = 'all-day';
279
-
280
- $weekdays = array(
281
- 'monday' => 'Mo',
282
- 'tuesday' => 'Tu',
283
- 'wednesday' => 'We',
284
- 'thursday' => 'Th',
285
- 'friday' => 'Fr',
286
- 'saturday' => 'Sa',
287
- 'sunday' => 'Su',
288
- );
289
-
290
- ob_start();
291
- ?>
292
-
293
- <div class="sap-scheduler-rule clearfix<?php echo $list ? ' list' : ''; ?>">
294
- <div class="sap-scheduler-date weekly">
295
- <ul class="sap-selector">
296
-
297
- <li>
298
- <div class="dashicons dashicons-calendar"></div>
299
- <?php _e( 'Weekly', 'restaurant-reservations' ); ?>
300
- </li>
301
-
302
- </ul>
303
-
304
- <ul class="sap-scheduler-weekdays">
305
- <li class="label">
306
- <?php _e( 'Days of the week', 'restaurant-reservations' ); ?>
307
- </li>
308
- <?php
309
- foreach ( $weekdays as $slug => $label ) :
310
- $input_name = 'rtb-setting[schedule_open][' . $id . '][weekdays][' . esc_attr( $slug ) . ']';
311
- ?>
312
- <li>
313
- &nbsp;<input type="checkbox" name="<?php echo $input_name; ?>" id="<?php echo $input_name; ?>" value="1" data-day="<?php echo esc_attr( $slug ); ?>"><label for="<?php echo $input_name; ?>"><?php echo ucfirst( $label ); ?></label>
314
- </li>
315
- <?php endforeach; ?>
316
- </ul>
317
-
318
- </div>
319
-
320
- <div class="sap-scheduler-time all-day">
321
-
322
- <ul class="sap-selector">
323
- <li>
324
- <div class="dashicons dashicons-clock"></div>
325
- <a href="#" data-format="time-slot">
326
- <?php _e( 'Time', 'restaurant-reservations' ); ?>
327
- </a>
328
- </li>
329
- <li>
330
- <a href="#" data-format="all-day" class="selected">
331
- <?php _e( 'All day', 'restaurant-reservations' ); ?>
332
- </a>
333
- </li>
334
- </ul>
335
-
336
- <div class="sap-scheduler-time-input clearfix">
337
-
338
- <div class="start">
339
- <label for="rtb-setting[schedule_open][<?php echo $id; ?>][time][start]">
340
- <?php _e( 'Start', 'restaurant-reservations' ); ?>
341
- </label>
342
- <input type="text" name="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][start]'; ?>" id="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][start]'; ?>">
343
- </div>
344
-
345
- <div class="end">
346
- <label for="rtb-setting[schedule_open][<?php echo $id; ?>][time][end]">
347
- <?php _e( 'End', 'restaurant-reservations' ); ?>
348
- </label>
349
- <input type="text" name="<?php echo'rtb-setting[schedule_open][' . $id . '][time][end]'; ?>" id="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][end]'; ?>">
350
- </div>
351
-
352
- </div>
353
-
354
- <div class="sap-scheduler-all-day">
355
- <?php printf( __( 'All day long. Want to %sset a time slot%s?', 'restaurant-reservations' ), '<a href="#" data-format="time-slot">', '</a>' ); ?>
356
- </div>
357
-
358
- </div>
359
-
360
- <div class="sap-scheduler-brief">
361
- <div class="date">
362
- <div class="dashicons dashicons-calendar"></div>
363
- <span class="value"></span>
364
- </div>
365
- <div class="time">
366
- <div class="dashicons dashicons-clock"></div>
367
- <span class="value"></span>
368
- </div>
369
- </div>
370
- <div class="sap-scheduler-control">
371
- <a href="#" class="toggle" title="<?php _e( 'Open and close this rule', 'restaurant-reservations' ); ?>">
372
- <div class="dashicons dashicons-<?php echo $list ? 'edit' : 'arrow-up-alt2'; ?>"></div>
373
- <span class="screen-reader-text">
374
- <?php _e( 'Open and close this rule', 'restaurant-reservations' ); ?>
375
- </span>
376
- </a>
377
- <a href="#" class="delete" title="<?php _e( 'Delete rule', 'restaurant-reservations' ); ?>">
378
- <div class="dashicons dashicons-dismiss"></div>
379
- <span class="screen-reader-text">
380
- <?php _e( 'Delete scheduling rule', 'restaurant-reservations' ); ?>
381
- </span>
382
- </a>
383
- </div>
384
- </div>
385
-
386
- <?php
387
- $output = ob_get_clean();
388
-
389
- return $output;
390
- }
391
- }
392
-
393
-
394
  ?>
1
+ <?php
2
+
3
+ /**
4
+ * Class to handle everything related to the walk-through that runs on plugin activation
5
+ */
6
+
7
+ if ( !defined( 'ABSPATH' ) )
8
+ exit;
9
+
10
+ class rtbInstallationWalkthrough {
11
+
12
+ public function __construct() {
13
+ add_action( 'admin_menu', array( $this, 'register_install_screen' ) );
14
+ add_action( 'admin_head', array( $this, 'hide_install_screen_menu_item' ) );
15
+ add_action( 'admin_init', array( $this, 'redirect' ), 9999 );
16
+
17
+ add_action( 'admin_head', array( $this, 'admin_enqueue') );
18
+
19
+ add_action( 'wp_ajax_rtb_welcome_add_menu_page', array( $this, 'add_reservations_page' ) );
20
+ add_action( 'wp_ajax_rtb_welcome_set_schedule', array( $this, 'set_schedule' ) );
21
+ add_action( 'wp_ajax_rtb_welcome_set_options', array( $this, 'set_options' ) );
22
+ }
23
+
24
+ public function redirect() {
25
+ if ( ! get_transient( 'rtb-getting-started' ) )
26
+ return;
27
+
28
+ delete_transient( 'rtb-getting-started' );
29
+
30
+ if ( is_network_admin() || isset( $_GET['activate-multi'] ) )
31
+ return;
32
+
33
+ $bookings = get_posts( array( 'post_type' => 'rtb-booking', 'post_status' => 'any' ) );
34
+ if ( ! empty( $bookings ) ) {
35
+ set_transient( 'rtb-admin-install-notice', true, 5 );
36
+ return;
37
+ }
38
+
39
+ wp_safe_redirect( admin_url( 'index.php?page=rtb-getting-started' ) );
40
+ exit;
41
+ }
42
+
43
+ public function register_install_screen() {
44
+ add_dashboard_page(
45
+ esc_html__( 'Five-Star Restaurant Reservations - Welcome!', 'restaurant-reservations' ),
46
+ esc_html__( 'Five-Star Restaurant Reservations - Welcome!', 'restaurant-reservations' ),
47
+ 'manage_options',
48
+ 'rtb-getting-started',
49
+ array($this, 'display_install_screen')
50
+ );
51
+ }
52
+
53
+ public function hide_install_screen_menu_item() {
54
+ remove_submenu_page( 'index.php', 'rtb-getting-started' );
55
+ }
56
+
57
+ public function add_reservations_page() {
58
+ $reservations_page = wp_insert_post(array(
59
+ 'post_title' => (isset($_POST['reservations_page_title']) ? sanitize_text_field( $_POST['reservations_page_title'] ) : ''),
60
+ 'post_content' => '',
61
+ 'post_status' => 'publish',
62
+ 'post_type' => 'page'
63
+ ));
64
+
65
+ if ( $reservations_page ) {
66
+ $rtb_options = get_option( 'rtb-settings' );
67
+ $rtb_options['booking-page'] = $reservations_page;
68
+ update_option( 'rtb-settings', $rtb_options );
69
+ }
70
+
71
+ exit();
72
+ }
73
+
74
+ public function set_schedule() {
75
+ $rtb_options = get_option( 'rtb-settings' );
76
+ $rtb_options['schedule-open'] = json_decode( stripslashes( $_POST['schedule_open'] ), true );
77
+ update_option( 'rtb-settings', $rtb_options );
78
+
79
+ exit();
80
+ }
81
+
82
+ public function set_options() {
83
+ $rtb_options = get_option( 'rtb-settings' );
84
+ $rtb_options['party-size-min'] = sanitize_text_field( $_POST['party_size_min'] );
85
+ $rtb_options['party-size'] = sanitize_text_field( $_POST['party_size'] );
86
+ $rtb_options['early-bookings'] = sanitize_text_field( $_POST['early_bookings'] );
87
+ $rtb_options['late-bookings'] = sanitize_text_field( $_POST['late_bookings'] );
88
+ $rtb_options['time-interval'] = sanitize_text_field( $_POST['time_interval'] );
89
+ update_option( 'rtb-settings', $rtb_options );
90
+
91
+ exit();
92
+ }
93
+
94
+ function admin_enqueue() {
95
+
96
+ if ( ! isset( $_GET['page'] ) or $_GET['page'] != 'rtb-getting-started' ) { return; }
97
+
98
+ wp_enqueue_style( 'rtb-admin-css', RTB_PLUGIN_URL . '/lib/simple-admin-pages/css/admin.css', array(), RTB_VERSION );
99
+ wp_enqueue_style( 'rtb-welcome-screen', RTB_PLUGIN_URL . '/assets/css/admin-rtb-welcome-screen.css', array(), RTB_VERSION );
100
+ wp_enqueue_style( 'pickadate-default', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.css', array(), RTB_VERSION );
101
+ wp_enqueue_style( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.date.css', array(), RTB_VERSION );
102
+ wp_enqueue_style( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.time.css', array(), RTB_VERSION );
103
+
104
+ wp_enqueue_script( 'rtb-getting-started', RTB_PLUGIN_URL . '/assets/js/admin-rtb-welcome-screen.js', array('jquery'), RTB_VERSION );
105
+ wp_enqueue_script( 'pickadate', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.js', array('jquery'), RTB_VERSION, true );
106
+ wp_enqueue_script( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.date.js', array('jquery'), RTB_VERSION, true );
107
+ wp_enqueue_script( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.time.js', array('jquery'), RTB_VERSION, true );
108
+ wp_enqueue_script( 'pickadate-legacy', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/legacy.js', array('jquery'), RTB_VERSION, true );
109
+ wp_enqueue_script( 'sap-scheduler', RTB_PLUGIN_URL . '/lib/simple-admin-pages/js/scheduler.js', array('jquery'), RTB_VERSION, true );
110
+
111
+ $sap_scheduler_settings[ 'schedule-open' ] = array(
112
+ 'time_interval' => 15,
113
+ 'time_format' => 'h:i A',
114
+ 'date_format' => 'd mmmm, yyyy',
115
+ 'template' => $this->get_template(),
116
+ 'weekdays' => array(
117
+ 'monday' => 'Mo',
118
+ 'tuesday' => 'Tu',
119
+ 'wednesday' => 'We',
120
+ 'thursday' => 'Th',
121
+ 'friday' => 'Fr',
122
+ 'saturday' => 'Sa',
123
+ 'sunday' => 'Su',
124
+ ),
125
+ 'weeks' => array(
126
+ 'first' => '1st',
127
+ 'second' => '2nd',
128
+ 'third' => '3rd',
129
+ 'fourth' => '4th',
130
+ 'last' => 'last',
131
+ ),
132
+ 'disable_weekdays' => false,
133
+ 'disable_weeks' => true,
134
+ 'disable_date' => true,
135
+ 'disable_time' => false,
136
+ 'disable_multiple' => false,
137
+ 'summaries' => array(
138
+ 'never' => __( 'Never', 'restaurant-reservations' ),
139
+ 'weekly_always' => __( 'Every day', 'restaurant-reservations' ),
140
+ 'monthly_weekdays' => sprintf( __( '%s on the %s week of the month', 'restaurant-reservations' ), '{days}', '{weeks}' ),
141
+ 'monthly_weeks' => sprintf( __( '%s week of the month', 'restaurant-reservations' ), '{weeks}' ),
142
+ 'all_day' => __( 'All day', 'restaurant-reservations' ),
143
+ 'before' => __( 'Ends at', 'restaurant-reservations' ),
144
+ 'after' => __( 'Starts at', 'restaurant-reservations' ),
145
+ 'separator' => __( '&mdash', 'restaurant-reservations' ),
146
+ ),
147
+ );
148
+
149
+ // This gets called multiple times, but only the last call is actually
150
+ // pushed to the script.
151
+ wp_localize_script(
152
+ 'sap-scheduler',
153
+ 'sap_scheduler',
154
+ array(
155
+ 'settings' => $sap_scheduler_settings
156
+ )
157
+ );
158
+ }
159
+
160
+ public function display_install_screen() { ?>
161
+ <div class='rtb-welcome-screen'>
162
+ <?php if (!isset($_GET['exclude'])) { ?>
163
+ <div class='rtb-welcome-screen-header'>
164
+ <h1><?php _e('Welcome to the Five-Star Restaurant Reservations Plugin', 'restaurant-reservations'); ?></h1>
165
+ <p><?php _e('Thanks for choosing the Five-Star Restaurant Reservations! The following will help you get started with the setup of your reservations system by creating your reservations page, setting times when bookings are allowed as well as configuring a few key options.', 'restaurant-reservations'); ?></p>
166
+ </div>
167
+ <?php } ?>
168
+ <?php if (!isset($_GET['exclude'])) { ?>
169
+ <div class='rtb-welcome-screen-box rtb-welcome-screen-reservations_page rtb-welcome-screen-open' data-screen='reservations_page'>
170
+ <h2><?php _e('Add a Reservations Page', 'restaurant-reservations'); ?></h2>
171
+ <div class='rtb-welcome-screen-box-content'>
172
+ <p><?php _e('You can create a dedicated reservations booking page below, or skip this step and add your reservations to a page you\'ve already created manually.', 'restaurant-reservations'); ?></p>
173
+ <div class='rtb-welcome-screen-menu-page'>
174
+ <div class='rtb-welcome-screen-add-reservations-page-name rtb-welcome-screen-box-content-divs'><label><?php _e('Page Title:', 'restaurant-reservations'); ?></label><input type='text' value='Reservations' /></div>
175
+ <div class='rtb-welcome-screen-add-reservations-page-button' data-nextaction='schedule_open'><?php _e('Create Page', 'restaurant-reservations'); ?></div>
176
+ </div>
177
+ <div class='rtb-welcome-screen-next-button rtb-welcome-screen-next-button-not-top-margin' data-nextaction='schedule_open'><?php _e('Next Step', 'restaurant-reservations'); ?></div>
178
+ <div class='clear'></div>
179
+ </div>
180
+ </div>
181
+ <?php } ?>
182
+ <div class='rtb-welcome-screen-box rtb-welcome-screen-schedule_open' data-screen='schedule_open'>
183
+ <h2><?php echo (isset($_GET['exclude']) ? '1.' : '2.') . __(' Create Booking Schedule', 'restaurant-reservations'); ?></h2>
184
+ <div class='rtb-welcome-screen-box-content'>
185
+ <p><?php _e('Choose what times each week your restaurant is available to book reservations.', 'restaurant-reservations'); ?></p>
186
+ <div class='rtb-welcome-screen-created-schedule-open'>
187
+ <div class="sap-scheduler" id="schedule-open"></div>
188
+ <div class="sap-add-scheduler">
189
+ <a href="#" class="button">
190
+ <?php _e('Add new scheduling rule', 'restaurant-reservations' ); ?>
191
+ </a>
192
+ </div>
193
+ </div>
194
+ <div class='rtb-welcome-screen-save-schedule-open-button'><?php _e('Save Schedule', 'restaurant-reservations'); ?></div>
195
+ <div class="rtb-welcome-clear"></div>
196
+ <div class='rtb-welcome-screen-next-button' data-nextaction='options'><?php _e('Next Step', 'restaurant-reservations'); ?></div>
197
+ <div class='rtb-welcome-screen-previous-button' data-previousaction='reservations_page'><?php _e('Previous Step', 'restaurant-reservations'); ?></div>
198
+ <div class='clear'></div>
199
+ </div>
200
+ </div>
201
+
202
+ <div class='rtb-welcome-screen-box rtb-welcome-screen-options' data-screen='options'>
203
+ <h2><?php echo (isset($_GET['exclude']) ? '2.' : '3.') . __(' Set Key Options', 'restaurant-reservations'); ?></h2>
204
+ <div class='rtb-welcome-screen-box-content'>
205
+ <p><?php _e('Set a min/max party size for bookings, choose how early and late bookings can be made, and pick the time interval between different booking options.', 'restaurant-reservations'); ?></p>
206
+ <div class='rtb-welcome-screen-option'>
207
+ <label><?php _e('Min Party Size:', 'restaurant-reservations'); ?></label>
208
+ <select name='min-party-size'>
209
+ <?php for ( $i = 1; $i <= 100; $i++ ) { ?>
210
+ <option value='<?php echo $i; ?>'><?php echo $i; ?></option>
211
+ <?php } ?>
212
+ </select>
213
+ </div>
214
+ <div class='rtb-welcome-screen-option'>
215
+ <label><?php _e('Max Party Size:', 'restaurant-reservations'); ?></label>
216
+ <select name='party-size'>
217
+ <option value='0'><?php _e('Any Size', 'restaurant-reservations' ); ?></option>
218
+ <?php for ( $i = 1; $i <= 100; $i++ ) { ?>
219
+ <option value='<?php echo $i; ?>'><?php echo $i; ?></option>
220
+ <?php } ?>
221
+ </select>
222
+ </div>
223
+ <div class='rtb-welcome-screen-option'>
224
+ <label><?php _e('Early Bookings:', 'restaurant-reservations'); ?></label>
225
+ <select name='early-bookings'>
226
+ <option><?php _e('Any Time', 'restaurant-reservations' ); ?></option>
227
+ <option value='1'><?php _e('From 1 day in advance', 'restaurant-reservations' ); ?></option>
228
+ <option value='7'><?php _e('From 1 week in advance', 'restaurant-reservations' ); ?></option>
229
+ <option value='14'><?php _e('From 2 weeks in advance', 'restaurant-reservations' ); ?></option>
230
+ <option value='30'><?php _e('From 30 days in advance', 'restaurant-reservations' ); ?></option>
231
+ <option value='90'><?php _e('From 90 days in advance', 'restaurant-reservations' ); ?></option>
232
+ </select>
233
+ </div>
234
+ <div class='rtb-welcome-screen-option'>
235
+ <label><?php _e('Late Bookings:', 'restaurant-reservations'); ?></label>
236
+ <select name='late-bookings'>
237
+ <option><?php _e('Up to the last minute', 'restaurant-reservations' ); ?></option>
238
+ <option value='15'><?php _e('At least 15 minutes in advance', 'restaurant-reservations' ); ?></option>
239
+ <option value='30'><?php _e('At least 30 minutes in advance', 'restaurant-reservations' ); ?></option>
240
+ <option value='45'><?php _e('At least 45 minutes in advance', 'restaurant-reservations' ); ?></option>
241
+ <option value='60'><?php _e('At least 1 hour in advance', 'restaurant-reservations' ); ?></option>
242
+ <option value='240'><?php _e('At least 4 hours in advance', 'restaurant-reservations' ); ?></option>
243
+ <option value='1440'><?php _e('At least 24 hours in advance', 'restaurant-reservations' ); ?></option>
244
+ <option value='same_day'><?php _e('Block same-day-bookings', 'restaurant-reservations' ); ?></option>
245
+ </select>
246
+ </div>
247
+ <div class='rtb-welcome-screen-option'>
248
+ <label><?php _e('Time Interval:', 'restaurant-reservations'); ?></label>
249
+ <select name='time-interval'>
250
+ <option><?php _e('Every 30 minutes', 'restaurant-reservations' ); ?></option>
251
+ <option value='15'><?php _e('Every 15 minutes', 'restaurant-reservations' ); ?></option>
252
+ <option value='10'><?php _e('Every 10 minutes', 'restaurant-reservations' ); ?></option>
253
+ <option value='5'><?php _e('Every 5 minutes', 'restaurant-reservations' ); ?></option>
254
+ </select>
255
+ </div>
256
+ <div class='rtb-welcome-screen-save-options-button'><?php _e('Save Options', 'restaurant-reservations'); ?></div>
257
+ <div class="rtb-welcome-clear"></div>
258
+ <div class='rtb-welcome-screen-previous-button' data-previousaction='schedule_open'><?php _e('Previous Step', 'restaurant-reservations'); ?></div>
259
+ <div class='rtb-welcome-screen-finish-button'><a href='admin.php?page=rtb-dashboard'><?php _e('Finish', 'restaurant-reservations'); ?></a></div>
260
+ <div class='clear'></div>
261
+ </div>
262
+ </div>
263
+
264
+ <div class='rtb-welcome-screen-skip-container'>
265
+ <a href='admin.php?page=rtb-dashboard'><div class='rtb-welcome-screen-skip-button'><?php _e('Skip Setup', 'restaurant-reservations'); ?></div></a>
266
+ </div>
267
+ </div>
268
+
269
+ <?php }
270
+
271
+ /**
272
+ * Retrieve the template for a scheduling rule
273
+ * @since 2.0
274
+ */
275
+ public function get_template( $id = 0, $values = array(), $list = false ) {
276
+
277
+ $date_format = 'weekly';
278
+ $time_format = 'all-day';
279
+
280
+ $weekdays = array(
281
+ 'monday' => 'Mo',
282
+ 'tuesday' => 'Tu',
283
+ 'wednesday' => 'We',
284
+ 'thursday' => 'Th',
285
+ 'friday' => 'Fr',
286
+ 'saturday' => 'Sa',
287
+ 'sunday' => 'Su',
288
+ );
289
+
290
+ ob_start();
291
+ ?>
292
+
293
+ <div class="sap-scheduler-rule clearfix<?php echo $list ? ' list' : ''; ?>">
294
+ <div class="sap-scheduler-date weekly">
295
+ <ul class="sap-selector">
296
+
297
+ <li>
298
+ <div class="dashicons dashicons-calendar"></div>
299
+ <?php _e( 'Weekly', 'restaurant-reservations' ); ?>
300
+ </li>
301
+
302
+ </ul>
303
+
304
+ <ul class="sap-scheduler-weekdays">
305
+ <li class="label">
306
+ <?php _e( 'Days of the week', 'restaurant-reservations' ); ?>
307
+ </li>
308
+ <?php
309
+ foreach ( $weekdays as $slug => $label ) :
310
+ $input_name = 'rtb-setting[schedule_open][' . $id . '][weekdays][' . esc_attr( $slug ) . ']';
311
+ ?>
312
+ <li>
313
+ &nbsp;<input type="checkbox" name="<?php echo $input_name; ?>" id="<?php echo $input_name; ?>" value="1" data-day="<?php echo esc_attr( $slug ); ?>"><label for="<?php echo $input_name; ?>"><?php echo ucfirst( $label ); ?></label>
314
+ </li>
315
+ <?php endforeach; ?>
316
+ </ul>
317
+
318
+ </div>
319
+
320
+ <div class="sap-scheduler-time all-day">
321
+
322
+ <ul class="sap-selector">
323
+ <li>
324
+ <div class="dashicons dashicons-clock"></div>
325
+ <a href="#" data-format="time-slot">
326
+ <?php _e( 'Time', 'restaurant-reservations' ); ?>
327
+ </a>
328
+ </li>
329
+ <li>
330
+ <a href="#" data-format="all-day" class="selected">
331
+ <?php _e( 'All day', 'restaurant-reservations' ); ?>
332
+ </a>
333
+ </li>
334
+ </ul>
335
+
336
+ <div class="sap-scheduler-time-input clearfix">
337
+
338
+ <div class="start">
339
+ <label for="rtb-setting[schedule_open][<?php echo $id; ?>][time][start]">
340
+ <?php _e( 'Start', 'restaurant-reservations' ); ?>
341
+ </label>
342
+ <input type="text" name="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][start]'; ?>" id="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][start]'; ?>">
343
+ </div>
344
+
345
+ <div class="end">
346
+ <label for="rtb-setting[schedule_open][<?php echo $id; ?>][time][end]">
347
+ <?php _e( 'End', 'restaurant-reservations' ); ?>
348
+ </label>
349
+ <input type="text" name="<?php echo'rtb-setting[schedule_open][' . $id . '][time][end]'; ?>" id="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][end]'; ?>">
350
+ </div>
351
+
352
+ </div>
353
+
354
+ <div class="sap-scheduler-all-day">
355
+ <?php printf( __( 'All day long. Want to %sset a time slot%s?', 'restaurant-reservations' ), '<a href="#" data-format="time-slot">', '</a>' ); ?>
356
+ </div>
357
+
358
+ </div>
359
+
360
+ <div class="sap-scheduler-brief">
361
+ <div class="date">
362
+ <div class="dashicons dashicons-calendar"></div>
363
+ <span class="value"></span>
364
+ </div>
365
+ <div class="time">
366
+ <div class="dashicons dashicons-clock"></div>
367
+ <span class="value"></span>
368
+ </div>
369
+ </div>
370
+ <div class="sap-scheduler-control">
371
+ <a href="#" class="toggle" title="<?php _e( 'Open and close this rule', 'restaurant-reservations' ); ?>">
372
+ <div class="dashicons dashicons-<?php echo $list ? 'edit' : 'arrow-up-alt2'; ?>"></div>
373
+ <span class="screen-reader-text">
374
+ <?php _e( 'Open and close this rule', 'restaurant-reservations' ); ?>
375
+ </span>
376
+ </a>
377
+ <a href="#" class="delete" title="<?php _e( 'Delete rule', 'restaurant-reservations' ); ?>">
378
+ <div class="dashicons dashicons-dismiss"></div>
379
+ <span class="screen-reader-text">
380
+ <?php _e( 'Delete scheduling rule', 'restaurant-reservations' ); ?>
381
+ </span>
382
+ </a>
383
+ </div>
384
+ </div>
385
+
386
+ <?php
387
+ $output = ob_get_clean();
388
+
389
+ return $output;
390
+ }
391
+ }
392
+
393
+
394
  ?>
includes/Licenses.class.php CHANGED
@@ -1,270 +1,270 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbLicenses' ) ) {
5
- /**
6
- * Class to handle license keys when an addon
7
- * is enabled.
8
- *
9
- * This will add a tab to the settings page as long as at least
10
- * one addon has enabled it by setting its $enabled parameter
11
- * to `true`. It will also perform the license check and plugin
12
- * update procedures.
13
- *
14
- * If no addons are enabled, it does not phone home or perform
15
- * any additional actions.
16
- *
17
- * @since 1.4.1
18
- */
19
- class rtbLicenses {
20
-
21
- /**
22
- * Array of licensed products to manage
23
- *
24
- * @since 1.4.1
25
- */
26
- public $licensed_products = array();
27
-
28
- /**
29
- * Path to load license setting class file
30
- *
31
- * @since 1.4.1
32
- */
33
- public $sap_extension_path;
34
-
35
- /**
36
- * Filename of the setting class to handle a
37
- * license key input field.
38
- *
39
- * @since 1.4.1
40
- */
41
- public $sap_setting_file;
42
-
43
- /**
44
- * Class name of the setting file to load
45
- * when handling a license key input field.
46
- *
47
- * Should contain the class referenced in
48
- * $sap_setting_class.
49
- *
50
- * @since 1.4.1
51
- */
52
- public $sap_setting_class;
53
-
54
- /**
55
- * Initialize the license handling
56
- *
57
- * @since 1.4.1
58
- */
59
- public function __construct() {
60
-
61
- $this->sap_extension_path = RTB_PLUGIN_DIR . '/includes/';
62
- $this->sap_setting_file = 'AdminPageSettingLicenseKey.class.php';
63
- $this->sap_setting_class = 'rtbAdminPageSettingLicenseKey';
64
-
65
- // Check and process updates
66
- add_action( 'admin_init', array( $this, 'load_plugin_updater' ), 20 );
67
-
68
- // Add a licenses tab as the last tab in the settings page
69
- add_filter( 'rtb_settings_page', array( $this, 'add_licenses_tab' ), 100 );
70
-
71
- // Enqueue assets on licenses page
72
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
73
-
74
- // Show a success/failed message on license activation/deactivation
75
- add_action( 'admin_notices', array( $this, 'admin_notices' ) );
76
- }
77
-
78
- /**
79
- * Check if a licensing system should be enabled
80
- *
81
- * @since 1.4.1
82
- */
83
- public function is_enabled() {
84
-
85
- if ( count( $this->licensed_products ) ) {
86
- return true;
87
- }
88
-
89
- return false;
90
- }
91
-
92
- /**
93
- * Add a licensed product to manage
94
- *
95
- * This should be called in admin_init (before priority 20) so that
96
- * the updater knows what products to check.
97
- *
98
- * @since 1.4.1
99
- */
100
- public function add_licensed_product( $product ) {
101
- $this->licensed_products[ $product['id'] ] = $product;
102
- }
103
-
104
- /**
105
- * Add a licenses tab as the last tab in the settings page
106
- *
107
- * @since 1.4.1
108
- */
109
- public function add_licenses_tab( $sap ) {
110
-
111
- if ( !$this->is_enabled() ) {
112
- return $sap;
113
- }
114
-
115
- $sap->add_section(
116
- 'rtb-settings',
117
- array(
118
- 'id' => 'rtb-licenses',
119
- 'title' => __( 'Licenses', 'restaurant-reservations' ),
120
- 'description' => sprintf(
121
- __( 'Activate license keys for any commercial addons you have purchased. %sView all addons%s.', 'restaurant-reservations' ),
122
- '<a href="' . admin_url( 'admin.php?page=rtb-addons' ) . '">',
123
- '</a>'
124
- ),
125
- 'is_tab' => true,
126
- )
127
- );
128
-
129
- $sap->lib_extension_path = $this->sap_extension_path;
130
-
131
- global $rtb_controller;
132
- foreach( $this->licensed_products as $product ) {
133
- $sap->add_setting(
134
- 'rtb-settings',
135
- 'rtb-licenses',
136
- array(
137
- 'id' => 'rtb-license-key',
138
- 'filename' => $rtb_controller->licenses->sap_setting_file,
139
- 'class' => $rtb_controller->licenses->sap_setting_class,
140
- ),
141
- $product
142
- );
143
- }
144
-
145
- do_action( 'rtb_settings_licenses', $sap );
146
-
147
-
148
- return $sap;
149
- }
150
-
151
- /**
152
- * Check if we're on the licenses page
153
- *
154
- * @since 1.4.1
155
- */
156
- public function is_license_page() {
157
-
158
- global $rtb_controller;
159
-
160
- // Use the page reference in $admin_page_hooks because
161
- // it changes in SOME hooks when it is translated.
162
- // https://core.trac.wordpress.org/ticket/18857
163
- global $admin_page_hooks;
164
-
165
- $screen = get_current_screen();
166
- if ( empty( $screen ) || empty( $admin_page_hooks['rtb-bookings'] ) ) {
167
- return false;
168
- }
169
-
170
- if ( $screen->base != $admin_page_hooks['rtb-bookings'] . '_page_rtb-settings' || empty( $_GET['tab'] ) || $_GET['tab'] !== 'rtb-licenses' ) {
171
- return false;
172
- }
173
-
174
- return true;
175
- }
176
-
177
- /**
178
- * Enqueue JavaScript and CSS files on the licenses page
179
- *
180
- * @since 1.4.1
181
- */
182
- public function enqueue_assets() {
183
-
184
- if ( !$this->is_license_page() ) {
185
- return;
186
- }
187
-
188
- wp_enqueue_style( 'rtb-admin', RTB_PLUGIN_URL . '/assets/css/admin.css', array(), RTB_VERSION );
189
- }
190
-
191
- /**
192
- * Show admin notices on license activation/deactivation attempts
193
- *
194
- * @since 1.4.1
195
- */
196
- public function admin_notices() {
197
-
198
- if ( !$this->is_license_page() || !isset( $_GET['license_result'] ) || $_GET['license_result'] != 0 || empty( $_GET['action'] ) ) {
199
- return;
200
- }
201
-
202
- $error = empty( $_GET['result_error'] ) ? '' : $_GET['result_error'];
203
-
204
- if ( $_GET['action'] == 'deactivate' ) {
205
- $msg = __( 'Your attempt to deactivate a license key failed. Please try again later or contact support for help.', 'restaurant-reservations' );
206
- } else {
207
-
208
- if ( $error == 'no_activations_left' ) {
209
- $msg = sprintf( __( 'You have reached the activation limit for this license. If you have the license activated on other sites you will need to deactivate them or purchase more license keys from %sTheme of the Crop%s.', 'restaurant-reservations' ), '<a href="http://themeofthecrop.com/">', '</a>' );
210
- } else {
211
- $msg = __( 'Your attempt to activate a license key failed. Please check the license key and try again.', 'restaurant-reservations' );
212
- }
213
- }
214
-
215
- ?>
216
-
217
- <div class="error">
218
- <p><?php echo $msg; ?></p>
219
- </div>
220
-
221
- <?php
222
-
223
- }
224
-
225
- /**
226
- * Load plugin updater library for Easy Digital Downloads Software
227
- * Licensing API
228
- *
229
- * @since 0.3
230
- */
231
- public function load_plugin_updater() {
232
-
233
- if ( !$this->is_enabled() ) {
234
- return;
235
- }
236
-
237
- if ( !class_exists( 'RTB_EDD_SL_Plugin_Updater' ) ) {
238
-
239
- if ( !file_exists( RTB_PLUGIN_DIR . '/lib/EDD_SL_Plugin_Updater.class.php' ) ) {
240
- return;
241
- }
242
-
243
- require_once( RTB_PLUGIN_DIR . '/lib/EDD_SL_Plugin_Updater.class.php' );
244
- }
245
-
246
- global $rtb_controller;
247
-
248
- $this->updaters = array();
249
- foreach( $this->licensed_products as $product ) {
250
-
251
- $license = $rtb_controller->settings->get_setting( $product['id'] );
252
-
253
- if ( empty( $license ) || empty( $license['api_key']) ) {
254
- continue;
255
- }
256
-
257
- new RTB_EDD_SL_Plugin_Updater(
258
- $product['store_url'],
259
- $product['plugin_path'],
260
- array(
261
- 'version' => $product['version'],
262
- 'license' => $license['api_key'],
263
- 'item_name' => $product['product'],
264
- 'author' => $product['author'],
265
- )
266
- );
267
- }
268
- }
269
- }
270
- } // endif
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbLicenses' ) ) {
5
+ /**
6
+ * Class to handle license keys when an addon
7
+ * is enabled.
8
+ *
9
+ * This will add a tab to the settings page as long as at least
10
+ * one addon has enabled it by setting its $enabled parameter
11
+ * to `true`. It will also perform the license check and plugin
12
+ * update procedures.
13
+ *
14
+ * If no addons are enabled, it does not phone home or perform
15
+ * any additional actions.
16
+ *
17
+ * @since 1.4.1
18
+ */
19
+ class rtbLicenses {
20
+
21
+ /**
22
+ * Array of licensed products to manage
23
+ *
24
+ * @since 1.4.1
25
+ */
26
+ public $licensed_products = array();
27
+
28
+ /**
29
+ * Path to load license setting class file
30
+ *
31
+ * @since 1.4.1
32
+ */
33
+ public $sap_extension_path;
34
+
35
+ /**
36
+ * Filename of the setting class to handle a
37
+ * license key input field.
38
+ *
39
+ * @since 1.4.1
40
+ */
41
+ public $sap_setting_file;
42
+
43
+ /**
44
+ * Class name of the setting file to load
45
+ * when handling a license key input field.
46
+ *
47
+ * Should contain the class referenced in
48
+ * $sap_setting_class.
49
+ *
50
+ * @since 1.4.1
51
+ */
52
+ public $sap_setting_class;
53
+
54
+ /**
55
+ * Initialize the license handling
56
+ *
57
+ * @since 1.4.1
58
+ */
59
+ public function __construct() {
60
+
61
+ $this->sap_extension_path = RTB_PLUGIN_DIR . '/includes/';
62
+ $this->sap_setting_file = 'AdminPageSettingLicenseKey.class.php';
63
+ $this->sap_setting_class = 'rtbAdminPageSettingLicenseKey';
64
+
65
+ // Check and process updates
66
+ add_action( 'admin_init', array( $this, 'load_plugin_updater' ), 20 );
67
+
68
+ // Add a licenses tab as the last tab in the settings page
69
+ add_filter( 'rtb_settings_page', array( $this, 'add_licenses_tab' ), 100 );
70
+
71
+ // Enqueue assets on licenses page
72
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
73
+
74
+ // Show a success/failed message on license activation/deactivation
75
+ add_action( 'admin_notices', array( $this, 'admin_notices' ) );
76
+ }
77
+
78
+ /**
79
+ * Check if a licensing system should be enabled
80
+ *
81
+ * @since 1.4.1
82
+ */
83
+ public function is_enabled() {
84
+
85
+ if ( count( $this->licensed_products ) ) {
86
+ return true;
87
+ }
88
+
89
+ return false;
90
+ }
91
+
92
+ /**
93
+ * Add a licensed product to manage
94
+ *
95
+ * This should be called in admin_init (before priority 20) so that
96
+ * the updater knows what products to check.
97
+ *
98
+ * @since 1.4.1
99
+ */
100
+ public function add_licensed_product( $product ) {
101
+ $this->licensed_products[ $product['id'] ] = $product;
102
+ }
103
+
104
+ /**
105
+ * Add a licenses tab as the last tab in the settings page
106
+ *
107
+ * @since 1.4.1
108
+ */
109
+ public function add_licenses_tab( $sap ) {
110
+
111
+ if ( !$this->is_enabled() ) {
112
+ return $sap;
113
+ }
114
+
115
+ $sap->add_section(
116
+ 'rtb-settings',
117
+ array(
118
+ 'id' => 'rtb-licenses',
119
+ 'title' => __( 'Licenses', 'restaurant-reservations' ),
120
+ 'description' => sprintf(
121
+ __( 'Activate license keys for any commercial addons you have purchased. %sView all addons%s.', 'restaurant-reservations' ),
122
+ '<a href="' . admin_url( 'admin.php?page=rtb-addons' ) . '">',
123
+ '</a>'
124
+ ),
125
+ 'is_tab' => true,
126
+ )
127
+ );
128
+
129
+ $sap->lib_extension_path = $this->sap_extension_path;
130
+
131
+ global $rtb_controller;
132
+ foreach( $this->licensed_products as $product ) {
133
+ $sap->add_setting(
134
+ 'rtb-settings',
135
+ 'rtb-licenses',
136
+ array(
137
+ 'id' => 'rtb-license-key',
138
+ 'filename' => $rtb_controller->licenses->sap_setting_file,
139
+ 'class' => $rtb_controller->licenses->sap_setting_class,
140
+ ),
141
+ $product
142
+ );
143
+ }
144
+
145
+ do_action( 'rtb_settings_licenses', $sap );
146
+
147
+
148
+ return $sap;
149
+ }
150
+
151
+ /**
152
+ * Check if we're on the licenses page
153
+ *
154
+ * @since 1.4.1
155
+ */
156
+ public function is_license_page() {
157
+
158
+ global $rtb_controller;
159
+
160
+ // Use the page reference in $admin_page_hooks because
161
+ // it changes in SOME hooks when it is translated.
162
+ // https://core.trac.wordpress.org/ticket/18857
163
+ global $admin_page_hooks;
164
+
165
+ $screen = get_current_screen();
166
+ if ( empty( $screen ) || empty( $admin_page_hooks['rtb-bookings'] ) ) {
167
+ return false;
168
+ }
169
+
170
+ if ( $screen->base != $admin_page_hooks['rtb-bookings'] . '_page_rtb-settings' || empty( $_GET['tab'] ) || $_GET['tab'] !== 'rtb-licenses' ) {
171
+ return false;
172
+ }
173
+
174
+ return true;
175
+ }
176
+
177
+ /**
178
+ * Enqueue JavaScript and CSS files on the licenses page
179
+ *
180
+ * @since 1.4.1
181
+ */
182
+ public function enqueue_assets() {
183
+
184
+ if ( !$this->is_license_page() ) {
185
+ return;
186
+ }
187
+
188
+ wp_enqueue_style( 'rtb-admin', RTB_PLUGIN_URL . '/assets/css/admin.css', array(), RTB_VERSION );
189
+ }
190
+
191
+ /**
192
+ * Show admin notices on license activation/deactivation attempts
193
+ *
194
+ * @since 1.4.1
195
+ */
196
+ public function admin_notices() {
197
+
198
+ if ( !$this->is_license_page() || !isset( $_GET['license_result'] ) || $_GET['license_result'] != 0 || empty( $_GET['action'] ) ) {
199
+ return;
200
+ }
201
+
202
+ $error = empty( $_GET['result_error'] ) ? '' : $_GET['result_error'];
203
+
204
+ if ( $_GET['action'] == 'deactivate' ) {
205
+ $msg = __( 'Your attempt to deactivate a license key failed. Please try again later or contact support for help.', 'restaurant-reservations' );
206
+ } else {
207
+
208
+ if ( $error == 'no_activations_left' ) {
209
+ $msg = sprintf( __( 'You have reached the activation limit for this license. If you have the license activated on other sites you will need to deactivate them or purchase more license keys from %sTheme of the Crop%s.', 'restaurant-reservations' ), '<a href="http://themeofthecrop.com/">', '</a>' );
210
+ } else {
211
+ $msg = __( 'Your attempt to activate a license key failed. Please check the license key and try again.', 'restaurant-reservations' );
212
+ }
213
+ }
214
+
215
+ ?>
216
+
217
+ <div class="error">
218
+ <p><?php echo $msg; ?></p>
219
+ </div>
220
+
221
+ <?php
222
+
223
+ }
224
+
225
+ /**
226
+ * Load plugin updater library for Easy Digital Downloads Software
227
+ * Licensing API
228
+ *
229
+ * @since 0.3
230
+ */
231
+ public function load_plugin_updater() {
232
+
233
+ if ( !$this->is_enabled() ) {
234
+ return;
235
+ }
236
+
237
+ if ( !class_exists( 'RTB_EDD_SL_Plugin_Updater' ) ) {
238
+
239
+ if ( !file_exists( RTB_PLUGIN_DIR . '/lib/EDD_SL_Plugin_Updater.class.php' ) ) {
240
+ return;
241
+ }
242
+
243
+ require_once( RTB_PLUGIN_DIR . '/lib/EDD_SL_Plugin_Updater.class.php' );
244
+ }
245
+
246
+ global $rtb_controller;
247
+
248
+ $this->updaters = array();
249
+ foreach( $this->licensed_products as $product ) {
250
+
251
+ $license = $rtb_controller->settings->get_setting( $product['id'] );
252
+
253
+ if ( empty( $license ) || empty( $license['api_key']) ) {
254
+ continue;
255
+ }
256
+
257
+ new RTB_EDD_SL_Plugin_Updater(
258
+ $product['store_url'],
259
+ $product['plugin_path'],
260
+ array(
261
+ 'version' => $product['version'],
262
+ 'license' => $license['api_key'],
263
+ 'item_name' => $product['product'],
264
+ 'author' => $product['author'],
265
+ )
266
+ );
267
+ }
268
+ }
269
+ }
270
+ } // endif
includes/MailChimp.class.php CHANGED
@@ -1,539 +1,539 @@
1
- <?php
2
- /**
3
- * Class used to add in MailChimp compatibility
4
- */
5
- if ( ! defined( 'ABSPATH' ) )
6
- exit;
7
-
8
- if ( !class_exists( 'mcfrtbInit' ) ) {
9
- class mcfrtbInit {
10
-
11
- public $api_key = null;
12
-
13
- public $status = null;
14
-
15
- public $api_call_cache = array();
16
-
17
- public function __construct() {
18
-
19
- add_action( 'plugins_loaded', array( $this, 'init' ) );
20
-
21
- }
22
-
23
- /**
24
- * Initialize the plugin and register hooks
25
- */
26
- public function init() {
27
- global $rtb_controller;
28
-
29
- // Initialize the plugin
30
- add_action( 'init', array( $this, 'load_config' ), 9 ); // Load before the settings panel is defined in Restaurant Reservations
31
- add_action( 'mcfrtb_list_merge_fields', array( $this, 'maybe_add_location_merge_field' ) );
32
- add_action( 'mcfrtb_list_merge_fields', array( $this, 'maybe_add_merge_options' ) );
33
-
34
- // Load assets
35
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
36
-
37
- // Receive ajax calls for mailchimp lists
38
- add_action( 'wp_ajax_nopriv_mcfrtb-get-lists' , array( $this , 'ajax_nopriv_get_lists' ) );
39
- add_action( 'wp_ajax_mcfrtb-get-lists', array( $this, 'ajax_get_lists' ) );
40
-
41
- // Receive ajax calls for merge fields
42
- add_action( 'wp_ajax_nopriv_mcfrtb-load-merge-fields' , array( $this , 'ajax_nopriv_load_merge_fields' ) );
43
- add_action( 'wp_ajax_mcfrtb-load-merge-fields', array( $this, 'ajax_load_merge_fields' ) );
44
-
45
-
46
- // Process subscription calls
47
- add_action( 'wp_ajax_nopriv_mcfrtb-subscribe' , array( $this , 'subscribe' ) );
48
- add_action( 'wp_ajax_mcfrtb-subscribe', array( $this, 'subscribe' ) );
49
- add_filter( 'mcfrtb_merge_fields_data', array( $this, 'maybe_send_location_merge_field' ), 10, 3 );
50
- add_filter( 'mcfrtb_merge_fields_data', array( $this, 'maybe_send_merge_fields' ), 10, 3 );
51
-
52
- // Only add the opt-in details with the frontend form
53
- if ( !is_admin() and $rtb_controller->permissions->check_permission( 'mailchimp' ) ) {
54
-
55
- // Add optin checkbox to booking form
56
- add_filter( 'rtb_booking_form_fields', array( $this, 'add_optin_field' ), 10, 2 );
57
-
58
- // Validate the optin request data
59
- add_filter( 'rtb_validate_booking_submission', array( $this, 'validate_optin_request' ) );
60
-
61
- // Enqueue assets to send subscription request
62
- add_filter( 'rtb_insert_booking', array( $this, 'enqueue_subscription_call' ) );
63
- }
64
- }
65
-
66
-
67
- /**
68
- * Load the configuration parameters
69
- */
70
- public function load_config() {
71
-
72
- global $rtb_controller;
73
- $api_key = $rtb_controller->settings->get_setting( 'mc-apikey' );
74
-
75
- if( ! is_array( $api_key ) ) {
76
- $api_key = [];
77
- }
78
-
79
- $this->api_key = array_key_exists('api_key', $api_key) ? $api_key['api_key'] : '';
80
- $this->status = array_key_exists('status', $api_key) ? $api_key['status'] : '';
81
-
82
- $this->merge_fields = apply_filters(
83
- 'mcfrtb_list_merge_fields',
84
- array(
85
- 'datetime' => __( 'Date/Time of Booking', 'restaurant-reservations' ),
86
- 'name' => __( 'Name', 'restaurant-reservations' ),
87
- 'party' => __( 'Party Size', 'restaurant-reservations' ),
88
- 'phone' => __( 'Phone Number', 'restaurant-reservations' ),
89
- 'message' => __( 'Message', 'restaurant-reservations' ),
90
- )
91
- );
92
-
93
- }
94
-
95
- /**
96
- * Add merge field for location if multi-location support is active
97
- *
98
- * @param array $fields Key/value list of booking data available for merge
99
- * @since 1.2
100
- */
101
- public function maybe_add_location_merge_field( $fields ) {
102
-
103
- global $rtb_controller;
104
-
105
- if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) {
106
- $fields['location'] = __( 'Location', 'restaurant-reservations' );
107
- }
108
-
109
- return $fields;
110
- }
111
-
112
- /**
113
- * Add merge field options for custom fields
114
- *
115
- * @param array $fields Key/value list of booking data available for merge
116
- * @since 1.3
117
- */
118
- public function maybe_add_merge_options( $fields ) {
119
-
120
- $custom_fields = rtb_get_custom_fields();
121
-
122
- $custom_merge_fields = array();
123
- foreach( $custom_fields as $custom_field ) {
124
- $custom_merge_fields['cf-' . $custom_field->slug] = $custom_field->title;
125
- }
126
-
127
- return array_merge( $fields, $custom_merge_fields );
128
- }
129
-
130
- /**
131
- * Enqueue the admin-only CSS and Javascript
132
- * @since 0.0.1
133
- */
134
- public function enqueue_admin_assets() {
135
-
136
- global $rtb_controller;
137
-
138
- // Use the page reference in $admin_page_hooks because
139
- // it changes in SOME hooks when it is translated.
140
- // https://core.trac.wordpress.org/ticket/18857
141
- global $admin_page_hooks;
142
-
143
- $screen = get_current_screen();
144
- if ( empty( $screen ) || empty( $admin_page_hooks['rtb-bookings'] ) ) {
145
- return;
146
- }
147
-
148
- if ( $screen->base == 'toplevel_page_rtb-bookings' || $screen->base == $admin_page_hooks['rtb-bookings'] . '_page_rtb-settings' ) {
149
-
150
- wp_enqueue_script( 'rtb-admin-mc', RTB_PLUGIN_URL . '/assets/js/mailchimp-admin.js', array( 'jquery' ), '', true );
151
- wp_localize_script(
152
- 'rtb-admin-mc',
153
- 'rtb_admin_mc',
154
- array(
155
- 'ajax_nonce' => wp_create_nonce( 'rtb-admin-mc' ),
156
- 'merge_fields' => $this->merge_fields,
157
- 'lists' => $rtb_controller->settings->get_setting( 'mc-lists' ),
158
- 'strings' => array(
159
- 'merge_booking_data' => __( 'Booking Form Data', 'restaurant-reservations' ),
160
- 'merge_list_field' => __( 'MailChimp List Field', 'restaurant-reservations' ),
161
- 'merge_description' => __( 'Connect information from the booking request to <a href="http://kb.mailchimp.com/article/getting-started-with-merge-tags" target="_blank">merge fields</a> in your MailChimp list.', 'restaurant-reservations' ),
162
- 'api_unknown_error' => __( 'There was an unexpected error when trying to retrieve the list\'s merge fields.', 'restaurant-reservations' ),
163
- 'merge_email_label' => __( 'Email', 'restaurant-reservations' ),
164
- 'merge_email_description' => __( 'The email field is automatically merged.', 'restaurant-reservations' ),
165
- )
166
- )
167
- );
168
- }
169
- }
170
-
171
-
172
- /**
173
- * Handle ajax request for lists from logged out user
174
- */
175
- public function ajax_nopriv_get_lists() {
176
-
177
- wp_send_json_error(
178
- array(
179
- 'error' => 'loggedout',
180
- 'msg' => __( 'You have been logged out. Please login again to retrieve the mailing lists.', 'restaurant-reservations' ),
181
- )
182
- );
183
- }
184
-
185
- /**
186
- * Handle ajax request for lists
187
- */
188
- public function ajax_get_lists() {
189
-
190
- if ( !check_ajax_referer( 'rtb-admin-mc', 'nonce' ) || !current_user_can( 'manage_options' )) {
191
- wp_send_json_error(
192
- array(
193
- 'error' => 'nopriv',
194
- 'msg' => __( 'You do not have permission to retrieve the mailing lists. Please login to an administrator account if you have one.', 'restaurant-reservations' ),
195
- )
196
- );
197
- }
198
-
199
- $this->load_api( $this->api_key );
200
-
201
- $this->api_call( '/lists' )->send_json_response();
202
- }
203
-
204
- /**
205
- * Handle ajax request for list merge fields from logged out user
206
- */
207
- public function ajax_nopriv_load_merge_fields() {
208
-
209
- wp_send_json_error(
210
- array(
211
- 'error' => 'loggedout',
212
- 'msg' => __( 'You have been logged out. Please login again to retrieve the merge fields for this list.', 'restaurant-reservations' ),
213
- )
214
- );
215
- }
216
-
217
- /**
218
- * Handle ajax request for list merge fields
219
- */
220
- public function ajax_load_merge_fields() {
221
-
222
- if ( !check_ajax_referer( 'rtb-admin-mc', 'nonce' ) || !current_user_can( 'manage_options' ) || empty( $_POST['list'] ) ) {
223
- wp_send_json_error(
224
- array(
225
- 'error' => 'nopriv',
226
- 'msg' => __( 'You do not have permission to modify the merge field settings. Please login to an administrator account if you have one.', 'restaurant-reservations' ),
227
- )
228
- );
229
- }
230
-
231
- $this->load_api( $this->api_key );
232
-
233
- $this->api_call( '/lists/' . sanitize_key( $_POST['list'] ) . '/merge-fields' )->send_json_response();
234
- }
235
-
236
- /**
237
- * Load the api request class
238
- *
239
- * @param string $api_key MailChimp API key
240
- */
241
- public function load_api( $api_key = '' ) {
242
-
243
- // Don't load it twice
244
- if ( !empty( $this->mc ) ) {
245
- return;
246
- }
247
-
248
- require_once( RTB_PLUGIN_DIR . '/includes/MailChimpRequest.class.php' );
249
-
250
- // Update the api key
251
- if ( $api_key ) {
252
- $this->api_key = $api_key;
253
- }
254
-
255
- // Load the API wrapper library
256
- $this->mc = new mcrftbMailChimpRequest( $this->api_key );
257
- }
258
-
259
- /**
260
- * Make a call to the API or pull results from cache
261
- *
262
- * @param string $method HTTP method. Only GET and POST supported for now
263
- * @param string $endpoint API endpoint to query, eg: /lists
264
- * @param array $params Parameters to pass with the API request
265
- */
266
- public function api_call( $endpoint = '', $method = 'GET', $params = array() ) {
267
- return $this->mc->call( $endpoint, $method, $params );
268
- }
269
-
270
- /**
271
- * Check if the API key is valid
272
- */
273
- public function is_valid_api_key() {
274
-
275
- if ( empty( $this->api_key ) || empty( $this->mc ) ) {
276
- return false;
277
- }
278
-
279
- // Bad API key if no data center available
280
- if ( strpos( $this->api_key, '-' ) === false ) {
281
- return false;
282
- }
283
-
284
- // Make a test call to the API
285
- $result = $this->api_call( '/lists' )->get_response();
286
- if ( empty( $result ) || ( is_object( $result ) && get_class( $result ) == 'WP_Error' ) ) {
287
- return false;
288
- } else {
289
- return true;
290
- }
291
-
292
- return false;
293
- }
294
-
295
- /**
296
- * Add the optin checkbox field to the booking form
297
- */
298
- public function add_optin_field( $fields, $request ) {
299
-
300
- global $rtb_controller;
301
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
302
- $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
303
-
304
- if ( $optout !== 'no' && !empty( $lists['list'] ) ) {
305
- $optprompt = $rtb_controller->settings->get_setting( 'mc-optprompt' );
306
-
307
- $fields['optin'] = array(
308
- 'fields' => array(
309
- 'mc-optin' => array(
310
- 'title' => $optprompt,
311
- 'request_input' => empty( $request->mc_optin ) ? '' : $request->mc_optin,
312
- 'callback' => array( $this, 'print_optin_field' ),
313
- )
314
- ),
315
- 'order' => 1000,
316
- );
317
- }
318
-
319
- return $fields;
320
- }
321
-
322
- /**
323
- * Print the optin checkbox field on the booking form
324
- */
325
- public function print_optin_field( $slug, $title, $value ) {
326
-
327
- global $rtb_controller;
328
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
329
- $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
330
-
331
- // Check the box if it's been selected or if the setting is
332
- // auto-checked and the form hasn't been submitted with it
333
- // un-checked
334
- $checked = $value ? true : false;
335
- if ( !$checked && $optout == 'checked' && ( empty( $_POST['action'] ) || $_POST['action'] !== 'booking_request' ) ) {
336
- $checked = true;
337
- }
338
-
339
- if ( $optout !== 'no' && !empty( $lists['list'] ) ) {
340
- $label = $rtb_controller->settings->get_setting( 'mc-optprompt' );
341
- ?>
342
-
343
- <div class="mc-optin">
344
- <label>
345
- <input type="checkbox" name="<?php echo esc_attr( $slug ); ?>" value="1"<?php checked( $checked ); ?>>
346
- <?php echo $label; ?>
347
- </label>
348
- </div>
349
-
350
- <?php
351
- }
352
- }
353
-
354
- /**
355
- * Validate the optin request data
356
- */
357
- public function validate_optin_request( $request ) {
358
-
359
- global $rtb_controller;
360
- if ( $rtb_controller->settings->get_setting( 'mc-optout' ) !== 'no' && !empty( $_POST['mc-optin'] ) && $_POST['mc-optin'] == '1' ) {
361
- $request->mc_optin = true;
362
- }
363
- }
364
-
365
- /**
366
- * Enqueue some JavaScript to subscribe the user after they've
367
- * booked.
368
- */
369
- public function enqueue_subscription_call( $booking ) {
370
-
371
- global $rtb_controller;
372
-
373
- // Did they opt out?
374
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
375
- if ( !$optout && empty( $booking->mc_optin ) ) {
376
- return;
377
- }
378
-
379
- // Do we have a list and email address to make the subscription
380
- $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
381
- if ( empty( $lists['list'] ) || empty( $booking->email ) ) {
382
- return;
383
- }
384
-
385
- wp_enqueue_script( 'rtb-mc-subscribe', RTB_PLUGIN_URL . '/assets/js/mailchimp-subscribe.js', array( 'jquery' ), '', true );
386
- wp_localize_script(
387
- 'rtb-mc-subscribe',
388
- 'rtb_subscribe_mc',
389
- array(
390
- 'ajax_nonce' => wp_create_nonce( 'rtb-mc-subscribe' ),
391
- 'ajax_url' => admin_url( 'admin-ajax.php' ),
392
- 'booking' => $booking,
393
- )
394
- );
395
-
396
- }
397
-
398
- /**
399
- * Process a subscription request
400
- */
401
- public function subscribe() {
402
-
403
- if ( !check_ajax_referer( 'rtb-mc-subscribe', 'nonce' ) || empty( $_POST['booking'] ) ) {
404
- wp_send_json_error(
405
- array(
406
- 'error' => 'badnonce',
407
- 'msg' => __( 'The subscription request has been rejected because it does not appear to have come from this site.', 'restaurant-reservations' ),
408
- )
409
- );
410
-
411
- return;
412
- }
413
-
414
- $booking = $_POST['booking'];
415
-
416
- global $rtb_controller;
417
-
418
- // Did they opt out?
419
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
420
- if ( $optout != 'no' && empty( $booking['mc_optin'] ) ) {
421
- return;
422
- }
423
-
424
- // Do we have a list and email address to make the subscription
425
- $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
426
- if ( empty( $lists['list'] ) || empty( $booking['email'] ) ) {
427
- return;
428
- }
429
-
430
- // Prepare post parameters to send
431
- $params = array(
432
- 'email_address' => $booking['email'],
433
- 'status' => 'pending',
434
- 'merge_fields' => (object) $this->get_merge_fields_data( $lists['fields'], $booking ),
435
- );
436
-
437
- // Pass in the user's IP for geolocation if available
438
- if ( !empty( $_SERVER['REMOTE_ADDR'] ) ) {
439
- $params['ip_signup'] = $_SERVER['REMOTE_ADDR'];
440
- $params['ip_opt'] = $_SERVER['REMOTE_ADDR'];
441
- }
442
-
443
- $params = apply_filters( 'mcfrtb_mailchimp_subscribe_args', $params, $booking );
444
-
445
- $this->load_api( $this->api_key );
446
-
447
- $this->api_call( '/lists/' . $lists['list'] . '/members', 'POST', $params )->send_json_response();
448
- }
449
-
450
- /**
451
- * Get merge fields array to send to the MailChimp API
452
- *
453
- * @merge_fields array Merge fields data pulled locally from settings
454
- */
455
- public function get_merge_fields_data( $merge_fields, $booking ) {
456
-
457
- $output = array();
458
-
459
- foreach( $this->merge_fields as $field => $title ) {
460
- if ( !empty( $merge_fields[$field] ) ) {
461
-
462
- if ( $field == 'datetime' ) {
463
- $output[$merge_fields[$field]] = $booking['date'];
464
- }
465
-
466
- if ( $field == 'name' ) {
467
- $output[$merge_fields[$field]] = $booking['name'];
468
- }
469
-
470
- if ( $field == 'party' ) {
471
- $output[$merge_fields[$field]] = $booking['party'];
472
- }
473
-
474
- if ( $field == 'phone' ) {
475
- $output[$merge_fields[$field]] = $booking['phone'];
476
- }
477
-
478
- if ( $field == 'message' ) {
479
- $output[$merge_fields[$field]] = $booking['message'];
480
- }
481
- }
482
- }
483
-
484
- return apply_filters( 'mcfrtb_merge_fields_data', $output, $merge_fields, $booking );
485
- }
486
-
487
- /**
488
- * Add location to the data merge field when appropriate
489
- *
490
- * @param array $send Key/value array of merge data to be sent
491
- * @param array $merge_fields Key/value array of configured merge fields
492
- * @param rtbBooking $booking Booking object
493
- * @since 1.2
494
- */
495
- public function maybe_send_location_merge_field( $send, $merge_fields, $booking ) {
496
-
497
- global $rtb_controller;
498
-
499
- if ( empty( $rtb_controller->locations ) || empty( $rtb_controller->locations->post_type ) ) {
500
- return $send;
501
- }
502
-
503
- if ( !empty( $booking['location'] ) && !empty( $merge_fields['location'] ) ) {
504
- $term = get_term( $booking['location'] );
505
- if ( !empty( $term ) && is_a( $term, 'WP_Term' ) ) {
506
- $send[$merge_fields['location']] = $term->name;
507
- }
508
- }
509
-
510
- return $send;
511
- }
512
-
513
- /**
514
- * Send merge field data for custom fields
515
- *
516
- * @param array $send Key/value array of merge data to be sent
517
- * @param array $merge_fields Key/value array of configured merge fields
518
- * @param rtbBooking $booking Booking object
519
- * @since 1.3
520
- */
521
- public function maybe_send_merge_fields( $send, $merge_fields, $booking ) {
522
- global $rtb_controller;
523
-
524
- $custom_fields = rtb_get_custom_fields();
525
-
526
- foreach( $custom_fields as $custom_field ) {
527
- if ( !empty( $merge_fields['cf-' . $custom_field->slug] ) && isset( $booking['custom_fields'] ) && isset( $booking['custom_fields'][$custom_field->slug] ) ) {
528
- if ( $custom_field->type == 'confirm' ) {
529
- $send[$merge_fields['cf-' . $custom_field->slug]] = 'Checked';
530
- } else {
531
- $send[$merge_fields['cf-' . $custom_field->slug]] = $rtb_controller->fields->get_display_value( $booking['custom_fields'][$custom_field->slug], $custom_field, '', false );
532
- }
533
- }
534
- }
535
-
536
- return $send;
537
- }
538
- }
539
- } // endif;
1
+ <?php
2
+ /**
3
+ * Class used to add in MailChimp compatibility
4
+ */
5
+ if ( ! defined( 'ABSPATH' ) )
6
+ exit;
7
+
8
+ if ( !class_exists( 'mcfrtbInit' ) ) {
9
+ class mcfrtbInit {
10
+
11
+ public $api_key = null;
12
+
13
+ public $status = null;
14
+
15
+ public $api_call_cache = array();
16
+
17
+ public function __construct() {
18
+
19
+ add_action( 'plugins_loaded', array( $this, 'init' ) );
20
+
21
+ }
22
+
23
+ /**
24
+ * Initialize the plugin and register hooks
25
+ */
26
+ public function init() {
27
+ global $rtb_controller;
28
+
29
+ // Initialize the plugin
30
+ add_action( 'init', array( $this, 'load_config' ), 9 ); // Load before the settings panel is defined in Restaurant Reservations
31
+ add_action( 'mcfrtb_list_merge_fields', array( $this, 'maybe_add_location_merge_field' ) );
32
+ add_action( 'mcfrtb_list_merge_fields', array( $this, 'maybe_add_merge_options' ) );
33
+
34
+ // Load assets
35
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
36
+
37
+ // Receive ajax calls for mailchimp lists
38
+ add_action( 'wp_ajax_nopriv_mcfrtb-get-lists' , array( $this , 'ajax_nopriv_get_lists' ) );
39
+ add_action( 'wp_ajax_mcfrtb-get-lists', array( $this, 'ajax_get_lists' ) );
40
+
41
+ // Receive ajax calls for merge fields
42
+ add_action( 'wp_ajax_nopriv_mcfrtb-load-merge-fields' , array( $this , 'ajax_nopriv_load_merge_fields' ) );
43
+ add_action( 'wp_ajax_mcfrtb-load-merge-fields', array( $this, 'ajax_load_merge_fields' ) );
44
+
45
+
46
+ // Process subscription calls
47
+ add_action( 'wp_ajax_nopriv_mcfrtb-subscribe' , array( $this , 'subscribe' ) );
48
+ add_action( 'wp_ajax_mcfrtb-subscribe', array( $this, 'subscribe' ) );
49
+ add_filter( 'mcfrtb_merge_fields_data', array( $this, 'maybe_send_location_merge_field' ), 10, 3 );
50
+ add_filter( 'mcfrtb_merge_fields_data', array( $this, 'maybe_send_merge_fields' ), 10, 3 );
51
+
52
+ // Only add the opt-in details with the frontend form
53
+ if ( !is_admin() and $rtb_controller->permissions->check_permission( 'mailchimp' ) ) {
54
+
55
+ // Add optin checkbox to booking form
56
+ add_filter( 'rtb_booking_form_fields', array( $this, 'add_optin_field' ), 10, 2 );
57
+
58
+ // Validate the optin request data
59
+ add_filter( 'rtb_validate_booking_submission', array( $this, 'validate_optin_request' ) );
60
+
61
+ // Enqueue assets to send subscription request
62
+ add_filter( 'rtb_insert_booking', array( $this, 'enqueue_subscription_call' ) );
63
+ }
64
+ }
65
+
66
+
67
+ /**
68
+ * Load the configuration parameters
69
+ */
70
+ public function load_config() {
71
+
72
+ global $rtb_controller;
73
+ $api_key = $rtb_controller->settings->get_setting( 'mc-apikey' );
74
+
75
+ if( ! is_array( $api_key ) ) {
76
+ $api_key = [];
77
+ }
78
+
79
+ $this->api_key = array_key_exists('api_key', $api_key) ? $api_key['api_key'] : '';
80
+ $this->status = array_key_exists('status', $api_key) ? $api_key['status'] : '';
81
+
82
+ $this->merge_fields = apply_filters(
83
+ 'mcfrtb_list_merge_fields',
84
+ array(
85
+ 'datetime' => __( 'Date/Time of Booking', 'restaurant-reservations' ),
86
+ 'name' => __( 'Name', 'restaurant-reservations' ),
87
+ 'party' => __( 'Party Size', 'restaurant-reservations' ),
88
+ 'phone' => __( 'Phone Number', 'restaurant-reservations' ),
89
+ 'message' => __( 'Message', 'restaurant-reservations' ),
90
+ )
91
+ );
92
+
93
+ }
94
+
95
+ /**
96
+ * Add merge field for location if multi-location support is active
97
+ *
98
+ * @param array $fields Key/value list of booking data available for merge
99
+ * @since 1.2
100
+ */
101
+ public function maybe_add_location_merge_field( $fields ) {
102
+
103
+ global $rtb_controller;
104
+
105
+ if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) {
106
+ $fields['location'] = __( 'Location', 'restaurant-reservations' );
107
+ }
108
+
109
+ return $fields;
110
+ }
111
+
112
+ /**
113
+ * Add merge field options for custom fields
114
+ *
115
+ * @param array $fields Key/value list of booking data available for merge
116
+ * @since 1.3
117
+ */
118
+ public function maybe_add_merge_options( $fields ) {
119
+
120
+ $custom_fields = rtb_get_custom_fields();
121
+
122
+ $custom_merge_fields = array();
123
+ foreach( $custom_fields as $custom_field ) {
124
+ $custom_merge_fields['cf-' . $custom_field->slug] = $custom_field->title;
125
+ }
126
+
127
+ return array_merge( $fields, $custom_merge_fields );
128
+ }
129
+
130
+ /**
131
+ * Enqueue the admin-only CSS and Javascript
132
+ * @since 0.0.1
133
+ */
134
+ public function enqueue_admin_assets() {
135
+
136
+ global $rtb_controller;
137
+
138
+ // Use the page reference in $admin_page_hooks because
139
+ // it changes in SOME hooks when it is translated.
140
+ // https://core.trac.wordpress.org/ticket/18857
141
+ global $admin_page_hooks;
142
+
143
+ $screen = get_current_screen();
144
+ if ( empty( $screen ) || empty( $admin_page_hooks['rtb-bookings'] ) ) {
145
+ return;
146
+ }
147
+
148
+ if ( $screen->base == 'toplevel_page_rtb-bookings' || $screen->base == $admin_page_hooks['rtb-bookings'] . '_page_rtb-settings' ) {
149
+
150
+ wp_enqueue_script( 'rtb-admin-mc', RTB_PLUGIN_URL . '/assets/js/mailchimp-admin.js', array( 'jquery' ), '', true );
151
+ wp_localize_script(
152
+ 'rtb-admin-mc',
153
+ 'rtb_admin_mc',
154
+ array(
155
+ 'ajax_nonce' => wp_create_nonce( 'rtb-admin-mc' ),
156
+ 'merge_fields' => $this->merge_fields,
157
+ 'lists' => $rtb_controller->settings->get_setting( 'mc-lists' ),
158
+ 'strings' => array(
159
+ 'merge_booking_data' => __( 'Booking Form Data', 'restaurant-reservations' ),
160
+ 'merge_list_field' => __( 'MailChimp List Field', 'restaurant-reservations' ),
161
+ 'merge_description' => __( 'Connect information from the booking request to <a href="http://kb.mailchimp.com/article/getting-started-with-merge-tags" target="_blank">merge fields</a> in your MailChimp list.', 'restaurant-reservations' ),
162
+ 'api_unknown_error' => __( 'There was an unexpected error when trying to retrieve the list\'s merge fields.', 'restaurant-reservations' ),
163
+ 'merge_email_label' => __( 'Email', 'restaurant-reservations' ),
164
+ 'merge_email_description' => __( 'The email field is automatically merged.', 'restaurant-reservations' ),
165
+ )
166
+ )
167
+ );
168
+ }
169
+ }
170
+
171
+
172
+ /**
173
+ * Handle ajax request for lists from logged out user
174
+ */
175
+ public function ajax_nopriv_get_lists() {
176
+
177
+ wp_send_json_error(
178
+ array(
179
+ 'error' => 'loggedout',
180
+ 'msg' => __( 'You have been logged out. Please login again to retrieve the mailing lists.', 'restaurant-reservations' ),
181
+ )
182
+ );
183
+ }
184
+
185
+ /**
186
+ * Handle ajax request for lists
187
+ */
188
+ public function ajax_get_lists() {
189
+
190
+ if ( !check_ajax_referer( 'rtb-admin-mc', 'nonce' ) || !current_user_can( 'manage_options' )) {
191
+ wp_send_json_error(
192
+ array(
193
+ 'error' => 'nopriv',
194
+ 'msg' => __( 'You do not have permission to retrieve the mailing lists. Please login to an administrator account if you have one.', 'restaurant-reservations' ),
195
+ )
196
+ );
197
+ }
198
+
199
+ $this->load_api( $this->api_key );
200
+
201
+ $this->api_call( '/lists' )->send_json_response();
202
+ }
203
+
204
+ /**
205
+ * Handle ajax request for list merge fields from logged out user
206
+ */
207
+ public function ajax_nopriv_load_merge_fields() {
208
+
209
+ wp_send_json_error(
210
+ array(
211
+ 'error' => 'loggedout',
212
+ 'msg' => __( 'You have been logged out. Please login again to retrieve the merge fields for this list.', 'restaurant-reservations' ),
213
+ )
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Handle ajax request for list merge fields
219
+ */
220
+ public function ajax_load_merge_fields() {
221
+
222
+ if ( !check_ajax_referer( 'rtb-admin-mc', 'nonce' ) || !current_user_can( 'manage_options' ) || empty( $_POST['list'] ) ) {
223
+ wp_send_json_error(
224
+ array(
225
+ 'error' => 'nopriv',
226
+ 'msg' => __( 'You do not have permission to modify the merge field settings. Please login to an administrator account if you have one.', 'restaurant-reservations' ),
227
+ )
228
+ );
229
+ }
230
+
231
+ $this->load_api( $this->api_key );
232
+
233
+ $this->api_call( '/lists/' . sanitize_key( $_POST['list'] ) . '/merge-fields' )->send_json_response();
234
+ }
235
+
236
+ /**
237
+ * Load the api request class
238
+ *
239
+ * @param string $api_key MailChimp API key
240
+ */
241
+ public function load_api( $api_key = '' ) {
242
+
243
+ // Don't load it twice
244
+ if ( !empty( $this->mc ) ) {
245
+ return;
246
+ }
247
+
248
+ require_once( RTB_PLUGIN_DIR . '/includes/MailChimpRequest.class.php' );
249
+
250
+ // Update the api key
251
+ if ( $api_key ) {
252
+ $this->api_key = $api_key;
253
+ }
254
+
255
+ // Load the API wrapper library
256
+ $this->mc = new mcrftbMailChimpRequest( $this->api_key );
257
+ }
258
+
259
+ /**
260
+ * Make a call to the API or pull results from cache
261
+ *
262
+ * @param string $method HTTP method. Only GET and POST supported for now
263
+ * @param string $endpoint API endpoint to query, eg: /lists
264
+ * @param array $params Parameters to pass with the API request
265
+ */
266
+ public function api_call( $endpoint = '', $method = 'GET', $params = array() ) {
267
+ return $this->mc->call( $endpoint, $method, $params );
268
+ }
269
+
270
+ /**
271
+ * Check if the API key is valid
272
+ */
273
+ public function is_valid_api_key() {
274
+
275
+ if ( empty( $this->api_key ) || empty( $this->mc ) ) {
276
+ return false;
277
+ }
278
+
279
+ // Bad API key if no data center available
280
+ if ( strpos( $this->api_key, '-' ) === false ) {
281
+ return false;
282
+ }
283
+
284
+ // Make a test call to the API
285
+ $result = $this->api_call( '/lists' )->get_response();
286
+ if ( empty( $result ) || ( is_object( $result ) && get_class( $result ) == 'WP_Error' ) ) {
287
+ return false;
288
+ } else {
289
+ return true;
290
+ }
291
+
292
+ return false;
293
+ }
294
+
295
+ /**
296
+ * Add the optin checkbox field to the booking form
297
+ */
298
+ public function add_optin_field( $fields, $request ) {
299
+
300
+ global $rtb_controller;
301
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
302
+ $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
303
+
304
+ if ( $optout !== 'no' && !empty( $lists['list'] ) ) {
305
+ $optprompt = $rtb_controller->settings->get_setting( 'mc-optprompt' );
306
+
307
+ $fields['optin'] = array(
308
+ 'fields' => array(
309
+ 'mc-optin' => array(
310
+ 'title' => $optprompt,
311
+ 'request_input' => empty( $request->mc_optin ) ? '' : $request->mc_optin,
312
+ 'callback' => array( $this, 'print_optin_field' ),
313
+ )
314
+ ),
315
+ 'order' => 1000,
316
+ );
317
+ }
318
+
319
+ return $fields;
320
+ }
321
+
322
+ /**
323
+ * Print the optin checkbox field on the booking form
324
+ */
325
+ public function print_optin_field( $slug, $title, $value ) {
326
+
327
+ global $rtb_controller;
328
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
329
+ $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
330
+
331
+ // Check the box if it's been selected or if the setting is
332
+ // auto-checked and the form hasn't been submitted with it
333
+ // un-checked
334
+ $checked = $value ? true : false;
335
+ if ( !$checked && $optout == 'checked' && ( empty( $_POST['action'] ) || $_POST['action'] !== 'booking_request' ) ) {
336
+ $checked = true;
337
+ }
338
+
339
+ if ( $optout !== 'no' && !empty( $lists['list'] ) ) {
340
+ $label = $rtb_controller->settings->get_setting( 'mc-optprompt' );
341
+ ?>
342
+
343
+ <div class="mc-optin">
344
+ <label>
345
+ <input type="checkbox" name="<?php echo esc_attr( $slug ); ?>" value="1"<?php checked( $checked ); ?>>
346
+ <?php echo $label; ?>
347
+ </label>
348
+ </div>
349
+
350
+ <?php
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Validate the optin request data
356
+ */
357
+ public function validate_optin_request( $request ) {
358
+
359
+ global $rtb_controller;
360
+ if ( $rtb_controller->settings->get_setting( 'mc-optout' ) !== 'no' && !empty( $_POST['mc-optin'] ) && $_POST['mc-optin'] == '1' ) {
361
+ $request->mc_optin = true;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Enqueue some JavaScript to subscribe the user after they've
367
+ * booked.
368
+ */
369
+ public function enqueue_subscription_call( $booking ) {
370
+
371
+ global $rtb_controller;
372
+
373
+ // Did they opt out?
374
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
375
+ if ( !$optout && empty( $booking->mc_optin ) ) {
376
+ return;
377
+ }
378
+
379
+ // Do we have a list and email address to make the subscription
380
+ $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
381
+ if ( empty( $lists['list'] ) || empty( $booking->email ) ) {
382
+ return;
383
+ }
384
+
385
+ wp_enqueue_script( 'rtb-mc-subscribe', RTB_PLUGIN_URL . '/assets/js/mailchimp-subscribe.js', array( 'jquery' ), '', true );
386
+ wp_localize_script(
387
+ 'rtb-mc-subscribe',
388
+ 'rtb_subscribe_mc',
389
+ array(
390
+ 'ajax_nonce' => wp_create_nonce( 'rtb-mc-subscribe' ),
391
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
392
+ 'booking' => $booking,
393
+ )
394
+ );
395
+
396
+ }
397
+
398
+ /**
399
+ * Process a subscription request
400
+ */
401
+ public function subscribe() {
402
+
403
+ if ( !check_ajax_referer( 'rtb-mc-subscribe', 'nonce' ) || empty( $_POST['booking'] ) ) {
404
+ wp_send_json_error(
405
+ array(
406
+ 'error' => 'badnonce',
407
+ 'msg' => __( 'The subscription request has been rejected because it does not appear to have come from this site.', 'restaurant-reservations' ),
408
+ )
409
+ );
410
+
411
+ return;
412
+ }
413
+
414
+ $booking = $_POST['booking'];
415
+
416
+ global $rtb_controller;
417
+
418
+ // Did they opt out?
419
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
420
+ if ( $optout != 'no' && empty( $booking['mc_optin'] ) ) {
421
+ return;
422
+ }
423
+
424
+ // Do we have a list and email address to make the subscription
425
+ $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
426
+ if ( empty( $lists['list'] ) || empty( $booking['email'] ) ) {
427
+ return;
428
+ }
429
+
430
+ // Prepare post parameters to send
431
+ $params = array(
432
+ 'email_address' => $booking['email'],
433
+ 'status' => 'pending',
434
+ 'merge_fields' => (object) $this->get_merge_fields_data( $lists['fields'], $booking ),
435
+ );
436
+
437
+ // Pass in the user's IP for geolocation if available
438
+ if ( !empty( $_SERVER['REMOTE_ADDR'] ) ) {
439
+ $params['ip_signup'] = $_SERVER['REMOTE_ADDR'];
440
+ $params['ip_opt'] = $_SERVER['REMOTE_ADDR'];
441
+ }
442
+
443
+ $params = apply_filters( 'mcfrtb_mailchimp_subscribe_args', $params, $booking );
444
+
445
+ $this->load_api( $this->api_key );
446
+
447
+ $this->api_call( '/lists/' . $lists['list'] . '/members', 'POST', $params )->send_json_response();
448
+ }
449
+
450
+ /**
451
+ * Get merge fields array to send to the MailChimp API
452
+ *
453
+ * @merge_fields array Merge fields data pulled locally from settings
454
+ */
455
+ public function get_merge_fields_data( $merge_fields, $booking ) {
456
+
457
+ $output = array();
458
+
459
+ foreach( $this->merge_fields as $field => $title ) {
460
+ if ( !empty( $merge_fields[$field] ) ) {
461
+
462
+ if ( $field == 'datetime' ) {
463
+ $output[$merge_fields[$field]] = $booking['date'];
464
+ }
465
+
466
+ if ( $field == 'name' ) {
467
+ $output[$merge_fields[$field]] = $booking['name'];
468
+ }
469
+
470
+ if ( $field == 'party' ) {
471
+ $output[$merge_fields[$field]] = $booking['party'];
472
+ }
473
+
474
+ if ( $field == 'phone' ) {
475
+ $output[$merge_fields[$field]] = $booking['phone'];
476
+ }
477
+
478
+ if ( $field == 'message' ) {
479
+ $output[$merge_fields[$field]] = $booking['message'];
480
+ }
481
+ }
482
+ }
483
+
484
+ return apply_filters( 'mcfrtb_merge_fields_data', $output, $merge_fields, $booking );
485
+ }
486
+
487
+ /**
488
+ * Add location to the data merge field when appropriate
489
+ *
490
+ * @param array $send Key/value array of merge data to be sent
491
+ * @param array $merge_fields Key/value array of configured merge fields
492
+ * @param rtbBooking $booking Booking object
493
+ * @since 1.2
494
+ */
495
+ public function maybe_send_location_merge_field( $send, $merge_fields, $booking ) {
496
+
497
+ global $rtb_controller;
498
+
499
+ if ( empty( $rtb_controller->locations ) || empty( $rtb_controller->locations->post_type ) ) {
500
+ return $send;
501
+ }
502
+
503
+ if ( !empty( $booking['location'] ) && !empty( $merge_fields['location'] ) ) {
504
+ $term = get_term( $booking['location'] );
505
+ if ( !empty( $term ) && is_a( $term, 'WP_Term' ) ) {
506
+ $send[$merge_fields['location']] = $term->name;
507
+ }
508
+ }
509
+
510
+ return $send;
511
+ }
512
+
513
+ /**
514
+ * Send merge field data for custom fields
515
+ *
516
+ * @param array $send Key/value array of merge data to be sent
517
+ * @param array $merge_fields Key/value array of configured merge fields
518
+ * @param rtbBooking $booking Booking object
519
+ * @since 1.3
520
+ */
521
+ public function maybe_send_merge_fields( $send, $merge_fields, $booking ) {
522
+ global $rtb_controller;
523
+
524
+ $custom_fields = rtb_get_custom_fields();
525
+
526
+ foreach( $custom_fields as $custom_field ) {
527
+ if ( !empty( $merge_fields['cf-' . $custom_field->slug] ) && isset( $booking['custom_fields'] ) && isset( $booking['custom_fields'][$custom_field->slug] ) ) {
528
+ if ( $custom_field->type == 'confirm' ) {
529
+ $send[$merge_fields['cf-' . $custom_field->slug]] = 'Checked';
530
+ } else {
531
+ $send[$merge_fields['cf-' . $custom_field->slug]] = $rtb_controller->fields->get_display_value( $booking['custom_fields'][$custom_field->slug], $custom_field, '', false );
532
+ }
533
+ }
534
+ }
535
+
536
+ return $send;
537
+ }
538
+ }
539
+ } // endif;
includes/Migration.class.php CHANGED
@@ -1,141 +1,141 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbMigrationManager' ) ) {
5
- /**
6
- * Class to handle post update migrations for Restaurant Reservations
7
- *
8
- * @since 2.2.4
9
- */
10
- class rtbMigrationManager {
11
-
12
- public function __construct() {
13
-
14
- $this->convert_draft_status_to_payment_pending_status();
15
-
16
- $this->convert_payment_gateway_setting();
17
-
18
- $this->update_invalid_setting_values();
19
-
20
- $this->update_setting_value_types();
21
- }
22
-
23
-
24
-
25
- /************************* Migration callback ahead *************************/
26
-
27
- /**
28
- * Changes rtb-booking post status from draft to payment_pending while a booking has been created but
29
- * the payment is yet to be paid. Update all existing rtb-booking posts with status draft to
30
- * payment_pending. This change will let admins check and process/delete the existing bookings
31
- * without payment, and also let the use pay if they have accidently move elsewhere before
32
- * the payment is completed.
33
- * Currently users or admins can not do anything with bookings with the status draft. If the same
34
- * booking needs to be made again, at least name or phone number or email needs to be cahgned
35
- * for the same slot otherwise validation will fail stating there is a duplicate booking
36
- *
37
- * @removal 2022-01-01
38
- */
39
- public function convert_draft_status_to_payment_pending_status() {
40
- global $wpdb;
41
-
42
- $wpdb->query("
43
- UPDATE
44
- {$wpdb->posts}
45
- SET
46
- `post_status` = 'payment_pending'
47
- WHERE
48
- `post_status` = 'draft' AND `post_type` = 'rtb-booking'
49
- ");
50
- }
51
-
52
- /**
53
- * Update the rtb-payment-gateway setting from string to array
54
- * Previously this setting was a radio, now it is a checkbox.
55
- *
56
- * @since 2.3.0
57
- *
58
- * @removal 2022-04-01
59
- */
60
- public function convert_payment_gateway_setting()
61
- {
62
- $settings_page = 'rtb-settings';
63
- $options = get_option( $settings_page );
64
-
65
- if ( ! is_array( $options ) || ! array_key_exists( 'rtb-payment-gateway', $options ) ) { return; }
66
-
67
- if( ! is_array( $options['rtb-payment-gateway'] ) && version_compare( RTB_VERSION, '2.2.11', '>' ) ) {
68
- $options['rtb-payment-gateway'] = [ $options['rtb-payment-gateway'] ];
69
-
70
- update_option( $settings_page, $options );
71
- }
72
- }
73
-
74
- /**
75
- * Update setting of type count, which are 'time-reminder-user' and
76
- * 'time-late-user'. When no value is being set in admin, it has underscore
77
- * at least. Whereas, its expected to be empty
78
- *
79
- * @since 2.3.1
80
- *
81
- * @removal 2022-04-01
82
- */
83
- public function update_invalid_setting_values() {
84
- $settings_page = 'rtb-settings';
85
- $options = get_option( $settings_page );
86
-
87
- if(
88
- is_array( $options )
89
- &&
90
- isset( $options['time-reminder-user'] )
91
- &&
92
- '_' == $options['time-reminder-user']
93
- &&
94
- version_compare( RTB_VERSION, '2.2.11', '>' )
95
- )
96
- {
97
- $options['time-reminder-user'] = '';
98
- }
99
-
100
- if(
101
- is_array( $options )
102
- &&
103
- isset( $options['time-late-user'] )
104
- &&
105
- '_' == $options['time-late-user']
106
- &&
107
- version_compare( RTB_VERSION, '2.2.11', '>' )
108
- )
109
- {
110
- $options['time-late-user'] = '';
111
- }
112
-
113
- update_option( $settings_page, $options );
114
- }
115
-
116
- /**
117
- * Updates a number of settings, which now are using the "select" type instead of the "count"
118
- * type, since they have no units to select
119
- *
120
- * @since 2.3.0
121
- *
122
- * @removal 2022-07-01
123
- */
124
- public function update_setting_value_types() {
125
-
126
- $options = get_option( 'rtb-settings' );
127
-
128
- if ( is_array( $options ) ) {
129
-
130
- $options['rtb-dining-block-length'] = ! empty( $options['rtb-dining-block-length'] ) ? intval( $options['rtb-dining-block-length'] ) : '';
131
- $options['rtb-max-tables-count'] = ! empty( $options['rtb-max-tables-count'] ) ? intval( $options['rtb-max-tables-count'] ) : '';
132
- $options['rtb-max-people-count'] = ! empty( $options['rtb-max-people-count'] ) ? intval( $options['rtb-max-people-count'] ) : '';
133
- $options['auto-confirm-max-reservations'] = ! empty( $options['auto-confirm-max-reservations'] ) ? intval( $options['auto-confirm-max-reservations'] ) : '';
134
- $options['auto-confirm-max-seats'] = ! empty( $options['auto-confirm-max-seats'] ) ? intval( $options['auto-confirm-max-seats'] ) : '';
135
-
136
- update_option( 'rtb-settings', $options );
137
- }
138
- }
139
- }
140
-
141
  }
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbMigrationManager' ) ) {
5
+ /**
6
+ * Class to handle post update migrations for Restaurant Reservations
7
+ *
8
+ * @since 2.2.4
9
+ */
10
+ class rtbMigrationManager {
11
+
12
+ public function __construct() {
13
+
14
+ $this->convert_draft_status_to_payment_pending_status();
15
+
16
+ $this->convert_payment_gateway_setting();
17
+
18
+ $this->update_invalid_setting_values();
19
+
20
+ $this->update_setting_value_types();
21
+ }
22
+
23
+
24
+
25
+ /************************* Migration callback ahead *************************/
26
+
27
+ /**
28
+ * Changes rtb-booking post status from draft to payment_pending while a booking has been created but
29
+ * the payment is yet to be paid. Update all existing rtb-booking posts with status draft to
30
+ * payment_pending. This change will let admins check and process/delete the existing bookings
31
+ * without payment, and also let the use pay if they have accidently move elsewhere before
32
+ * the payment is completed.
33
+ * Currently users or admins can not do anything with bookings with the status draft. If the same
34
+ * booking needs to be made again, at least name or phone number or email needs to be cahgned
35
+ * for the same slot otherwise validation will fail stating there is a duplicate booking
36
+ *
37
+ * @removal 2022-01-01
38
+ */
39
+ public function convert_draft_status_to_payment_pending_status() {
40
+ global $wpdb;
41
+
42
+ $wpdb->query("
43
+ UPDATE
44
+ {$wpdb->posts}
45
+ SET
46
+ `post_status` = 'payment_pending'
47
+ WHERE
48
+ `post_status` = 'draft' AND `post_type` = 'rtb-booking'
49
+ ");
50
+ }
51
+
52
+ /**
53
+ * Update the rtb-payment-gateway setting from string to array
54
+ * Previously this setting was a radio, now it is a checkbox.
55
+ *
56
+ * @since 2.3.0
57
+ *
58
+ * @removal 2022-04-01
59
+ */
60
+ public function convert_payment_gateway_setting()
61
+ {
62
+ $settings_page = 'rtb-settings';
63
+ $options = get_option( $settings_page );
64
+
65
+ if ( ! is_array( $options ) || ! array_key_exists( 'rtb-payment-gateway', $options ) ) { return; }
66
+
67
+ if( ! is_array( $options['rtb-payment-gateway'] ) && version_compare( RTB_VERSION, '2.2.11', '>' ) ) {
68
+ $options['rtb-payment-gateway'] = [ $options['rtb-payment-gateway'] ];
69
+
70
+ update_option( $settings_page, $options );
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Update setting of type count, which are 'time-reminder-user' and
76
+ * 'time-late-user'. When no value is being set in admin, it has underscore
77
+ * at least. Whereas, its expected to be empty
78
+ *
79
+ * @since 2.3.1
80
+ *
81
+ * @removal 2022-04-01
82
+ */
83
+ public function update_invalid_setting_values() {
84
+ $settings_page = 'rtb-settings';
85
+ $options = get_option( $settings_page );
86
+
87
+ if(
88
+ is_array( $options )
89
+ &&
90
+ isset( $options['time-reminder-user'] )
91
+ &&
92
+ '_' == $options['time-reminder-user']
93
+ &&
94
+ version_compare( RTB_VERSION, '2.2.11', '>' )
95
+ )
96
+ {
97
+ $options['time-reminder-user'] = '';
98
+ }
99
+
100
+ if(
101
+ is_array( $options )
102
+ &&
103
+ isset( $options['time-late-user'] )
104
+ &&
105
+ '_' == $options['time-late-user']
106
+ &&
107
+ version_compare( RTB_VERSION, '2.2.11', '>' )
108
+ )
109
+ {
110
+ $options['time-late-user'] = '';
111
+ }
112
+
113
+ update_option( $settings_page, $options );
114
+ }
115
+
116
+ /**
117
+ * Updates a number of settings, which now are using the "select" type instead of the "count"
118
+ * type, since they have no units to select
119
+ *
120
+ * @since 2.3.0
121
+ *
122
+ * @removal 2022-07-01
123
+ */
124
+ public function update_setting_value_types() {
125
+
126
+ $options = get_option( 'rtb-settings' );
127
+
128
+ if ( is_array( $options ) ) {
129
+
130
+ $options['rtb-dining-block-length'] = ! empty( $options['rtb-dining-block-length'] ) ? intval( $options['rtb-dining-block-length'] ) : '';
131
+ $options['rtb-max-tables-count'] = ! empty( $options['rtb-max-tables-count'] ) ? intval( $options['rtb-max-tables-count'] ) : '';
132
+ $options['rtb-max-people-count'] = ! empty( $options['rtb-max-people-count'] ) ? intval( $options['rtb-max-people-count'] ) : '';
133
+ $options['auto-confirm-max-reservations'] = ! empty( $options['auto-confirm-max-reservations'] ) ? intval( $options['auto-confirm-max-reservations'] ) : '';
134
+ $options['auto-confirm-max-seats'] = ! empty( $options['auto-confirm-max-seats'] ) ? intval( $options['auto-confirm-max-seats'] ) : '';
135
+
136
+ update_option( 'rtb-settings', $options );
137
+ }
138
+ }
139
+ }
140
+
141
  }
includes/MultipleLocations.class.php CHANGED
@@ -1,1009 +1,1009 @@
1
- <?php
2
- /**
3
- * Methods for handling multiple locations
4
- *
5
- * @package RestaurantReservations
6
- * @copyright Copyright (c) 2016, Theme of the Crop
7
- * @license GPL-2.0+
8
- * @since 1.6
9
- */
10
- defined( 'ABSPATH' ) || exit;
11
-
12
- if ( ! class_exists( 'rtbMultipleLocations', false ) ) {
13
- /**
14
- * Class to handle custom post type and post meta fields
15
- *
16
- * @since 1.6
17
- */
18
- class rtbMultipleLocations {
19
-
20
- /**
21
- * Post type slug where locations can be found
22
- *
23
- * @since 1.6
24
- */
25
- public $post_type = false;
26
-
27
- /**
28
- * Taxonomy to use when assigning bookings to locations
29
- *
30
- * @since 1.6
31
- */
32
- public $location_taxonomy = 'rtb_location';
33
-
34
- /**
35
- * Set the loading hook
36
- *
37
- * @since 1.6
38
- */
39
- public function __construct() {
40
- add_action( 'plugins_loaded', array( $this, 'load' ), 100 );
41
- }
42
-
43
- /**
44
- * Load locations support
45
- *
46
- * @since 1.6
47
- */
48
- public function load() {
49
-
50
- /**
51
- * Allow third-party plugins to enable multiple locations
52
- *
53
- * Expects a post type slug pointing to the locations or false if
54
- * multiple locations are not enabled.
55
- *
56
- * @since 1.6
57
- */
58
- $this->post_type = apply_filters( 'rtb_set_locations_post_type', false );
59
-
60
- if ( !$this->post_type ) {
61
- return;
62
- }
63
-
64
- $this->hooks();
65
- }
66
-
67
- /**
68
- * Set up hooks
69
- *
70
- * @since 1.6
71
- */
72
- public function hooks() {
73
- add_action( 'init', array( $this, 'register_taxonomy' ), 1000 ); // after custom post types declared (hopefully!)
74
- add_action( 'save_post_' . $this->post_type, array( $this, 'save_location' ), 10, 3 );
75
- add_action( 'before_delete_post', array( $this, 'delete_location' ) );
76
- add_action( 'rtb_booking_form_fields', array( $this, 'add_location_field' ), 10, 3 );
77
- add_action( 'rtb_pre_validate_booking_submission', array( $this, 'validate_location' ) );
78
- add_action( 'rtb_insert_booking', array( $this, 'save_booking_location' ) );
79
- add_action( 'rtb_update_booking', array( $this, 'save_booking_location' ) );
80
- add_action( 'rtb_booking_load_post_data', array( $this, 'load_booking_location' ), 10, 2 );
81
- add_filter( 'rtb_query_args', array( $this, 'modify_query' ), 10, 2 );
82
- add_filter( 'rtb_bookings_all_table_columns', array( $this, 'add_location_column' ) );
83
- add_filter( 'rtb_bookings_table_column', array( $this, 'print_location_column' ), 10, 3 );
84
- add_action( 'edit_form_after_title', array( $this, 'add_meta_nonce' ) );
85
- add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
86
- add_filter( 'the_content', array( $this, 'append_to_content' ) );
87
- add_filter( 'rtb_notification_email_to_email', array( $this, 'notification_to_email' ), 10, 2 );
88
- add_filter( 'rtb_notification_email_from_email', array( $this, 'notification_from_email' ), 10, 2 );
89
- add_filter( 'rtb_notification_email_from_name', array( $this, 'notification_from_name' ), 10, 2 );
90
- add_filter( 'rtb_notification_template_tags', array( $this, 'notification_template_tags' ), 10, 2 );
91
- add_filter( 'rtb_notification_template_tag_descriptions', array( $this, 'notification_template_tag_descriptions' ) );
92
- add_action( 'admin_init', array( $this, 'fix_autodraft_term_error' ) );
93
- add_filter( 'rtb_settings_page', array( $this, 'maybe_add_location_settings' ) );
94
- add_action( 'admin_init', array( $this, 'remove_location_select_setting' ) );
95
- }
96
-
97
- /**
98
- * Register the location taxonomy
99
- *
100
- * @since 1.6
101
- */
102
- public function register_taxonomy() {
103
-
104
- $args = array(
105
- 'label' => _x( 'Location', 'Name for grouping bookings', 'restaurant-reservations' ),
106
- 'hierarchical' => false,
107
- 'public' => true,
108
- 'rewrite' => false,
109
- );
110
-
111
- /**
112
- * Allow third-party plugins to modify the location taxonomy
113
- * arguments.
114
- *
115
- * @since 1.6
116
- */
117
- $args = apply_filters( 'rtb_locations_args', $args );
118
-
119
- register_taxonomy( $this->location_taxonomy, RTB_BOOKING_POST_TYPE, $args );
120
- }
121
-
122
- /**
123
- * Generate taxonomy terms linked to locations and keep them sync'd
124
- * with any changes
125
- *
126
- * @since 1.6
127
- */
128
- public function save_location( $post_id, $post, $update ) {
129
-
130
- if (
131
- $post->post_status === 'auto-draft' ||
132
- ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ||
133
- !current_user_can( 'edit_post', $post_id ) ||
134
- !isset( $_POST['rtb_location_meta_nonce'] ) ||
135
- !wp_verify_nonce( $_POST['rtb_location_meta_nonce'], 'rtb_location_meta' )
136
- ) {
137
- return $post_id;
138
- }
139
-
140
- $term_id = get_post_meta( $post_id, $this->location_taxonomy, true );
141
-
142
- // Create a new term for this location
143
- if ( !$term_id ) {
144
-
145
- $term = wp_insert_term(
146
- sanitize_text_field( $post->post_title ),
147
- $this->location_taxonomy
148
- );
149
-
150
- if ( !is_a( $term, 'WP_Error' ) ) {
151
- update_post_meta( $post_id, $this->location_taxonomy, $term['term_id'] );
152
- $term_id = $term['term_id'];
153
- }
154
-
155
- // Update the term for this location
156
- } else {
157
- wp_update_term(
158
- $term_id,
159
- $this->location_taxonomy,
160
- array(
161
- 'name' => sanitize_text_field( $post->post_title ),
162
- 'slug' => sanitize_text_field( $post->post_name ),
163
- )
164
- );
165
- }
166
-
167
- if ( !empty( $_POST['rtb_append_booking_form'] ) ) {
168
- update_post_meta( $post_id, 'rtb_append_booking_form', true );
169
- } else {
170
- delete_post_meta( $post_id, 'rtb_append_booking_form' );
171
- }
172
-
173
- if ( $term_id ) {
174
-
175
- if ( !empty( $_POST['rtb_reply_to_name'] ) ) {
176
- $reply_to_name = sanitize_text_field( $_POST['rtb_reply_to_name'] );
177
- update_term_meta( $term_id, 'rtb_reply_to_name', $reply_to_name );
178
- } else {
179
- delete_term_meta( $term_id, 'rtb_reply_to_name' );
180
- }
181
-
182
- if ( !empty( $_POST['rtb_reply_to_address'] ) ) {
183
- $reply_to_address = sanitize_email( $_POST['rtb_reply_to_address'] );
184
- update_term_meta( $term_id, 'rtb_reply_to_address', $reply_to_address );
185
- } else {
186
- delete_term_meta( $term_id, 'rtb_reply_to_address' );
187
- }
188
-
189
- if ( !empty( $_POST['rtb_admin_email_address'] ) ) {
190
- $email = sanitize_text_field( $_POST['rtb_admin_email_address'] );
191
- update_term_meta( $term_id, 'rtb_admin_email_address', $email );
192
- } else {
193
- delete_term_meta( $term_id, 'rtb_admin_email_address' );
194
- }
195
- }
196
-
197
- return $post_id;
198
- }
199
-
200
- /**
201
- * Delete taxonomy terms linked to locations when a location is deleted
202
- *
203
- * Only does this when no bookings are associated with that term.
204
- * Otherwise it may be important to keep the bookings grouped for
205
- * historical data.
206
- *
207
- * @since 1.6
208
- */
209
- public function delete_location( $post_id ) {
210
-
211
- if ( !current_user_can( 'delete_posts' ) ) {
212
- return $post_id;
213
- }
214
-
215
- $term_id = get_post_meta( $post_id, $this->location_taxonomy, true );
216
-
217
- $term = get_term( $term_id, $this->location_taxonomy );
218
-
219
- if ( !$term || is_a( $term, 'WP_Error' ) ) {
220
- return;
221
- }
222
-
223
- $query = new rtbQuery( array( 'location' => $term_id ), 'delete-location-term-check' );
224
- $query->prepare_args();
225
- $query->get_bookings();
226
-
227
- // Don't delete taxonomy terms if there are bookings assigned to
228
- // this location, so the booking associations can remain as
229
- // historical data.
230
- if ( count( $query->bookings ) ) {
231
- add_term_meta( $term_id, 'rtb_location_removed', true );
232
- } else {
233
- wp_delete_term( $term_id, $this->location_taxonomy );
234
- }
235
-
236
- }
237
-
238
- /**
239
- * Get location term id from location post id
240
- *
241
- * Transforms a location post id into its associated term id. If the
242
- * id doesn't match a location post, it will check if the received id
243
- * matches a term id and return it if so. Between versions 1.6 and
244
- * and 1.6.1, only term ids were accepted as shortcodes, and this
245
- * provides a backwards-compatible fallback.
246
- *
247
- * @param $location_id int The location id (post or term)
248
- * @return int The location term id. Default: 0
249
- */
250
- public function get_location_term_id( $location_id ) {
251
-
252
- $location_id = absint( $location_id );
253
- $term_id = 0;
254
-
255
- if ( get_post_type( $location_id ) === $this->post_type ) {
256
- $term_id = get_post_meta( $location_id, $this->location_taxonomy, true );
257
- } elseif ( term_exists( $location_id, $this->location_taxonomy ) ) {
258
- $term_id = $location_id;
259
- }
260
-
261
- return $term_id;
262
- }
263
-
264
-
265
- /**
266
- * Add the location selection field to the booking form
267
- *
268
- * @since 1.6
269
- */
270
- public function add_location_field( $fields, $request = null, $args = array() ) {
271
-
272
- // If the location is specified, don't add a field.
273
- // A hidden field is added automatically in rtb_print_booking_form()
274
- if ( !empty( $args['location'] ) ) {
275
- $args['location'] = $this->get_location_term_id( $args['location'] );
276
- if ( !empty( $args['location'] ) ) {
277
- return $fields;
278
- }
279
- }
280
-
281
- if ( $request === null ) {
282
- global $rtb_controller;
283
- $request = $rtb_controller->request;
284
- }
285
-
286
- // Select a fieldset in which to place the field
287
- $placement = false;
288
- if ( isset( $fields['reservation'] ) && isset( $fields['reservation']['fields'] ) ) {
289
- $placement = &$fields['reservation']['fields'];
290
- } else {
291
- $key = key( reset( $fields ) );
292
- if ( isset( $fields[$key]['fields'] ) ) {
293
- $placement = &$fields[$key]['fields'];
294
- }
295
- }
296
-
297
- // If we couldn't find any working fieldset, then something odd is
298
- // going on. Just pretend we were never here.
299
- if ( $placement === false ) {
300
- return $fields;
301
- }
302
-
303
- $placement = array_merge(
304
- array(
305
- 'location' => array(
306
- 'title' => __( 'Location', 'restaurant-reservations' ),
307
- 'request_input' => empty( $request->location ) ? '' : $request->location,
308
- 'callback' => 'rtb_print_form_select_field',
309
- 'callback_args' => array(
310
- 'options' => $this->get_location_options(),
311
- ),
312
- 'empty_option' => true,
313
- 'required' => true,
314
- )
315
- ),
316
- $placement
317
- );
318
-
319
- return $fields;
320
- }
321
-
322
- /**
323
- * Retrieve a key/value array of location terms and names
324
- *
325
- * @param bool $active_only Whether or not to retrieve only currently
326
- * active locations. Default: true - don't retrieve locations that
327
- * have been removed
328
- * @since 1.6
329
- */
330
- public function get_location_options( $active_only = true ) {
331
-
332
- $terms = get_terms(
333
- array(
334
- 'taxonomy' => $this->location_taxonomy,
335
- 'hide_empty' => false,
336
- )
337
- );
338
-
339
- $options = array();
340
- foreach( $terms as $term ) {
341
- $archived = get_term_meta( $term->term_id, 'rtb_location_removed', true );
342
- if ( !$active_only || !$archived ) {
343
- $options[$term->term_id] = $term->name;
344
- }
345
- }
346
-
347
- return $options;
348
- }
349
-
350
- /**
351
- * Validate location in post data
352
- *
353
- * @since 1.6
354
- */
355
- public function validate_location( $booking ) {
356
-
357
- $booking->location = empty( $_POST['rtb-location'] ) ? '' : absint( $_POST['rtb-location'] );
358
- if ( empty( $booking->location ) ) {
359
- $booking->validation_errors[] = array(
360
- 'field' => 'location',
361
- 'post_variable' => $booking->location,
362
- 'message' => __( 'Please select a location for your booking.', 'restaurant-reservations' ),
363
- );
364
-
365
- } elseif ( !term_exists( $booking->location, $this->location_taxonomy ) ) {
366
- $booking->validation_errors[] = array(
367
- 'field' => 'location',
368
- 'post_variable' => $booking->location,
369
- 'message' => __( 'The location you selected is not valid. Please select another location.', 'restaurant-reservations' ),
370
- );
371
- }
372
- }
373
-
374
- /**
375
- * Save the booking location when the booking is created or updated.
376
- *
377
- * @since 1.6
378
- */
379
- public function save_booking_location( $booking ) {
380
-
381
- if ( !empty( $booking->location ) ) {
382
- wp_set_object_terms( $booking->ID, $booking->location, $this->location_taxonomy );
383
- }
384
- }
385
-
386
- /**
387
- * Load the booking location when teh booking is loaded
388
- *
389
- * @since 1.6
390
- */
391
- public function load_booking_location( $booking, $post ) {
392
-
393
- $terms = wp_get_object_terms( $booking->ID, $this->location_taxonomy, array( 'fields' => 'ids' ) );
394
-
395
- if ( is_a( $terms, 'WP_Error' ) ) {
396
- return;
397
- }
398
-
399
- $booking->location = current( $terms );
400
- }
401
-
402
- /**
403
- * Add location column to the list table
404
- *
405
- * @since 1.6
406
- */
407
- public function add_location_column( $columns ) {
408
-
409
- $first = array_splice( $columns, 0, 2 );
410
- $first['location'] = __( 'Location', 'restaurant-reservations' );
411
-
412
- return array_merge( $first, $columns );
413
- }
414
-
415
- /**
416
- * Print the value in the location column for the list table
417
- *
418
- * @since 1.6
419
- */
420
- public function print_location_column( $value, $booking, $column_name ) {
421
-
422
- if ( $column_name !== 'location' ) {
423
- return $value;
424
- }
425
-
426
- $terms = wp_get_object_terms( $booking->ID, $this->location_taxonomy );
427
-
428
- if ( empty( $terms ) || is_a( $terms, 'WP_Error' ) ) {
429
- return '';
430
- }
431
-
432
- $location = current( $terms );
433
-
434
- return $location->name;
435
- }
436
-
437
- /**
438
- * Modify queries to add location taxonomy parameters
439
- *
440
- * @param array $args Array of arguments passed to rtbQuery
441
- * @since 1.6
442
- */
443
- public function modify_query( $args, $context = '' ) {
444
-
445
- global $rtb_controller;
446
-
447
- if ( !empty( $args['location'] ) && !empty( $rtb_controller->locations->post_type ) ) {
448
-
449
- if ( !is_array( $args['location'] ) ) {
450
- $args['location'] = array( $args['location'] );
451
- }
452
-
453
- $args['tax_query'] = array(
454
- array(
455
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
456
- 'field' => 'term_id',
457
- 'terms' => $args['location'],
458
-
459
- )
460
- );
461
- }
462
-
463
- return $args;
464
- }
465
-
466
- /**
467
- * Add meta box to the location post editing screen
468
- *
469
- * @since 1.6
470
- */
471
- public function add_meta_boxes() {
472
-
473
- $meta_boxes = array(
474
-
475
- // Metabox to enter schema type
476
- array(
477
- 'id' => 'rtb_location',
478
- 'title' => __( 'Reservations', 'restaurant-reservations' ),
479
- 'callback' => array( $this, 'print_location_metabox' ),
480
- 'post_type' => $this->post_type,
481
- 'context' => 'side',
482
- 'priority' => 'default',
483
- ),
484
- );
485
-
486
- // Create filter so addons can modify the metaboxes
487
- $meta_boxes = apply_filters( 'rtb_location_metaboxes', $meta_boxes );
488
-
489
- // Create the metaboxes
490
- foreach ( $meta_boxes as $meta_box ) {
491
- add_meta_box(
492
- $meta_box['id'],
493
- $meta_box['title'],
494
- $meta_box['callback'],
495
- $meta_box['post_type'],
496
- $meta_box['context'],
497
- $meta_box['priority']
498
- );
499
- }
500
- }
501
-
502
- /**
503
- * Output a hidden nonce field to secure the saving of term meta
504
- *
505
- * @since 1.6
506
- */
507
- public function add_meta_nonce() {
508
- global $post;
509
- if ( $post->post_type == $this->post_type ) {
510
- wp_nonce_field( 'rtb_location_meta', 'rtb_location_meta_nonce' );
511
- }
512
- }
513
-
514
- /**
515
- * Print metabox on location post editing screen
516
- *
517
- * @since 1.6
518
- */
519
- public function print_location_metabox( $post ) {
520
-
521
- global $rtb_controller;
522
-
523
- $notification_email = '';
524
- $reply_to_name = '';
525
- $reply_to_address = '';
526
- $term_id = get_post_meta( $post->ID, $this->location_taxonomy, true );
527
- $admin_email_option = $rtb_controller->settings->get_setting( 'admin-email-option' );
528
- if ( $term_id ) {
529
- $reply_to_name = get_term_meta( $term_id, 'rtb_reply_to_name', true );
530
- $reply_to_address = get_term_meta( $term_id, 'rtb_reply_to_address', true );
531
- if ( $admin_email_option ) {
532
- $notification_email = get_term_meta( $term_id, 'rtb_admin_email_address', true );
533
- }
534
- }
535
-
536
- $append_booking_form = get_post_meta( $post->ID, 'rtb_append_booking_form', true );
537
-
538
- ?>
539
-
540
- <style type="text/css">.rtb-location-meta-input + .rtb-location-meta-input { margin-top: 2em; }</style>
541
-
542
- <div class="rtb-location-meta-input rtb-location-meta-append-form">
543
- <label>
544
- <input type="checkbox" name="rtb_append_booking_form" value="1"<?php if ( $append_booking_form ) : ?> checked="checked"<?php endif; ?>>
545
- <?php esc_html_e( "Automatically add the booking form to this page.", 'restaurant-reservations' ); ?>
546
- </label>
547
- </div>
548
-
549
- <div class="rtb-location-meta-input rtb-location-meta-reply-to-name">
550
- <label for="rtb_reply_to_name">
551
- <?php esc_html_e( 'Reply-To Name', 'restaurant-reservations' ); ?>
552
- </label>
553
- <input type="text" name="rtb_reply_to_name" id="rtb_reply_to_name" value="<?php esc_attr_e( $reply_to_name ); ?>" placeholder="<?php esc_attr_e( $rtb_controller->settings->get_setting( 'reply-to-name' ) ); ?>">
554
- <p class="description">
555
- <?php esc_html_e( 'The name which should appear in the Reply-To field of a user notification email.', 'restaurant-reservations' ); ?>
556
- </p>
557
- </div>
558
-
559
- <div class="rtb-location-meta-input rtb-location-meta-reply-to-address">
560
- <label for="rtb_reply_to_address">
561
- <?php esc_html_e( 'Reply-To Email Address', 'restaurant-reservations' ); ?>
562
- </label>
563
- <input type="text" name="rtb_reply_to_address" id="rtb_reply_to_address" value="<?php esc_attr_e( $reply_to_address ); ?>" placeholder="<?php esc_attr_e( $rtb_controller->settings->get_setting( 'reply-to-address' ) ); ?>">
564
- <p class="description">
565
- <?php esc_html_e( 'The email address which should appear in the Reply-To field of a user notification email.', 'restaurant-reservations' ); ?>
566
- </p>
567
- </div>
568
-
569
- <?php if ( $admin_email_option ) : ?>
570
- <div class="rtb-location-meta-input rtb-location-meta-admin-email">
571
- <label for="rtb_admin_email_address">
572
- <?php esc_html_e( 'Admin Notification Email Address', 'restaurant-reservations' ); ?>
573
- </label>
574
- <input type="text" name="rtb_admin_email_address" id="rtb_admin_email_address" value="<?php esc_attr_e( $notification_email ); ?>" placeholder="<?php esc_attr_e( $rtb_controller->settings->get_setting( 'admin-email-address' ) ); ?>">
575
- <p class="description">
576
- <?php esc_html_e( 'The email address where admin notifications for bookings at this location should be sent.', 'restaurant-reservations' ); ?>
577
- </p>
578
- </div>
579
- <?php endif; ?>
580
-
581
- <?php
582
- }
583
-
584
- /**
585
- * Append booking form to a location's `post_content`
586
- * @since 0.0.1
587
- */
588
- public function append_to_content( $content ) {
589
-
590
- if ( !is_main_query() || !in_the_loop() || post_password_required() ) {
591
- return $content;
592
- }
593
-
594
- global $post;
595
-
596
- $append_booking_form = get_post_meta( $post->ID, 'rtb_append_booking_form', true );
597
-
598
- if ( !$append_booking_form ) {
599
- return $content;
600
- }
601
-
602
- $term_id = get_post_meta( $post->ID, $this->location_taxonomy, true );
603
-
604
- if ( empty( $term_id ) ) {
605
- return $content;
606
- }
607
-
608
- return $content . do_shortcode( '[booking-form location=' . absint( $term_id ) .']' );
609
- }
610
-
611
- /**
612
- * Modify the notification email recipient for each location
613
- *
614
- * @since 1.6
615
- */
616
- public function notification_to_email( $email, $notification ) {
617
-
618
- if ( $notification->target == 'user' || empty( $notification->booking->location ) ) {
619
- return $email;
620
- }
621
-
622
- $val = get_term_meta( $notification->booking->location, 'rtb_admin_email_address', true );
623
- $email = empty( $val ) ? $email : $val;
624
-
625
- return $email;
626
- }
627
-
628
- /**
629
- * Modify the notification email sender address for each location
630
- *
631
- * @since 1.6
632
- */
633
- public function notification_from_email( $email, $notification ) {
634
-
635
- if ( $notification->target != 'user' || empty( $notification->booking->location ) ) {
636
- return $email;
637
- }
638
-
639
- $val = get_term_meta( $notification->booking->location, 'rtb_reply_to_address', true );
640
- $email = empty( $val ) ? $email : $val;
641
-
642
- return $email;
643
- }
644
-
645
- /**
646
- * Modify the notification email sender name for each location
647
- *
648
- * @since 1.6
649
- */
650
- public function notification_from_name( $name, $notification ) {
651
-
652
- if ( $notification->target != 'user' || empty( $notification->booking->location ) ) {
653
- return $name;
654
- }
655
-
656
- $val = get_term_meta( $notification->booking->location, 'rtb_reply_to_name', true );
657
- $name = empty( $val ) ? $name : $val;
658
-
659
- return $name;
660
- }
661
-
662
- /**
663
- * Add a location template tag for notifications
664
- *
665
- * @since 1.6.1
666
- */
667
- public function notification_template_tags( $template_tags, $notification ) {
668
-
669
- $term = empty( $notification->booking->location ) ? null : get_term( $notification->booking->location, $this->location_taxonomy );
670
- $location_name = is_null( $term ) || is_wp_error( $term ) ? '' : $term->name;
671
-
672
- $table_number = empty( $notification->booking->table ) ? '' : $notification->booking->table;
673
- $table_number = is_array($table_number) ? implode(',', $table_number ) : $table_number;
674
-
675
- return array_merge(
676
- array(
677
- '{location}' => $location_name,
678
- '{table}' => $table_number,
679
- ),
680
- $template_tags
681
- );
682
- }
683
-
684
- /**
685
- * Add a description for the location template tag
686
- *
687
- * @since 1.6.1
688
- */
689
- public function notification_template_tag_descriptions( $descriptions ) {
690
- return array_merge(
691
- array( '{location}' => __( 'Location for which this booking was made.', 'restaurant-reservations' ) ),
692
- $descriptions
693
- );
694
- }
695
-
696
- /**
697
- * Removes Auto-Draft locations that were added due to a bug in v1.7
698
- *
699
- * Version 1.7 introduced a bug which caused a location term to be
700
- * created if the location Add New page was loaded. This term
701
- * corresponded to an auto-draft post object and will be removed when
702
- * that object is removed. This provides a one-time fix in v1.7.1
703
- *
704
- * @see https://github.com/NateWr/restaurant-reservations/issues/91
705
- * @see https://developer.wordpress.org/reference/functions/wp_delete_auto_drafts/
706
- * @since 1.7.1
707
- */
708
- public function fix_autodraft_term_error() {
709
-
710
- if ( get_option( 'rtb_autodraft_terms_fixed', false ) ) {
711
- return;
712
- }
713
-
714
- global $wpdb;
715
-
716
- if ( !$wpdb ) {
717
- return;
718
- }
719
-
720
- $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND post_type = '$this->post_type';" );
721
- foreach ( (array) $old_posts as $delete ) {
722
- // Force delete.
723
- wp_delete_post( $delete, true );
724
- }
725
-
726
- // Set the `rtb_location_removed` term meta on any terms that are
727
- // no longer attached to posts
728
- global $wp_version;
729
- if ( version_compare( $wp_version, '3.9', '>=' ) ) {
730
- $live_terms = $wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key='$this->location_taxonomy';" );
731
- $all_terms = get_terms( array(
732
- 'taxonomy' => $this->location_taxonomy,
733
- 'hide_empty' => false,
734
- 'meta_query' => array(
735
- array(
736
- 'compare' => 'NOT EXISTS',
737
- 'key' => 'rtb_location_removed',
738
- )
739
- )
740
- ) );
741
- if ( is_array( $all_terms ) ) {
742
- foreach( $all_terms as $term ) {
743
- if ( !in_array( $term->term_id, $live_terms ) ) {
744
- $query = new rtbQuery( array( 'location' => $term->term_id ), 'delete-location-term-check' );
745
- $query->prepare_args();
746
- $query->get_bookings();
747
-
748
- // Don't delete taxonomy terms if there are bookings assigned to
749
- // this location, so the booking associations can remain as
750
- // historical data.
751
- if ( count( $query->bookings ) ) {
752
- add_term_meta( $term->term_id, 'rtb_location_removed', true );
753
- } else {
754
- wp_delete_term( $term->term_id, $this->location_taxonomy );
755
- }
756
- }
757
- }
758
- }
759
- }
760
-
761
- update_option( 'rtb_autodraft_terms_fixed', true );
762
- }
763
-
764
- /**
765
- * If multiple locations exist, adds a select box to the settings page which
766
- * allows a user to select whether certain settings are global or location-specific.
767
- * Also adds in location-specific settings for a number of different settings, and
768
- * makes all settings conditional on the value of the select box.
769
- *
770
- * @since 2.3.6
771
- */
772
- public function maybe_add_location_settings( $sap ) {
773
- global $rtb_controller;
774
-
775
- $args = array(
776
- 'taxonomy' => $this->location_taxonomy,
777
- 'hide_empty' => false,
778
- );
779
-
780
- $terms = get_terms( $args );
781
-
782
- if ( ! $this->do_locations_exist() ) { return $sap; }
783
-
784
- foreach ( $sap->pages['rtb-settings']->sections as $key => $section ) {
785
-
786
- foreach ( $section->settings as $setting_key => $setting ) {
787
-
788
- $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->conditional_on = 'location-select';
789
- $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->conditional_on_value = false;
790
-
791
- $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->set_conditional_display();
792
- }
793
- }
794
-
795
-
796
- $location_options = array(
797
- '' => __( 'Global', 'restaurant-reservations' ),
798
- );
799
-
800
- foreach ( $terms as $term ) {
801
-
802
- $location_options[ $term->slug ] = $term->term_id . ' - '.$term->name;
803
- }
804
-
805
- // Schedule location-specific options
806
- $sap->add_section(
807
- 'rtb-settings',
808
- array(
809
- 'id' => 'rtb-schedule-location-select',
810
- 'title' => __( 'Select Schedule Location', 'restaurant-reservations' ),
811
- 'tab' => 'rtb-schedule-tab',
812
- 'rank' => 2,
813
- )
814
- );
815
-
816
- $sap->add_setting(
817
- 'rtb-settings',
818
- 'rtb-schedule-location-select',
819
- 'select',
820
- array(
821
- 'id' => 'location-select',
822
- 'title' => __( 'Schedule Location', 'restaurant-reservations' ),
823
- 'description' => __( 'Select which location the schedule will apply to. If a specific location doesn\'t have a schedule set, then it will fall back to the global schedule when booking.', 'restaurant-reservations' ),
824
- 'blank_option' => false,
825
- 'options' => $location_options,
826
- )
827
- );
828
-
829
- // Translateable strings for scheduler components
830
- $scheduler_strings = array(
831
- 'add_rule' => __( 'Add new scheduling rule', 'restaurant-reservations' ),
832
- 'weekly' => _x( 'Weekly', 'Format of a scheduling rule', 'restaurant-reservations' ),
833
- 'monthly' => _x( 'Monthly', 'Format of a scheduling rule', 'restaurant-reservations' ),
834
- 'date' => _x( 'Date', 'Format of a scheduling rule', 'restaurant-reservations' ),
835
- 'weekdays' => _x( 'Days of the week', 'Label for selecting days of the week in a scheduling rule', 'restaurant-reservations' ),
836
- 'month_weeks' => _x( 'Weeks of the month', 'Label for selecting weeks of the month in a scheduling rule', 'restaurant-reservations' ),
837
- 'date_label' => _x( 'Date', 'Label to select a date for a scheduling rule', 'restaurant-reservations' ),
838
- 'time_label' => _x( 'Time', 'Label to select a time slot for a scheduling rule', 'restaurant-reservations' ),
839
- 'allday' => _x( 'All day', 'Label to set a scheduling rule to last all day', 'restaurant-reservations' ),
840
- 'start' => _x( 'Start', 'Label for the starting time of a scheduling rule', 'restaurant-reservations' ),
841
- 'end' => _x( 'End', 'Label for the ending time of a scheduling rule', 'restaurant-reservations' ),
842
- 'set_time_prompt' => _x( 'All day long. Want to %sset a time slot%s?', 'Prompt displayed when a scheduling rule is set without any time restrictions', 'restaurant-reservations' ),
843
- 'toggle' => _x( 'Open and close this rule', 'Toggle a scheduling rule open and closed', 'restaurant-reservations' ),
844
- 'delete' => _x( 'Delete rule', 'Delete a scheduling rule', 'restaurant-reservations' ),
845
- 'delete_schedule' => __( 'Delete scheduling rule', 'restaurant-reservations' ),
846
- 'never' => _x( 'Never', 'Brief default description of a scheduling rule when no weekdays or weeks are included in the rule', 'restaurant-reservations' ),
847
- 'weekly_always' => _x( 'Every day', 'Brief default description of a scheduling rule when all the weekdays/weeks are included in the rule', 'restaurant-reservations' ),
848
- 'monthly_weekdays' => _x( '%s on the %s week of the month', 'Brief default description of a scheduling rule when some weekdays are included on only some weeks of the month. %s should be left alone and will be replaced by a comma-separated list of days and weeks in the following format: M, T, W on the first, second week of the month', 'restaurant-reservations' ),
849
- 'monthly_weeks' => _x( '%s week of the month', 'Brief default description of a scheduling rule when some weeks of the month are included but all or no weekdays are selected. %s should be left alone and will be replaced by a comma-separated list of weeks in the following format: First, second week of the month', 'restaurant-reservations' ),
850
- 'all_day' => _x( 'All day', 'Brief default description of a scheduling rule when no times are set', 'restaurant-reservations' ),
851
- 'before' => _x( 'Ends at', 'Brief default description of a scheduling rule when an end time is set but no start time. If the end time is 6pm, it will read: Ends at 6pm', 'restaurant-reservations' ),
852
- 'after' => _x( 'Starts at', 'Brief default description of a scheduling rule when a start time is set but no end time. If the start time is 6pm, it will read: Starts at 6pm', 'restaurant-reservations' ),
853
- 'separator' => _x( '&mdash;', 'Separator between times of a scheduling rule', 'restaurant-reservations' ),
854
- );
855
-
856
- foreach ( $terms as $term ) {
857
-
858
- $sap->add_setting(
859
- 'rtb-settings',
860
- 'rtb-schedule',
861
- 'scheduler',
862
- array(
863
- 'id' => $term->slug . '-schedule-open',
864
- 'title' => __( 'Schedule', 'restaurant-reservations' ),
865
- 'description' => __( 'Define the weekly schedule during which you accept bookings.', 'restaurant-reservations' ),
866
- 'weekdays' => array(
867
- 'monday' => _x( 'Mo', 'Monday abbreviation', 'restaurant-reservations' ),
868
- 'tuesday' => _x( 'Tu', 'Tuesday abbreviation', 'restaurant-reservations' ),
869
- 'wednesday' => _x( 'We', 'Wednesday abbreviation', 'restaurant-reservations' ),
870
- 'thursday' => _x( 'Th', 'Thursday abbreviation', 'restaurant-reservations' ),
871
- 'friday' => _x( 'Fr', 'Friday abbreviation', 'restaurant-reservations' ),
872
- 'saturday' => _x( 'Sa', 'Saturday abbreviation', 'restaurant-reservations' ),
873
- 'sunday' => _x( 'Su', 'Sunday abbreviation', 'restaurant-reservations' )
874
- ),
875
- 'time_format' => $rtb_controller->settings->get_setting( 'time-format' ),
876
- 'date_format' => $rtb_controller->settings->get_setting( 'date-format' ),
877
- 'disable_weeks' => true,
878
- 'disable_date' => true,
879
- 'strings' => $scheduler_strings,
880
- 'conditional_on' => 'location-select',
881
- 'conditional_on_value' => $term->slug,
882
- )
883
- );
884
-
885
- $sap->add_setting(
886
- 'rtb-settings',
887
- 'rtb-schedule',
888
- 'scheduler',
889
- array(
890
- 'id' => $term->slug . '-schedule-closed',
891
- 'title' => __( 'Exceptions', 'restaurant-reservations' ),
892
- 'description' => __( "Define special opening hours for holidays, events or other needs. Leave the time empty if you're closed all day.", 'restaurant-reservations' ),
893
- 'time_format' => esc_attr( $rtb_controller->settings->get_setting( 'time-format' ) ),
894
- 'date_format' => esc_attr( $rtb_controller->settings->get_setting( 'date-format' ) ),
895
- 'disable_weekdays' => true,
896
- 'disable_weeks' => true,
897
- 'strings' => $scheduler_strings,
898
- 'conditional_on' => 'location-select',
899
- 'conditional_on_value' => $term->slug,
900
- )
901
- );
902
- }
903
-
904
- // Restriction location-specific options
905
- $sap->add_section(
906
- 'rtb-settings',
907
- array(
908
- 'id' => 'rtb-restrictions-location-select',
909
- 'title' => __( 'Select Seat Restrictions Location', 'restaurant-reservations' ),
910
- 'tab' => 'rtb-premium',
911
- 'rank' => 11,
912
- )
913
- );
914
-
915
- $sap->add_setting(
916
- 'rtb-settings',
917
- 'rtb-restrictions-location-select',
918
- 'select',
919
- array(
920
- 'id' => 'location-select',
921
- 'title' => __( 'Seat Restrictions Location', 'restaurant-reservations' ),
922
- 'description' => __( 'Select which location the restrictions will apply to. If a specific location doesn\'t have restrictions set, then the global total number will be used as a fall-back.', 'restaurant-reservations' ),
923
- 'blank_option' => false,
924
- 'options' => $location_options,
925
- )
926
- );
927
-
928
- $max_reservation_options = array();
929
- $max_reservations_upper_limit = apply_filters( 'rtb-max-reservations-upper-limit', 100 );
930
-
931
- for ( $i = 1; $i <= $max_reservations_upper_limit; $i++ ) {
932
-
933
- $max_reservation_options[$i] = $i;
934
- }
935
-
936
- $max_people_options = array();
937
- $max_people_upper_limit = apply_filters( 'rtb-max-people-upper-limit', 400 );
938
-
939
- for ( $i = 1; $i <= $max_people_upper_limit; $i++ ) {
940
-
941
- $max_people_options[$i] = $i;
942
- }
943
-
944
- foreach ( $terms as $term ) {
945
-
946
- $sap->add_setting(
947
- 'rtb-settings',
948
- 'rtb-seat-assignments',
949
- 'select',
950
- array(
951
- 'id' => $term->slug . '-rtb-max-tables-count',
952
- 'title' => __( 'Max Reservations', 'restaurant-reservations' ),
953
- 'description' => __( 'How many reservations, if enabled, should be allowed at the same time at this location? Set dining block length to change how long a meal typically lasts.', 'restaurant-reservations' ),
954
- 'options' => $max_reservation_options,
955
- 'conditional_on' => 'location-select',
956
- 'conditional_on_value' => $term->slug,
957
- )
958
- );
959
-
960
- $sap->add_setting(
961
- 'rtb-settings',
962
- 'rtb-seat-assignments',
963
- 'select',
964
- array(
965
- 'id' => $term->slug . '-rtb-max-people-count',
966
- 'title' => __( 'Max People', 'restaurant-reservations' ),
967
- 'description' => __( 'How many people, if enabled, should be allowed to be present at this restaurant location at the same time? Set dining block length to change how long a meal typically lasts. May not work correctly if max reservations is set.', 'restaurant-reservations' ),
968
- 'options' => $max_people_options,
969
- 'conditional_on' => 'location-select',
970
- 'conditional_on_value' => $term->slug,
971
- )
972
- );
973
- }
974
-
975
- return $sap;
976
- }
977
-
978
- /**
979
- * Blank out the location setting, so that it's always set to 'Global'
980
- * on page load, except for immediately after saving a setting.
981
- *
982
- * @since 2.3.6
983
- */
984
- public function remove_location_select_setting() {
985
- global $rtb_controller;
986
-
987
- $rtb_controller->settings->set_setting( 'location-select', null );
988
-
989
- $rtb_controller->settings->save_settings();
990
- }
991
-
992
- /**
993
- * Returns true if locations have been created, false otherwise
994
- *
995
- * @since 2.3.6
996
- */
997
- public function do_locations_exist() {
998
-
999
- $args = array(
1000
- 'taxonomy' => $this->location_taxonomy,
1001
- 'hide_empty' => false,
1002
- );
1003
-
1004
- $terms = get_terms( $args );
1005
-
1006
- return ( ! empty( $terms ) and ! is_wp_error( $terms ) );
1007
- }
1008
- }
1009
- }
1
+ <?php
2
+ /**
3
+ * Methods for handling multiple locations
4
+ *
5
+ * @package RestaurantReservations
6
+ * @copyright Copyright (c) 2016, Theme of the Crop
7
+ * @license GPL-2.0+
8
+ * @since 1.6
9
+ */
10
+ defined( 'ABSPATH' ) || exit;
11
+
12
+ if ( ! class_exists( 'rtbMultipleLocations', false ) ) {
13
+ /**
14
+ * Class to handle custom post type and post meta fields
15
+ *
16
+ * @since 1.6
17
+ */
18
+ class rtbMultipleLocations {
19
+
20
+ /**
21
+ * Post type slug where locations can be found
22
+ *
23
+ * @since 1.6
24
+ */
25
+ public $post_type = false;
26
+
27
+ /**
28
+ * Taxonomy to use when assigning bookings to locations
29
+ *
30
+ * @since 1.6
31
+ */
32
+ public $location_taxonomy = 'rtb_location';
33
+
34
+ /**
35
+ * Set the loading hook
36
+ *
37
+ * @since 1.6
38
+ */
39
+ public function __construct() {
40
+ add_action( 'plugins_loaded', array( $this, 'load' ), 100 );
41
+ }
42
+
43
+ /**
44
+ * Load locations support
45
+ *
46
+ * @since 1.6
47
+ */
48
+ public function load() {
49
+
50
+ /**
51
+ * Allow third-party plugins to enable multiple locations
52
+ *
53
+ * Expects a post type slug pointing to the locations or false if
54
+ * multiple locations are not enabled.
55
+ *
56
+ * @since 1.6
57
+ */
58
+ $this->post_type = apply_filters( 'rtb_set_locations_post_type', false );
59
+
60
+ if ( !$this->post_type ) {
61
+ return;
62
+ }
63
+
64
+ $this->hooks();
65
+ }
66
+
67
+ /**
68
+ * Set up hooks
69
+ *
70
+ * @since 1.6
71
+ */
72
+ public function hooks() {
73
+ add_action( 'init', array( $this, 'register_taxonomy' ), 1000 ); // after custom post types declared (hopefully!)
74
+ add_action( 'save_post_' . $this->post_type, array( $this, 'save_location' ), 10, 3 );
75
+ add_action( 'before_delete_post', array( $this, 'delete_location' ) );
76
+ add_action( 'rtb_booking_form_fields', array( $this, 'add_location_field' ), 10, 3 );
77
+ add_action( 'rtb_pre_validate_booking_submission', array( $this, 'validate_location' ) );
78
+ add_action( 'rtb_insert_booking', array( $this, 'save_booking_location' ) );
79
+ add_action( 'rtb_update_booking', array( $this, 'save_booking_location' ) );
80
+ add_action( 'rtb_booking_load_post_data', array( $this, 'load_booking_location' ), 10, 2 );
81
+ add_filter( 'rtb_query_args', array( $this, 'modify_query' ), 10, 2 );
82
+ add_filter( 'rtb_bookings_all_table_columns', array( $this, 'add_location_column' ) );
83
+ add_filter( 'rtb_bookings_table_column', array( $this, 'print_location_column' ), 10, 3 );
84
+ add_action( 'edit_form_after_title', array( $this, 'add_meta_nonce' ) );
85
+ add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
86
+ add_filter( 'the_content', array( $this, 'append_to_content' ) );
87
+ add_filter( 'rtb_notification_email_to_email', array( $this, 'notification_to_email' ), 10, 2 );
88
+ add_filter( 'rtb_notification_email_from_email', array( $this, 'notification_from_email' ), 10, 2 );
89
+ add_filter( 'rtb_notification_email_from_name', array( $this, 'notification_from_name' ), 10, 2 );
90
+ add_filter( 'rtb_notification_template_tags', array( $this, 'notification_template_tags' ), 10, 2 );
91
+ add_filter( 'rtb_notification_template_tag_descriptions', array( $this, 'notification_template_tag_descriptions' ) );
92
+ add_action( 'admin_init', array( $this, 'fix_autodraft_term_error' ) );
93
+ add_filter( 'rtb_settings_page', array( $this, 'maybe_add_location_settings' ) );
94
+ add_action( 'admin_init', array( $this, 'remove_location_select_setting' ) );
95
+ }
96
+
97
+ /**
98
+ * Register the location taxonomy
99
+ *
100
+ * @since 1.6
101
+ */
102
+ public function register_taxonomy() {
103
+
104
+ $args = array(
105
+ 'label' => _x( 'Location', 'Name for grouping bookings', 'restaurant-reservations' ),
106
+ 'hierarchical' => false,
107
+ 'public' => true,
108
+ 'rewrite' => false,
109
+ );
110
+
111
+ /**
112
+ * Allow third-party plugins to modify the location taxonomy
113
+ * arguments.
114
+ *
115
+ * @since 1.6
116
+ */
117
+ $args = apply_filters( 'rtb_locations_args', $args );
118
+
119
+ register_taxonomy( $this->location_taxonomy, RTB_BOOKING_POST_TYPE, $args );
120
+ }
121
+
122
+ /**
123
+ * Generate taxonomy terms linked to locations and keep them sync'd
124
+ * with any changes
125
+ *
126
+ * @since 1.6
127
+ */
128
+ public function save_location( $post_id, $post, $update ) {
129
+
130
+ if (
131
+ $post->post_status === 'auto-draft' ||
132
+ ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ||
133
+ !current_user_can( 'edit_post', $post_id ) ||
134
+ !isset( $_POST['rtb_location_meta_nonce'] ) ||
135
+ !wp_verify_nonce( $_POST['rtb_location_meta_nonce'], 'rtb_location_meta' )
136
+ ) {
137
+ return $post_id;
138
+ }
139
+
140
+ $term_id = get_post_meta( $post_id, $this->location_taxonomy, true );
141
+
142
+ // Create a new term for this location
143
+ if ( !$term_id ) {
144
+
145
+ $term = wp_insert_term(
146
+ sanitize_text_field( $post->post_title ),
147
+ $this->location_taxonomy
148
+ );
149
+
150
+ if ( !is_a( $term, 'WP_Error' ) ) {
151
+ update_post_meta( $post_id, $this->location_taxonomy, $term['term_id'] );
152
+ $term_id = $term['term_id'];
153
+ }
154
+
155
+ // Update the term for this location
156
+ } else {
157
+ wp_update_term(
158
+ $term_id,
159
+ $this->location_taxonomy,
160
+ array(
161
+ 'name' => sanitize_text_field( $post->post_title ),
162
+ 'slug' => sanitize_text_field( $post->post_name ),
163
+ )
164
+ );
165
+ }
166
+
167
+ if ( !empty( $_POST['rtb_append_booking_form'] ) ) {
168
+ update_post_meta( $post_id, 'rtb_append_booking_form', true );
169
+ } else {
170
+ delete_post_meta( $post_id, 'rtb_append_booking_form' );
171
+ }
172
+
173
+ if ( $term_id ) {
174
+
175
+ if ( !empty( $_POST['rtb_reply_to_name'] ) ) {
176
+ $reply_to_name = sanitize_text_field( $_POST['rtb_reply_to_name'] );
177
+ update_term_meta( $term_id, 'rtb_reply_to_name', $reply_to_name );
178
+ } else {
179
+ delete_term_meta( $term_id, 'rtb_reply_to_name' );
180
+ }
181
+
182
+ if ( !empty( $_POST['rtb_reply_to_address'] ) ) {
183
+ $reply_to_address = sanitize_email( $_POST['rtb_reply_to_address'] );
184
+ update_term_meta( $term_id, 'rtb_reply_to_address', $reply_to_address );
185
+ } else {
186
+ delete_term_meta( $term_id, 'rtb_reply_to_address' );
187
+ }
188
+
189
+ if ( !empty( $_POST['rtb_admin_email_address'] ) ) {
190
+ $email = sanitize_text_field( $_POST['rtb_admin_email_address'] );
191
+ update_term_meta( $term_id, 'rtb_admin_email_address', $email );
192
+ } else {
193
+ delete_term_meta( $term_id, 'rtb_admin_email_address' );
194
+ }
195
+ }
196
+
197
+ return $post_id;
198
+ }
199
+
200
+ /**
201
+ * Delete taxonomy terms linked to locations when a location is deleted
202
+ *
203
+ * Only does this when no bookings are associated with that term.
204
+ * Otherwise it may be important to keep the bookings grouped for
205
+ * historical data.
206
+ *
207
+ * @since 1.6
208
+ */
209
+ public function delete_location( $post_id ) {
210
+
211
+ if ( !current_user_can( 'delete_posts' ) ) {
212
+ return $post_id;
213
+ }
214
+
215
+ $term_id = get_post_meta( $post_id, $this->location_taxonomy, true );
216
+
217
+ $term = get_term( $term_id, $this->location_taxonomy );
218
+
219
+ if ( !$term || is_a( $term, 'WP_Error' ) ) {
220
+ return;
221
+ }
222
+
223
+ $query = new rtbQuery( array( 'location' => $term_id ), 'delete-location-term-check' );
224
+ $query->prepare_args();
225
+ $query->get_bookings();
226
+
227
+ // Don't delete taxonomy terms if there are bookings assigned to
228
+ // this location, so the booking associations can remain as
229
+ // historical data.
230
+ if ( count( $query->bookings ) ) {
231
+ add_term_meta( $term_id, 'rtb_location_removed', true );
232
+ } else {
233
+ wp_delete_term( $term_id, $this->location_taxonomy );
234
+ }
235
+
236
+ }
237
+
238
+ /**
239
+ * Get location term id from location post id
240
+ *
241
+ * Transforms a location post id into its associated term id. If the
242
+ * id doesn't match a location post, it will check if the received id
243
+ * matches a term id and return it if so. Between versions 1.6 and
244
+ * and 1.6.1, only term ids were accepted as shortcodes, and this
245
+ * provides a backwards-compatible fallback.
246
+ *
247
+ * @param $location_id int The location id (post or term)
248
+ * @return int The location term id. Default: 0
249
+ */
250
+ public function get_location_term_id( $location_id ) {
251
+
252
+ $location_id = absint( $location_id );
253
+ $term_id = 0;
254
+
255
+ if ( get_post_type( $location_id ) === $this->post_type ) {
256
+ $term_id = get_post_meta( $location_id, $this->location_taxonomy, true );
257
+ } elseif ( term_exists( $location_id, $this->location_taxonomy ) ) {
258
+ $term_id = $location_id;
259
+ }
260
+
261
+ return $term_id;
262
+ }
263
+
264
+
265
+ /**
266
+ * Add the location selection field to the booking form
267
+ *
268
+ * @since 1.6
269
+ */
270
+ public function add_location_field( $fields, $request = null, $args = array() ) {
271
+
272
+ // If the location is specified, don't add a field.
273
+ // A hidden field is added automatically in rtb_print_booking_form()
274
+ if ( !empty( $args['location'] ) ) {
275
+ $args['location'] = $this->get_location_term_id( $args['location'] );
276
+ if ( !empty( $args['location'] ) ) {
277
+ return $fields;
278
+ }
279
+ }
280
+
281
+ if ( $request === null ) {
282
+ global $rtb_controller;
283
+ $request = $rtb_controller->request;
284
+ }
285
+
286
+ // Select a fieldset in which to place the field
287
+ $placement = false;
288
+ if ( isset( $fields['reservation'] ) && isset( $fields['reservation']['fields'] ) ) {
289
+ $placement = &$fields['reservation']['fields'];
290
+ } else {
291
+ $key = key( reset( $fields ) );
292
+ if ( isset( $fields[$key]['fields'] ) ) {
293
+ $placement = &$fields[$key]['fields'];
294
+ }
295
+ }
296
+
297
+ // If we couldn't find any working fieldset, then something odd is
298
+ // going on. Just pretend we were never here.
299
+ if ( $placement === false ) {
300
+ return $fields;
301
+ }
302
+
303
+ $placement = array_merge(
304
+ array(
305
+ 'location' => array(
306
+ 'title' => __( 'Location', 'restaurant-reservations' ),
307
+ 'request_input' => empty( $request->location ) ? '' : $request->location,
308
+ 'callback' => 'rtb_print_form_select_field',
309
+ 'callback_args' => array(
310
+ 'options' => $this->get_location_options(),
311
+ ),
312
+ 'empty_option' => true,
313
+ 'required' => true,
314
+ )
315
+ ),
316
+ $placement
317
+ );
318
+
319
+ return $fields;
320
+ }
321
+
322
+ /**
323
+ * Retrieve a key/value array of location terms and names
324
+ *
325
+ * @param bool $active_only Whether or not to retrieve only currently
326
+ * active locations. Default: true - don't retrieve locations that
327
+ * have been removed
328
+ * @since 1.6
329
+ */
330
+ public function get_location_options( $active_only = true ) {
331
+
332
+ $terms = get_terms(
333
+ array(
334
+ 'taxonomy' => $this->location_taxonomy,
335
+ 'hide_empty' => false,
336
+ )
337
+ );
338
+
339
+ $options = array();
340
+ foreach( $terms as $term ) {
341
+ $archived = get_term_meta( $term->term_id, 'rtb_location_removed', true );
342
+ if ( !$active_only || !$archived ) {
343
+ $options[$term->term_id] = $term->name;
344
+ }
345
+ }
346
+
347
+ return $options;
348
+ }
349
+
350
+ /**
351
+ * Validate location in post data
352
+ *
353
+ * @since 1.6
354
+ */
355
+ public function validate_location( $booking ) {
356
+
357
+ $booking->location = empty( $_POST['rtb-location'] ) ? '' : absint( $_POST['rtb-location'] );
358
+ if ( empty( $booking->location ) ) {
359
+ $booking->validation_errors[] = array(
360
+ 'field' => 'location',
361
+ 'post_variable' => $booking->location,
362
+ 'message' => __( 'Please select a location for your booking.', 'restaurant-reservations' ),
363
+ );
364
+
365
+ } elseif ( !term_exists( $booking->location, $this->location_taxonomy ) ) {
366
+ $booking->validation_errors[] = array(
367
+ 'field' => 'location',
368
+ 'post_variable' => $booking->location,
369
+ 'message' => __( 'The location you selected is not valid. Please select another location.', 'restaurant-reservations' ),
370
+ );
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Save the booking location when the booking is created or updated.
376
+ *
377
+ * @since 1.6
378
+ */
379
+ public function save_booking_location( $booking ) {
380
+
381
+ if ( !empty( $booking->location ) ) {
382
+ wp_set_object_terms( $booking->ID, $booking->location, $this->location_taxonomy );
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Load the booking location when teh booking is loaded
388
+ *
389
+ * @since 1.6
390
+ */
391
+ public function load_booking_location( $booking, $post ) {
392
+
393
+ $terms = wp_get_object_terms( $booking->ID, $this->location_taxonomy, array( 'fields' => 'ids' ) );
394
+
395
+ if ( is_a( $terms, 'WP_Error' ) ) {
396
+ return;
397
+ }
398
+
399
+ $booking->location = current( $terms );
400
+ }
401
+
402
+ /**
403
+ * Add location column to the list table
404
+ *
405
+ * @since 1.6
406
+ */
407
+ public function add_location_column( $columns ) {
408
+
409
+ $first = array_splice( $columns, 0, 2 );
410
+ $first['location'] = __( 'Location', 'restaurant-reservations' );
411
+
412
+ return array_merge( $first, $columns );
413
+ }
414
+
415
+ /**
416
+ * Print the value in the location column for the list table
417
+ *
418
+ * @since 1.6
419
+ */
420
+ public function print_location_column( $value, $booking, $column_name ) {
421
+
422
+ if ( $column_name !== 'location' ) {
423
+ return $value;
424
+ }
425
+
426
+ $terms = wp_get_object_terms( $booking->ID, $this->location_taxonomy );
427
+
428
+ if ( empty( $terms ) || is_a( $terms, 'WP_Error' ) ) {
429
+ return '';
430
+ }
431
+
432
+ $location = current( $terms );
433
+
434
+ return $location->name;
435
+ }
436
+
437
+ /**
438
+ * Modify queries to add location taxonomy parameters
439
+ *
440
+ * @param array $args Array of arguments passed to rtbQuery
441
+ * @since 1.6
442
+ */
443
+ public function modify_query( $args, $context = '' ) {
444
+
445
+ global $rtb_controller;
446
+
447
+ if ( !empty( $args['location'] ) && !empty( $rtb_controller->locations->post_type ) ) {
448
+
449
+ if ( !is_array( $args['location'] ) ) {
450
+ $args['location'] = array( $args['location'] );
451
+ }
452
+
453
+ $args['tax_query'] = array(
454
+ array(
455
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
456
+ 'field' => 'term_id',
457
+ 'terms' => $args['location'],
458
+
459
+ )
460
+ );
461
+ }
462
+
463
+ return $args;
464
+ }
465
+
466
+ /**
467
+ * Add meta box to the location post editing screen
468
+ *
469
+ * @since 1.6
470
+ */
471
+ public function add_meta_boxes() {
472
+
473
+ $meta_boxes = array(
474
+
475
+ // Metabox to enter schema type
476
+ array(
477
+ 'id' => 'rtb_location',
478
+ 'title' => __( 'Reservations', 'restaurant-reservations' ),
479
+ 'callback' => array( $this, 'print_location_metabox' ),
480
+ 'post_type' => $this->post_type,
481
+ 'context' => 'side',
482
+ 'priority' => 'default',
483
+ ),
484
+ );
485
+
486
+ // Create filter so addons can modify the metaboxes
487
+ $meta_boxes = apply_filters( 'rtb_location_metaboxes', $meta_boxes );
488
+
489
+ // Create the metaboxes
490
+ foreach ( $meta_boxes as $meta_box ) {
491
+ add_meta_box(
492
+ $meta_box['id'],
493
+ $meta_box['title'],
494
+ $meta_box['callback'],
495
+ $meta_box['post_type'],
496
+ $meta_box['context'],
497
+ $meta_box['priority']
498
+ );
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Output a hidden nonce field to secure the saving of term meta
504
+ *
505
+ * @since 1.6
506
+ */
507
+ public function add_meta_nonce() {
508
+ global $post;
509
+ if ( $post->post_type == $this->post_type ) {
510
+ wp_nonce_field( 'rtb_location_meta', 'rtb_location_meta_nonce' );
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Print metabox on location post editing screen
516
+ *
517
+ * @since 1.6
518
+ */
519
+ public function print_location_metabox( $post ) {
520
+
521
+ global $rtb_controller;
522
+
523
+ $notification_email = '';
524
+ $reply_to_name = '';
525
+ $reply_to_address = '';
526
+ $term_id = get_post_meta( $post->ID, $this->location_taxonomy, true );
527
+ $admin_email_option = $rtb_controller->settings->get_setting( 'admin-email-option' );
528
+ if ( $term_id ) {
529
+ $reply_to_name = get_term_meta( $term_id, 'rtb_reply_to_name', true );
530
+ $reply_to_address = get_term_meta( $term_id, 'rtb_reply_to_address', true );
531
+ if ( $admin_email_option ) {
532
+ $notification_email = get_term_meta( $term_id, 'rtb_admin_email_address', true );
533
+ }
534
+ }
535
+
536
+ $append_booking_form = get_post_meta( $post->ID, 'rtb_append_booking_form', true );
537
+
538
+ ?>
539
+
540
+ <style type="text/css">.rtb-location-meta-input + .rtb-location-meta-input { margin-top: 2em; }</style>
541
+
542
+ <div class="rtb-location-meta-input rtb-location-meta-append-form">
543
+ <label>
544
+ <input type="checkbox" name="rtb_append_booking_form" value="1"<?php if ( $append_booking_form ) : ?> checked="checked"<?php endif; ?>>
545
+ <?php esc_html_e( "Automatically add the booking form to this page.", 'restaurant-reservations' ); ?>
546
+ </label>
547
+ </div>
548
+
549
+ <div class="rtb-location-meta-input rtb-location-meta-reply-to-name">
550
+ <label for="rtb_reply_to_name">
551
+ <?php esc_html_e( 'Reply-To Name', 'restaurant-reservations' ); ?>
552
+ </label>
553
+ <input type="text" name="rtb_reply_to_name" id="rtb_reply_to_name" value="<?php esc_attr_e( $reply_to_name ); ?>" placeholder="<?php esc_attr_e( $rtb_controller->settings->get_setting( 'reply-to-name' ) ); ?>">
554
+ <p class="description">
555
+ <?php esc_html_e( 'The name which should appear in the Reply-To field of a user notification email.', 'restaurant-reservations' ); ?>
556
+ </p>
557
+ </div>
558
+
559
+ <div class="rtb-location-meta-input rtb-location-meta-reply-to-address">
560
+ <label for="rtb_reply_to_address">
561
+ <?php esc_html_e( 'Reply-To Email Address', 'restaurant-reservations' ); ?>
562
+ </label>
563
+ <input type="text" name="rtb_reply_to_address" id="rtb_reply_to_address" value="<?php esc_attr_e( $reply_to_address ); ?>" placeholder="<?php esc_attr_e( $rtb_controller->settings->get_setting( 'reply-to-address' ) ); ?>">
564
+ <p class="description">
565
+ <?php esc_html_e( 'The email address which should appear in the Reply-To field of a user notification email.', 'restaurant-reservations' ); ?>
566
+ </p>
567
+ </div>
568
+
569
+ <?php if ( $admin_email_option ) : ?>
570
+ <div class="rtb-location-meta-input rtb-location-meta-admin-email">
571
+ <label for="rtb_admin_email_address">
572
+ <?php esc_html_e( 'Admin Notification Email Address', 'restaurant-reservations' ); ?>
573
+ </label>
574
+ <input type="text" name="rtb_admin_email_address" id="rtb_admin_email_address" value="<?php esc_attr_e( $notification_email ); ?>" placeholder="<?php esc_attr_e( $rtb_controller->settings->get_setting( 'admin-email-address' ) ); ?>">
575
+ <p class="description">
576
+ <?php esc_html_e( 'The email address where admin notifications for bookings at this location should be sent.', 'restaurant-reservations' ); ?>
577
+ </p>
578
+ </div>
579
+ <?php endif; ?>
580
+
581
+ <?php
582
+ }
583
+
584
+ /**
585
+ * Append booking form to a location's `post_content`
586
+ * @since 0.0.1
587
+ */
588
+ public function append_to_content( $content ) {
589
+
590
+ if ( !is_main_query() || !in_the_loop() || post_password_required() ) {
591
+ return $content;
592
+ }
593
+
594
+ global $post;
595
+
596
+ $append_booking_form = get_post_meta( $post->ID, 'rtb_append_booking_form', true );
597
+
598
+ if ( !$append_booking_form ) {
599
+ return $content;
600
+ }
601
+
602
+ $term_id = get_post_meta( $post->ID, $this->location_taxonomy, true );
603
+
604
+ if ( empty( $term_id ) ) {
605
+ return $content;
606
+ }
607
+
608
+ return $content . do_shortcode( '[booking-form location=' . absint( $term_id ) .']' );
609
+ }
610
+
611
+ /**
612
+ * Modify the notification email recipient for each location
613
+ *
614
+ * @since 1.6
615
+ */
616
+ public function notification_to_email( $email, $notification ) {
617
+
618
+ if ( $notification->target == 'user' || empty( $notification->booking->location ) ) {
619
+ return $email;
620
+ }
621
+
622
+ $val = get_term_meta( $notification->booking->location, 'rtb_admin_email_address', true );
623
+ $email = empty( $val ) ? $email : $val;
624
+
625
+ return $email;
626
+ }
627
+
628
+ /**
629
+ * Modify the notification email sender address for each location
630
+ *
631
+ * @since 1.6
632
+ */
633
+ public function notification_from_email( $email, $notification ) {
634
+
635
+ if ( $notification->target != 'user' || empty( $notification->booking->location ) ) {
636
+ return $email;
637
+ }
638
+
639
+ $val = get_term_meta( $notification->booking->location, 'rtb_reply_to_address', true );
640
+ $email = empty( $val ) ? $email : $val;
641
+
642
+ return $email;
643
+ }
644
+
645
+ /**
646
+ * Modify the notification email sender name for each location
647
+ *
648
+ * @since 1.6
649
+ */
650
+ public function notification_from_name( $name, $notification ) {
651
+
652
+ if ( $notification->target != 'user' || empty( $notification->booking->location ) ) {
653
+ return $name;
654
+ }
655
+
656
+ $val = get_term_meta( $notification->booking->location, 'rtb_reply_to_name', true );
657
+ $name = empty( $val ) ? $name : $val;
658
+
659
+ return $name;
660
+ }
661
+
662
+ /**
663
+ * Add a location template tag for notifications
664
+ *
665
+ * @since 1.6.1
666
+ */
667
+ public function notification_template_tags( $template_tags, $notification ) {
668
+
669
+ $term = empty( $notification->booking->location ) ? null : get_term( $notification->booking->location, $this->location_taxonomy );
670
+ $location_name = is_null( $term ) || is_wp_error( $term ) ? '' : $term->name;
671
+
672
+ $table_number = empty( $notification->booking->table ) ? '' : $notification->booking->table;
673
+ $table_number = is_array($table_number) ? implode(',', $table_number ) : $table_number;
674
+
675
+ return array_merge(
676
+ array(
677
+ '{location}' => $location_name,
678
+ '{table}' => $table_number,
679
+ ),
680
+ $template_tags
681
+ );
682
+ }
683
+
684
+ /**
685
+ * Add a description for the location template tag
686
+ *
687
+ * @since 1.6.1
688
+ */
689
+ public function notification_template_tag_descriptions( $descriptions ) {
690
+ return array_merge(
691
+ array( '{location}' => __( 'Location for which this booking was made.', 'restaurant-reservations' ) ),
692
+ $descriptions
693
+ );
694
+ }
695
+
696
+ /**
697
+ * Removes Auto-Draft locations that were added due to a bug in v1.7
698
+ *
699
+ * Version 1.7 introduced a bug which caused a location term to be
700
+ * created if the location Add New page was loaded. This term
701
+ * corresponded to an auto-draft post object and will be removed when
702
+ * that object is removed. This provides a one-time fix in v1.7.1
703
+ *
704
+ * @see https://github.com/NateWr/restaurant-reservations/issues/91
705
+ * @see https://developer.wordpress.org/reference/functions/wp_delete_auto_drafts/
706
+ * @since 1.7.1
707
+ */
708
+ public function fix_autodraft_term_error() {
709
+
710
+ if ( get_option( 'rtb_autodraft_terms_fixed', false ) ) {
711
+ return;
712
+ }
713
+
714
+ global $wpdb;
715
+
716
+ if ( !$wpdb ) {
717
+ return;
718
+ }
719
+
720
+ $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND post_type = '$this->post_type';" );
721
+ foreach ( (array) $old_posts as $delete ) {
722
+ // Force delete.
723
+ wp_delete_post( $delete, true );
724
+ }
725
+
726
+ // Set the `rtb_location_removed` term meta on any terms that are
727
+ // no longer attached to posts
728
+ global $wp_version;
729
+ if ( version_compare( $wp_version, '3.9', '>=' ) ) {
730
+ $live_terms = $wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key='$this->location_taxonomy';" );
731
+ $all_terms = get_terms( array(
732
+ 'taxonomy' => $this->location_taxonomy,
733
+ 'hide_empty' => false,
734
+ 'meta_query' => array(
735
+ array(
736
+ 'compare' => 'NOT EXISTS',
737
+ 'key' => 'rtb_location_removed',
738
+ )
739
+ )
740
+ ) );
741
+ if ( is_array( $all_terms ) ) {
742
+ foreach( $all_terms as $term ) {
743
+ if ( !in_array( $term->term_id, $live_terms ) ) {
744
+ $query = new rtbQuery( array( 'location' => $term->term_id ), 'delete-location-term-check' );
745
+ $query->prepare_args();
746
+ $query->get_bookings();
747
+
748
+ // Don't delete taxonomy terms if there are bookings assigned to
749
+ // this location, so the booking associations can remain as
750
+ // historical data.
751
+ if ( count( $query->bookings ) ) {
752
+ add_term_meta( $term->term_id, 'rtb_location_removed', true );
753
+ } else {
754
+ wp_delete_term( $term->term_id, $this->location_taxonomy );
755
+ }
756
+ }
757
+ }
758
+ }
759
+ }
760
+
761
+ update_option( 'rtb_autodraft_terms_fixed', true );
762
+ }
763
+
764
+ /**
765
+ * If multiple locations exist, adds a select box to the settings page which
766
+ * allows a user to select whether certain settings are global or location-specific.
767
+ * Also adds in location-specific settings for a number of different settings, and
768
+ * makes all settings conditional on the value of the select box.
769
+ *
770
+ * @since 2.3.6
771
+ */
772
+ public function maybe_add_location_settings( $sap ) {
773
+ global $rtb_controller;
774
+
775
+ $args = array(
776
+ 'taxonomy' => $this->location_taxonomy,
777
+ 'hide_empty' => false,
778
+ );
779
+
780
+ $terms = get_terms( $args );
781
+
782
+ if ( ! $this->do_locations_exist() ) { return $sap; }
783
+
784
+ foreach ( $sap->pages['rtb-settings']->sections as $key => $section ) {
785
+
786
+ foreach ( $section->settings as $setting_key => $setting ) {
787
+
788
+ $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->conditional_on = 'location-select';
789
+ $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->conditional_on_value = false;
790
+
791
+ $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->set_conditional_display();
792
+ }
793
+ }
794
+
795
+
796
+ $location_options = array(
797
+ '' => __( 'Global', 'restaurant-reservations' ),
798
+ );
799
+
800
+ foreach ( $terms as $term ) {
801
+
802
+ $location_options[ $term->slug ] = $term->term_id . ' - '.$term->name;
803
+ }
804
+
805
+ // Schedule location-specific options
806
+ $sap->add_section(
807
+ 'rtb-settings',
808
+ array(
809
+ 'id' => 'rtb-schedule-location-select',
810
+ 'title' => __( 'Select Schedule Location', 'restaurant-reservations' ),
811
+ 'tab' => 'rtb-schedule-tab',
812
+ 'rank' => 2,
813
+ )
814
+ );
815
+
816
+ $sap->add_setting(
817
+ 'rtb-settings',
818
+ 'rtb-schedule-location-select',
819
+ 'select',
820
+ array(
821
+ 'id' => 'location-select',
822
+ 'title' => __( 'Schedule Location', 'restaurant-reservations' ),
823
+ 'description' => __( 'Select which location the schedule will apply to. If a specific location doesn\'t have a schedule set, then it will fall back to the global schedule when booking.', 'restaurant-reservations' ),
824
+ 'blank_option' => false,
825
+ 'options' => $location_options,
826
+ )
827
+ );
828
+
829
+ // Translateable strings for scheduler components
830
+ $scheduler_strings = array(
831
+ 'add_rule' => __( 'Add new scheduling rule', 'restaurant-reservations' ),
832
+ 'weekly' => _x( 'Weekly', 'Format of a scheduling rule', 'restaurant-reservations' ),
833
+ 'monthly' => _x( 'Monthly', 'Format of a scheduling rule', 'restaurant-reservations' ),
834
+ 'date' => _x( 'Date', 'Format of a scheduling rule', 'restaurant-reservations' ),
835
+ 'weekdays' => _x( 'Days of the week', 'Label for selecting days of the week in a scheduling rule', 'restaurant-reservations' ),
836
+ 'month_weeks' => _x( 'Weeks of the month', 'Label for selecting weeks of the month in a scheduling rule', 'restaurant-reservations' ),
837
+ 'date_label' => _x( 'Date', 'Label to select a date for a scheduling rule', 'restaurant-reservations' ),
838
+ 'time_label' => _x( 'Time', 'Label to select a time slot for a scheduling rule', 'restaurant-reservations' ),
839
+ 'allday' => _x( 'All day', 'Label to set a scheduling rule to last all day', 'restaurant-reservations' ),
840
+ 'start' => _x( 'Start', 'Label for the starting time of a scheduling rule', 'restaurant-reservations' ),
841
+ 'end' => _x( 'End', 'Label for the ending time of a scheduling rule', 'restaurant-reservations' ),
842
+ 'set_time_prompt' => _x( 'All day long. Want to %sset a time slot%s?', 'Prompt displayed when a scheduling rule is set without any time restrictions', 'restaurant-reservations' ),
843
+ 'toggle' => _x( 'Open and close this rule', 'Toggle a scheduling rule open and closed', 'restaurant-reservations' ),
844
+ 'delete' => _x( 'Delete rule', 'Delete a scheduling rule', 'restaurant-reservations' ),
845
+ 'delete_schedule' => __( 'Delete scheduling rule', 'restaurant-reservations' ),
846
+ 'never' => _x( 'Never', 'Brief default description of a scheduling rule when no weekdays or weeks are included in the rule', 'restaurant-reservations' ),
847
+ 'weekly_always' => _x( 'Every day', 'Brief default description of a scheduling rule when all the weekdays/weeks are included in the rule', 'restaurant-reservations' ),
848
+ 'monthly_weekdays' => _x( '%s on the %s week of the month', 'Brief default description of a scheduling rule when some weekdays are included on only some weeks of the month. %s should be left alone and will be replaced by a comma-separated list of days and weeks in the following format: M, T, W on the first, second week of the month', 'restaurant-reservations' ),
849
+ 'monthly_weeks' => _x( '%s week of the month', 'Brief default description of a scheduling rule when some weeks of the month are included but all or no weekdays are selected. %s should be left alone and will be replaced by a comma-separated list of weeks in the following format: First, second week of the month', 'restaurant-reservations' ),
850
+ 'all_day' => _x( 'All day', 'Brief default description of a scheduling rule when no times are set', 'restaurant-reservations' ),
851
+ 'before' => _x( 'Ends at', 'Brief default description of a scheduling rule when an end time is set but no start time. If the end time is 6pm, it will read: Ends at 6pm', 'restaurant-reservations' ),
852
+ 'after' => _x( 'Starts at', 'Brief default description of a scheduling rule when a start time is set but no end time. If the start time is 6pm, it will read: Starts at 6pm', 'restaurant-reservations' ),
853
+ 'separator' => _x( '&mdash;', 'Separator between times of a scheduling rule', 'restaurant-reservations' ),
854
+ );
855
+
856
+ foreach ( $terms as $term ) {
857
+
858
+ $sap->add_setting(
859
+ 'rtb-settings',
860
+ 'rtb-schedule',
861
+ 'scheduler',
862
+ array(
863
+ 'id' => $term->slug . '-schedule-open',
864
+ 'title' => __( 'Schedule', 'restaurant-reservations' ),
865
+ 'description' => __( 'Define the weekly schedule during which you accept bookings.', 'restaurant-reservations' ),
866
+ 'weekdays' => array(
867
+ 'monday' => _x( 'Mo', 'Monday abbreviation', 'restaurant-reservations' ),
868
+ 'tuesday' => _x( 'Tu', 'Tuesday abbreviation', 'restaurant-reservations' ),
869
+ 'wednesday' => _x( 'We', 'Wednesday abbreviation', 'restaurant-reservations' ),
870
+ 'thursday' => _x( 'Th', 'Thursday abbreviation', 'restaurant-reservations' ),
871
+ 'friday' => _x( 'Fr', 'Friday abbreviation', 'restaurant-reservations' ),
872
+ 'saturday' => _x( 'Sa', 'Saturday abbreviation', 'restaurant-reservations' ),
873
+ 'sunday' => _x( 'Su', 'Sunday abbreviation', 'restaurant-reservations' )
874
+ ),
875
+ 'time_format' => $rtb_controller->settings->get_setting( 'time-format' ),
876
+ 'date_format' => $rtb_controller->settings->get_setting( 'date-format' ),
877
+ 'disable_weeks' => true,
878
+ 'disable_date' => true,
879
+ 'strings' => $scheduler_strings,
880
+ 'conditional_on' => 'location-select',
881
+ 'conditional_on_value' => $term->slug,
882
+ )
883
+ );
884
+
885
+ $sap->add_setting(
886
+ 'rtb-settings',
887
+ 'rtb-schedule',
888
+ 'scheduler',
889
+ array(
890
+ 'id' => $term->slug . '-schedule-closed',
891
+ 'title' => __( 'Exceptions', 'restaurant-reservations' ),
892
+ 'description' => __( "Define special opening hours for holidays, events or other needs. Leave the time empty if you're closed all day.", 'restaurant-reservations' ),
893
+ 'time_format' => esc_attr( $rtb_controller->settings->get_setting( 'time-format' ) ),
894
+ 'date_format' => esc_attr( $rtb_controller->settings->get_setting( 'date-format' ) ),
895
+ 'disable_weekdays' => true,
896
+ 'disable_weeks' => true,
897
+ 'strings' => $scheduler_strings,
898
+ 'conditional_on' => 'location-select',
899
+ 'conditional_on_value' => $term->slug,
900
+ )
901
+ );
902
+ }
903
+
904
+ // Restriction location-specific options
905
+ $sap->add_section(
906
+ 'rtb-settings',
907
+ array(
908
+ 'id' => 'rtb-restrictions-location-select',
909
+ 'title' => __( 'Select Seat Restrictions Location', 'restaurant-reservations' ),
910
+ 'tab' => 'rtb-premium',
911
+ 'rank' => 11,
912
+ )
913
+ );
914
+
915
+ $sap->add_setting(
916
+ 'rtb-settings',
917
+ 'rtb-restrictions-location-select',
918
+ 'select',
919
+ array(
920
+ 'id' => 'location-select',
921
+ 'title' => __( 'Seat Restrictions Location', 'restaurant-reservations' ),
922
+ 'description' => __( 'Select which location the restrictions will apply to. If a specific location doesn\'t have restrictions set, then the global total number will be used as a fall-back.', 'restaurant-reservations' ),
923
+ 'blank_option' => false,
924
+ 'options' => $location_options,
925
+ )
926
+ );
927
+
928
+ $max_reservation_options = array();
929
+ $max_reservations_upper_limit = apply_filters( 'rtb-max-reservations-upper-limit', 100 );
930
+
931
+ for ( $i = 1; $i <= $max_reservations_upper_limit; $i++ ) {
932
+
933
+ $max_reservation_options[$i] = $i;
934
+ }
935
+
936
+ $max_people_options = array();
937
+ $max_people_upper_limit = apply_filters( 'rtb-max-people-upper-limit', 400 );
938
+
939
+ for ( $i = 1; $i <= $max_people_upper_limit; $i++ ) {
940
+
941
+ $max_people_options[$i] = $i;
942
+ }
943
+
944
+ foreach ( $terms as $term ) {
945
+
946
+ $sap->add_setting(
947
+ 'rtb-settings',
948
+ 'rtb-seat-assignments',
949
+ 'select',
950
+ array(
951
+ 'id' => $term->slug . '-rtb-max-tables-count',
952
+ 'title' => __( 'Max Reservations', 'restaurant-reservations' ),
953
+ 'description' => __( 'How many reservations, if enabled, should be allowed at the same time at this location? Set dining block length to change how long a meal typically lasts.', 'restaurant-reservations' ),
954
+ 'options' => $max_reservation_options,
955
+ 'conditional_on' => 'location-select',
956
+ 'conditional_on_value' => $term->slug,
957
+ )
958
+ );
959
+
960
+ $sap->add_setting(
961
+ 'rtb-settings',
962
+ 'rtb-seat-assignments',
963
+ 'select',
964
+ array(
965
+ 'id' => $term->slug . '-rtb-max-people-count',
966
+ 'title' => __( 'Max People', 'restaurant-reservations' ),
967
+ 'description' => __( 'How many people, if enabled, should be allowed to be present at this restaurant location at the same time? Set dining block length to change how long a meal typically lasts. May not work correctly if max reservations is set.', 'restaurant-reservations' ),
968
+ 'options' => $max_people_options,
969
+ 'conditional_on' => 'location-select',
970
+ 'conditional_on_value' => $term->slug,
971
+ )
972
+ );
973
+ }
974
+
975
+ return $sap;
976
+ }
977
+
978
+ /**
979
+ * Blank out the location setting, so that it's always set to 'Global'
980
+ * on page load, except for immediately after saving a setting.
981
+ *
982
+ * @since 2.3.6
983
+ */
984
+ public function remove_location_select_setting() {
985
+ global $rtb_controller;
986
+
987
+ $rtb_controller->settings->set_setting( 'location-select', null );
988
+
989
+ $rtb_controller->settings->save_settings();
990
+ }
991
+
992
+ /**
993
+ * Returns true if locations have been created, false otherwise
994
+ *
995
+ * @since 2.3.6
996
+ */
997
+ public function do_locations_exist() {
998
+
999
+ $args = array(
1000
+ 'taxonomy' => $this->location_taxonomy,
1001
+ 'hide_empty' => false,
1002
+ );
1003
+
1004
+ $terms = get_terms( $args );
1005
+
1006
+ return ( ! empty( $terms ) and ! is_wp_error( $terms ) );
1007
+ }
1008
+ }
1009
+ }
includes/Notification.Email.class.php CHANGED
@@ -1,304 +1,304 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbNotificationEmail' ) ) {
5
- /**
6
- * Class to handle an email notification for Restaurant Reservations
7
- *
8
- * This class extends rtbNotification and must implement the following methods:
9
- * prepare_notification() - set up and validate data
10
- * send_notification()
11
- *
12
- * @since 0.0.1
13
- */
14
- class rtbNotificationEmail extends rtbNotification {
15
-
16
- /**
17
- * Recipient email
18
- * @since 0.0.1
19
- */
20
- public $to_email;
21
-
22
- /**
23
- * From email
24
- * @since 0.0.1
25
- */
26
- public $from_email;
27
-
28
- /**
29
- * From name
30
- * @since 0.0.1
31
- */
32
- public $from_name;
33
-
34
- /**
35
- * Email subject
36
- * @since 0.0.1
37
- */
38
- public $subject;
39
-
40
- /**
41
- * Email message body
42
- * @since 0.0.1
43
- */
44
- public $message;
45
-
46
- /**
47
- * Email headers
48
- * @since 0.0.1
49
- */
50
- public $headers;
51
-
52
- /**
53
- * Inidividual booking related to this notification, if applicable
54
- * @since 0.0.1
55
- */
56
- public $booking;
57
-
58
- /**
59
- * Prepare and validate notification data
60
- *
61
- * @return boolean if the data is valid and ready for transport
62
- * @since 0.0.1
63
- */
64
- public function prepare_notification() {
65
-
66
- $this->set_to_email();
67
- $this->set_from_email();
68
- $this->set_subject();
69
- $this->set_headers();
70
- $this->set_message();
71
-
72
- // Return false if we're missing any of the required information
73
- if ( empty( $this->to_email) ||
74
- empty( $this->from_email) ||
75
- empty( $this->from_name) ||
76
- empty( $this->subject) ||
77
- empty( $this->headers) ||
78
- empty( $this->message) ) {
79
- return false;
80
- }
81
-
82
- return true;
83
- }
84
-
85
- /**
86
- * Set to email
87
- * @since 0.0.1
88
- */
89
- public function set_to_email() {
90
- global $rtb_controller;
91
-
92
- if ( $this->target == 'user' ) {
93
-
94
- $to_email = empty( $this->booking->email ) ? null : $this->booking->email;
95
- }
96
- else {
97
-
98
- $to_email = $rtb_controller->settings->get_setting( 'admin-email-address' );
99
- }
100
-
101
- $this->to_email = apply_filters( 'rtb_notification_email_to_email', $to_email, $this );
102
-
103
- }
104
-
105
- /**
106
- * Set from email
107
- * @since 0.0.1
108
- */
109
- public function set_from_email() {
110
- global $rtb_controller;
111
-
112
- if ( ! empty( $this->from_email ) and ! empty( $this->from_name ) ) {
113
-
114
- $from_email = $this->from_email;
115
- $from_name = $this->from_name;
116
- }
117
- elseif ( $this->target == 'user' ) {
118
-
119
- $from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
120
- $from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
121
- }
122
- else {
123
-
124
- $from_email = $this->booking->email;
125
- $from_name = $this->booking->name;
126
- }
127
-
128
- $this->from_email = apply_filters( 'rtb_notification_email_from_email', $from_email, $this );
129
- $this->from_name = apply_filters( 'rtb_notification_email_from_name', $from_name, $this );
130
-
131
- }
132
-
133
- /**
134
- * Set email subject
135
- * @since 0.0.1
136
- */
137
- public function set_subject() {
138
-
139
- global $rtb_controller;
140
-
141
- if( $this->event == 'new_submission' ) {
142
- if ( $this->target == 'admin' ) {
143
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-admin' );
144
- } elseif ( $this->target == 'user' ) {
145
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-user' );
146
- }
147
-
148
- } elseif ( $this->event == 'rtb_confirmed_booking' ) {
149
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-confirmed-admin' );
150
-
151
- }elseif ( $this->event == 'pending_to_confirmed' ) {
152
- $subject = $rtb_controller->settings->get_setting( 'subject-confirmed-user' );
153
-
154
- } elseif ( $this->event == 'pending_to_closed' ) {
155
- $subject = $rtb_controller->settings->get_setting( 'subject-rejected-user' );
156
-
157
- } elseif ( $this->event == 'booking_cancelled' ) {
158
- if ( $this->target == 'admin' ) {
159
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-cancelled-admin' );
160
- } elseif ( $this->target == 'user' ) {
161
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-cancelled-user' );
162
- }
163
-
164
- } elseif ( $this->event == 'late_user' ) {
165
- $subject = $rtb_controller->settings->get_setting( 'subject-late-user' );
166
-
167
- } elseif ( $this->event == 'reminder' ) {
168
- $subject = $rtb_controller->settings->get_setting( 'subject-reminder-user' );
169
-
170
- // Use a subject that's been appended manually if available
171
- } else {
172
- $subject = empty( $this->subject ) ? '' : $this->subject;
173
- }
174
-
175
- $this->subject = $this->process_subject_template( apply_filters( 'rtb_notification_email_subject', $subject, $this ) );
176
-
177
- }
178
-
179
- /**
180
- * Set email headers
181
- * @since 0.0.1
182
- */
183
- public function set_headers( $headers = null ) {
184
-
185
- global $rtb_controller;
186
-
187
- $from_email = apply_filters( 'rtb_notification_email_header_from_email', $rtb_controller->settings->get_setting( 'from-email-address' ) );
188
-
189
- $headers = "From: =?UTF-8?Q?" .
190
- quoted_printable_encode(
191
- html_entity_decode(
192
- $rtb_controller->settings->get_setting( 'reply-to-name' ),
193
- ENT_QUOTES,
194
- 'UTF-8'
195
- )
196
- ) .
197
- "?= <" . $from_email . ">\r\n";
198
-
199
- $headers .= "Reply-To: =?UTF-8?Q?" .
200
- quoted_printable_encode(
201
- html_entity_decode(
202
- $this->from_name,
203
- ENT_QUOTES,
204
- 'UTF-8'
205
- )
206
- ) .
207
- "?= <" . $this->from_email . ">\r\n";
208
-
209
- $headers .= "Content-Type: text/html; charset=utf-8\r\n";
210
-
211
- $this->headers = apply_filters( 'rtb_notification_email_headers', $headers, $this );
212
-
213
- }
214
-
215
- /**
216
- * Set email message body
217
- * @since 0.0.1
218
- */
219
- public function set_message() {
220
-
221
- if ( $this->event == 'new_submission' ) {
222
- if ( $this->target == 'user' ) {
223
- $template = $this->get_template( 'template-booking-user' );
224
- } elseif ( $this->target == 'admin' ) {
225
- $template = $this->get_template( 'template-booking-admin' );
226
- }
227
-
228
- } elseif ( $this->event == 'rtb_confirmed_booking' ) {
229
- if ( $this->target == 'admin' ) {
230
- $template = $this->get_template( 'template-booking-confirmed-admin' );
231
- }
232
-
233
- } elseif ( $this->event == 'pending_to_confirmed' ) {
234
- if ( $this->target == 'user' ) {
235
- $template = $this->get_template( 'template-confirmed-user' );
236
- }
237
-
238
- } elseif ( $this->event == 'pending_to_closed' ) {
239
- if ( $this->target == 'user' ) {
240
- $template = $this->get_template( 'template-rejected-user' );
241
- }
242
-
243
- } elseif ( $this->event == 'booking_cancelled' ) {
244
- if ( $this->target == 'user' ) {
245
- $template = $this->get_template( 'template-booking-cancelled-user' );
246
- } elseif ( $this->target == 'admin' ) {
247
- $template = $this->get_template( 'template-booking-cancelled-admin' );
248
- }
249
-
250
- } elseif ( $this->event == 'late_user' ) {
251
- if ( $this->target == 'user' ) {
252
- $template = $this->get_template( 'template-late-user' );
253
- }
254
-
255
- } elseif ( $this->event == 'reminder' ) {
256
- if ( $this->target == 'user' ) {
257
- $template = $this->get_template( 'template-reminder-user' );
258
- }
259
-
260
- // Use a message that's been appended manually if available
261
- } else {
262
- $template = empty( $this->message ) ? '' : $this->message;
263
- }
264
-
265
- $template = apply_filters( 'rtb_notification_email_template', $template, $this );
266
-
267
- if ( ! empty( $this->manual_message ) ) {
268
- $this->message = $this->manual_message;
269
- }
270
- elseif ( empty( $template ) ) {
271
- $this->message = '';
272
- } else {
273
- $this->message = wpautop( $this->process_template( $template ) );
274
- }
275
-
276
- }
277
-
278
- /**
279
- * Process template tags for email subjects
280
- * @since 0.0.1
281
- */
282
- public function process_subject_template( $subject ) {
283
-
284
- $template_tags = array(
285
- '{user_name}' => ! empty( $this->booking->name ) ? $this->booking->name : '',
286
- '{party}' => ! empty( $this->booking->party ) ? $this->booking->party : '',
287
- '{date}' => ! empty( $this->booking->date ) ? $this->booking->format_date( $this->booking->date ) : '',
288
- );
289
-
290
- $template_tags = apply_filters( 'rtb_notification_email_subject_template_tags', $template_tags, $this );
291
-
292
- return str_replace( array_keys( $template_tags ), array_values( $template_tags ), $subject );
293
-
294
- }
295
-
296
- /**
297
- * Send notification
298
- * @since 0.0.1
299
- */
300
- public function send_notification() {
301
- return wp_mail( $this->to_email, $this->subject, $this->message, $this->headers, apply_filters( 'rtb_notification_email_attachments', array(), $this ) );
302
- }
303
- }
304
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbNotificationEmail' ) ) {
5
+ /**
6
+ * Class to handle an email notification for Restaurant Reservations
7
+ *
8
+ * This class extends rtbNotification and must implement the following methods:
9
+ * prepare_notification() - set up and validate data
10
+ * send_notification()
11
+ *
12
+ * @since 0.0.1
13
+ */
14
+ class rtbNotificationEmail extends rtbNotification {
15
+
16
+ /**
17
+ * Recipient email
18
+ * @since 0.0.1
19
+ */
20
+ public $to_email;
21
+
22
+ /**
23
+ * From email
24
+ * @since 0.0.1
25
+ */
26
+ public $from_email;
27
+
28
+ /**
29
+ * From name
30
+ * @since 0.0.1
31
+ */
32
+ public $from_name;
33
+
34
+ /**
35
+ * Email subject
36
+ * @since 0.0.1
37
+ */
38
+ public $subject;
39
+
40
+ /**
41
+ * Email message body
42
+ * @since 0.0.1
43
+ */
44
+ public $message;
45
+
46
+ /**
47
+ * Email headers
48
+ * @since 0.0.1
49
+ */
50
+ public $headers;
51
+
52
+ /**
53
+ * Inidividual booking related to this notification, if applicable
54
+ * @since 0.0.1
55
+ */
56
+ public $booking;
57
+
58
+ /**
59
+ * Prepare and validate notification data
60
+ *
61
+ * @return boolean if the data is valid and ready for transport
62
+ * @since 0.0.1
63
+ */
64
+ public function prepare_notification() {
65
+
66
+ $this->set_to_email();
67
+ $this->set_from_email();
68
+ $this->set_subject();
69
+ $this->set_headers();
70
+ $this->set_message();
71
+
72
+ // Return false if we're missing any of the required information
73
+ if ( empty( $this->to_email) ||
74
+ empty( $this->from_email) ||
75
+ empty( $this->from_name) ||
76
+ empty( $this->subject) ||
77
+ empty( $this->headers) ||
78
+ empty( $this->message) ) {
79
+ return false;
80
+ }
81
+
82
+ return true;
83
+ }
84
+
85
+ /**
86
+ * Set to email
87
+ * @since 0.0.1
88
+ */
89
+ public function set_to_email() {
90
+ global $rtb_controller;
91
+
92
+ if ( $this->target == 'user' ) {
93
+
94
+ $to_email = empty( $this->booking->email ) ? null : $this->booking->email;
95
+ }
96
+ else {
97
+
98
+ $to_email = $rtb_controller->settings->get_setting( 'admin-email-address' );
99
+ }
100
+
101
+ $this->to_email = apply_filters( 'rtb_notification_email_to_email', $to_email, $this );
102
+
103
+ }
104
+
105
+ /**
106
+ * Set from email
107
+ * @since 0.0.1
108
+ */
109
+ public function set_from_email() {
110
+ global $rtb_controller;
111
+
112
+ if ( ! empty( $this->from_email ) and ! empty( $this->from_name ) ) {
113
+
114
+ $from_email = $this->from_email;
115
+ $from_name = $this->from_name;
116
+ }
117
+ elseif ( $this->target == 'user' ) {
118
+
119
+ $from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
120
+ $from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
121
+ }
122
+ else {
123
+
124
+ $from_email = $this->booking->email;
125
+ $from_name = $this->booking->name;
126
+ }
127
+
128
+ $this->from_email = apply_filters( 'rtb_notification_email_from_email', $from_email, $this );
129
+ $this->from_name = apply_filters( 'rtb_notification_email_from_name', $from_name, $this );
130
+
131
+ }
132
+
133
+ /**
134
+ * Set email subject
135
+ * @since 0.0.1
136
+ */
137
+ public function set_subject() {
138
+
139
+ global $rtb_controller;
140
+
141
+ if( $this->event == 'new_submission' ) {
142
+ if ( $this->target == 'admin' ) {
143
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-admin' );
144
+ } elseif ( $this->target == 'user' ) {
145
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-user' );
146
+ }
147
+
148
+ } elseif ( $this->event == 'rtb_confirmed_booking' ) {
149
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-confirmed-admin' );
150
+
151
+ }elseif ( $this->event == 'pending_to_confirmed' ) {
152
+ $subject = $rtb_controller->settings->get_setting( 'subject-confirmed-user' );
153
+
154
+ } elseif ( $this->event == 'pending_to_closed' ) {
155
+ $subject = $rtb_controller->settings->get_setting( 'subject-rejected-user' );
156
+
157
+ } elseif ( $this->event == 'booking_cancelled' ) {
158
+ if ( $this->target == 'admin' ) {
159
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-cancelled-admin' );
160
+ } elseif ( $this->target == 'user' ) {
161
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-cancelled-user' );
162
+ }
163
+
164
+ } elseif ( $this->event == 'late_user' ) {
165
+ $subject = $rtb_controller->settings->get_setting( 'subject-late-user' );
166
+
167
+ } elseif ( $this->event == 'reminder' ) {
168
+ $subject = $rtb_controller->settings->get_setting( 'subject-reminder-user' );
169
+
170
+ // Use a subject that's been appended manually if available
171
+ } else {
172
+ $subject = empty( $this->subject ) ? '' : $this->subject;
173
+ }
174
+
175
+ $this->subject = $this->process_subject_template( apply_filters( 'rtb_notification_email_subject', $subject, $this ) );
176
+
177
+ }
178
+
179
+ /**
180
+ * Set email headers
181
+ * @since 0.0.1
182
+ */
183
+ public function set_headers( $headers = null ) {
184
+
185
+ global $rtb_controller;
186
+
187
+ $from_email = apply_filters( 'rtb_notification_email_header_from_email', $rtb_controller->settings->get_setting( 'from-email-address' ) );
188
+
189
+ $headers = "From: =?UTF-8?Q?" .
190
+ quoted_printable_encode(
191
+ html_entity_decode(
192
+ $rtb_controller->settings->get_setting( 'reply-to-name' ),
193
+ ENT_QUOTES,
194
+ 'UTF-8'
195
+ )
196
+ ) .
197
+ "?= <" . $from_email . ">\r\n";
198
+
199
+ $headers .= "Reply-To: =?UTF-8?Q?" .
200
+ quoted_printable_encode(
201
+ html_entity_decode(
202
+ $this->from_name,
203
+ ENT_QUOTES,
204
+ 'UTF-8'
205
+ )
206
+ ) .
207
+ "?= <" . $this->from_email . ">\r\n";
208
+
209
+ $headers .= "Content-Type: text/html; charset=utf-8\r\n";
210
+
211
+ $this->headers = apply_filters( 'rtb_notification_email_headers', $headers, $this );
212
+
213
+ }
214
+
215
+ /**
216
+ * Set email message body
217
+ * @since 0.0.1
218
+ */
219
+ public function set_message() {
220
+
221
+ if ( $this->event == 'new_submission' ) {
222
+ if ( $this->target == 'user' ) {
223
+ $template = $this->get_template( 'template-booking-user' );
224
+ } elseif ( $this->target == 'admin' ) {
225
+ $template = $this->get_template( 'template-booking-admin' );
226
+ }
227
+
228
+ } elseif ( $this->event == 'rtb_confirmed_booking' ) {
229
+ if ( $this->target == 'admin' ) {
230
+ $template = $this->get_template( 'template-booking-confirmed-admin' );
231
+ }
232
+
233
+ } elseif ( $this->event == 'pending_to_confirmed' ) {
234
+ if ( $this->target == 'user' ) {
235
+ $template = $this->get_template( 'template-confirmed-user' );
236
+ }
237
+
238
+ } elseif ( $this->event == 'pending_to_closed' ) {
239
+ if ( $this->target == 'user' ) {
240
+ $template = $this->get_template( 'template-rejected-user' );
241
+ }
242
+
243
+ } elseif ( $this->event == 'booking_cancelled' ) {
244
+ if ( $this->target == 'user' ) {
245
+ $template = $this->get_template( 'template-booking-cancelled-user' );
246
+ } elseif ( $this->target == 'admin' ) {
247
+ $template = $this->get_template( 'template-booking-cancelled-admin' );
248
+ }
249
+
250
+ } elseif ( $this->event == 'late_user' ) {
251
+ if ( $this->target == 'user' ) {
252
+ $template = $this->get_template( 'template-late-user' );
253
+ }
254
+
255
+ } elseif ( $this->event == 'reminder' ) {
256
+ if ( $this->target == 'user' ) {
257
+ $template = $this->get_template( 'template-reminder-user' );
258
+ }
259
+
260
+ // Use a message that's been appended manually if available
261
+ } else {
262
+ $template = empty( $this->message ) ? '' : $this->message;
263
+ }
264
+
265
+ $template = apply_filters( 'rtb_notification_email_template', $template, $this );
266
+
267
+ if ( ! empty( $this->manual_message ) ) {
268
+ $this->message = $this->manual_message;
269
+ }
270
+ elseif ( empty( $template ) ) {
271
+ $this->message = '';
272
+ } else {
273
+ $this->message = wpautop( $this->process_template( $template ) );
274
+ }
275
+
276
+ }
277
+
278
+ /**
279
+ * Process template tags for email subjects
280
+ * @since 0.0.1
281
+ */
282
+ public function process_subject_template( $subject ) {
283
+
284
+ $template_tags = array(
285
+ '{user_name}' => ! empty( $this->booking->name ) ? $this->booking->name : '',
286
+ '{party}' => ! empty( $this->booking->party ) ? $this->booking->party : '',
287
+ '{date}' => ! empty( $this->booking->date ) ? $this->booking->format_date( $this->booking->date ) : '',
288
+ );
289
+
290
+ $template_tags = apply_filters( 'rtb_notification_email_subject_template_tags', $template_tags, $this );
291
+
292
+ return str_replace( array_keys( $template_tags ), array_values( $template_tags ), $subject );
293
+
294
+ }
295
+
296
+ /**
297
+ * Send notification
298
+ * @since 0.0.1
299
+ */
300
+ public function send_notification() {
301
+ return wp_mail( $this->to_email, $this->subject, $this->message, $this->headers, apply_filters( 'rtb_notification_email_attachments', array(), $this ) );
302
+ }
303
+ }
304
+ } // endif;
includes/Notification.SMS.class.php CHANGED
@@ -1,151 +1,151 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbNotificationSMS' ) ) {
5
- /**
6
- * Class to handle an SMS notification for Restaurant Reservations
7
- *
8
- * This class extends rtbNotification and must implement the following methods:
9
- * prepare_notification() - set up and validate data
10
- * send_notification()
11
- *
12
- * @since 2.1.0
13
- */
14
- class rtbNotificationSMS extends rtbNotification {
15
-
16
- /**
17
- * Recipient phone number
18
- * @since 2.1.0
19
- */
20
- public $phone_number;
21
-
22
- /**
23
- * Text message body
24
- * @since 2.1.0
25
- */
26
- public $message;
27
-
28
- /**
29
- * The license key received for RTB Ultimate
30
- * @since 2.1.0
31
- */
32
- public $license_key;
33
-
34
- /**
35
- * Email used for purchase, to validate message sending
36
- * @since 2.1.0
37
- */
38
- public $purchase_email;
39
-
40
-
41
- /**
42
- * Prepare and validate notification data
43
- *
44
- * @return boolean if the data is valid and ready for transport
45
- * @since 2.1.0
46
- */
47
- public function prepare_notification() {
48
-
49
- $this->set_phone_number();
50
- $this->set_message();
51
- $this->set_license_key();
52
- $this->set_purchase_email();
53
-
54
- // Return false if we're missing any of the required information
55
- if ( empty( $this->phone_number) ||
56
- empty( $this->message) ||
57
- empty( $this->license_key) ||
58
- empty( $this->purchase_email) ) {
59
- return false;
60
- }
61
-
62
- return true;
63
- }
64
-
65
- /**
66
- * Set phone number
67
- * @since 2.1.0
68
- */
69
- public function set_phone_number() {
70
-
71
- $phone_number = $this->booking->phone;
72
-
73
- $this->phone_number = apply_filters( 'rtb_notification_sms_phone_number', $phone_number, $this );
74
-
75
- }
76
-
77
- /**
78
- * Set text message body
79
- * @since 2.1.0
80
- */
81
- public function set_message() {
82
-
83
- if ( $this->event == 'late_user' ) {
84
- if ( $this->target == 'user' ) {
85
- $template = $this->get_template( 'template-late-user' );
86
- }
87
-
88
- } elseif ( $this->event == 'reminder' ) {
89
- if ( $this->target == 'user' ) {
90
- $template = $this->get_template( 'template-reminder-user' );
91
- }
92
-
93
- // Use a message that's been appended manually if available
94
- } else {
95
- $template = empty( $this->message ) ? '' : $this->message;
96
- }
97
-
98
- $this->message = apply_filters( 'rtb_notification_sms_template', $this->process_template( $template ), $this );
99
-
100
- }
101
-
102
- /**
103
- * Set license key
104
- * @since 2.1.0
105
- */
106
- public function set_license_key() {
107
-
108
- if ( ! get_option( 'rtb-ultimate-license-key' ) ) { add_option( 'rtb-ultimate-license-key', 'no_license_key_entered' ); }
109
-
110
- $this->license_key = get_option( 'rtb-ultimate-license-key' );
111
-
112
- }
113
-
114
- /**
115
- * Set purchase email
116
- * @since 2.1.0
117
- */
118
- public function set_purchase_email() {
119
-
120
- global $rtb_controller;
121
-
122
- $this->purchase_email = $rtb_controller->settings->get_setting( 'ultimate-purchase-email' );
123
-
124
- }
125
-
126
- /**
127
- * Send notification
128
- * @since 2.1.0
129
- */
130
- public function send_notification() {
131
- global $rtb_controller;
132
-
133
- $url = add_query_arg(
134
- array(
135
- 'license_key' => urlencode( $this->license_key ),
136
- 'admin_email' => urlencode( $this->purchase_email ),
137
- 'phone_number' => urlencode( $this->phone_number ),
138
- 'message' => urlencode( $this->message ),
139
- 'country_code' => urlencode( $rtb_controller->settings->get_setting( 'rtb-country-code' ) )
140
- ),
141
- 'http://www.fivestarplugins.com/sms-handling/sms-client.php'
142
- );
143
-
144
- $opts = array('http'=>array('method'=>"GET"));
145
- $context = stream_context_create($opts);
146
- $return = json_decode( file_get_contents( $url, false, $context ) );
147
-
148
- return isset($return->success) ? $return->success : false;
149
- }
150
- }
151
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbNotificationSMS' ) ) {
5
+ /**
6
+ * Class to handle an SMS notification for Restaurant Reservations
7
+ *
8
+ * This class extends rtbNotification and must implement the following methods:
9
+ * prepare_notification() - set up and validate data
10
+ * send_notification()
11
+ *
12
+ * @since 2.1.0
13
+ */
14
+ class rtbNotificationSMS extends rtbNotification {
15
+
16
+ /**
17
+ * Recipient phone number
18
+ * @since 2.1.0
19
+ */
20
+ public $phone_number;
21
+
22
+ /**
23
+ * Text message body
24
+ * @since 2.1.0
25
+ */
26
+ public $message;
27
+
28
+ /**
29
+ * The license key received for RTB Ultimate
30
+ * @since 2.1.0
31
+ */
32
+ public $license_key;
33
+
34
+ /**
35
+ * Email used for purchase, to validate message sending
36
+ * @since 2.1.0
37
+ */
38
+ public $purchase_email;
39
+
40
+
41
+ /**
42
+ * Prepare and validate notification data
43
+ *
44
+ * @return boolean if the data is valid and ready for transport
45
+ * @since 2.1.0
46
+ */
47
+ public function prepare_notification() {
48
+
49
+ $this->set_phone_number();
50
+ $this->set_message();
51
+ $this->set_license_key();
52
+ $this->set_purchase_email();
53
+
54
+ // Return false if we're missing any of the required information
55
+ if ( empty( $this->phone_number) ||
56
+ empty( $this->message) ||
57
+ empty( $this->license_key) ||
58
+ empty( $this->purchase_email) ) {
59
+ return false;
60
+ }
61
+
62
+ return true;
63
+ }
64
+
65
+ /**
66
+ * Set phone number
67
+ * @since 2.1.0
68
+ */
69
+ public function set_phone_number() {
70
+
71
+ $phone_number = $this->booking->phone;
72
+
73
+ $this->phone_number = apply_filters( 'rtb_notification_sms_phone_number', $phone_number, $this );
74
+
75
+ }
76
+
77
+ /**
78
+ * Set text message body
79
+ * @since 2.1.0
80
+ */
81
+ public function set_message() {
82
+
83
+ if ( $this->event == 'late_user' ) {
84
+ if ( $this->target == 'user' ) {
85
+ $template = $this->get_template( 'template-late-user' );
86
+ }
87
+
88
+ } elseif ( $this->event == 'reminder' ) {
89
+ if ( $this->target == 'user' ) {
90
+ $template = $this->get_template( 'template-reminder-user' );
91
+ }
92
+
93
+ // Use a message that's been appended manually if available
94
+ } else {
95
+ $template = empty( $this->message ) ? '' : $this->message;
96
+ }
97
+
98
+ $this->message = apply_filters( 'rtb_notification_sms_template', $this->process_template( $template ), $this );
99
+
100
+ }
101
+
102
+ /**
103
+ * Set license key
104
+ * @since 2.1.0
105
+ */
106
+ public function set_license_key() {
107
+
108
+ if ( ! get_option( 'rtb-ultimate-license-key' ) ) { add_option( 'rtb-ultimate-license-key', 'no_license_key_entered' ); }
109
+
110
+ $this->license_key = get_option( 'rtb-ultimate-license-key' );
111
+
112
+ }
113
+
114
+ /**
115
+ * Set purchase email
116
+ * @since 2.1.0
117
+ */
118
+ public function set_purchase_email() {
119
+
120
+ global $rtb_controller;
121
+
122
+ $this->purchase_email = $rtb_controller->settings->get_setting( 'ultimate-purchase-email' );
123
+
124
+ }
125
+
126
+ /**
127
+ * Send notification
128
+ * @since 2.1.0
129
+ */
130
+ public function send_notification() {
131
+ global $rtb_controller;
132
+
133
+ $url = add_query_arg(
134
+ array(
135
+ 'license_key' => urlencode( $this->license_key ),
136
+ 'admin_email' => urlencode( $this->purchase_email ),
137
+ 'phone_number' => urlencode( $this->phone_number ),
138
+ 'message' => urlencode( $this->message ),
139
+ 'country_code' => urlencode( $rtb_controller->settings->get_setting( 'rtb-country-code' ) )
140
+ ),
141
+ 'http://www.fivestarplugins.com/sms-handling/sms-client.php'
142
+ );
143
+
144
+ $opts = array('http'=>array('method'=>"GET"));
145
+ $context = stream_context_create($opts);
146
+ $return = json_decode( file_get_contents( $url, false, $context ) );
147
+
148
+ return isset($return->success) ? $return->success : false;
149
+ }
150
+ }
151
+ } // endif;
includes/Notification.class.php CHANGED
@@ -1,126 +1,126 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbNotification' ) ) {
5
- /**
6
- * Base class to handle a notification for Restaurant Reservations
7
- *
8
- * This class sets up the notification content and sends it when run by
9
- * rtbNotifications. This class should be extended for each type of
10
- * notification. So, there would be a rtbNotificationEmail class or a
11
- * rtbNotificationSMS class.
12
- *
13
- * @since 0.0.1
14
- */
15
- abstract class rtbNotification {
16
-
17
- /**
18
- * Event which should trigger this notification
19
- * @since 0.0.1
20
- */
21
- public $event;
22
-
23
- /**
24
- * Target of the notification (who/what will receive it)
25
- * @since 0.0.1
26
- */
27
- public $target;
28
-
29
- /**
30
- * Define the notification essentials
31
- * @since 0.0.1
32
- */
33
- public function __construct( $event, $target ) {
34
-
35
- $this->event = $event;
36
- $this->target = $target;
37
-
38
- }
39
-
40
- /**
41
- * Set booking data passed from rtbNotifications
42
- *
43
- * @var object $booking
44
- * @since 0.0.1
45
- */
46
- public function set_booking( $booking ) {
47
- $this->booking = $booking;
48
- }
49
-
50
- /**
51
- * Prepare and validate notification data
52
- *
53
- * @return boolean if the data is valid and ready for transport
54
- * @since 0.0.1
55
- */
56
- abstract public function prepare_notification();
57
-
58
- /**
59
- * Retrieve a notification template
60
- * @since 0.0.1
61
- */
62
- public function get_template( $type ) {
63
-
64
- global $rtb_controller;
65
-
66
- $template = $rtb_controller->settings->get_setting( $type );
67
-
68
- if ( $template === null ) {
69
- return '';
70
- } else {
71
- return $template;
72
- }
73
- }
74
-
75
- /**
76
- * Process a template and insert booking details
77
- * @since 0.0.1
78
- */
79
- public function process_template( $message ) {
80
- global $rtb_controller;
81
-
82
- if ( empty( $this->booking ) ) { return; }
83
-
84
- $booking_page_id = $rtb_controller->settings->get_setting( 'booking-page' );
85
- $booking_page_url = get_permalink( $booking_page_id );
86
-
87
- $cancellation_url = add_query_arg(
88
- array(
89
- 'action' => 'cancel',
90
- 'booking_id' => $this->booking->ID,
91
- 'booking_email' => $this->booking->email
92
- ),
93
- $booking_page_url
94
- );
95
-
96
- $template_tags = array(
97
- '{user_email}' => $this->booking->email,
98
- '{user_name}' => $this->booking->name,
99
- '{party}' => $this->booking->party,
100
- '{table}' => implode(',', $this->booking->table ),
101
- '{date}' => $this->booking->format_date( $this->booking->date ),
102
- '{phone}' => $this->booking->phone,
103
- '{message}' => $this->booking->message,
104
- '{bookings_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&status=pending' ) . '">' . __( 'View pending bookings', 'restaurant-reservations' ) . '</a>',
105
- '{cancel_link}' => '<a href="' . esc_attr( $cancellation_url ) . '">' . __( 'Cancel booking', 'restaurant-reservations' ) . '</a>',
106
- '{confirm_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=confirm&booking=' . esc_attr( $this->booking->ID ) ) . '">' . __( 'Confirm this booking', 'restaurant-reservations' ) . '</a>',
107
- '{close_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=close&booking=' . esc_attr( $this->booking->ID ) ) . '">' . __( 'Reject this booking', 'restaurant-reservations' ) . '</a>',
108
- '{site_name}' => get_bloginfo( 'name' ),
109
- '{site_link}' => '<a href="' . home_url( '/' ) . '">' . get_bloginfo( 'name' ) . '</a>',
110
- '{current_time}' => date_i18n( get_option( 'date_format' ), current_time( 'timestamp' ) ) . ' ' . date_i18n( get_option( 'time_format' ), current_time( 'timestamp' ) ),
111
- );
112
-
113
- $template_tags = apply_filters( 'rtb_notification_template_tags', $template_tags, $this );
114
-
115
- return str_replace( array_keys( $template_tags ), array_values( $template_tags ), $message );
116
-
117
- }
118
-
119
- /**
120
- * Send notification
121
- * @since 0.0.1
122
- */
123
- abstract public function send_notification();
124
-
125
- }
126
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbNotification' ) ) {
5
+ /**
6
+ * Base class to handle a notification for Restaurant Reservations
7
+ *
8
+ * This class sets up the notification content and sends it when run by
9
+ * rtbNotifications. This class should be extended for each type of
10
+ * notification. So, there would be a rtbNotificationEmail class or a
11
+ * rtbNotificationSMS class.
12
+ *
13
+ * @since 0.0.1
14
+ */
15
+ abstract class rtbNotification {
16
+
17
+ /**
18
+ * Event which should trigger this notification
19
+ * @since 0.0.1
20
+ */
21
+ public $event;
22
+
23
+ /**
24
+ * Target of the notification (who/what will receive it)
25
+ * @since 0.0.1
26
+ */
27
+ public $target;
28
+
29
+ /**
30
+ * Define the notification essentials
31
+ * @since 0.0.1
32
+ */
33
+ public function __construct( $event, $target ) {
34
+
35
+ $this->event = $event;
36
+ $this->target = $target;
37
+
38
+ }
39
+
40
+ /**
41
+ * Set booking data passed from rtbNotifications
42
+ *
43
+ * @var object $booking
44
+ * @since 0.0.1
45
+ */
46
+ public function set_booking( $booking ) {
47
+ $this->booking = $booking;
48
+ }
49
+
50
+ /**
51
+ * Prepare and validate notification data
52
+ *
53
+ * @return boolean if the data is valid and ready for transport
54
+ * @since 0.0.1
55
+ */
56
+ abstract public function prepare_notification();
57
+
58
+ /**
59
+ * Retrieve a notification template
60
+ * @since 0.0.1
61
+ */
62
+ public function get_template( $type ) {
63
+
64
+ global $rtb_controller;
65
+
66
+ $template = $rtb_controller->settings->get_setting( $type );
67
+
68
+ if ( $template === null ) {
69
+ return '';
70
+ } else {
71
+ return $template;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Process a template and insert booking details
77
+ * @since 0.0.1
78
+ */
79
+ public function process_template( $message ) {
80
+ global $rtb_controller;
81
+
82
+ if ( empty( $this->booking ) ) { return; }
83
+
84
+ $booking_page_id = $rtb_controller->settings->get_setting( 'booking-page' );
85
+ $booking_page_url = get_permalink( $booking_page_id );
86
+
87
+ $cancellation_url = add_query_arg(
88
+ array(
89
+ 'action' => 'cancel',
90
+ 'booking_id' => $this->booking->ID,
91
+ 'booking_email' => $this->booking->email
92
+ ),
93
+ $booking_page_url
94
+ );
95
+
96
+ $template_tags = array(
97
+ '{user_email}' => $this->booking->email,
98
+ '{user_name}' => $this->booking->name,
99
+ '{party}' => $this->booking->party,
100
+ '{table}' => implode(',', $this->booking->table ),
101
+ '{date}' => $this->booking->format_date( $this->booking->date ),
102
+ '{phone}' => $this->booking->phone,
103
+ '{message}' => $this->booking->message,
104
+ '{bookings_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&status=pending' ) . '">' . __( 'View pending bookings', 'restaurant-reservations' ) . '</a>',
105
+ '{cancel_link}' => '<a href="' . esc_attr( $cancellation_url ) . '">' . __( 'Cancel booking', 'restaurant-reservations' ) . '</a>',
106
+ '{confirm_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=confirm&booking=' . esc_attr( $this->booking->ID ) ) . '">' . __( 'Confirm this booking', 'restaurant-reservations' ) . '</a>',
107
+ '{close_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=close&booking=' . esc_attr( $this->booking->ID ) ) . '">' . __( 'Reject this booking', 'restaurant-reservations' ) . '</a>',
108
+ '{site_name}' => get_bloginfo( 'name' ),
109
+ '{site_link}' => '<a href="' . home_url( '/' ) . '">' . get_bloginfo( 'name' ) . '</a>',
110
+ '{current_time}' => date_i18n( get_option( 'date_format' ), current_time( 'timestamp' ) ) . ' ' . date_i18n( get_option( 'time_format' ), current_time( 'timestamp' ) ),
111
+ );
112
+
113
+ $template_tags = apply_filters( 'rtb_notification_template_tags', $template_tags, $this );
114
+
115
+ return str_replace( array_keys( $template_tags ), array_values( $template_tags ), $message );
116
+
117
+ }
118
+
119
+ /**
120
+ * Send notification
121
+ * @since 0.0.1
122
+ */
123
+ abstract public function send_notification();
124
+
125
+ }
126
+ } // endif;
includes/Notifications.class.php CHANGED
@@ -1,322 +1,322 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbNotifications' ) ) {
5
- /**
6
- * Class to process notifications for Restaurant Reservations
7
- *
8
- * This class contains the registered notifications and sends them when the
9
- * event is triggered.
10
- *
11
- * @since 0.0.1
12
- */
13
- class rtbNotifications {
14
-
15
- /**
16
- * Booking object (class rtbBooking)
17
- *
18
- * @var object
19
- * @since 0.0.1
20
- */
21
- public $booking;
22
-
23
- /**
24
- * Array of rtbNotification objects
25
- *
26
- * @var array
27
- * @since 0.0.1
28
- */
29
- public $notifications;
30
-
31
- /**
32
- * Register notifications hook early so that other early hooks can
33
- * be used by the notification system.
34
- * @since 0.0.1
35
- */
36
- public function __construct() {
37
-
38
- add_action( 'init', array( $this, 'register_notifications' ) );
39
-
40
- add_action( 'init', array( $this, 'maybe_send_daily_summary' ), 12 );
41
- }
42
-
43
- /**
44
- * Register notifications
45
- * @since 0.0.1
46
- */
47
- public function register_notifications() {
48
-
49
- // Hook into all events that require notifications
50
- $hooks = array(
51
- 'rtb_insert_booking' => array( $this, 'new_submission' ), // Booking submitted
52
- 'rtb_booking_paid' => array( $this, 'new_submission' ), // Booking deposit paid
53
- 'rtb_confirmed_booking' => array( $this, 'new_confirmed_submission' ), // Booking confirmed
54
- 'pending_to_confirmed' => array( $this, 'pending_to_confirmed' ), // Booking confirmed
55
- 'pending_to_closed' => array( $this, 'pending_to_closed' ), // Booking can not be made
56
- 'pending_to_cancelled' => array( $this, 'booking_cancelled' ), // Booking cancelled
57
- 'confirmed_to_cancelled' => array( $this, 'booking_cancelled' ), // Booking cancelled
58
- );
59
-
60
- $hooks = apply_filters( 'rtb_notification_transition_callbacks', $hooks );
61
-
62
- foreach ( $hooks as $hook => $callback ) {
63
- add_action( $hook, $callback );
64
- }
65
-
66
- // Register notifications
67
- require_once( RTB_PLUGIN_DIR . '/includes/Notification.class.php' );
68
- require_once( RTB_PLUGIN_DIR . '/includes/Notification.Email.class.php' );
69
-
70
- $this->notifications = array(
71
- new rtbNotificationEmail( 'new_submission', 'user' ),
72
- new rtbNotificationEmail( 'pending_to_confirmed', 'user' ),
73
- new rtbNotificationEmail( 'pending_to_closed', 'user' ),
74
- new rtbNotificationEmail( 'booking_cancelled', 'user' ),
75
- );
76
-
77
- global $rtb_controller;
78
- if ( $rtb_controller->settings->get_setting( 'admin-email-option' ) ) {
79
- $this->notifications[] = new rtbNotificationEmail( 'new_submission', 'admin' );
80
- }
81
-
82
- if ( $rtb_controller->settings->get_setting( 'admin-confirmed-email-option' ) ) {
83
- $this->notifications[] = new rtbNotificationEmail( 'rtb_confirmed_booking', 'admin' );
84
- }
85
-
86
- if ( $rtb_controller->settings->get_setting( 'admin-cancelled-email-option' ) ) {
87
- $this->notifications[] = new rtbNotificationEmail( 'booking_cancelled', 'admin' );
88
- }
89
-
90
- $this->notifications = apply_filters( 'rtb_notifications', $this->notifications );
91
- }
92
-
93
- /**
94
- * Send a summary of bookings today, if enabled
95
- * @since 2.3.6
96
- */
97
- public function maybe_send_daily_summary() {
98
- global $rtb_controller;
99
-
100
- if ( empty( $rtb_controller->settings->get_setting( 'daily-summary-address' ) ) ) { return; }
101
-
102
- if ( get_option( 'rtb-daily-summary-send-date' ) == date( 'Y-m-d' ) ) { return; }
103
-
104
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
105
-
106
- $display_table = $rtb_controller->permissions->check_permission( 'premium_table_restrictions' ) && $rtb_controller->settings->get_setting( 'enable-tables' );
107
-
108
- $args = array(
109
- 'post_type' => 'rtb-booking',
110
- 'posts_per_page' => -1,
111
- 'date_query' => array(
112
- 'year' => date( 'Y' ),
113
- 'month' => date( 'm' ),
114
- 'day' => date( 'd' )
115
- ),
116
- 'post_status' => array_keys( $rtb_controller->cpts->booking_statuses ),
117
- 'orderby' => 'date',
118
- 'order' => 'ASC'
119
- );
120
-
121
- $bookings = get_posts( $args );
122
-
123
- ob_start();
124
-
125
- ?>
126
- Please find a summary of today's reservations in the table below.
127
-
128
- <table class='rtb-view-bookings-table'>
129
- <thead>
130
- <tr>
131
- <th><?php _e('Time', 'restaurant-reservations'); ?></th>
132
- <th><?php _e('Party', 'restaurant-reservations'); ?></th>
133
- <th><?php _e('Name', 'restaurant-reservations'); ?></th>
134
- <th><?php _e('Email', 'restaurant-reservations'); ?></th>
135
- <th><?php _e('Phone', 'restaurant-reservations'); ?></th>
136
- <?php if ( $display_table ) {?> <th><?php _e('Table', 'restaurant-reservations'); ?></th><?php } ?>
137
- <th><?php _e('Status', 'restaurant-reservations'); ?></th>
138
- <th><?php _e('Details', 'restaurant-reservations'); ?></th>
139
- </tr>
140
- </thead>
141
- <tbody>
142
- <?php foreach ( $bookings as $booking ) { ?>
143
- <?php $booking_object = new rtbBooking(); ?>
144
- <?php $booking_object->load_post( $booking ); ?>
145
- <tr>
146
- <td><?php echo date( 'H:i:s', strtotime( $booking_object->date ) ); ?></td>
147
- <td><?php echo $booking_object->party; ?></td>
148
- <td><?php echo $booking_object->name; ?></td>
149
- <td><?php echo $booking_object->email; ?></td>
150
- <td><?php echo $booking_object->phone; ?></td>
151
- <?php if ( $display_table ) { $table = implode(', ', $booking_object->table ); echo "<td>{$table}</td>"; } ?>
152
- <td><?php echo $rtb_controller->cpts->booking_statuses[$booking_object->post_status]['label'] ?></td>
153
- <td><?php echo apply_filters( 'rtb_bookings_table_column_details', $booking_object->message, $booking_object ); ?></td>
154
- </tr>
155
- <?php } ?>
156
- </tbody>
157
- </table>
158
-
159
- <?php
160
-
161
- $email_content = ob_get_clean();
162
-
163
- $notification = new rtbNotificationEmail( 'daily_summary', 'admin' );
164
-
165
- $notification->to_email = $rtb_controller->settings->get_setting( 'daily-summary-address' );
166
- $notification->from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
167
- $notification->from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
168
- $notification->subject = __( 'Daily Email Summary', 'restaurant-reservations' );
169
- $notification->manual_message = $email_content;
170
-
171
- if ( $notification->prepare_notification() ) {
172
-
173
- if ( $notification->send_notification() ) {
174
-
175
- update_option( 'rtb-daily-summary-send-date', date( 'Y-m-d' ) );
176
- }
177
- }
178
- }
179
-
180
- /**
181
- * Set booking data
182
- * @since 0.0.1
183
- */
184
- public function set_booking( $booking_post ) {
185
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
186
- $this->booking = new rtbBooking();
187
- $this->booking->load_wp_post( $booking_post );
188
- }
189
-
190
- /**
191
- * New booking submissions
192
- *
193
- * @var object $booking
194
- * @since 0.0.1
195
- */
196
- public function new_submission( $booking ) {
197
-
198
- // Bail early if $booking is not a rtbBooking object
199
- if ( get_class( $booking ) != 'rtbBooking' ) {
200
- return;
201
- }
202
-
203
- // trigger an event so that admin notifications for a new confirmed booking can be sent
204
- if ( $booking->post_status == 'confirmed' ) {
205
- do_action( 'rtb_confirmed_booking', get_post( $booking->ID ) );
206
- }
207
-
208
- // If the post status is not pending, trigger a post status
209
- // transition as though it's gone from pending_to_{status}
210
- if ( $booking->post_status != 'pending' and $booking->post_status != 'draft' ) {
211
- do_action( 'pending_to_' . $booking->post_status, get_post( $booking->ID ) );
212
-
213
- // Otherwise proceed with the new_submission event
214
- } else {
215
- $this->booking = $booking;
216
- $this->event( 'new_submission' );
217
- }
218
- }
219
-
220
- /**
221
- * New confirmed booking
222
- * @since 2.1.0
223
- */
224
- public function new_confirmed_submission( $booking_post ) {
225
-
226
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
227
- return;
228
- }
229
-
230
- $this->set_booking( $booking_post );
231
-
232
- $this->event( 'rtb_confirmed_booking' );
233
-
234
- }
235
-
236
- /**
237
- * Booking confirmed
238
- * @since 0.0.1
239
- */
240
- public function pending_to_confirmed( $booking_post ) {
241
-
242
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
243
- return;
244
- }
245
-
246
- $this->set_booking( $booking_post );
247
-
248
- $this->event( 'pending_to_confirmed' );
249
-
250
- }
251
-
252
- /**
253
- * Booking can not be made
254
- * @since 0.0.1
255
- */
256
- public function pending_to_closed( $booking_post ) {
257
-
258
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
259
- return;
260
- }
261
-
262
- $this->set_booking( $booking_post );
263
-
264
- $this->event( 'pending_to_closed' );
265
-
266
- }
267
-
268
- /**
269
- * Booking has been cancelled by the guest
270
- */
271
- public function booking_cancelled( $booking_post ) {
272
-
273
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
274
- return;
275
- }
276
-
277
- $this->set_booking( $booking_post );
278
-
279
- $this->event( 'booking_cancelled' );
280
-
281
- }
282
-
283
- /**
284
- * Booking was confirmed and is now completed. Send out an optional
285
- * follow-up email.
286
- *
287
- * @since 0.0.1
288
- */
289
- public function confirmed_to_closed( $booking_post ) {
290
-
291
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
292
- return;
293
- }
294
-
295
- $this->set_booking( $booking_post );
296
-
297
- $this->event( 'confirmed_to_closed' );
298
-
299
- }
300
-
301
- /**
302
- * Process notifications for an event
303
- * @since 0.0.1
304
- */
305
- public function event( $event ) {
306
-
307
- foreach( $this->notifications as $notification ) {
308
-
309
- if ( $event == $notification->event ) {
310
- $notification->set_booking( $this->booking );
311
- if ( $notification->prepare_notification() ) {
312
- do_action( 'rtb_send_notification_before', $notification );
313
- $notification->send_notification();
314
- do_action( 'rtb_send_notification_after', $notification );
315
- }
316
- }
317
- }
318
-
319
- }
320
-
321
- }
322
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbNotifications' ) ) {
5
+ /**
6
+ * Class to process notifications for Restaurant Reservations
7
+ *
8
+ * This class contains the registered notifications and sends them when the
9
+ * event is triggered.
10
+ *
11
+ * @since 0.0.1
12
+ */
13
+ class rtbNotifications {
14
+
15
+ /**
16
+ * Booking object (class rtbBooking)
17
+ *
18
+ * @var object
19
+ * @since 0.0.1
20
+ */
21
+ public $booking;
22
+
23
+ /**
24
+ * Array of rtbNotification objects
25
+ *
26
+ * @var array
27
+ * @since 0.0.1
28
+ */
29
+ public $notifications;
30
+
31
+ /**
32
+ * Register notifications hook early so that other early hooks can
33
+ * be used by the notification system.
34
+ * @since 0.0.1
35
+ */
36
+ public function __construct() {
37
+
38
+ add_action( 'init', array( $this, 'register_notifications' ) );
39
+
40
+ add_action( 'init', array( $this, 'maybe_send_daily_summary' ), 12 );
41
+ }
42
+
43
+ /**
44
+ * Register notifications
45
+ * @since 0.0.1
46
+ */
47
+ public function register_notifications() {
48
+
49
+ // Hook into all events that require notifications
50
+ $hooks = array(
51
+ 'rtb_insert_booking' => array( $this, 'new_submission' ), // Booking submitted
52
+ 'rtb_booking_paid' => array( $this, 'new_submission' ), // Booking deposit paid
53
+ 'rtb_confirmed_booking' => array( $this, 'new_confirmed_submission' ), // Booking confirmed
54
+ 'pending_to_confirmed' => array( $this, 'pending_to_confirmed' ), // Booking confirmed
55
+ 'pending_to_closed' => array( $this, 'pending_to_closed' ), // Booking can not be made
56
+ 'pending_to_cancelled' => array( $this, 'booking_cancelled' ), // Booking cancelled
57
+ 'confirmed_to_cancelled' => array( $this, 'booking_cancelled' ), // Booking cancelled
58
+ );
59
+
60
+ $hooks = apply_filters( 'rtb_notification_transition_callbacks', $hooks );
61
+
62
+ foreach ( $hooks as $hook => $callback ) {
63
+ add_action( $hook, $callback );
64
+ }
65
+
66
+ // Register notifications
67
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.class.php' );
68
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.Email.class.php' );
69
+
70
+ $this->notifications = array(
71
+ new rtbNotificationEmail( 'new_submission', 'user' ),
72
+ new rtbNotificationEmail( 'pending_to_confirmed', 'user' ),
73
+ new rtbNotificationEmail( 'pending_to_closed', 'user' ),
74
+ new rtbNotificationEmail( 'booking_cancelled', 'user' ),
75
+ );
76
+
77
+ global $rtb_controller;
78
+ if ( $rtb_controller->settings->get_setting( 'admin-email-option' ) ) {
79
+ $this->notifications[] = new rtbNotificationEmail( 'new_submission', 'admin' );
80
+ }
81
+
82
+ if ( $rtb_controller->settings->get_setting( 'admin-confirmed-email-option' ) ) {
83
+ $this->notifications[] = new rtbNotificationEmail( 'rtb_confirmed_booking', 'admin' );
84
+ }
85
+
86
+ if ( $rtb_controller->settings->get_setting( 'admin-cancelled-email-option' ) ) {
87
+ $this->notifications[] = new rtbNotificationEmail( 'booking_cancelled', 'admin' );
88
+ }
89
+
90
+ $this->notifications = apply_filters( 'rtb_notifications', $this->notifications );
91
+ }
92
+
93
+ /**
94
+ * Send a summary of bookings today, if enabled
95
+ * @since 2.3.6
96
+ */
97
+ public function maybe_send_daily_summary() {
98
+ global $rtb_controller;
99
+
100
+ if ( empty( $rtb_controller->settings->get_setting( 'daily-summary-address' ) ) ) { return; }
101
+
102
+ if ( get_option( 'rtb-daily-summary-send-date' ) == date( 'Y-m-d' ) ) { return; }
103
+
104
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
105
+
106
+ $display_table = $rtb_controller->permissions->check_permission( 'premium_table_restrictions' ) && $rtb_controller->settings->get_setting( 'enable-tables' );
107
+
108
+ $args = array(
109
+ 'post_type' => 'rtb-booking',
110
+ 'posts_per_page' => -1,
111
+ 'date_query' => array(
112
+ 'year' => date( 'Y' ),
113
+ 'month' => date( 'm' ),
114
+ 'day' => date( 'd' )
115
+ ),
116
+ 'post_status' => array_keys( $rtb_controller->cpts->booking_statuses ),
117
+ 'orderby' => 'date',
118
+ 'order' => 'ASC'
119
+ );
120
+
121
+ $bookings = get_posts( $args );
122
+
123
+ ob_start();
124
+
125
+ ?>
126
+ <?php _e('Please find a summary of today\'s reservations in the table below.', 'restaurant-reservations'); ?>
127
+
128
+ <table class='rtb-view-bookings-table'>
129
+ <thead>
130
+ <tr>
131
+ <th><?php _e('Time', 'restaurant-reservations'); ?></th>
132
+ <th><?php _e('Party', 'restaurant-reservations'); ?></th>
133
+ <th><?php _e('Name', 'restaurant-reservations'); ?></th>
134
+ <th><?php _e('Email', 'restaurant-reservations'); ?></th>
135
+ <th><?php _e('Phone', 'restaurant-reservations'); ?></th>
136
+ <?php if ( $display_table ) {?> <th><?php _e('Table', 'restaurant-reservations'); ?></th><?php } ?>
137
+ <th><?php _e('Status', 'restaurant-reservations'); ?></th>
138
+ <th><?php _e('Details', 'restaurant-reservations'); ?></th>
139
+ </tr>
140
+ </thead>
141
+ <tbody>
142
+ <?php foreach ( $bookings as $booking ) { ?>
143
+ <?php $booking_object = new rtbBooking(); ?>
144
+ <?php $booking_object->load_post( $booking ); ?>
145
+ <tr>
146
+ <td><?php echo date( 'H:i:s', strtotime( $booking_object->date ) ); ?></td>
147
+ <td><?php echo $booking_object->party; ?></td>
148
+ <td><?php echo $booking_object->name; ?></td>
149
+ <td><?php echo $booking_object->email; ?></td>
150
+ <td><?php echo $booking_object->phone; ?></td>
151
+ <?php if ( $display_table ) { $table = implode(', ', $booking_object->table ); echo "<td>{$table}</td>"; } ?>
152
+ <td><?php echo $rtb_controller->cpts->booking_statuses[$booking_object->post_status]['label'] ?></td>
153
+ <td><?php echo apply_filters( 'rtb_bookings_table_column_details', $booking_object->message, $booking_object ); ?></td>
154
+ </tr>
155
+ <?php } ?>
156
+ </tbody>
157
+ </table>
158
+
159
+ <?php
160
+
161
+ $email_content = ob_get_clean();
162
+
163
+ $notification = new rtbNotificationEmail( 'daily_summary', 'admin' );
164
+
165
+ $notification->to_email = $rtb_controller->settings->get_setting( 'daily-summary-address' );
166
+ $notification->from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
167
+ $notification->from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
168
+ $notification->subject = __( 'Daily Email Summary', 'restaurant-reservations' );
169
+ $notification->manual_message = $email_content;
170
+
171
+ if ( $notification->prepare_notification() ) {
172
+
173
+ if ( $notification->send_notification() ) {
174
+
175
+ update_option( 'rtb-daily-summary-send-date', date( 'Y-m-d' ) );
176
+ }
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Set booking data
182
+ * @since 0.0.1
183
+ */
184
+ public function set_booking( $booking_post ) {
185
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
186
+ $this->booking = new rtbBooking();
187
+ $this->booking->load_wp_post( $booking_post );
188
+ }
189
+
190
+ /**
191
+ * New booking submissions
192
+ *
193
+ * @var object $booking
194
+ * @since 0.0.1
195
+ */
196
+ public function new_submission( $booking ) {
197
+
198
+ // Bail early if $booking is not a rtbBooking object
199
+ if ( get_class( $booking ) != 'rtbBooking' ) {
200
+ return;
201
+ }
202
+
203
+ // trigger an event so that admin notifications for a new confirmed booking can be sent
204
+ if ( $booking->post_status == 'confirmed' ) {
205
+ do_action( 'rtb_confirmed_booking', get_post( $booking->ID ) );
206
+ }
207
+
208
+ // If the post status is not pending, trigger a post status
209
+ // transition as though it's gone from pending_to_{status}
210
+ if ( $booking->post_status != 'pending' and $booking->post_status != 'draft' ) {
211
+ do_action( 'pending_to_' . $booking->post_status, get_post( $booking->ID ) );
212
+
213
+ // Otherwise proceed with the new_submission event
214
+ } else {
215
+ $this->booking = $booking;
216
+ $this->event( 'new_submission' );
217
+ }
218
+ }
219
+
220
+ /**
221
+ * New confirmed booking
222
+ * @since 2.1.0
223
+ */
224
+ public function new_confirmed_submission( $booking_post ) {
225
+
226
+ if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
227
+ return;
228
+ }
229
+
230
+ $this->set_booking( $booking_post );
231
+
232
+ $this->event( 'rtb_confirmed_booking' );
233
+
234
+ }
235
+
236
+ /**
237
+ * Booking confirmed
238
+ * @since 0.0.1
239
+ */
240
+ public function pending_to_confirmed( $booking_post ) {
241
+
242
+ if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
243
+ return;
244
+ }
245
+
246
+ $this->set_booking( $booking_post );
247
+
248
+ $this->event( 'pending_to_confirmed' );
249
+
250
+ }
251
+
252
+ /**
253
+ * Booking can not be made
254
+ * @since 0.0.1
255
+ */
256
+ public function pending_to_closed( $booking_post ) {
257
+
258
+ if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
259
+ return;
260
+ }
261
+
262
+ $this->set_booking( $booking_post );
263
+
264
+ $this->event( 'pending_to_closed' );
265
+
266
+ }
267
+
268
+ /**
269
+ * Booking has been cancelled by the guest
270
+ */
271
+ public function booking_cancelled( $booking_post ) {
272
+
273
+ if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
274
+ return;
275
+ }
276
+
277
+ $this->set_booking( $booking_post );
278
+
279
+ $this->event( 'booking_cancelled' );
280
+
281
+ }
282
+
283
+ /**
284
+ * Booking was confirmed and is now completed. Send out an optional
285
+ * follow-up email.
286
+ *
287
+ * @since 0.0.1
288
+ */
289
+ public function confirmed_to_closed( $booking_post ) {
290
+
291
+ if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
292
+ return;
293
+ }
294
+
295
+ $this->set_booking( $booking_post );
296
+
297
+ $this->event( 'confirmed_to_closed' );
298
+
299
+ }
300
+
301
+ /**
302
+ * Process notifications for an event
303
+ * @since 0.0.1
304
+ */
305
+ public function event( $event ) {
306
+
307
+ foreach( $this->notifications as $notification ) {
308
+
309
+ if ( $event == $notification->event ) {
310
+ $notification->set_booking( $this->booking );
311
+ if ( $notification->prepare_notification() ) {
312
+ do_action( 'rtb_send_notification_before', $notification );
313
+ $notification->send_notification();
314
+ do_action( 'rtb_send_notification_after', $notification );
315
+ }
316
+ }
317
+ }
318
+
319
+ }
320
+
321
+ }
322
+ } // endif;
includes/PaymentGateway.interface.php CHANGED
@@ -1,41 +1,41 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !interface_exists( 'rtbPaymentGateway' ) ) {
5
- /**
6
- * Base interface to implement a Payment Gateway for Restaurant Reservations
7
- *
8
- * This class enforces the Payment Gateway base settings and their processing
9
- * methods. This class should be implemented for each type of
10
- * Payment Gateway. So, there would be a rtbPaymentStripe class or a
11
- * rtbPaymentPayPal class.
12
- *
13
- * @since 2.3.0
14
- */
15
- interface rtbPaymentGateway {
16
-
17
- /**
18
- * Register the gateway.
19
- *
20
- * $gateway_list['stripe'] => [
21
- * 'label' => __( 'Stripe', 'restaurant-reservations' ),
22
- * 'instance' => $this
23
- * ];
24
- *
25
- * @param array $gateway_list
26
- * @return array $gateway_list
27
- */
28
- public static function register_gateway( array $gateway_list );
29
-
30
- /**
31
- * Print the payment form. The booking has been made and is on payment_pending
32
- * status. Display the payment form/button here
33
- *
34
- * @param rtbBooking $booking booking for which the payment is required
35
- * @return void Print the HTML
36
- */
37
- public function print_payment_form( $booking );
38
-
39
- }
40
-
41
  }
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !interface_exists( 'rtbPaymentGateway' ) ) {
5
+ /**
6
+ * Base interface to implement a Payment Gateway for Restaurant Reservations
7
+ *
8
+ * This class enforces the Payment Gateway base settings and their processing
9
+ * methods. This class should be implemented for each type of
10
+ * Payment Gateway. So, there would be a rtbPaymentStripe class or a
11
+ * rtbPaymentPayPal class.
12
+ *
13
+ * @since 2.3.0
14
+ */
15
+ interface rtbPaymentGateway {
16
+
17
+ /**
18
+ * Register the gateway.
19
+ *
20
+ * $gateway_list['stripe'] => [
21
+ * 'label' => __( 'Stripe', 'restaurant-reservations' ),
22
+ * 'instance' => $this
23
+ * ];
24
+ *
25
+ * @param array $gateway_list
26
+ * @return array $gateway_list
27
+ */
28
+ public static function register_gateway( array $gateway_list );
29
+
30
+ /**
31
+ * Print the payment form. The booking has been made and is on payment_pending
32
+ * status. Display the payment form/button here
33
+ *
34
+ * @param rtbBooking $booking booking for which the payment is required
35
+ * @return void Print the HTML
36
+ */
37
+ public function print_payment_form( $booking );
38
+
39
+ }
40
+
41
  }
includes/PaymentGatewayPayPal.class.php CHANGED
@@ -1,216 +1,216 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbPaymentGatewayPayPal' ) ) {
5
- /**
6
- * This class is responsible for payment processing via Paypal
7
- *
8
- * @since 2.3.0
9
- */
10
- class rtbPaymentGatewayPayPal implements rtbPaymentGateway {
11
-
12
- private static $_instance;
13
-
14
- private final function __construct() {
15
-
16
- $this->register_hooks();
17
-
18
- }
19
-
20
- public static function register_gateway (array $gateway_list )
21
- {
22
- return array_merge(
23
- $gateway_list,
24
- [
25
- 'paypal' => [
26
- 'label' => __( 'PayPal', 'restaurant-reservations' ),
27
- 'instance' => self::get_instance()
28
- ]
29
- ]
30
- );
31
- }
32
-
33
- /**
34
- * Get singleton instance of the class
35
- *
36
- * @return rtbPaymentGatewayPayPal instance
37
- */
38
- public static function get_instance()
39
- {
40
- if( ! isset( self::$_instance ) ) {
41
- self::$_instance = new rtbPaymentGatewayPayPal();
42
- }
43
-
44
- return self::$_instance;
45
- }
46
-
47
- public function register_hooks() {
48
- // If there's an IPN request, add our setup function to potentially handle it
49
- if ( isset($_POST['ipn_track_id']) ) {
50
- add_action( 'init', [$this, 'handle_ipn'], 11 );
51
-
52
- // add an output buffer layer for the plugin
53
- add_action( 'init', [$this, 'ob_start'] );
54
- add_action( 'shutdown', [$this, 'flush_ob_end'] );
55
- }
56
- }
57
-
58
- public function print_payment_form( $booking )
59
- {
60
- global $rtb_controller;
61
-
62
- // Define the form's action parameter
63
- $booking_page = $rtb_controller->settings->get_setting( 'booking-page' );
64
- if ( !empty( $booking_page ) ) {
65
- $booking_page = get_permalink( $booking_page );
66
- }
67
-
68
- $item_name = substr(get_bloginfo('name'), 0, 100) . 'Reservation Deposit';
69
- $amount = $booking->calculate_deposit();
70
- $business = $rtb_controller->settings->get_setting( 'rtb-paypal-email' );
71
- $currency = $rtb_controller->settings->get_setting( 'rtb-currency' );
72
- $notify_url = get_site_url();
73
-
74
- echo "
75
- <form action='https://www.paypal.com/cgi-bin/webscr' method='post' class='standard-form'>
76
- <input type='hidden' name='item_name_1' value='{$item_name}' />
77
- <input type='hidden' name='custom' value='booking_id={$booking->ID}' />
78
- <input type='hidden' name='quantity_1' value='1' />
79
- <input type='hidden' name='amount_1' value='{$amount}' />
80
- <input type='hidden' name='cmd' value='_cart' />
81
- <input type='hidden' name='upload' value='1' />
82
- <input type='hidden' name='business' value='{$business}' />
83
- <input type='hidden' name='currency_code' value='{$currency}' />
84
- <input type='hidden' name='return' value='{$booking_page}' />
85
- <input type='hidden' name='notify_url' value='{$notify_url}' />
86
- <input type='submit' class='submit-button' value='Pay via PayPal' />
87
- </form>
88
- ";
89
- }
90
-
91
- /**
92
- * Handle PayPal IPN requests
93
- *
94
- * @since 2.1.0
95
- */
96
- public function handle_ipn()
97
- {
98
- global $rtb_controller;
99
-
100
- if ( ! $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
101
- return;
102
- }
103
-
104
- // CONFIG: Enable debug mode. This means we'll log requests into 'ipn.log' in the same directory.
105
- // Especially useful if you encounter network errors or other intermittent problems with IPN (validation).
106
- // Set this to 0 once you go live or don't require logging.
107
- $debug = get_option( 'rtb_enable_payment_debugging' );
108
-
109
- // Set to 0 once you're ready to go live
110
- define("RTB_USE_SANDBOX", 0);
111
- define("RTB_LOG_FILE", "ipn.log");
112
-
113
- // Read POST data
114
- // reading posted data directly from $_POST causes serialization
115
- // issues with array data in POST. Reading raw POST data from input stream instead.
116
- $raw_post_data = file_get_contents('php://input');
117
- $raw_post_array = explode('&', $raw_post_data);
118
- $myPost = array();
119
- foreach ($raw_post_array as $keyval) {
120
- $keyval = explode ('=', $keyval);
121
- if (count($keyval) == 2)
122
- $myPost[$keyval[0]] = urldecode($keyval[1]);
123
- }
124
-
125
- // read the post from PayPal system and add 'cmd'
126
- $req = 'cmd=_notify-validate';
127
- if(function_exists('get_magic_quotes_gpc')) {
128
- $get_magic_quotes_exists = true;
129
- }
130
-
131
- foreach ($myPost as $key => $value) {
132
- if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
133
- $value = urlencode(stripslashes($value));
134
- } else {
135
- $value = urlencode($value);
136
- }
137
- $req .= "&$key=$value";
138
- }
139
-
140
- // Post IPN data back to PayPal to validate the IPN data is genuine
141
- // Without this step anyone can fake IPN data
142
- if(RTB_USE_SANDBOX == true) {
143
- $paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr";
144
- } else {
145
- $paypal_url = "https://www.paypal.com/cgi-bin/webscr";
146
- }
147
-
148
- $response = wp_remote_post($paypal_url, array(
149
- 'method' => 'POST',
150
- 'body' => $req,
151
- 'timeout' => 30
152
- ));
153
-
154
- // Inspect IPN validation result and act accordingly
155
- // Split response headers and payload, a better way for strcmp
156
- $tokens = explode("\r\n\r\n", trim($response['body']));
157
- $res = trim(end($tokens));
158
-
159
- if ( $debug ) {
160
- update_option( 'rtb_debugging', get_option( 'rtb_debugging' ) . print_r( date('[Y-m-d H:i e] '). "IPN response: $res - $req ". PHP_EOL, true ) );
161
- }
162
-
163
- if (strcmp ($res, "VERIFIED") == 0) {
164
-
165
- $paypal_receipt_number = $_POST['txn_id'];
166
- $payment_amount = $_POST['mc_gross'];
167
-
168
- parse_str($_POST['custom'], $custom_vars);
169
- $booking_id = intval( $custom_vars['booking_id'] );
170
-
171
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
172
-
173
- $booking = new rtbBooking();
174
- $booking->load_post( $booking_id );
175
-
176
- if ( ! $booking ) { return; }
177
-
178
- $booking->deposit = sanitize_text_field( $payment_amount );
179
- $booking->receipt_id = sanitize_text_field( $paypal_receipt_number );
180
-
181
- $booking->payment_paid();
182
- }
183
- }
184
-
185
- /**
186
- * Opens a buffer when handling PayPal IPN requests
187
- *
188
- * @since 2.1.0
189
- */
190
- public function ob_start()
191
- {
192
- ob_start();
193
- }
194
-
195
- /**
196
- * Closes a buffer when handling PayPal IPN requests
197
- *
198
- * @since 2.1.0
199
- */
200
- public function flush_ob_end()
201
- {
202
- if ( ob_get_length() ) {
203
- ob_end_clean();
204
- }
205
- }
206
- }
207
-
208
- }
209
-
210
- /**
211
- * Gateway has to register itself
212
- */
213
- add_filter(
214
- 'rtb-payment-gateway-register',
215
- ['rtbPaymentGatewayPayPal', 'register_gateway']
216
  );
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbPaymentGatewayPayPal' ) ) {
5
+ /**
6
+ * This class is responsible for payment processing via Paypal
7
+ *
8
+ * @since 2.3.0
9
+ */
10
+ class rtbPaymentGatewayPayPal implements rtbPaymentGateway {
11
+
12
+ private static $_instance;
13
+
14
+ private final function __construct() {
15
+
16
+ $this->register_hooks();
17
+
18
+ }
19
+
20
+ public static function register_gateway (array $gateway_list )
21
+ {
22
+ return array_merge(
23
+ $gateway_list,
24
+ [
25
+ 'paypal' => [
26
+ 'label' => __( 'PayPal', 'restaurant-reservations' ),
27
+ 'instance' => self::get_instance()
28
+ ]
29
+ ]
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Get singleton instance of the class
35
+ *
36
+ * @return rtbPaymentGatewayPayPal instance
37
+ */
38
+ public static function get_instance()
39
+ {
40
+ if( ! isset( self::$_instance ) ) {
41
+ self::$_instance = new rtbPaymentGatewayPayPal();
42
+ }
43
+
44
+ return self::$_instance;
45
+ }
46
+
47
+ public function register_hooks() {
48
+ // If there's an IPN request, add our setup function to potentially handle it
49
+ if ( isset($_POST['ipn_track_id']) ) {
50
+ add_action( 'init', [$this, 'handle_ipn'], 11 );
51
+
52
+ // add an output buffer layer for the plugin
53
+ add_action( 'init', [$this, 'ob_start'] );
54
+ add_action( 'shutdown', [$this, 'flush_ob_end'] );
55
+ }
56
+ }
57
+
58
+ public function print_payment_form( $booking )
59
+ {
60
+ global $rtb_controller;
61
+
62
+ // Define the form's action parameter
63
+ $booking_page = $rtb_controller->settings->get_setting( 'booking-page' );
64
+ if ( !empty( $booking_page ) ) {
65
+ $booking_page = get_permalink( $booking_page );
66
+ }
67
+
68
+ $item_name = substr(get_bloginfo('name'), 0, 100) . 'Reservation Deposit';
69
+ $amount = $booking->calculate_deposit();
70
+ $business = $rtb_controller->settings->get_setting( 'rtb-paypal-email' );
71
+ $currency = $rtb_controller->settings->get_setting( 'rtb-currency' );
72
+ $notify_url = get_site_url();
73
+
74
+ echo "
75
+ <form action='https://www.paypal.com/cgi-bin/webscr' method='post' class='standard-form'>
76
+ <input type='hidden' name='item_name_1' value='{$item_name}' />
77
+ <input type='hidden' name='custom' value='booking_id={$booking->ID}' />
78
+ <input type='hidden' name='quantity_1' value='1' />
79
+ <input type='hidden' name='amount_1' value='{$amount}' />
80
+ <input type='hidden' name='cmd' value='_cart' />
81
+ <input type='hidden' name='upload' value='1' />
82
+ <input type='hidden' name='business' value='{$business}' />
83
+ <input type='hidden' name='currency_code' value='{$currency}' />
84
+ <input type='hidden' name='return' value='{$booking_page}' />
85
+ <input type='hidden' name='notify_url' value='{$notify_url}' />
86
+ <input type='submit' class='submit-button' value='Pay via PayPal' />
87
+ </form>
88
+ ";
89
+ }
90
+
91
+ /**
92
+ * Handle PayPal IPN requests
93
+ *
94
+ * @since 2.1.0
95
+ */
96
+ public function handle_ipn()
97
+ {
98
+ global $rtb_controller;
99
+
100
+ if ( ! $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
101
+ return;
102
+ }
103
+
104
+ // CONFIG: Enable debug mode. This means we'll log requests into 'ipn.log' in the same directory.
105
+ // Especially useful if you encounter network errors or other intermittent problems with IPN (validation).
106
+ // Set this to 0 once you go live or don't require logging.
107
+ $debug = get_option( 'rtb_enable_payment_debugging' );
108
+
109
+ // Set to 0 once you're ready to go live
110
+ define("RTB_USE_SANDBOX", 0);
111
+ define("RTB_LOG_FILE", "ipn.log");
112
+
113
+ // Read POST data
114
+ // reading posted data directly from $_POST causes serialization
115
+ // issues with array data in POST. Reading raw POST data from input stream instead.
116
+ $raw_post_data = file_get_contents('php://input');
117
+ $raw_post_array = explode('&', $raw_post_data);
118
+ $myPost = array();
119
+ foreach ($raw_post_array as $keyval) {
120
+ $keyval = explode ('=', $keyval);
121
+ if (count($keyval) == 2)
122
+ $myPost[$keyval[0]] = urldecode($keyval[1]);
123
+ }
124
+
125
+ // read the post from PayPal system and add 'cmd'
126
+ $req = 'cmd=_notify-validate';
127
+ if(function_exists('get_magic_quotes_gpc')) {
128
+ $get_magic_quotes_exists = true;
129
+ }
130
+
131
+ foreach ($myPost as $key => $value) {
132
+ if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
133
+ $value = urlencode(stripslashes($value));
134
+ } else {
135
+ $value = urlencode($value);
136
+ }
137
+ $req .= "&$key=$value";
138
+ }
139
+
140
+ // Post IPN data back to PayPal to validate the IPN data is genuine
141
+ // Without this step anyone can fake IPN data
142
+ if(RTB_USE_SANDBOX == true) {
143
+ $paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr";
144
+ } else {
145
+ $paypal_url = "https://www.paypal.com/cgi-bin/webscr";
146
+ }
147
+
148
+ $response = wp_remote_post($paypal_url, array(
149
+ 'method' => 'POST',
150
+ 'body' => $req,
151
+ 'timeout' => 30
152
+ ));
153
+
154
+ // Inspect IPN validation result and act accordingly
155
+ // Split response headers and payload, a better way for strcmp
156
+ $tokens = explode("\r\n\r\n", trim($response['body']));
157
+ $res = trim(end($tokens));
158
+
159
+ if ( $debug ) {
160
+ update_option( 'rtb_debugging', get_option( 'rtb_debugging' ) . print_r( date('[Y-m-d H:i e] '). "IPN response: $res - $req ". PHP_EOL, true ) );
161
+ }
162
+
163
+ if (strcmp ($res, "VERIFIED") == 0) {
164
+
165
+ $paypal_receipt_number = $_POST['txn_id'];
166
+ $payment_amount = $_POST['mc_gross'];
167
+
168
+ parse_str($_POST['custom'], $custom_vars);
169
+ $booking_id = intval( $custom_vars['booking_id'] );
170
+
171
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
172
+
173
+ $booking = new rtbBooking();
174
+ $booking->load_post( $booking_id );
175
+
176
+ if ( ! $booking ) { return; }
177
+
178
+ $booking->deposit = sanitize_text_field( $payment_amount );
179
+ $booking->receipt_id = sanitize_text_field( $paypal_receipt_number );
180
+
181
+ $booking->payment_paid();
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Opens a buffer when handling PayPal IPN requests
187
+ *
188
+ * @since 2.1.0
189
+ */
190
+ public function ob_start()
191
+ {
192
+ ob_start();
193
+ }
194
+
195
+ /**
196
+ * Closes a buffer when handling PayPal IPN requests
197
+ *
198
+ * @since 2.1.0
199
+ */
200
+ public function flush_ob_end()
201
+ {
202
+ if ( ob_get_length() ) {
203
+ ob_end_clean();
204
+ }
205
+ }
206
+ }
207
+
208
+ }
209
+
210
+ /**
211
+ * Gateway has to register itself
212
+ */
213
+ add_filter(
214
+ 'rtb-payment-gateway-register',
215
+ ['rtbPaymentGatewayPayPal', 'register_gateway']
216
  );
includes/PaymentGatewayStripe.class.php CHANGED
@@ -1,462 +1,462 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbPaymentGatewayStripe' ) ) {
5
- /**
6
- * This class is responsible for payment processing via Stripe
7
- *
8
- * @since 2.3.0
9
- */
10
- class rtbPaymentGatewayStripe implements rtbPaymentGateway {
11
-
12
- private static $_instance;
13
-
14
- private final function __construct() {
15
-
16
- $this->register_hooks();
17
-
18
- }
19
-
20
- public static function register_gateway (array $gateway_list )
21
- {
22
- return array_merge(
23
- $gateway_list,
24
- [
25
- 'stripe' => [
26
- 'label' => __( 'Stripe', 'restaurant-reservations' ),
27
- 'instance' => self::get_instance()
28
- ]
29
- ]
30
- );
31
- }
32
-
33
- /**
34
- * Get singleton instance of the class
35
- *
36
- * @return rtbPaymentGatewayStripe instance
37
- */
38
- public static function get_instance()
39
- {
40
- if( ! isset( self::$_instance ) ) {
41
- self::$_instance = new rtbPaymentGatewayStripe();
42
- }
43
-
44
- return self::$_instance;
45
- }
46
-
47
- public function register_hooks()
48
- {
49
- add_action( 'rtb_booking_form_init', [$this, 'process_payment'] );
50
-
51
- add_action( 'wp_ajax_rtb_stripe_get_intent', array( $this, 'create_stripe_pmtIntnt' ) );
52
- add_action( 'wp_ajax_nopriv_rtb_stripe_get_intent', array( $this, 'create_stripe_pmtIntnt' ) );
53
-
54
- add_action( 'wp_ajax_rtb_stripe_pmt_succeed', array( $this, 'stripe_sca_succeed' ) );
55
- add_action( 'wp_ajax_nopriv_rtb_stripe_pmt_succeed', array( $this, 'stripe_sca_succeed' ) );
56
-
57
- add_filter( 'rtb_booking_metadata_defaults', [$this, 'default_booking_stripe_info'], 30, 1 );
58
- add_action( 'rtb_booking_load_post_data', [$this, 'populate_booking_stripe_info'], 30, 2 );
59
- add_filter( 'rtb_insert_booking_metadata', [$this, 'save_booking_gateway_info'], 30, 2 );
60
- }
61
-
62
- public function print_payment_form( $booking )
63
- {
64
- global $rtb_controller;
65
-
66
- // Function alias
67
- $_gs = [$rtb_controller->settings, 'get_setting'];
68
- $SCA = $_gs( 'rtb-stripe-sca' );
69
-
70
- $btn_disabled = '';
71
- $stripe_lib_version = 'v2';
72
-
73
- if( $SCA )
74
- {
75
- $btn_disabled = "disabled='disabled'";
76
- $stripe_lib_version = 'v3';
77
- }
78
-
79
- // Stripe Lib
80
- wp_enqueue_script(
81
- 'rtb-stripe',
82
- "https://js.stripe.com/{$stripe_lib_version}/",
83
- array( 'jquery' ),
84
- RTB_VERSION,
85
- true
86
- );
87
-
88
- // Stripe-JS processing logic
89
- wp_enqueue_script(
90
- 'rtb-stripe-payment',
91
- RTB_PLUGIN_URL . '/assets/js/stripe-payment.js',
92
- array( 'jquery', 'rtb-stripe' ),
93
- RTB_VERSION,
94
- true
95
- );
96
-
97
- wp_localize_script(
98
- 'rtb-stripe-payment',
99
- 'rtb_stripe_payment',
100
- array(
101
- 'stripe_mode' => $_gs( 'rtb-stripe-mode' ),
102
- 'stripe_sca' => $SCA,
103
- 'live_publishable_key' => $_gs( 'rtb-stripe-live-publishable' ),
104
- 'test_publishable_key' => $_gs( 'rtb-stripe-test-publishable' ),
105
- )
106
- );
107
-
108
- $payment_amount = $_gs( 'rtb-currency-symbol-location' ) == 'before'
109
- ? $_gs( 'rtb-stripe-currency-symbol' ) . $booking->calculate_deposit()
110
- : $booking->calculate_deposit() . $_gs( 'rtb-stripe-currency-symbol' );
111
-
112
- $cc_exp_single_field = null != $_gs( 'rtb-expiration-field-single' )
113
- ? "<input type='text' data-stripe='exp_month_year' class='single-masked'>"
114
- : "<input type='text' size='2' data-stripe='exp_month'>
115
- <span> / </span>
116
- <input type='text' size='4' data-stripe='exp_year'>";
117
- ?>
118
-
119
- <h2>
120
- <?php _e('Deposit Required: ', 'restaurant-reservations' ) . $payment_amount; ?>
121
- </h2>
122
-
123
- <div class='payment-errors'></div>
124
-
125
- <form
126
- action='#'
127
- method='POST'
128
- id='stripe-payment-form'
129
- data-booking_id='<?php echo $booking->ID ;?>'>
130
-
131
- <?php if( $SCA ) { ?>
132
-
133
- <div class='form-row'>
134
- <label><?php _e('Card Detail', 'restaurant-reservations'); ?></label>
135
- <span id="cardElement"></span>
136
- </div>
137
-
138
- <?php } else { ?>
139
-
140
- <div class='form-row'>
141
- <label><?php _e('Card Number', 'restaurant-reservations'); ?></label>
142
- <input type='text' size='20' autocomplete='off' data-stripe='card_number'/>
143
- </div>
144
- <div class='form-row'>
145
- <label><?php _e('CVC', 'restaurant-reservations'); ?></label>
146
- <input type='text' size='4' autocomplete='off' data-stripe='card_cvc'/>
147
- </div>
148
- <div class='form-row'>
149
- <label><?php _e('Expiration (MM/YYYY)', 'restaurant-reservations'); ?></label>
150
- <?php echo $cc_exp_single_field; ?>
151
- </div>
152
- <input type='hidden' name='action' value='rtb_stripe_booking_payment'/>
153
- <input type='hidden' name='currency' value='<?php echo $_gs( 'rtb-currency' ); ?>' data-stripe='currency' />
154
- <input type='hidden' name='payment_amount' value='<?php echo $booking->calculate_deposit(); ?>' />
155
- <input type='hidden' name='booking_id' value='<?php echo $booking->ID; ?>' />
156
-
157
- <?php } ?>
158
-
159
- <p class="stripe-payment-help-text">
160
- <?php _e( 'Please wait. Do not refresh until the button enables or the page reloads.', 'restaurant-reservations' ); ?>
161
- </p>
162
-
163
- <button type='submit' id='stripe-submit' <?php echo $btn_disabled; ?>>
164
- <?php _e( 'Make Deposit', 'restaurant-reservations'); ?>
165
- </button>
166
-
167
- </form>
168
- <?php
169
- }
170
-
171
- /**
172
- * Maybe process payment, if coming from Stripe payment form
173
- *
174
- * @return void Redirect and exit or return
175
- */
176
- public function process_payment()
177
- {
178
- global $rtb_controller;
179
-
180
- if ( ! isset( $_POST['stripeToken'] ) || ! isset( $_POST['booking_id'] ) ) {
181
- return;
182
- }
183
-
184
- // Function alias
185
- $_gs = [$rtb_controller->settings, 'get_setting'];
186
- $booking_id = $_POST['booking_id'];
187
-
188
- // Define the form's action parameter
189
- $booking_page = $_gs( 'booking-page' );
190
- if ( ! empty( $booking_page ) ) {
191
- $booking_page = get_permalink( $booking_page );
192
- }
193
- else {
194
- $booking_page = get_permalink();
195
- }
196
-
197
- // load the stripe libraries
198
- require_once(RTB_PLUGIN_DIR . '/lib/stripe/init.php');
199
-
200
- // retrieve the token generated by stripe.js
201
- $token = $_POST['stripeToken'];
202
-
203
- // JPY currency does not have any decimal palces
204
- $is_JPY = "JPY" != $_gs( 'rtb-currency' );
205
- $payment_amount = $is_JPY ? ( $_POST['payment_amount'] * 100 ) : $_POST['payment_amount'];
206
-
207
- $stripe_secret = 'test' == $_gs( 'rtb-stripe-mode' )
208
- ? $_gs( 'rtb-stripe-test-secret' )
209
- : $_gs( 'rtb-stripe-live-secret' );
210
-
211
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
212
- $booking = new rtbBooking();
213
- $booking->load_post( $booking_id );
214
-
215
- try {
216
- \Stripe\Stripe::setApiKey( $stripe_secret );
217
- $charge = \Stripe\Charge::create(array(
218
- 'amount' => $payment_amount,
219
- 'currency' => strtolower( $_gs( 'rtb-currency' ) ),
220
- 'card' => $token
221
- )
222
- );
223
-
224
- $booking->deposit = $is_JPY ? $payment_amount / 100 : $payment_amount;
225
- $booking->receipt_id = $charge->id;
226
-
227
- $booking->determine_status( true );
228
-
229
- $booking->insert_post_data();
230
-
231
- do_action( 'rtb_booking_paid', $booking );
232
-
233
- // redirect on successful payment
234
- $redirect = add_query_arg(
235
- array(
236
- 'payment' => 'paid',
237
- 'booking_id' => $booking_id
238
- ),
239
- $booking_page
240
- );
241
-
242
- } catch (Exception $e) {
243
-
244
- $booking->post_status = 'payment_failed';
245
- $booking->payment_failure_message = $e->getDeclineCode();
246
-
247
- $booking->insert_post_data();
248
-
249
- // redirect on failed payment
250
- $redirect = add_query_arg(
251
- array(
252
- 'payment' => 'failed',
253
- 'booking_id' => $booking_id,
254
- 'error_code' => urlencode( $e->getDeclineCode() )
255
- ),
256
- $booking_page
257
- );
258
- }
259
-
260
- // redirect back to our previous page with the added query variable
261
- wp_redirect($redirect); exit;
262
- }
263
-
264
- /**
265
- * Create Stripe payment intent for reservation deposits
266
- * Respond to AJAX/XHR request
267
- *
268
- * @since 2.2.8
269
- */
270
- public function create_stripe_pmtIntnt()
271
- {
272
- global $rtb_controller;
273
-
274
- $response = function ($success, $msg, $data = []) {
275
- echo json_encode(
276
- array_merge(
277
- [
278
- 'success' => $success,
279
- 'message' => $msg
280
- ],
281
- $data
282
- )
283
- );
284
-
285
- exit(0);
286
- };
287
-
288
- if( ! isset( $_POST['booking_id'] ) ) {
289
- $response(false, 'Invalid booking.');
290
- }
291
-
292
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
293
- $booking = new rtbBooking();
294
- $booking->load_post( $_POST['booking_id'] );
295
-
296
- $payment_amount = "JPY" != $rtb_controller->settings->get_setting( 'rtb-currency' )
297
- ? $booking->calculate_deposit() * 100
298
- : $booking->calculate_deposit();
299
-
300
- // load the stripe libraries
301
- require_once(RTB_PLUGIN_DIR . '/lib/stripe/init.php');
302
-
303
- $stripe_secret = 'test' == $rtb_controller->settings->get_setting( 'rtb-stripe-mode' )
304
- ? $rtb_controller->settings->get_setting( 'rtb-stripe-test-secret' )
305
- : $rtb_controller->settings->get_setting( 'rtb-stripe-live-secret' );
306
-
307
- try {
308
- \Stripe\Stripe::setApiKey( $stripe_secret );
309
-
310
- // $customer = \Stripe\Customer::create([
311
- // 'email' => $booking->email,
312
- // 'name' => $booking->name
313
- // ]);
314
-
315
- // $booking->stripe_customer_id = $customer->id;
316
- // $booking->insert_post_data();
317
-
318
- $metadata = array_filter([
319
- 'Booking ID' => $booking->ID,
320
- 'Email' => $booking->email,
321
- 'Name' => $booking->name,
322
- 'Date' => $booking->date,
323
- 'Party' => $booking->party
324
- ]);
325
-
326
- if( is_array( $booking->table ) && ! empty( $booking->table ) ) {
327
- $metadata['Table'] = implode('+', $booking->table);
328
- }
329
-
330
- $desc = implode(', ', $metadata );
331
- $stmt_desc = substr( implode( ';', $metadata ), 0, 22 );
332
-
333
- $intent = \Stripe\PaymentIntent::create([
334
- 'amount' => $payment_amount,
335
- 'currency' => $rtb_controller->settings->get_setting( 'rtb-currency' ),
336
- 'payment_method_types' => ['card'],
337
- 'receipt_email' => $booking->email,
338
- 'description' => apply_filters( 'rtb-stripe-payment-desc', $desc ),
339
- 'statement_descriptor' => apply_filters( 'rtb-stripe-payment-stmnt-desc', $stmt_desc ),
340
- 'metadata' => $metadata
341
- ]);
342
-
343
- $response(
344
- true,
345
- 'Payment Intent generated succsssfully',
346
- [
347
- 'clientSecret' => $intent->client_secret,
348
- 'name' => $booking->name,
349
- 'email' => $booking->email,
350
- ]
351
- );
352
- }
353
- catch(Exception $ex) {
354
- $response( false, 'Please try again.', ['error' => $ex->getError()] );
355
- }
356
- }
357
-
358
- /**
359
- * Stripe SCA payment's final status for reservation deposits
360
- * Respond to AJAX/XHR request
361
- *
362
- * @since 2.2.8
363
- */
364
- public function stripe_sca_succeed()
365
- {
366
- global $rtb_controller;
367
-
368
- $response = function ($success = false, $urlParams = '') {
369
- echo json_encode([
370
- 'success' => $success,
371
- 'urlParams' => $urlParams
372
- ]);
373
-
374
- exit(0);
375
- };
376
-
377
- $success = false;
378
- $urlParams = '';
379
-
380
- if( ! isset( $_POST['booking_id'] ) ) {
381
- $response();
382
- }
383
-
384
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
385
- $booking = new rtbBooking();
386
- $booking->load_post( $_POST['booking_id'] );
387
-
388
- if( isset( $_POST['success'] ) && 'false' != $_POST['success'] ) {
389
-
390
- $booking->deposit = "JPY" != $rtb_controller->settings->get_setting( 'rtb-currency' )
391
- ? $_POST['payment_amount'] / 100
392
- : $_POST['payment_amount'];
393
-
394
- $booking->receipt_id = $_POST['payment_id'];
395
- $booking->payment_paid();
396
-
397
- // urlParams on successful payment
398
- $success = true;
399
-
400
- $urlParams = array(
401
- 'payment' => 'paid',
402
- 'booking_id' => $booking->ID
403
- );
404
-
405
- }
406
- else {
407
- $payment_failure_message = ! empty( $_POST['message'] )
408
- ? $_POST['message'] : '';
409
-
410
- $booking->payment_failed( $payment_failure_message );
411
- }
412
-
413
- $response($success, $urlParams);
414
- }
415
-
416
- /**
417
- * Repopulate $booking with stripe meta information
418
- *
419
- * @param rtbBooking $booking
420
- * @param WP_Post $wp_post
421
- */
422
- public function populate_booking_stripe_info( rtbBooking $booking, $wp_post )
423
- {
424
- if ( is_array( $meta = get_post_meta( $booking->ID, 'rtb', true ) ) ) {
425
- $booking->stripe_customer_id = isset( $meta['stripe_customer_id'] )
426
- ? $meta['stripe_customer_id']
427
- : '';
428
- }
429
- }
430
-
431
- /**
432
- * Set $booking's default stripe meta information
433
- *
434
- * @param rtbBooking $booking
435
- * @param WP_Post $wp_post
436
- */
437
- public function default_booking_stripe_info( $info_list )
438
- {
439
- $info_list['stripe_customer_id'] = '';
440
-
441
- return $info_list;
442
- }
443
-
444
- public function save_booking_gateway_info ( $meta, rtbBooking $booking )
445
- {
446
- if ( isset( $booking->stripe_customer_id ) && !empty( $booking->stripe_customer_id ) ) {
447
- $meta['stripe_customer_id'] = $this->stripe_customer_id;
448
- }
449
-
450
- return $meta;
451
- }
452
- }
453
-
454
- }
455
-
456
- /**
457
- * Gateway has to register itself
458
- */
459
- add_filter(
460
- 'rtb-payment-gateway-register',
461
- ['rtbPaymentGatewayStripe', 'register_gateway']
462
  );
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbPaymentGatewayStripe' ) ) {
5
+ /**
6
+ * This class is responsible for payment processing via Stripe
7
+ *
8
+ * @since 2.3.0
9
+ */
10
+ class rtbPaymentGatewayStripe implements rtbPaymentGateway {
11
+
12
+ private static $_instance;
13
+
14
+ private final function __construct() {
15
+
16
+ $this->register_hooks();
17
+
18
+ }
19
+
20
+ public static function register_gateway (array $gateway_list )
21
+ {
22
+ return array_merge(
23
+ $gateway_list,
24
+ [
25
+ 'stripe' => [
26
+ 'label' => __( 'Stripe', 'restaurant-reservations' ),
27
+ 'instance' => self::get_instance()
28
+ ]
29
+ ]
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Get singleton instance of the class
35
+ *
36
+ * @return rtbPaymentGatewayStripe instance
37
+ */
38
+ public static function get_instance()
39
+ {
40
+ if( ! isset( self::$_instance ) ) {
41
+ self::$_instance = new rtbPaymentGatewayStripe();
42
+ }
43
+
44
+ return self::$_instance;
45
+ }
46
+
47
+ public function register_hooks()
48
+ {
49
+ add_action( 'rtb_booking_form_init', [$this, 'process_payment'] );
50
+
51
+ add_action( 'wp_ajax_rtb_stripe_get_intent', array( $this, 'create_stripe_pmtIntnt' ) );
52
+ add_action( 'wp_ajax_nopriv_rtb_stripe_get_intent', array( $this, 'create_stripe_pmtIntnt' ) );
53
+
54
+ add_action( 'wp_ajax_rtb_stripe_pmt_succeed', array( $this, 'stripe_sca_succeed' ) );
55
+ add_action( 'wp_ajax_nopriv_rtb_stripe_pmt_succeed', array( $this, 'stripe_sca_succeed' ) );
56
+
57
+ add_filter( 'rtb_booking_metadata_defaults', [$this, 'default_booking_stripe_info'], 30, 1 );
58
+ add_action( 'rtb_booking_load_post_data', [$this, 'populate_booking_stripe_info'], 30, 2 );
59
+ add_filter( 'rtb_insert_booking_metadata', [$this, 'save_booking_gateway_info'], 30, 2 );
60
+ }
61
+
62
+ public function print_payment_form( $booking )
63
+ {
64
+ global $rtb_controller;
65
+
66
+ // Function alias
67
+ $_gs = [$rtb_controller->settings, 'get_setting'];
68
+ $SCA = $_gs( 'rtb-stripe-sca' );
69
+
70
+ $btn_disabled = '';
71
+ $stripe_lib_version = 'v2';
72
+
73
+ if( $SCA )
74
+ {
75
+ $btn_disabled = "disabled='disabled'";
76
+ $stripe_lib_version = 'v3';
77
+ }
78
+
79
+ // Stripe Lib
80
+ wp_enqueue_script(
81
+ 'rtb-stripe',
82
+ "https://js.stripe.com/{$stripe_lib_version}/",
83
+ array( 'jquery' ),
84
+ RTB_VERSION,
85
+ true
86
+ );
87
+
88
+ // Stripe-JS processing logic
89
+ wp_enqueue_script(
90
+ 'rtb-stripe-payment',
91
+ RTB_PLUGIN_URL . '/assets/js/stripe-payment.js',
92
+ array( 'jquery', 'rtb-stripe' ),
93
+ RTB_VERSION,
94
+ true
95
+ );
96
+
97
+ wp_localize_script(
98
+ 'rtb-stripe-payment',
99
+ 'rtb_stripe_payment',
100
+ array(
101
+ 'stripe_mode' => $_gs( 'rtb-stripe-mode' ),
102
+ 'stripe_sca' => $SCA,
103
+ 'live_publishable_key' => $_gs( 'rtb-stripe-live-publishable' ),
104
+ 'test_publishable_key' => $_gs( 'rtb-stripe-test-publishable' ),
105
+ )
106
+ );
107
+
108
+ $payment_amount = $_gs( 'rtb-currency-symbol-location' ) == 'before'
109
+ ? $_gs( 'rtb-stripe-currency-symbol' ) . $booking->calculate_deposit()
110
+ : $booking->calculate_deposit() . $_gs( 'rtb-stripe-currency-symbol' );
111
+
112
+ $cc_exp_single_field = null != $_gs( 'rtb-expiration-field-single' )
113
+ ? "<input type='text' data-stripe='exp_month_year' class='single-masked'>"
114
+ : "<input type='text' size='2' data-stripe='exp_month'>
115
+ <span> / </span>
116
+ <input type='text' size='4' data-stripe='exp_year'>";
117
+ ?>
118
+
119
+ <h2>
120
+ <?php _e('Deposit Required: ', 'restaurant-reservations' ) . $payment_amount; ?>
121
+ </h2>
122
+
123
+ <div class='payment-errors'></div>
124
+
125
+ <form
126
+ action='#'
127
+ method='POST'
128
+ id='stripe-payment-form'
129
+ data-booking_id='<?php echo $booking->ID ;?>'>
130
+
131
+ <?php if( $SCA ) { ?>
132
+
133
+ <div class='form-row'>
134
+ <label><?php _e('Card Detail', 'restaurant-reservations'); ?></label>
135
+ <span id="cardElement"></span>
136
+ </div>
137
+
138
+ <?php } else { ?>
139
+
140
+ <div class='form-row'>
141
+ <label><?php _e('Card Number', 'restaurant-reservations'); ?></label>
142
+ <input type='text' size='20' autocomplete='off' data-stripe='card_number'/>
143
+ </div>
144
+ <div class='form-row'>
145
+ <label><?php _e('CVC', 'restaurant-reservations'); ?></label>
146
+ <input type='text' size='4' autocomplete='off' data-stripe='card_cvc'/>
147
+ </div>
148
+ <div class='form-row'>
149
+ <label><?php _e('Expiration (MM/YYYY)', 'restaurant-reservations'); ?></label>
150
+ <?php echo $cc_exp_single_field; ?>
151
+ </div>
152
+ <input type='hidden' name='action' value='rtb_stripe_booking_payment'/>
153
+ <input type='hidden' name='currency' value='<?php echo $_gs( 'rtb-currency' ); ?>' data-stripe='currency' />
154
+ <input type='hidden' name='payment_amount' value='<?php echo $booking->calculate_deposit(); ?>' />
155
+ <input type='hidden' name='booking_id' value='<?php echo $booking->ID; ?>' />
156
+
157
+ <?php } ?>
158
+
159
+ <p class="stripe-payment-help-text">
160
+ <?php _e( 'Please wait. Do not refresh until the button enables or the page reloads.', 'restaurant-reservations' ); ?>
161
+ </p>
162
+
163
+ <button type='submit' id='stripe-submit' <?php echo $btn_disabled; ?>>
164
+ <?php _e( 'Make Deposit', 'restaurant-reservations'); ?>
165
+ </button>
166
+
167
+ </form>
168
+ <?php
169
+ }
170
+
171
+ /**
172
+ * Maybe process payment, if coming from Stripe payment form
173
+ *
174
+ * @return void Redirect and exit or return
175
+ */
176
+ public function process_payment()
177
+ {
178
+ global $rtb_controller;
179
+
180
+ if ( ! isset( $_POST['stripeToken'] ) || ! isset( $_POST['booking_id'] ) ) {
181
+ return;
182
+ }
183
+
184
+ // Function alias
185
+ $_gs = [$rtb_controller->settings, 'get_setting'];
186
+ $booking_id = $_POST['booking_id'];
187
+
188
+ // Define the form's action parameter
189
+ $booking_page = $_gs( 'booking-page' );
190
+ if ( ! empty( $booking_page ) ) {
191
+ $booking_page = get_permalink( $booking_page );
192
+ }
193
+ else {
194
+ $booking_page = get_permalink();
195
+ }
196
+
197
+ // load the stripe libraries
198
+ require_once(RTB_PLUGIN_DIR . '/lib/stripe/init.php');
199
+
200
+ // retrieve the token generated by stripe.js
201
+ $token = $_POST['stripeToken'];
202
+
203
+ // JPY currency does not have any decimal palces
204
+ $is_JPY = "JPY" != $_gs( 'rtb-currency' );
205
+ $payment_amount = $is_JPY ? ( $_POST['payment_amount'] * 100 ) : $_POST['payment_amount'];
206
+
207
+ $stripe_secret = 'test' == $_gs( 'rtb-stripe-mode' )
208
+ ? $_gs( 'rtb-stripe-test-secret' )
209
+ : $_gs( 'rtb-stripe-live-secret' );
210
+
211
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
212
+ $booking = new rtbBooking();
213
+ $booking->load_post( $booking_id );
214
+
215
+ try {
216
+ \Stripe\Stripe::setApiKey( $stripe_secret );
217
+ $charge = \Stripe\Charge::create(array(
218
+ 'amount' => $payment_amount,
219
+ 'currency' => strtolower( $_gs( 'rtb-currency' ) ),
220
+ 'card' => $token
221
+ )
222
+ );
223
+
224
+ $booking->deposit = $is_JPY ? $payment_amount / 100 : $payment_amount;
225
+ $booking->receipt_id = $charge->id;
226
+
227
+ $booking->determine_status( true );
228
+
229
+ $booking->insert_post_data();
230
+
231
+ do_action( 'rtb_booking_paid', $booking );
232
+
233
+ // redirect on successful payment
234
+ $redirect = add_query_arg(
235
+ array(
236
+ 'payment' => 'paid',
237
+ 'booking_id' => $booking_id
238
+ ),
239
+ $booking_page
240
+ );
241
+
242
+ } catch (Exception $e) {
243
+
244
+ $booking->post_status = 'payment_failed';
245
+ $booking->payment_failure_message = $e->getDeclineCode();
246
+
247
+ $booking->insert_post_data();
248
+
249
+ // redirect on failed payment
250
+ $redirect = add_query_arg(
251
+ array(
252
+ 'payment' => 'failed',
253
+ 'booking_id' => $booking_id,
254
+ 'error_code' => urlencode( $e->getDeclineCode() )
255
+ ),
256
+ $booking_page
257
+ );
258
+ }
259
+
260
+ // redirect back to our previous page with the added query variable
261
+ wp_redirect($redirect); exit;
262
+ }
263
+
264
+ /**
265
+ * Create Stripe payment intent for reservation deposits
266
+ * Respond to AJAX/XHR request
267
+ *
268
+ * @since 2.2.8
269
+ */
270
+ public function create_stripe_pmtIntnt()
271
+ {
272
+ global $rtb_controller;
273
+
274
+ $response = function ($success, $msg, $data = []) {
275
+ echo json_encode(
276
+ array_merge(
277
+ [
278
+ 'success' => $success,
279
+ 'message' => $msg
280
+ ],
281
+ $data
282
+ )
283
+ );
284
+
285
+ exit(0);
286
+ };
287
+
288
+ if( ! isset( $_POST['booking_id'] ) ) {
289
+ $response(false, 'Invalid booking.');
290
+ }
291
+
292
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
293
+ $booking = new rtbBooking();
294
+ $booking->load_post( $_POST['booking_id'] );
295
+
296
+ $payment_amount = "JPY" != $rtb_controller->settings->get_setting( 'rtb-currency' )
297
+ ? $booking->calculate_deposit() * 100
298
+ : $booking->calculate_deposit();
299
+
300
+ // load the stripe libraries
301
+ require_once(RTB_PLUGIN_DIR . '/lib/stripe/init.php');
302
+
303
+ $stripe_secret = 'test' == $rtb_controller->settings->get_setting( 'rtb-stripe-mode' )
304
+ ? $rtb_controller->settings->get_setting( 'rtb-stripe-test-secret' )
305
+ : $rtb_controller->settings->get_setting( 'rtb-stripe-live-secret' );
306
+
307
+ try {
308
+ \Stripe\Stripe::setApiKey( $stripe_secret );
309
+
310
+ // $customer = \Stripe\Customer::create([
311
+ // 'email' => $booking->email,
312
+ // 'name' => $booking->name
313
+ // ]);
314
+
315
+ // $booking->stripe_customer_id = $customer->id;
316
+ // $booking->insert_post_data();
317
+
318
+ $metadata = array_filter([
319
+ 'Booking ID' => $booking->ID,
320
+ 'Email' => $booking->email,
321
+ 'Name' => $booking->name,
322
+ 'Date' => $booking->date,
323
+ 'Party' => $booking->party
324
+ ]);
325
+
326
+ if( is_array( $booking->table ) && ! empty( $booking->table ) ) {
327
+ $metadata['Table'] = implode('+', $booking->table);
328
+ }
329
+
330
+ $desc = implode(', ', $metadata );
331
+ $stmt_desc = substr( implode( ';', $metadata ), 0, 22 );
332
+
333
+ $intent = \Stripe\PaymentIntent::create([
334
+ 'amount' => $payment_amount,
335
+ 'currency' => $rtb_controller->settings->get_setting( 'rtb-currency' ),
336
+ 'payment_method_types' => ['card'],
337
+ 'receipt_email' => $booking->email,
338
+ 'description' => apply_filters( 'rtb-stripe-payment-desc', $desc ),
339
+ 'statement_descriptor' => apply_filters( 'rtb-stripe-payment-stmnt-desc', $stmt_desc ),
340
+ 'metadata' => $metadata
341
+ ]);
342
+
343
+ $response(
344
+ true,
345
+ 'Payment Intent generated succsssfully',
346
+ [
347
+ 'clientSecret' => $intent->client_secret,
348
+ 'name' => $booking->name,
349
+ 'email' => $booking->email,
350
+ ]
351
+ );
352
+ }
353
+ catch(Exception $ex) {
354
+ $response( false, 'Please try again.', ['error' => $ex->getError()] );
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Stripe SCA payment's final status for reservation deposits
360
+ * Respond to AJAX/XHR request
361
+ *
362
+ * @since 2.2.8
363
+ */
364
+ public function stripe_sca_succeed()
365
+ {
366
+ global $rtb_controller;
367
+
368
+ $response = function ($success = false, $urlParams = '') {
369
+ echo json_encode([
370
+ 'success' => $success,
371
+ 'urlParams' => $urlParams
372
+ ]);
373
+
374
+ exit(0);
375
+ };
376
+
377
+ $success = false;
378
+ $urlParams = '';
379
+
380
+ if( ! isset( $_POST['booking_id'] ) ) {
381
+ $response();
382
+ }
383
+
384
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
385
+ $booking = new rtbBooking();
386
+ $booking->load_post( $_POST['booking_id'] );
387
+
388
+ if( isset( $_POST['success'] ) && 'false' != $_POST['success'] ) {
389
+
390
+ $booking->deposit = "JPY" != $rtb_controller->settings->get_setting( 'rtb-currency' )
391
+ ? $_POST['payment_amount'] / 100
392
+ : $_POST['payment_amount'];
393
+
394
+ $booking->receipt_id = $_POST['payment_id'];
395
+ $booking->payment_paid();
396
+
397
+ // urlParams on successful payment
398
+ $success = true;
399
+
400
+ $urlParams = array(
401
+ 'payment' => 'paid',
402
+ 'booking_id' => $booking->ID
403
+ );
404
+
405
+ }
406
+ else {
407
+ $payment_failure_message = ! empty( $_POST['message'] )
408
+ ? $_POST['message'] : '';
409
+
410
+ $booking->payment_failed( $payment_failure_message );
411
+ }
412
+
413
+ $response($success, $urlParams);
414
+ }
415
+
416
+ /**
417
+ * Repopulate $booking with stripe meta information
418
+ *
419
+ * @param rtbBooking $booking
420
+ * @param WP_Post $wp_post
421
+ */
422
+ public function populate_booking_stripe_info( rtbBooking $booking, $wp_post )
423
+ {
424
+ if ( is_array( $meta = get_post_meta( $booking->ID, 'rtb', true ) ) ) {
425
+ $booking->stripe_customer_id = isset( $meta['stripe_customer_id'] )
426
+ ? $meta['stripe_customer_id']
427
+ : '';
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Set $booking's default stripe meta information
433
+ *
434
+ * @param rtbBooking $booking
435
+ * @param WP_Post $wp_post
436
+ */
437
+ public function default_booking_stripe_info( $info_list )
438
+ {
439
+ $info_list['stripe_customer_id'] = '';
440
+
441
+ return $info_list;
442
+ }
443
+
444
+ public function save_booking_gateway_info ( $meta, rtbBooking $booking )
445
+ {
446
+ if ( isset( $booking->stripe_customer_id ) && !empty( $booking->stripe_customer_id ) ) {
447
+ $meta['stripe_customer_id'] = $this->stripe_customer_id;
448
+ }
449
+
450
+ return $meta;
451
+ }
452
+ }
453
+
454
+ }
455
+
456
+ /**
457
+ * Gateway has to register itself
458
+ */
459
+ add_filter(
460
+ 'rtb-payment-gateway-register',
461
+ ['rtbPaymentGatewayStripe', 'register_gateway']
462
  );
includes/PaymentManager.class.php CHANGED
@@ -1,383 +1,388 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbPaymentManager' ) ) {
5
- /**
6
- *
7
- * This class registers all the payment gateways and
8
- * acts as a bridge between the rest of the plugin and payment gateways.
9
- * It renders forms, processes payments, handles IPNs etc
10
- *
11
- * @since 2.3.0
12
- */
13
- class rtbPaymentManager {
14
-
15
- /**
16
- * Default values for Payment manager settings
17
- */
18
- public $defaults = array();
19
-
20
- /**
21
- * All the available payment processing gateway with their internal name,
22
- * display name and the PHP Class
23
- *
24
- * @var array [
25
- * 'paypal' => [
26
- * 'name' => 'PayPal',
27
- * 'instance' => 'new rtbPaymentGatewayPayPal()'
28
- * ]
29
- * ]
30
- */
31
- public $available_gateway_list = array();
32
-
33
- /**
34
- * List of enabled gateway list from admin
35
- *
36
- * @var array ['paypal']
37
- */
38
- public $enabled_gateway_list = array();
39
-
40
- /**
41
- * Gateway selected for the current booking
42
- * @var string
43
- */
44
- public $in_use_gateway = '';
45
-
46
- /**
47
- * Booking object. ID property does not exist when no booking loaded
48
- * @var rtbBooking
49
- */
50
- public $booking;
51
-
52
- public $booking_form_field_slug = 'payment-gateway';
53
-
54
- /**
55
- * Constructor
56
- */
57
- public function __construct()
58
- {
59
- $this->load_basics();
60
-
61
- do_action( 'rtb_payment_manager_init' );
62
- }
63
-
64
- /**
65
- * Acts like a constructor
66
- */
67
- public function load_basics()
68
- {
69
- global $rtb_controller;
70
-
71
- require_once RTB_PLUGIN_DIR . "/includes/PaymentGateway.interface.php";
72
- require_once RTB_PLUGIN_DIR . "/includes/PaymentGatewayPayPal.class.php";
73
- require_once RTB_PLUGIN_DIR . "/includes/PaymentGatewayStripe.class.php";
74
-
75
- do_action( 'rtb_payment_manager_load_gateways' );
76
-
77
- $this->available_gateway_list = apply_filters(
78
- 'rtb-payment-gateway-register',
79
- $this->available_gateway_list
80
- );
81
-
82
- $this->strip_invalid_gateway();
83
-
84
- $this->enabled_gateway_list = $rtb_controller->settings->get_setting( 'rtb-payment-gateway' );
85
-
86
- // Temporary, because migration function do not work on automatic plugin updates
87
- $this->enabled_gateway_list = is_array( $this->enabled_gateway_list )
88
- ? $this->enabled_gateway_list
89
- : [ $this->enabled_gateway_list ];
90
-
91
- $this->enabled_gateway_list = apply_filters(
92
- 'rtb-payment-active-gateway',
93
- $this->enabled_gateway_list,
94
- $this->available_gateway_list
95
- );
96
-
97
- // if multiple gateways enabled, print list to ask for one gateway
98
- if ( is_array( $this->enabled_gateway_list ) and 1 < count( $this->enabled_gateway_list ) ) {
99
- add_filter( 'rtb_booking_form_fields', [$this, 'add_field_booking_form_gateway'], 30, 3 );
100
- }
101
-
102
- // Determine $in_use_gateway
103
- add_action( 'rtb_validate_booking_submission', [$this, 'validate_booking_form_gateway'] );
104
-
105
- // Save gateway selected/used for booking as booking meta
106
- add_filter( 'rtb_insert_booking_metadata', [$this, 'save_booking_gateway_used'], 30, 2 );
107
-
108
- // Repopulate gateway information
109
- add_action( 'rtb_booking_load_post_data', [$this, 'populate_booking_gateway_used'], 30, 2 );
110
- }
111
-
112
- /**
113
- * Get available gateway list with gateway slug as key and label as value
114
- *
115
- * @return array
116
- */
117
- public function get_available_gateway_list()
118
- {
119
- $list = [];
120
-
121
- foreach ($this->available_gateway_list as $key => $value) {
122
- $list[$key] = $value['label'];
123
- }
124
-
125
- return $list;
126
- }
127
-
128
- /**
129
- * Get enabled gateway list with gateway slug as key and label as value
130
- *
131
- * @return array
132
- */
133
- public function get_enabled_gateway_list()
134
- {
135
- $list = [];
136
-
137
- foreach ($this->enabled_gateway_list as $key) {
138
- $list[$key] = $this->available_gateway_list[$key]['label'];
139
- }
140
-
141
- return $list;
142
- }
143
-
144
- /**
145
- * Is Payments functionality enabled?
146
- *
147
- * @return boolean
148
- */
149
- public function is_payment_enabled()
150
- {
151
- global $rtb_controller;
152
-
153
- return (
154
- $rtb_controller->settings->get_setting( 'require-deposit' )
155
- &&
156
- 0 < count( $this->get_enabled_gateway_list() )
157
- );
158
- }
159
-
160
- /**
161
- * Add the payment gateway selector field in the bookgin form
162
- *
163
- * @param array $fields Booking form field array. For more info, refer to
164
- * rtbSettings::get_booking_form_fields()
165
- * @param stdObject $request
166
- * @param array $args
167
- */
168
- public function add_field_booking_form_gateway( $fields, $request, $args )
169
- {
170
- if ( ! $this->is_payment_enabled() ) {
171
- return $fields;
172
- }
173
-
174
- /**
175
- * This is different from admin setting, that is why, to reduce the confusion
176
- * we use variabel instead of direct field name
177
- *
178
- * @var string rtb-payment-gateway
179
- */
180
- $prefixed_field_slug = "rtb-{$this->booking_form_field_slug}";
181
-
182
- $payment_gateway_field = [
183
- $this->booking_form_field_slug => [
184
- // 'legend' => __( 'Payment', 'restaurant-reservations' ),
185
- 'fields' => [
186
- // Field names are prefixed with "rtb-" while rendering field's HTML
187
- 'payment-gateway' => [
188
- 'required' => true,
189
- 'title' => __( 'Payment Gateway', 'restaurant-reservations' ),
190
- 'callback' => 'rtb_print_form_select_field',
191
- 'callback_args' => [
192
- 'options' => $this->get_enabled_gateway_list(),
193
- 'empty_option' => true
194
- ],
195
- 'request_input' => isset( $request->raw_input[$prefixed_field_slug] )
196
- ? $request->raw_input[$prefixed_field_slug]
197
- : (
198
- property_exists($request, $this->booking_form_field_slug)
199
- ? $request->{$this->booking_form_field_slug}
200
- : ''
201
- )
202
- ]
203
- ]
204
- ]
205
- ];
206
-
207
- return array_merge( $fields, $payment_gateway_field );
208
- }
209
-
210
- /**
211
- * Validate the payment gateway option
212
- *
213
- * $booking is not set yet
214
- *
215
- * @param stdObject $request
216
- */
217
- public function validate_booking_form_gateway( $request )
218
- {
219
- if ( ! $this->is_payment_enabled() ) {
220
- return;
221
- }
222
-
223
- $prefixed_field_slug = "rtb-{$this->booking_form_field_slug}";
224
-
225
- // Do not validate if only one gateway enabled
226
- if ( 1 === count( $this->enabled_gateway_list ) ) {
227
- $this->in_use_gateway = $this->enabled_gateway_list[0];
228
- }
229
- elseif (
230
- array_key_exists( $prefixed_field_slug, $_POST )
231
- &&
232
- ! empty( $_POST[$prefixed_field_slug] )
233
- &&
234
- in_array( $_POST[$prefixed_field_slug], $this->enabled_gateway_list )
235
- )
236
- {
237
- $this->in_use_gateway = $_POST[$prefixed_field_slug];
238
- }
239
- else {
240
- $request->validation_errors[] = array(
241
- 'field' => $prefixed_field_slug,
242
- 'message' => __( 'Please select a valid payment gateway.', 'restaurant-reservations' )
243
- );
244
- }
245
-
246
- if ( $this->isset_gateway_in_use() ) {
247
- $request->{$this->booking_form_field_slug} = $this->in_use_gateway;
248
- }
249
- }
250
-
251
- /**
252
- * Attach payment gateway information with the booking
253
- *
254
- * @param array $meta
255
- * @param rtbBooking $booking
256
- *
257
- * @return array
258
- */
259
- public function save_booking_gateway_used( $meta, rtbBooking $booking )
260
- {
261
- if ( isset( $booking->{$this->booking_form_field_slug} ) ) {
262
- $meta[$this->booking_form_field_slug] = $booking->{$this->booking_form_field_slug};
263
- }
264
-
265
- return $meta;
266
- }
267
-
268
- /**
269
- * Repopulate $booking with gateway information
270
- *
271
- * @param rtbBooking $booking
272
- * @param WP_Post $wp_post
273
- */
274
- public function populate_booking_gateway_used( rtbBooking $booking, $wp_post )
275
- {
276
- if ( is_array( $meta = get_post_meta( $booking->ID, 'rtb', true ) ) ) {
277
- $booking->{$this->booking_form_field_slug} = isset( $meta[$this->booking_form_field_slug] )
278
- ? $meta[$this->booking_form_field_slug]
279
- : '';
280
- }
281
- }
282
-
283
- /**
284
- * Print the payment form's HTML code, after a new booking has been inserted
285
- * notices.
286
- *
287
- * $booking must be set before this.
288
- */
289
- public function print_payment_form()
290
- {
291
- global $rtb_controller;
292
-
293
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
294
-
295
- if ( ! $this->isset_gateway_in_use() ) {
296
- $this->set_gateway_in_use( $this->booking->{$this->booking_form_field_slug} );
297
- }
298
-
299
- if (
300
- in_array( $this->in_use_gateway, $this->enabled_gateway_list )
301
- &&
302
- property_exists($this->booking, 'ID')
303
- )
304
- {
305
- $gateway = $this->available_gateway_list[$this->in_use_gateway]['instance'];
306
-
307
- $gateway->print_payment_form( $this->booking );
308
- }
309
- else {
310
- $this->print_invalid_gateway();
311
- }
312
- }
313
-
314
- public function print_invalid_gateway()
315
- {
316
- echo __(
317
- 'Invalid gateweay selected. Please contact us for the confirmation.',
318
- 'restaurant-reservations'
319
- );
320
- }
321
-
322
- public function process_payment()
323
- {
324
- // code...
325
- }
326
-
327
- public function is_payment_processed()
328
- {
329
- // code...
330
- }
331
-
332
- public function payment_processing_status()
333
- {
334
- // code...
335
- }
336
-
337
- /**
338
- * Set booking object
339
- *
340
- * @param rtbBooking $booking
341
- */
342
- public function set_booking(rtbBooking $booking)
343
- {
344
- $this->booking = $booking;
345
-
346
- return $this;
347
- }
348
-
349
- public function set_gateway_in_use( $gateway )
350
- {
351
- $this->in_use_gateway = $gateway;
352
- }
353
-
354
- public function get_gateway_in_use()
355
- {
356
- if ( $this->isset_gateway_in_use() ) {
357
- return $this->in_use_gateway;
358
- }
359
-
360
- return '';
361
- }
362
-
363
- public function isset_gateway_in_use()
364
- {
365
- return in_array( $this->in_use_gateway, $this->enabled_gateway_list );
366
- }
367
-
368
- /**
369
- * Remove any class registered as payment gateway without implementing the
370
- * payment gateway interface
371
- */
372
- public function strip_invalid_gateway() {
373
-
374
- foreach ( $this->available_gateway_list as $gateway => $data ) {
375
-
376
- if ( $data['instance'] instanceof rtbPaymentGateway ) { continue; }
377
-
378
- unset( $this->available_gateway_list[$gateway] );
379
- }
380
- }
381
- }
382
-
 
 
 
 
 
383
  }
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbPaymentManager' ) ) {
5
+ /**
6
+ *
7
+ * This class registers all the payment gateways and
8
+ * acts as a bridge between the rest of the plugin and payment gateways.
9
+ * It renders forms, processes payments, handles IPNs etc
10
+ *
11
+ * @since 2.3.0
12
+ */
13
+ class rtbPaymentManager {
14
+
15
+ /**
16
+ * Default values for Payment manager settings
17
+ */
18
+ public $defaults = array();
19
+
20
+ /**
21
+ * All the available payment processing gateway with their internal name,
22
+ * display name and the PHP Class
23
+ *
24
+ * @var array [
25
+ * 'paypal' => [
26
+ * 'name' => 'PayPal',
27
+ * 'instance' => 'new rtbPaymentGatewayPayPal()'
28
+ * ]
29
+ * ]
30
+ */
31
+ public $available_gateway_list = array();
32
+
33
+ /**
34
+ * List of enabled gateway list from admin
35
+ *
36
+ * @var array ['paypal']
37
+ */
38
+ public $enabled_gateway_list = array();
39
+
40
+ /**
41
+ * Gateway selected for the current booking
42
+ * @var string
43
+ */
44
+ public $in_use_gateway = '';
45
+
46
+ /**
47
+ * Booking object. ID property does not exist when no booking loaded
48
+ * @var rtbBooking
49
+ */
50
+ public $booking;
51
+
52
+ public $booking_form_field_slug = 'payment-gateway';
53
+
54
+ /**
55
+ * Constructor
56
+ */
57
+ public function __construct()
58
+ {
59
+ $this->load_basics();
60
+
61
+ do_action( 'rtb_payment_manager_init' );
62
+ }
63
+
64
+ /**
65
+ * Acts like a constructor
66
+ */
67
+ public function load_basics()
68
+ {
69
+ global $rtb_controller;
70
+
71
+ require_once RTB_PLUGIN_DIR . "/includes/PaymentGateway.interface.php";
72
+ require_once RTB_PLUGIN_DIR . "/includes/PaymentGatewayPayPal.class.php";
73
+ require_once RTB_PLUGIN_DIR . "/includes/PaymentGatewayStripe.class.php";
74
+
75
+ do_action( 'rtb_payment_manager_load_gateways' );
76
+
77
+ $this->available_gateway_list = apply_filters(
78
+ 'rtb-payment-gateway-register',
79
+ $this->available_gateway_list
80
+ );
81
+
82
+ $this->strip_invalid_gateway();
83
+
84
+ $this->enabled_gateway_list = $rtb_controller->settings->get_setting( 'rtb-payment-gateway' );
85
+
86
+ // Temporary, because migration function do not work on automatic plugin updates
87
+ $this->enabled_gateway_list = is_array( $this->enabled_gateway_list )
88
+ ? $this->enabled_gateway_list
89
+ : [ $this->enabled_gateway_list ];
90
+
91
+ $this->enabled_gateway_list = apply_filters(
92
+ 'rtb-payment-active-gateway',
93
+ $this->enabled_gateway_list,
94
+ $this->available_gateway_list
95
+ );
96
+
97
+ // if multiple gateways enabled, print list to ask for one gateway
98
+ if ( is_array( $this->enabled_gateway_list ) and 1 < count( $this->enabled_gateway_list ) ) {
99
+ add_filter( 'rtb_booking_form_fields', [$this, 'add_field_booking_form_gateway'], 30, 3 );
100
+ }
101
+
102
+ // Determine $in_use_gateway
103
+ add_action( 'rtb_validate_booking_submission', [$this, 'validate_booking_form_gateway'] );
104
+
105
+ // Save gateway selected/used for booking as booking meta
106
+ add_filter( 'rtb_insert_booking_metadata', [$this, 'save_booking_gateway_used'], 30, 2 );
107
+
108
+ // Repopulate gateway information
109
+ add_action( 'rtb_booking_load_post_data', [$this, 'populate_booking_gateway_used'], 30, 2 );
110
+ }
111
+
112
+ /**
113
+ * Get available gateway list with gateway slug as key and label as value
114
+ *
115
+ * @return array
116
+ */
117
+ public function get_available_gateway_list()
118
+ {
119
+ $list = [];
120
+
121
+ foreach ($this->available_gateway_list as $key => $value) {
122
+ $list[$key] = $value['label'];
123
+ }
124
+
125
+ return $list;
126
+ }
127
+
128
+ /**
129
+ * Get enabled gateway list with gateway slug as key and label as value
130
+ *
131
+ * @return array
132
+ */
133
+ public function get_enabled_gateway_list()
134
+ {
135
+ $list = [];
136
+
137
+ // Not gateway has been enabled
138
+ if( 1 > count( $this->enabled_gateway_list ) ) {
139
+ return $list;
140
+ }
141
+
142
+ foreach ($this->enabled_gateway_list as $key) {
143
+ $list[$key] = $this->available_gateway_list[$key]['label'];
144
+ }
145
+
146
+ return $list;
147
+ }
148
+
149
+ /**
150
+ * Is Payments functionality enabled?
151
+ *
152
+ * @return boolean
153
+ */
154
+ public function is_payment_enabled()
155
+ {
156
+ global $rtb_controller;
157
+
158
+ return (
159
+ $rtb_controller->settings->get_setting( 'require-deposit' )
160
+ &&
161
+ 0 < count( $this->enabled_gateway_list )
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Add the payment gateway selector field in the bookgin form
167
+ *
168
+ * @param array $fields Booking form field array. For more info, refer to
169
+ * rtbSettings::get_booking_form_fields()
170
+ * @param stdObject $request
171
+ * @param array $args
172
+ */
173
+ public function add_field_booking_form_gateway( $fields, $request, $args )
174
+ {
175
+ if ( ! $this->is_payment_enabled() ) {
176
+ return $fields;
177
+ }
178
+
179
+ /**
180
+ * This is different from admin setting, that is why, to reduce the confusion
181
+ * we use variabel instead of direct field name
182
+ *
183
+ * @var string rtb-payment-gateway
184
+ */
185
+ $prefixed_field_slug = "rtb-{$this->booking_form_field_slug}";
186
+
187
+ $payment_gateway_field = [
188
+ $this->booking_form_field_slug => [
189
+ // 'legend' => __( 'Payment', 'restaurant-reservations' ),
190
+ 'fields' => [
191
+ // Field names are prefixed with "rtb-" while rendering field's HTML
192
+ 'payment-gateway' => [
193
+ 'required' => true,
194
+ 'title' => __( 'Payment Gateway', 'restaurant-reservations' ),
195
+ 'callback' => 'rtb_print_form_select_field',
196
+ 'callback_args' => [
197
+ 'options' => $this->get_enabled_gateway_list(),
198
+ 'empty_option' => true
199
+ ],
200
+ 'request_input' => isset( $request->raw_input[$prefixed_field_slug] )
201
+ ? $request->raw_input[$prefixed_field_slug]
202
+ : (
203
+ property_exists($request, $this->booking_form_field_slug)
204
+ ? $request->{$this->booking_form_field_slug}
205
+ : ''
206
+ )
207
+ ]
208
+ ]
209
+ ]
210
+ ];
211
+
212
+ return array_merge( $fields, $payment_gateway_field );
213
+ }
214
+
215
+ /**
216
+ * Validate the payment gateway option
217
+ *
218
+ * $booking is not set yet
219
+ *
220
+ * @param stdObject $request
221
+ */
222
+ public function validate_booking_form_gateway( $request )
223
+ {
224
+ if ( ! $this->is_payment_enabled() ) {
225
+ return;
226
+ }
227
+
228
+ $prefixed_field_slug = "rtb-{$this->booking_form_field_slug}";
229
+
230
+ // Do not validate if only one gateway enabled
231
+ if ( 1 === count( $this->enabled_gateway_list ) ) {
232
+ $this->in_use_gateway = $this->enabled_gateway_list[0];
233
+ }
234
+ elseif (
235
+ array_key_exists( $prefixed_field_slug, $_POST )
236
+ &&
237
+ ! empty( $_POST[$prefixed_field_slug] )
238
+ &&
239
+ in_array( $_POST[$prefixed_field_slug], $this->enabled_gateway_list )
240
+ )
241
+ {
242
+ $this->in_use_gateway = $_POST[$prefixed_field_slug];
243
+ }
244
+ else {
245
+ $request->validation_errors[] = array(
246
+ 'field' => $prefixed_field_slug,
247
+ 'message' => __( 'Please select a valid payment gateway.', 'restaurant-reservations' )
248
+ );
249
+ }
250
+
251
+ if ( $this->isset_gateway_in_use() ) {
252
+ $request->{$this->booking_form_field_slug} = $this->in_use_gateway;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Attach payment gateway information with the booking
258
+ *
259
+ * @param array $meta
260
+ * @param rtbBooking $booking
261
+ *
262
+ * @return array
263
+ */
264
+ public function save_booking_gateway_used( $meta, rtbBooking $booking )
265
+ {
266
+ if ( isset( $booking->{$this->booking_form_field_slug} ) ) {
267
+ $meta[$this->booking_form_field_slug] = $booking->{$this->booking_form_field_slug};
268
+ }
269
+
270
+ return $meta;
271
+ }
272
+
273
+ /**
274
+ * Repopulate $booking with gateway information
275
+ *
276
+ * @param rtbBooking $booking
277
+ * @param WP_Post $wp_post
278
+ */
279
+ public function populate_booking_gateway_used( rtbBooking $booking, $wp_post )
280
+ {
281
+ if ( is_array( $meta = get_post_meta( $booking->ID, 'rtb', true ) ) ) {
282
+ $booking->{$this->booking_form_field_slug} = isset( $meta[$this->booking_form_field_slug] )
283
+ ? $meta[$this->booking_form_field_slug]
284
+ : '';
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Print the payment form's HTML code, after a new booking has been inserted
290
+ * notices.
291
+ *
292
+ * $booking must be set before this.
293
+ */
294
+ public function print_payment_form()
295
+ {
296
+ global $rtb_controller;
297
+
298
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
299
+
300
+ if ( ! $this->isset_gateway_in_use() ) {
301
+ $this->set_gateway_in_use( $this->booking->{$this->booking_form_field_slug} );
302
+ }
303
+
304
+ if (
305
+ in_array( $this->in_use_gateway, $this->enabled_gateway_list )
306
+ &&
307
+ property_exists($this->booking, 'ID')
308
+ )
309
+ {
310
+ $gateway = $this->available_gateway_list[$this->in_use_gateway]['instance'];
311
+
312
+ $gateway->print_payment_form( $this->booking );
313
+ }
314
+ else {
315
+ $this->print_invalid_gateway();
316
+ }
317
+ }
318
+
319
+ public function print_invalid_gateway()
320
+ {
321
+ echo __(
322
+ 'Invalid gateweay selected. Please contact us for the confirmation.',
323
+ 'restaurant-reservations'
324
+ );
325
+ }
326
+
327
+ public function process_payment()
328
+ {
329
+ // code...
330
+ }
331
+
332
+ public function is_payment_processed()
333
+ {
334
+ // code...
335
+ }
336
+
337
+ public function payment_processing_status()
338
+ {
339
+ // code...
340
+ }
341
+
342
+ /**
343
+ * Set booking object
344
+ *
345
+ * @param rtbBooking $booking
346
+ */
347
+ public function set_booking(rtbBooking $booking)
348
+ {
349
+ $this->booking = $booking;
350
+
351
+ return $this;
352
+ }
353
+
354
+ public function set_gateway_in_use( $gateway )
355
+ {
356
+ $this->in_use_gateway = $gateway;
357
+ }
358
+
359
+ public function get_gateway_in_use()
360
+ {
361
+ if ( $this->isset_gateway_in_use() ) {
362
+ return $this->in_use_gateway;
363
+ }
364
+
365
+ return '';
366
+ }
367
+
368
+ public function isset_gateway_in_use()
369
+ {
370
+ return in_array( $this->in_use_gateway, $this->enabled_gateway_list );
371
+ }
372
+
373
+ /**
374
+ * Remove any class registered as payment gateway without implementing the
375
+ * payment gateway interface
376
+ */
377
+ public function strip_invalid_gateway() {
378
+
379
+ foreach ( $this->available_gateway_list as $gateway => $data ) {
380
+
381
+ if ( $data['instance'] instanceof rtbPaymentGateway ) { continue; }
382
+
383
+ unset( $this->available_gateway_list[$gateway] );
384
+ }
385
+ }
386
+ }
387
+
388
  }
includes/Query.class.php CHANGED
@@ -1,253 +1,273 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbQuery' ) ) {
5
- /**
6
- * Class to handle common queries used to pull bookings from
7
- * the database.
8
- *
9
- * Bookings can be retrieved with specific date ranges, common
10
- * date params (today/upcoming), etc. This class is intended for
11
- * the base plugin as well as extensions or custom projects which
12
- * need a stable mechanism for reliably retrieving bookings data.
13
- *
14
- * Queries return an array of rtbBooking objects.
15
- *
16
- * @since 1.4.1
17
- */
18
- class rtbQuery {
19
-
20
- /**
21
- * Bookings
22
- *
23
- * Array of bookings retrieved after get_bookings() is called
24
- *
25
- * @since 1.4.1
26
- */
27
- public $bookings = array();
28
-
29
- /**
30
- * Query args
31
- *
32
- * Passed to WP_Query
33
- * http://codex.wordpress.org/Class_Reference/WP_Query
34
- *
35
- * @since 1.4.1
36
- */
37
- public $args = array();
38
-
39
- /**
40
- * Query context
41
- *
42
- * Defines the context in which the query is run.
43
- * Useful for hooking into the right query without
44
- * tampering with others.
45
- *
46
- * @since 1.4.1
47
- */
48
- public $context;
49
-
50
- /**
51
- * Instantiate the query with an array of arguments
52
- *
53
- * This supports all WP_Query args as well as several
54
- * short-hand arguments for common needs. Short-hands
55
- * include:
56
- *
57
- * date_range string today|upcoming|dates
58
- * start_date string don't get bookings before this
59
- * end_date string don't get bookings after this
60
- *
61
- * @see rtbQuery::prepare_args()
62
- * @param args array Options to tailor the query
63
- * @param context string Context for the query, used
64
- * in filters
65
- * @since 1.4.1
66
- */
67
- public function __construct( $args = array(), $context = '' ) {
68
-
69
- global $rtb_controller;
70
-
71
- $defaults = array(
72
- 'post_type' => RTB_BOOKING_POST_TYPE,
73
- 'posts_per_page' => 10,
74
- 'date_range' => 'upcoming',
75
- 'post_status' => array_keys( $rtb_controller->cpts->booking_statuses ),
76
- 'order' => 'ASC',
77
- 'paged' => 1,
78
- );
79
-
80
- $this->args = wp_parse_args( $args, $defaults );
81
-
82
- $this->context = $context;
83
-
84
- }
85
-
86
- /**
87
- * Parse the args array and convert custom arguments
88
- * for use by WP_Query
89
- *
90
- * @since 1.4.1
91
- */
92
- public function prepare_args() {
93
-
94
- $args = $this->args;
95
-
96
- if ( ! empty( $args['date_query'] ) ) {
97
- $args['date_query'] = $args['date_query'];
98
- }
99
-
100
- elseif ( is_string( $args['date_range'] ) ) {
101
-
102
- if ( !empty( $args['start_date'] ) || !empty( $args['end_date'] ) ) {
103
- $date_query = array( 'inclusive' => true );
104
-
105
- if ( !empty( $args['start_date'] ) ) {
106
- $date_query['after'] = sanitize_text_field( $args['start_date'] ) . ( ( isset( $args['start_time'] ) and $args['start_time'] ) ? $args['start_time'] : '' );
107
- }
108
-
109
- if ( !empty( $args['end_date'] ) ) {
110
- $date_query['before'] = sanitize_text_field( $args['end_date'] ) . ( ( isset( $args['end_time'] ) and $args['end_time'] ) ? $args['end_time'] : ' 23:59' );
111
- }
112
-
113
- if ( count( $date_query ) ) {
114
- $args['date_query'] = $date_query;
115
- }
116
- } elseif ( $args['date_range'] === 'today' ) {
117
- $args['year'] = date( 'Y', current_time( 'timestamp' ) );
118
- $args['monthnum'] = date( 'm', current_time( 'timestamp' ) );
119
- $args['day'] = date( 'd', current_time( 'timestamp' ) );
120
-
121
- } elseif ( $args['date_range'] === 'upcoming' ) {
122
- $args['date_query'] = array(
123
- array(
124
- 'after' => '-1 hour', // show bookings that have just passed
125
- )
126
- );
127
- } elseif ( $args['date_range'] === 'past' ) {
128
- $args['date_query'] = array(
129
- array(
130
- 'before' => 'now',
131
- )
132
- );
133
- }
134
- }
135
-
136
- if ( !empty( $args['post_status'] ) ) {
137
- if ( is_string( $args['post_status'] ) ) {
138
-
139
- // Parse a comma-separated string of statuses
140
- if ( strpos( $args['post_status'], ',' ) !== false ) {
141
- $statuses = explode( ',', $args['post_status'] );
142
- $args['post_status'] = array();
143
- foreach( $statuses as $status ) {
144
- $args['post_status'][] = sanitize_key( $status );
145
- }
146
- } else {
147
- $args['post_status'] = sanitize_key( $_REQUEST['status'] );
148
- }
149
- }
150
- }
151
-
152
- $this->args = $args;
153
-
154
- return $this->args;
155
- }
156
-
157
- /**
158
- * Parse $_REQUEST args and store in $this->args
159
- *
160
- * @since 1.4.1
161
- */
162
- public function parse_request_args() {
163
-
164
- $args = array();
165
-
166
- if ( !empty( $_REQUEST['paged'] ) ) {
167
- $args['paged'] = (int) $_REQUEST['paged'];
168
- }
169
-
170
- if ( !empty( $_REQUEST['posts_per_page'] ) ) {
171
- $args['posts_per_page'] = (int) $_REQUEST['posts_per_page'];
172
- }
173
-
174
- if ( !empty( $_REQUEST['status'] ) ) {
175
- if ( is_string( $_REQUEST['status'] ) ) {
176
- $args['post_status'] = sanitize_text_field( $_REQUEST['status'] );
177
- } elseif ( is_array( $_REQUEST['status'] ) ) {
178
- $args['post_status'] = array();
179
- foreach( $_REQUEST['status'] as $status ) {
180
- $args['post_status'][] = sanitize_key( $status );
181
- }
182
- }
183
- }
184
-
185
- if ( !empty( $_REQUEST['orderby'] ) ) {
186
- $args['orderby'] = sanitize_key( $_REQUEST['orderby'] );
187
- if ( $args['orderby'] === 'id' ) { $args['orderby'] = 'ID'; }
188
- if ( $args['orderby'] === 'status' ) { add_filter( 'posts_orderby', array( $this, 'orderby_status' ) ); }
189
- }
190
-
191
- if ( !empty( $_REQUEST['order'] ) && $_REQUEST['order'] === 'desc' ) {
192
- $args['order'] = $_REQUEST['order'];
193
- }
194
-
195
- if ( !empty( $_REQUEST['date_range'] ) ) {
196
- $args['date_range'] = sanitize_key( $_REQUEST['date_range'] );
197
- }
198
-
199
- if ( !empty( $_REQUEST['start_date'] ) ) {
200
- $args['start_date'] = sanitize_text_field( $_REQUEST['start_date'] );
201
- }
202
-
203
- if ( !empty( $_REQUEST['end_date'] ) ) {
204
- $args['end_date'] = sanitize_text_field( $_REQUEST['end_date'] );
205
- }
206
-
207
- if ( !empty( $_REQUEST['location'] ) ) {
208
- $args['location'] = absint( $_REQUEST['location'] );
209
- }
210
-
211
- $this->args = array_merge( $this->args, $args );
212
- }
213
-
214
- /**
215
- * Retrieve query results
216
- *
217
- * @since 1.4.1
218
- */
219
- public function get_bookings() {
220
-
221
- $bookings = array();
222
-
223
- $args = apply_filters( 'rtb_query_args', $this->args, $this->context );
224
-
225
- $query = new WP_Query( $args );
226
-
227
- if ( $query->have_posts() ) {
228
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
229
-
230
- while( $query->have_posts() ) {
231
- $query->the_post();
232
-
233
- $booking = new rtbBooking();
234
- if ( $booking->load_post( $query->post ) ) {
235
- $bookings[] = $booking;
236
- }
237
- }
238
- }
239
-
240
- $this->bookings = $bookings;
241
-
242
- wp_reset_query();
243
-
244
- return $this->bookings;
245
- }
246
-
247
- public function orderby_status() {
248
-
249
- return 'post_status ' . ( ( isset( $_REQUEST['order'] ) and $_REQUEST['order'] == 'desc' ) ? 'DESC' : 'ASC' );
250
- }
251
-
252
- }
253
- } // endif
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbQuery' ) ) {
5
+ /**
6
+ * Class to handle common queries used to pull bookings from
7
+ * the database.
8
+ *
9
+ * Bookings can be retrieved with specific date ranges, common
10
+ * date params (today/upcoming), etc. This class is intended for
11
+ * the base plugin as well as extensions or custom projects which
12
+ * need a stable mechanism for reliably retrieving bookings data.
13
+ *
14
+ * Queries return an array of rtbBooking objects.
15
+ *
16
+ * @since 1.4.1
17
+ */
18
+ class rtbQuery {
19
+
20
+ /**
21
+ * Bookings
22
+ *
23
+ * Array of bookings retrieved after get_bookings() is called
24
+ *
25
+ * @since 1.4.1
26
+ */
27
+ public $bookings = array();
28
+
29
+ /**
30
+ * Query args
31
+ *
32
+ * Passed to WP_Query
33
+ * http://codex.wordpress.org/Class_Reference/WP_Query
34
+ *
35
+ * @since 1.4.1
36
+ */
37
+ public $args = array();
38
+
39
+ /**
40
+ * Query context
41
+ *
42
+ * Defines the context in which the query is run.
43
+ * Useful for hooking into the right query without
44
+ * tampering with others.
45
+ *
46
+ * @since 1.4.1
47
+ */
48
+ public $context;
49
+
50
+ /**
51
+ * Instantiate the query with an array of arguments
52
+ *
53
+ * This supports all WP_Query args as well as several
54
+ * short-hand arguments for common needs. Short-hands
55
+ * include:
56
+ *
57
+ * date_range string today|upcoming|dates
58
+ * start_date string don't get bookings before this
59
+ * end_date string don't get bookings after this
60
+ *
61
+ * @see rtbQuery::prepare_args()
62
+ * @param args array Options to tailor the query
63
+ * @param context string Context for the query, used
64
+ * in filters
65
+ * @since 1.4.1
66
+ */
67
+ public function __construct( $args = array(), $context = '' ) {
68
+
69
+ global $rtb_controller;
70
+
71
+ $defaults = array(
72
+ 'post_type' => RTB_BOOKING_POST_TYPE,
73
+ 'posts_per_page' => 10,
74
+ 'date_range' => 'upcoming',
75
+ 'post_status' => array_keys( $rtb_controller->cpts->booking_statuses ),
76
+ 'order' => 'ASC',
77
+ 'paged' => 1,
78
+ );
79
+
80
+ $this->args = wp_parse_args( $args, $defaults );
81
+
82
+ if ( isset( $this->args['filter_name'] ) ) {
83
+ // Removed after we process our query
84
+ add_filter( 'posts_where', [$this, 'title_filter'], 10, 2 );
85
+ }
86
+
87
+ $this->context = $context;
88
+
89
+ }
90
+
91
+ /**
92
+ * Parse the args array and convert custom arguments
93
+ * for use by WP_Query
94
+ *
95
+ * @since 1.4.1
96
+ */
97
+ public function prepare_args() {
98
+
99
+ $args = $this->args;
100
+
101
+ if ( ! empty( $args['date_query'] ) ) {
102
+ $args['date_query'] = $args['date_query'];
103
+ }
104
+
105
+ elseif ( is_string( $args['date_range'] ) ) {
106
+
107
+ if ( !empty( $args['start_date'] ) || !empty( $args['end_date'] ) ) {
108
+ $date_query = array( 'inclusive' => true );
109
+
110
+ if ( !empty( $args['start_date'] ) ) {
111
+ $date_query['after'] = sanitize_text_field( $args['start_date'] ) . ( ( isset( $args['start_time'] ) and $args['start_time'] ) ? $args['start_time'] : '' );
112
+ }
113
+
114
+ if ( !empty( $args['end_date'] ) ) {
115
+ $date_query['before'] = sanitize_text_field( $args['end_date'] ) . ( ( isset( $args['end_time'] ) and $args['end_time'] ) ? $args['end_time'] : ' 23:59' );
116
+ }
117
+
118
+ if ( count( $date_query ) ) {
119
+ $args['date_query'] = $date_query;
120
+ }
121
+ } elseif ( $args['date_range'] === 'today' ) {
122
+ $args['year'] = date( 'Y', current_time( 'timestamp' ) );
123
+ $args['monthnum'] = date( 'm', current_time( 'timestamp' ) );
124
+ $args['day'] = date( 'd', current_time( 'timestamp' ) );
125
+
126
+ } elseif ( $args['date_range'] === 'upcoming' ) {
127
+ $args['date_query'] = array(
128
+ array(
129
+ 'after' => '-1 hour', // show bookings that have just passed
130
+ )
131
+ );
132
+ } elseif ( $args['date_range'] === 'past' ) {
133
+ $args['date_query'] = array(
134
+ array(
135
+ 'before' => 'now',
136
+ )
137
+ );
138
+ }
139
+ }
140
+
141
+ if ( !empty( $args['post_status'] ) ) {
142
+ if ( is_string( $args['post_status'] ) ) {
143
+
144
+ // Parse a comma-separated string of statuses
145
+ if ( strpos( $args['post_status'], ',' ) !== false ) {
146
+ $statuses = explode( ',', $args['post_status'] );
147
+ $args['post_status'] = array();
148
+ foreach( $statuses as $status ) {
149
+ $args['post_status'][] = sanitize_key( $status );
150
+ }
151
+ } else {
152
+ $args['post_status'] = sanitize_key( $_REQUEST['status'] );
153
+ }
154
+ }
155
+ }
156
+
157
+ $this->args = $args;
158
+
159
+ return $this->args;
160
+ }
161
+
162
+ /**
163
+ * Parse $_REQUEST args and store in $this->args
164
+ *
165
+ * @since 1.4.1
166
+ */
167
+ public function parse_request_args() {
168
+
169
+ $args = array();
170
+
171
+ if ( !empty( $_REQUEST['paged'] ) ) {
172
+ $args['paged'] = (int) $_REQUEST['paged'];
173
+ }
174
+
175
+ if ( !empty( $_REQUEST['posts_per_page'] ) ) {
176
+ $args['posts_per_page'] = (int) $_REQUEST['posts_per_page'];
177
+ }
178
+
179
+ if ( !empty( $_REQUEST['status'] ) ) {
180
+ if ( is_string( $_REQUEST['status'] ) ) {
181
+ $args['post_status'] = sanitize_text_field( $_REQUEST['status'] );
182
+ } elseif ( is_array( $_REQUEST['status'] ) ) {
183
+ $args['post_status'] = array();
184
+ foreach( $_REQUEST['status'] as $status ) {
185
+ $args['post_status'][] = sanitize_key( $status );
186
+ }
187
+ }
188
+ }
189
+
190
+ if ( !empty( $_REQUEST['orderby'] ) ) {
191
+ $args['orderby'] = sanitize_key( $_REQUEST['orderby'] );
192
+ if ( $args['orderby'] === 'id' ) { $args['orderby'] = 'ID'; }
193
+ if ( $args['orderby'] === 'status' ) { add_filter( 'posts_orderby', array( $this, 'orderby_status' ) ); }
194
+ }
195
+
196
+ if ( !empty( $_REQUEST['order'] ) && $_REQUEST['order'] === 'desc' ) {
197
+ $args['order'] = $_REQUEST['order'];
198
+ }
199
+
200
+ if ( !empty( $_REQUEST['date_range'] ) ) {
201
+ $args['date_range'] = sanitize_key( $_REQUEST['date_range'] );
202
+ }
203
+
204
+ if ( !empty( $_REQUEST['start_date'] ) ) {
205
+ $args['start_date'] = sanitize_text_field( $_REQUEST['start_date'] );
206
+ }
207
+
208
+ if ( !empty( $_REQUEST['end_date'] ) ) {
209
+ $args['end_date'] = sanitize_text_field( $_REQUEST['end_date'] );
210
+ }
211
+
212
+ if ( !empty( $_REQUEST['location'] ) ) {
213
+ $args['location'] = absint( $_REQUEST['location'] );
214
+ }
215
+
216
+ $this->args = array_merge( $this->args, $args );
217
+ }
218
+
219
+ /**
220
+ * Retrieve query results
221
+ *
222
+ * @since 1.4.1
223
+ */
224
+ public function get_bookings() {
225
+
226
+ $bookings = array();
227
+
228
+ $args = apply_filters( 'rtb_query_args', $this->args, $this->context );
229
+
230
+ $query = new WP_Query( $args );
231
+
232
+ // Running this only for our query
233
+ remove_filter( 'posts_where', [$this, 'title_filter'], 10 );
234
+
235
+ if ( $query->have_posts() ) {
236
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
237
+
238
+ while( $query->have_posts() ) {
239
+ $query->the_post();
240
+
241
+ $booking = new rtbBooking();
242
+ if ( $booking->load_post( $query->post ) ) {
243
+ $bookings[] = $booking;
244
+ }
245
+ }
246
+ }
247
+
248
+ $this->bookings = $bookings;
249
+
250
+ wp_reset_query();
251
+
252
+ return $this->bookings;
253
+ }
254
+
255
+ public function orderby_status() {
256
+
257
+ return 'post_status ' . ( ( isset( $_REQUEST['order'] ) and $_REQUEST['order'] == 'desc' ) ? 'DESC' : 'ASC' );
258
+ }
259
+
260
+ public function title_filter( $where, $wp_query )
261
+ {
262
+ global $wpdb;
263
+
264
+ if ( $name = $wp_query->get( 'filter_name' ) )
265
+ {
266
+ $where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . esc_sql( $wpdb->esc_like( $name ) ) . '%\'';
267
+ }
268
+
269
+ return $where;
270
+ }
271
+
272
+ }
273
+ } // endif
includes/ReviewAsk.class.php CHANGED
@@ -1,98 +1,98 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbReviewAsk' ) ) {
5
- /**
6
- * Class to handle plugin review ask
7
- *
8
- * @since 2.0.15
9
- */
10
- class rtbReviewAsk {
11
-
12
- public function __construct() {
13
- add_action( 'admin_notices', array( $this, 'maybe_add_review_ask' ) );
14
-
15
- add_action( 'wp_ajax_rtb_hide_review_ask', array( $this, 'hide_review_ask' ) );
16
- add_action( 'wp_ajax_rtb_send_feedback', array( $this, 'send_feedback' ) );
17
-
18
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_review_ask_scripts') );
19
- }
20
-
21
- public function maybe_add_review_ask() {
22
- $ask_review_time = get_option( 'rtb-review-ask-time' );
23
-
24
- $install_time = get_option( 'rtb-installation-time' );
25
- if ( ! $install_time ) { update_option( 'rtb-installation-time', time() ); }
26
-
27
- $ask_review_time = $ask_review_time != '' ? $ask_review_time : $install_time + 3600*24*4;
28
-
29
- if ($ask_review_time < time() and $install_time != '' and $install_time < time() - 3600*24*4) {
30
-
31
- global $pagenow;
32
-
33
- if ( $pagenow != 'post.php' && $pagenow != 'post-new.php' ) { ?>
34
-
35
- <div class='notice notice-info is-dismissible rtb-main-dashboard-review-ask' style='display:none'>
36
- <div class='rtb-review-ask-plugin-icon'></div>
37
- <div class='rtb-review-ask-text'>
38
- <p class='rtb-review-ask-starting-text'>Enjoying using the Five-Star Restaurant Reservations?</p>
39
- <p class='rtb-review-ask-feedback-text rtb-hidden'>Help us make the plugin better! Please take a minute to rate the plugin. Thanks!</p>
40
- <p class='rtb-review-ask-review-text rtb-hidden'>Please let us know what we could do to make the plugin better!<br /><span>(If you would like a response, please include your email address.)</span></p>
41
- <p class='rtb-review-ask-thank-you-text rtb-hidden'>Thank you for taking the time to help us!</p>
42
- </div>
43
- <div class='rtb-review-ask-actions'>
44
- <div class='rtb-review-ask-action rtb-review-ask-not-really rtb-review-ask-white'>Not Really</div>
45
- <div class='rtb-review-ask-action rtb-review-ask-yes rtb-review-ask-green'>Yes!</div>
46
- <div class='rtb-review-ask-action rtb-review-ask-no-thanks rtb-review-ask-white rtb-hidden'>No Thanks</div>
47
- <a href='https://wordpress.org/support/plugin/restaurant-reservations/reviews/' target='_blank'>
48
- <div class='rtb-review-ask-action rtb-review-ask-review rtb-review-ask-green rtb-hidden'>OK, Sure</div>
49
- </a>
50
- </div>
51
- <div class='rtb-review-ask-feedback-form rtb-hidden'>
52
- <div class='rtb-review-ask-feedback-explanation'>
53
- <textarea></textarea>
54
- <br>
55
- <input type="email" name="feedback_email_address" placeholder="<?php _e('Email Address', 'restaurant-reservations'); ?>">
56
- </div>
57
- <div class='rtb-review-ask-send-feedback rtb-review-ask-action rtb-review-ask-green'>Send Feedback</div>
58
- </div>
59
- <div class='rtb-clear'></div>
60
- </div>
61
-
62
- <?php
63
- }
64
- }
65
- else {
66
- wp_dequeue_script( 'rtb-review-ask-js' );
67
- }
68
- }
69
-
70
- public function enqueue_review_ask_scripts() {
71
- wp_enqueue_style( 'rtb-review-ask-css', RTB_PLUGIN_URL . '/assets/css/dashboard-review-ask.css' );
72
- wp_enqueue_script( 'rtb-review-ask-js', RTB_PLUGIN_URL . '/assets/js/dashboard-review-ask.js', array( 'jquery' ), RTB_VERSION, true );
73
- }
74
-
75
- public function hide_review_ask() {
76
-
77
- $ask_review_time = sanitize_text_field($_POST['ask_review_time']);
78
-
79
- if ( get_option( 'rtb-review-ask-time' ) < time() + 3600*24 * $ask_review_time ) {
80
- update_option( 'rtb-review-ask-time', time() + 3600*24 * $ask_review_time );
81
- }
82
-
83
- die();
84
- }
85
-
86
- public function send_feedback() {
87
- $headers = 'Content-type: text/html;charset=utf-8' . "\r\n";
88
- $feedback = sanitize_text_field($_POST['feedback']);
89
- $feedback .= '<br /><br />Email Address: ';
90
- $feedback .= sanitize_text_field($_POST['email_address']);
91
-
92
- wp_mail('contact@fivestarplugins.com', 'RTB Feedback - Dashboard Form', $feedback, $headers);
93
-
94
- die();
95
- }
96
- }
97
-
98
  }
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbReviewAsk' ) ) {
5
+ /**
6
+ * Class to handle plugin review ask
7
+ *
8
+ * @since 2.0.15
9
+ */
10
+ class rtbReviewAsk {
11
+
12
+ public function __construct() {
13
+ add_action( 'admin_notices', array( $this, 'maybe_add_review_ask' ) );
14
+
15
+ add_action( 'wp_ajax_rtb_hide_review_ask', array( $this, 'hide_review_ask' ) );
16
+ add_action( 'wp_ajax_rtb_send_feedback', array( $this, 'send_feedback' ) );
17
+
18
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_review_ask_scripts') );
19
+ }
20
+
21
+ public function maybe_add_review_ask() {
22
+ $ask_review_time = get_option( 'rtb-review-ask-time' );
23
+
24
+ $install_time = get_option( 'rtb-installation-time' );
25
+ if ( ! $install_time ) { update_option( 'rtb-installation-time', time() ); }
26
+
27
+ $ask_review_time = $ask_review_time != '' ? $ask_review_time : $install_time + 3600*24*4;
28
+
29
+ if ($ask_review_time < time() and $install_time != '' and $install_time < time() - 3600*24*4) {
30
+
31
+ global $pagenow;
32
+
33
+ if ( $pagenow != 'post.php' && $pagenow != 'post-new.php' ) { ?>
34
+
35
+ <div class='notice notice-info is-dismissible rtb-main-dashboard-review-ask' style='display:none'>
36
+ <div class='rtb-review-ask-plugin-icon'></div>
37
+ <div class='rtb-review-ask-text'>
38
+ <p class='rtb-review-ask-starting-text'>Enjoying using the Five-Star Restaurant Reservations?</p>
39
+ <p class='rtb-review-ask-feedback-text rtb-hidden'>Help us make the plugin better! Please take a minute to rate the plugin. Thanks!</p>
40
+ <p class='rtb-review-ask-review-text rtb-hidden'>Please let us know what we could do to make the plugin better!<br /><span>(If you would like a response, please include your email address.)</span></p>
41
+ <p class='rtb-review-ask-thank-you-text rtb-hidden'>Thank you for taking the time to help us!</p>
42
+ </div>
43
+ <div class='rtb-review-ask-actions'>
44
+ <div class='rtb-review-ask-action rtb-review-ask-not-really rtb-review-ask-white'>Not Really</div>
45
+ <div class='rtb-review-ask-action rtb-review-ask-yes rtb-review-ask-green'>Yes!</div>
46
+ <div class='rtb-review-ask-action rtb-review-ask-no-thanks rtb-review-ask-white rtb-hidden'>No Thanks</div>
47
+ <a href='https://wordpress.org/support/plugin/restaurant-reservations/reviews/' target='_blank'>
48
+ <div class='rtb-review-ask-action rtb-review-ask-review rtb-review-ask-green rtb-hidden'>OK, Sure</div>
49
+ </a>
50
+ </div>
51
+ <div class='rtb-review-ask-feedback-form rtb-hidden'>
52
+ <div class='rtb-review-ask-feedback-explanation'>
53
+ <textarea></textarea>
54
+ <br>
55
+ <input type="email" name="feedback_email_address" placeholder="<?php _e('Email Address', 'restaurant-reservations'); ?>">
56
+ </div>
57
+ <div class='rtb-review-ask-send-feedback rtb-review-ask-action rtb-review-ask-green'>Send Feedback</div>
58
+ </div>
59
+ <div class='rtb-clear'></div>
60
+ </div>
61
+
62
+ <?php
63
+ }
64
+ }
65
+ else {
66
+ wp_dequeue_script( 'rtb-review-ask-js' );
67
+ }
68
+ }
69
+
70
+ public function enqueue_review_ask_scripts() {
71
+ wp_enqueue_style( 'rtb-review-ask-css', RTB_PLUGIN_URL . '/assets/css/dashboard-review-ask.css' );
72
+ wp_enqueue_script( 'rtb-review-ask-js', RTB_PLUGIN_URL . '/assets/js/dashboard-review-ask.js', array( 'jquery' ), RTB_VERSION, true );
73
+ }
74
+
75
+ public function hide_review_ask() {
76
+
77
+ $ask_review_time = sanitize_text_field($_POST['ask_review_time']);
78
+
79
+ if ( get_option( 'rtb-review-ask-time' ) < time() + 3600*24 * $ask_review_time ) {
80
+ update_option( 'rtb-review-ask-time', time() + 3600*24 * $ask_review_time );
81
+ }
82
+
83
+ die();
84
+ }
85
+
86
+ public function send_feedback() {
87
+ $headers = 'Content-type: text/html;charset=utf-8' . "\r\n";
88
+ $feedback = sanitize_text_field($_POST['feedback']);
89
+ $feedback .= '<br /><br />Email Address: ';
90
+ $feedback .= sanitize_text_field($_POST['email_address']);
91
+
92
+ wp_mail('contact@fivestarplugins.com', 'RTB Feedback - Dashboard Form', $feedback, $headers);
93
+
94
+ die();
95
+ }
96
+ }
97
+
98
  }
includes/Settings.class.php CHANGED
@@ -1,3258 +1,3280 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbSettings' ) ) {
5
- /**
6
- * Class to handle configurable settings for Restaurant Reservations
7
- *
8
- * @since 0.0.1
9
- */
10
- class rtbSettings {
11
-
12
- /**
13
- * Default values for settings
14
- * @since 0.0.1
15
- */
16
- public $defaults = array();
17
-
18
- /**
19
- * Stored values for settings
20
- * @since 0.0.1
21
- */
22
- public $settings = array();
23
-
24
- /**
25
- * Should a premium setting be disabled or not
26
- * @since 2.4.0
27
- */
28
- public $premium_permissions = array();
29
-
30
- /**
31
- * Currencies accepted for deposits
32
- */
33
- public $currency_options = array(
34
- 'AUD' => 'Australian Dollar',
35
- 'BRL' => 'Brazilian Real',
36
- 'CAD' => 'Canadian Dollar',
37
- 'CZK' => 'Czech Koruna',
38
- 'DKK' => 'Danish Krone',
39
- 'EUR' => 'Euro',
40
- 'HKD' => 'Hong Kong Dollar',
41
- 'HUF' => 'Hungarian Forint',
42
- 'ILS' => 'Israeli New Sheqel',
43
- 'JPY' => 'Japanese Yen',
44
- 'MYR' => 'Malaysian Ringgit',
45
- 'MXN' => 'Mexican Peso',
46
- 'NOK' => 'Norwegian Krone',
47
- 'NZD' => 'New Zealand Dollar',
48
- 'PHP' => 'Philippine Peso',
49
- 'PLN' => 'Polish Zloty',
50
- 'GBP' => 'Pound Sterling',
51
- 'RUB' => 'Russian Ruble',
52
- 'SGD' => 'Singapore Dollar',
53
- 'SEK' => 'Swedish Krona',
54
- 'CHF' => 'Swiss Franc',
55
- 'TWD' => 'Taiwan New Dollar',
56
- 'THB' => 'Thai Baht',
57
- 'TRY' => 'Turkish Lira',
58
- 'USD' => 'U.S. Dollar'
59
- );
60
-
61
- /**
62
- * Payment gateways that can be used for deposits
63
- */
64
- public $payment_gateway_options = array();
65
-
66
- /**
67
- * Languages supported by the pickadate library
68
- */
69
- public $supported_i8n = array(
70
- 'ar' => 'ar',
71
- 'bg_BG' => 'bg_BG',
72
- 'bs_BA' => 'bs_BA',
73
- 'ca_ES' => 'ca_ES',
74
- 'cs_CZ' => 'cs_CZ',
75
- 'da_DK' => 'da_DK',
76
- 'de_DE' => 'de_DE',
77
- 'el_GR' => 'el_GR',
78
- 'es_ES' => 'es_ES',
79
- 'et_EE' => 'et_EE',
80
- 'eu_ES' => 'eu_ES',
81
- 'fa_IR' => 'fa_IR',
82
- 'fi_FI' => 'fi_FI',
83
- 'fr_FR' => 'fr_FR',
84
- 'gl_ES' => 'gl_ES',
85
- 'he_IL' => 'he_IL',
86
- 'hi_IN' => 'hi_IN',
87
- 'hr_HR' => 'hr_HR',
88
- 'hu_HU' => 'hu_HU',
89
- 'id_ID' => 'id_ID',
90
- 'is_IS' => 'is_IS',
91
- 'it_IT' => 'it_IT',
92
- 'ja_JP' => 'ja_JP',
93
- 'ko_KR' => 'ko_KR',
94
- 'lt_LT' => 'lt_LT',
95
- 'lv_LV' => 'lv_LV',
96
- 'nb_NO' => 'nb_NO',
97
- 'ne_NP' => 'ne_NP',
98
- 'nl_NL' => 'nl_NL',
99
- 'no_NO' => 'no_NO', // Old norwegian translation kept for backwards compatibility
100
- 'pl_PL' => 'pl_PL',
101
- 'pt_BR' => 'pt_BR',
102
- 'pt_PT' => 'pt_PT',
103
- 'ro_RO' => 'ro_RO',
104
- 'ru_RU' => 'ru_RU',
105
- 'sk_SK' => 'sk_SK',
106
- 'sl_SI' => 'sl_SI',
107
- 'sv_SE' => 'sv_SE',
108
- 'th_TH' => 'th_TH',
109
- 'tr_TR' => 'tr_TR',
110
- 'uk_UA' => 'uk_UA',
111
- 'zh_CN' => 'zh_CN',
112
- 'zh_TW' => 'zh_TW',
113
- );
114
-
115
- public $country_phone_array = array(
116
- // 'AD' => array( 'name' => 'ANDORRA', 'code' => '376' ),
117
- // 'AE' => array( 'name' => 'UNITED ARAB EMIRATES', 'code' => '971' ),
118
- // 'AF' => array( 'name' => 'AFGHANISTAN', 'code' => '93' ),
119
- // 'AG' => array( 'name' => 'ANTIGUA AND BARBUDA', 'code' => '1268' ),
120
- // 'AI' => array( 'name' => 'ANGUILLA', 'code' => '1264' ),
121
- // 'AL' => array( 'name' => 'ALBANIA', 'code' => '355' ),
122
- // 'AM' => array( 'name' => 'ARMENIA', 'code' => '374' ),
123
- // 'AN' => array( 'name' => 'NETHERLANDS ANTILLES', 'code' => '599' ),
124
- // 'AO' => array( 'name' => 'ANGOLA', 'code' => '244' ),
125
- // 'AQ' => array( 'name' => 'ANTARCTICA', 'code' => '672' ),
126
- 'AR' => array( 'name' => 'ARGENTINA', 'code' => '54' ),
127
- // 'AS' => array( 'name' => 'AMERICAN SAMOA', 'code' => '1684' ),
128
- 'AT' => array( 'name' => 'AUSTRIA', 'code' => '43' ),
129
- 'AU' => array( 'name' => 'AUSTRALIA', 'code' => '61' ),
130
- // 'AW' => array( 'name' => 'ARUBA', 'code' => '297' ),
131
- // 'AZ' => array( 'name' => 'AZERBAIJAN', 'code' => '994' ),
132
- // 'BA' => array( 'name' => 'BOSNIA AND HERZEGOVINA', 'code' => '387' ),
133
- // 'BB' => array( 'name' => 'BARBADOS', 'code' => '1246' ),
134
- // 'BD' => array( 'name' => 'BANGLADESH', 'code' => '880' ),
135
- 'BE' => array( 'name' => 'BELGIUM', 'code' => '32' ),
136
- // 'BF' => array( 'name' => 'BURKINA FASO', 'code' => '226' ),
137
- 'BG' => array( 'name' => 'BULGARIA', 'code' => '359' ),
138
- // 'BH' => array( 'name' => 'BAHRAIN', 'code' => '973' ),
139
- // 'BI' => array( 'name' => 'BURUNDI', 'code' => '257' ),
140
- // 'BJ' => array( 'name' => 'BENIN', 'code' => '229' ),
141
- // 'BL' => array( 'name' => 'SAINT BARTHELEMY', 'code' => '590' ),
142
- // 'BM' => array( 'name' => 'BERMUDA', 'code' => '1441' ),
143
- // 'BN' => array( 'name' => 'BRUNEI DARUSSALAM', 'code' => '673' ),
144
- // 'BO' => array( 'name' => 'BOLIVIA', 'code' => '591' ),
145
- 'BR' => array( 'name' => 'BRAZIL', 'code' => '55' ),
146
- // 'BS' => array( 'name' => 'BAHAMAS', 'code' => '1242' ),
147
- // 'BT' => array( 'name' => 'BHUTAN', 'code' => '975' ),
148
- // 'BW' => array( 'name' => 'BOTSWANA', 'code' => '267' ),
149
- // 'BY' => array( 'name' => 'BELARUS', 'code' => '375' ),
150
- // 'BZ' => array( 'name' => 'BELIZE', 'code' => '501' ),
151
- 'CA' => array( 'name' => 'CANADA', 'code' => '1' ),
152
- // 'CC' => array( 'name' => 'COCOS (KEELING) ISLANDS', 'code' => '61' ),
153
- // 'CD' => array( 'name' => 'CONGO, THE DEMOCRATIC REPUBLIC OF THE', 'code' => '243' ),
154
- // 'CF' => array( 'name' => 'CENTRAL AFRICAN REPUBLIC', 'code' => '236' ),
155
- // 'CG' => array( 'name' => 'CONGO', 'code' => '242' ),
156
- 'CH' => array( 'name' => 'SWITZERLAND', 'code' => '41' ),
157
- // 'CI' => array( 'name' => 'COTE D IVOIRE', 'code' => '225' ),
158
- // 'CK' => array( 'name' => 'COOK ISLANDS', 'code' => '682' ),
159
- // 'CL' => array( 'name' => 'CHILE', 'code' => '56' ),
160
- // 'CM' => array( 'name' => 'CAMEROON', 'code' => '237' ),
161
- 'CN' => array( 'name' => 'CHINA', 'code' => '86' ),
162
- // 'CO' => array( 'name' => 'COLOMBIA', 'code' => '57' ),
163
- // 'CR' => array( 'name' => 'COSTA RICA', 'code' => '506' ),
164
- // 'CU' => array( 'name' => 'CUBA', 'code' => '53' ),
165
- // 'CV' => array( 'name' => 'CAPE VERDE', 'code' => '238' ),
166
- // 'CX' => array( 'name' => 'CHRISTMAS ISLAND', 'code' => '61' ),
167
- // 'CY' => array( 'name' => 'CYPRUS', 'code' => '357' ),
168
- 'CZ' => array( 'name' => 'CZECH REPUBLIC', 'code' => '420' ),
169
- 'DE' => array( 'name' => 'GERMANY', 'code' => '49' ),
170
- // 'DJ' => array( 'name' => 'DJIBOUTI', 'code' => '253' ),
171
- 'DK' => array( 'name' => 'DENMARK', 'code' => '45' ),
172
- // 'DM' => array( 'name' => 'DOMINICA', 'code' => '1767' ),
173
- // 'DO' => array( 'name' => 'DOMINICAN REPUBLIC', 'code' => '1809' ),
174
- // 'DZ' => array( 'name' => 'ALGERIA', 'code' => '213' ),
175
- // 'EC' => array( 'name' => 'ECUADOR', 'code' => '593' ),
176
- 'EE' => array( 'name' => 'ESTONIA', 'code' => '372' ),
177
- // 'EG' => array( 'name' => 'EGYPT', 'code' => '20' ),
178
- // 'ER' => array( 'name' => 'ERITREA', 'code' => '291' ),
179
- 'ES' => array( 'name' => 'SPAIN', 'code' => '34' ),
180
- // 'ET' => array( 'name' => 'ETHIOPIA', 'code' => '251' ),
181
- 'FI' => array( 'name' => 'FINLAND', 'code' => '358' ),
182
- // 'FJ' => array( 'name' => 'FIJI', 'code' => '679' ),
183
- // 'FK' => array( 'name' => 'FALKLAND ISLANDS (MALVINAS)', 'code' => '500' ),
184
- // 'FM' => array( 'name' => 'MICRONESIA, FEDERATED STATES OF', 'code' => '691' ),
185
- // 'FO' => array( 'name' => 'FAROE ISLANDS', 'code' => '298' ),
186
- 'FR' => array( 'name' => 'FRANCE', 'code' => '33' ),
187
- // 'GA' => array( 'name' => 'GABON', 'code' => '241' ),
188
- 'GB' => array( 'name' => 'UNITED KINGDOM', 'code' => '44' ),
189
- // 'GD' => array( 'name' => 'GRENADA', 'code' => '1473' ),
190
- // 'GE' => array( 'name' => 'GEORGIA', 'code' => '995' ),
191
- // 'GH' => array( 'name' => 'GHANA', 'code' => '233' ),
192
- // 'GI' => array( 'name' => 'GIBRALTAR', 'code' => '350' ),
193
- 'GL' => array( 'name' => 'GREENLAND', 'code' => '299' ),
194
- // 'GM' => array( 'name' => 'GAMBIA', 'code' => '220' ),
195
- // 'GN' => array( 'name' => 'GUINEA', 'code' => '224' ),
196
- // 'GQ' => array( 'name' => 'EQUATORIAL GUINEA', 'code' => '240' ),
197
- 'GR' => array( 'name' => 'GREECE', 'code' => '30' ),
198
- // 'GT' => array( 'name' => 'GUATEMALA', 'code' => '502' ),
199
- // 'GU' => array( 'name' => 'GUAM', 'code' => '1671' ),
200
- // 'GW' => array( 'name' => 'GUINEA-BISSAU', 'code' => '245' ),
201
- // 'GY' => array( 'name' => 'GUYANA', 'code' => '592' ),
202
- 'HK' => array( 'name' => 'HONG KONG', 'code' => '852' ),
203
- // 'HN' => array( 'name' => 'HONDURAS', 'code' => '504' ),
204
- 'HR' => array( 'name' => 'CROATIA', 'code' => '385' ),
205
- // 'HT' => array( 'name' => 'HAITI', 'code' => '509' ),
206
- 'HU' => array( 'name' => 'HUNGARY', 'code' => '36' ),
207
- 'ID' => array( 'name' => 'INDONESIA', 'code' => '62' ),
208
- 'IE' => array( 'name' => 'IRELAND', 'code' => '353' ),
209
- 'IL' => array( 'name' => 'ISRAEL', 'code' => '972' ),
210
- // 'IM' => array( 'name' => 'ISLE OF MAN', 'code' => '44' ),
211
- 'IN' => array( 'name' => 'INDIA', 'code' => '91' ),
212
- // 'IQ' => array( 'name' => 'IRAQ', 'code' => '964' ),
213
- // 'IR' => array( 'name' => 'IRAN, ISLAMIC REPUBLIC OF', 'code' => '98' ),
214
- 'IS' => array( 'name' => 'ICELAND', 'code' => '354' ),
215
- 'IT' => array( 'name' => 'ITALY', 'code' => '39' ),
216
- // 'JM' => array( 'name' => 'JAMAICA', 'code' => '1876' ),
217
- // 'JO' => array( 'name' => 'JORDAN', 'code' => '962' ),
218
- 'JP' => array( 'name' => 'JAPAN', 'code' => '81' ),
219
- // 'KE' => array( 'name' => 'KENYA', 'code' => '254' ),
220
- // 'KG' => array( 'name' => 'KYRGYZSTAN', 'code' => '996' ),
221
- // 'KH' => array( 'name' => 'CAMBODIA', 'code' => '855' ),
222
- // 'KI' => array( 'name' => 'KIRIBATI', 'code' => '686' ),
223
- // 'KM' => array( 'name' => 'COMOROS', 'code' => '269' ),
224
- // 'KN' => array( 'name' => 'SAINT KITTS AND NEVIS', 'code' => '1869' ),
225
- // 'KP' => array( 'name' => 'KOREA DEMOCRATIC PEOPLES REPUBLIC OF', 'code' => '850' ),
226
- 'KR' => array( 'name' => 'KOREA REPUBLIC OF', 'code' => '82' ),
227
- // 'KW' => array( 'name' => 'KUWAIT', 'code' => '965' ),
228
- // 'KY' => array( 'name' => 'CAYMAN ISLANDS', 'code' => '1345' ),
229
- // 'KZ' => array( 'name' => 'KAZAKSTAN', 'code' => '7' ),
230
- // 'LA' => array( 'name' => 'LAO PEOPLES DEMOCRATIC REPUBLIC', 'code' => '856' ),
231
- // 'LB' => array( 'name' => 'LEBANON', 'code' => '961' ),
232
- // 'LC' => array( 'name' => 'SAINT LUCIA', 'code' => '1758' ),
233
- 'LI' => array( 'name' => 'LIECHTENSTEIN', 'code' => '423' ),
234
- // 'LK' => array( 'name' => 'SRI LANKA', 'code' => '94' ),
235
- // 'LR' => array( 'name' => 'LIBERIA', 'code' => '231' ),
236
- // 'LS' => array( 'name' => 'LESOTHO', 'code' => '266' ),
237
- 'LT' => array( 'name' => 'LITHUANIA', 'code' => '370' ),
238
- 'LU' => array( 'name' => 'LUXEMBOURG', 'code' => '352' ),
239
- 'LV' => array( 'name' => 'LATVIA', 'code' => '371' ),
240
- // 'LY' => array( 'name' => 'LIBYAN ARAB JAMAHIRIYA', 'code' => '218' ),
241
- // 'MA' => array( 'name' => 'MOROCCO', 'code' => '212' ),
242
- // 'MC' => array( 'name' => 'MONACO', 'code' => '377' ),
243
- // 'MD' => array( 'name' => 'MOLDOVA, REPUBLIC OF', 'code' => '373' ),
244
- 'ME' => array( 'name' => 'MONTENEGRO', 'code' => '382' ),
245
- // 'MF' => array( 'name' => 'SAINT MARTIN', 'code' => '1599' ),
246
- // 'MG' => array( 'name' => 'MADAGASCAR', 'code' => '261' ),
247
- // 'MH' => array( 'name' => 'MARSHALL ISLANDS', 'code' => '692' ),
248
- // 'MK' => array( 'name' => 'MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF', 'code' => '389' ),
249
- // 'ML' => array( 'name' => 'MALI', 'code' => '223' ),
250
- // 'MM' => array( 'name' => 'MYANMAR', 'code' => '95' ),
251
- // 'MN' => array( 'name' => 'MONGOLIA', 'code' => '976' ),
252
- // 'MO' => array( 'name' => 'MACAU', 'code' => '853' ),
253
- // 'MP' => array( 'name' => 'NORTHERN MARIANA ISLANDS', 'code' => '1670' ),
254
- // 'MR' => array( 'name' => 'MAURITANIA', 'code' => '222' ),
255
- // 'MS' => array( 'name' => 'MONTSERRAT', 'code' => '1664' ),
256
- // 'MT' => array( 'name' => 'MALTA', 'code' => '356' ),
257
- // 'MU' => array( 'name' => 'MAURITIUS', 'code' => '230' ),
258
- // 'MV' => array( 'name' => 'MALDIVES', 'code' => '960' ),
259
- // 'MW' => array( 'name' => 'MALAWI', 'code' => '265' ),
260
- 'MX' => array( 'name' => 'MEXICO', 'code' => '52' ),
261
- 'MY' => array( 'name' => 'MALAYSIA', 'code' => '60' ),
262
- // 'MZ' => array( 'name' => 'MOZAMBIQUE', 'code' => '258' ),
263
- // 'NA' => array( 'name' => 'NAMIBIA', 'code' => '264' ),
264
- // 'NC' => array( 'name' => 'NEW CALEDONIA', 'code' => '687' ),
265
- // 'NE' => array( 'name' => 'NIGER', 'code' => '227' ),
266
- // 'NG' => array( 'name' => 'NIGERIA', 'code' => '234' ),
267
- // 'NI' => array( 'name' => 'NICARAGUA', 'code' => '505' ),
268
- 'NL' => array( 'name' => 'NETHERLANDS', 'code' => '31' ),
269
- 'NO' => array( 'name' => 'NORWAY', 'code' => '47' ),
270
- // 'NP' => array( 'name' => 'NEPAL', 'code' => '977' ),
271
- // 'NR' => array( 'name' => 'NAURU', 'code' => '674' ),
272
- // 'NU' => array( 'name' => 'NIUE', 'code' => '683' ),
273
- 'NZ' => array( 'name' => 'NEW ZEALAND', 'code' => '64' ),
274
- // 'OM' => array( 'name' => 'OMAN', 'code' => '968' ),
275
- // 'PA' => array( 'name' => 'PANAMA', 'code' => '507' ),
276
- // 'PE' => array( 'name' => 'PERU', 'code' => '51' ),
277
- // 'PF' => array( 'name' => 'FRENCH POLYNESIA', 'code' => '689' ),
278
- // 'PG' => array( 'name' => 'PAPUA NEW GUINEA', 'code' => '675' ),
279
- // 'PH' => array( 'name' => 'PHILIPPINES', 'code' => '63' ),
280
- // 'PK' => array( 'name' => 'PAKISTAN', 'code' => '92' ),
281
- 'PL' => array( 'name' => 'POLAND', 'code' => '48' ),
282
- // 'PM' => array( 'name' => 'SAINT PIERRE AND MIQUELON', 'code' => '508' ),
283
- // 'PN' => array( 'name' => 'PITCAIRN', 'code' => '870' ),
284
- 'PR' => array( 'name' => 'PUERTO RICO', 'code' => '1' ),
285
- 'PT' => array( 'name' => 'PORTUGAL', 'code' => '351' ),
286
- // 'PW' => array( 'name' => 'PALAU', 'code' => '680' ),
287
- // 'PY' => array( 'name' => 'PARAGUAY', 'code' => '595' ),
288
- // 'QA' => array( 'name' => 'QATAR', 'code' => '974' ),
289
- 'RO' => array( 'name' => 'ROMANIA', 'code' => '40' ),
290
- 'RS' => array( 'name' => 'SERBIA', 'code' => '381' ),
291
- 'RU' => array( 'name' => 'RUSSIAN FEDERATION', 'code' => '7' ),
292
- // 'RW' => array( 'name' => 'RWANDA', 'code' => '250' ),
293
- // 'SA' => array( 'name' => 'SAUDI ARABIA', 'code' => '966' ),
294
- // 'SB' => array( 'name' => 'SOLOMON ISLANDS', 'code' => '677' ),
295
- // 'SC' => array( 'name' => 'SEYCHELLES', 'code' => '248' ),
296
- // 'SD' => array( 'name' => 'SUDAN', 'code' => '249' ),
297
- 'SE' => array( 'name' => 'SWEDEN', 'code' => '46' ),
298
- 'SG' => array( 'name' => 'SINGAPORE', 'code' => '65' ),
299
- // 'SH' => array( 'name' => 'SAINT HELENA', 'code' => '290' ),
300
- 'SI' => array( 'name' => 'SLOVENIA', 'code' => '386' ),
301
- 'SK' => array( 'name' => 'SLOVAKIA', 'code' => '421' ),
302
- // 'SL' => array( 'name' => 'SIERRA LEONE', 'code' => '232' ),
303
- // 'SM' => array( 'name' => 'SAN MARINO', 'code' => '378' ),
304
- // 'SN' => array( 'name' => 'SENEGAL', 'code' => '221' ),
305
- // 'SO' => array( 'name' => 'SOMALIA', 'code' => '252' ),
306
- // 'SR' => array( 'name' => 'SURINAME', 'code' => '597' ),
307
- // 'ST' => array( 'name' => 'SAO TOME AND PRINCIPE', 'code' => '239' ),
308
- // 'SV' => array( 'name' => 'EL SALVADOR', 'code' => '503' ),
309
- // 'SY' => array( 'name' => 'SYRIAN ARAB REPUBLIC', 'code' => '963' ),
310
- // 'SZ' => array( 'name' => 'SWAZILAND', 'code' => '268' ),
311
- // 'TC' => array( 'name' => 'TURKS AND CAICOS ISLANDS', 'code' => '1649' ),
312
- // 'TD' => array( 'name' => 'CHAD', 'code' => '235' ),
313
- // 'TG' => array( 'name' => 'TOGO', 'code' => '228' ),
314
- 'TH' => array( 'name' => 'THAILAND', 'code' => '66' ),
315
- // 'TJ' => array( 'name' => 'TAJIKISTAN', 'code' => '992' ),
316
- // 'TK' => array( 'name' => 'TOKELAU', 'code' => '690' ),
317
- // 'TL' => array( 'name' => 'TIMOR-LESTE', 'code' => '670' ),
318
- // 'TM' => array( 'name' => 'TURKMENISTAN', 'code' => '993' ),
319
- // 'TN