Restaurant Reservations - Version 2.5.13

Version Description

(2022-07-12) = - Fixed a string/array variable-type confusion error.

Download this release

Release Info

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

Code changes from version 2.5.12 to 2.5.13

Files changed (46) hide show
  1. GPL.txt +279 -279
  2. assets/css/admin-rtb-welcome-screen.css +367 -367
  3. assets/css/booking-form.css +395 -391
  4. assets/css/dashboard-review-ask.css +60 -60
  5. assets/css/editor.css +581 -581
  6. assets/css/helper-install-notice.css +19 -19
  7. assets/css/plugin-deactivation.css +67 -67
  8. assets/js/admin-rtb-welcome-screen.js +92 -92
  9. assets/js/admin-settings.js +76 -76
  10. assets/js/block-booking-form.js +48 -48
  11. assets/js/booking-form.js +982 -982
  12. assets/js/columns.js +10 -10
  13. assets/js/dashboard-review-ask.js +79 -79
  14. assets/js/editor.js +1236 -1236
  15. assets/js/helper-install-notice.js +10 -10
  16. assets/js/mailchimp-admin.js +1 -1
  17. assets/js/plugin-deactivation.js +52 -52
  18. assets/js/rtb-recaptcha.js +4 -4
  19. assets/js/stripe-payment.js +196 -196
  20. includes/Addons.class.php +362 -362
  21. includes/AdminBookings.class.php +928 -928
  22. includes/AdminPageSettingLicenseKey.class.php +429 -429
  23. includes/Ajax.class.php +950 -950
  24. includes/Blocks.class.php +99 -99
  25. includes/Booking.class.php +1510 -1510
  26. includes/Compatibility.class.php +191 -191
  27. includes/Cron.class.php +252 -252
  28. includes/CustomFields.class.php +193 -193
  29. includes/CustomPostTypes.class.php +445 -445
  30. includes/DeactivationSurvey.class.php +83 -83
  31. includes/Editor.class.php +845 -845
  32. includes/Export.CSV.class.php +381 -381
  33. includes/Export.PDF.class.php +365 -365
  34. includes/ExportHandler.class.php +309 -309
  35. includes/Field.class.php +611 -611
  36. includes/Helper.class.php +103 -103
  37. includes/Import.class.php +279 -279
  38. includes/InstallationWalkthrough.class.php +424 -424
  39. includes/Licenses.class.php +270 -270
  40. includes/MailChimp.class.php +615 -615
  41. includes/Migration.class.php +140 -140
  42. includes/MultipleLocations.class.php +1098 -1098
  43. includes/Notification.Email.class.php +308 -308
  44. includes/Notification.SMS.class.php +151 -151
  45. includes/Notification.class.php +126 -126
  46. includes/Notifications.class.php +211 -360
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
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/booking-form.css CHANGED
@@ -1,392 +1,396 @@
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
- /* VIEW BOOKINGS TABLE AND ARRIVAL LIGHTBOX */
114
-
115
- .rtb-view-bookings-table {
116
- margin-top: 24px;
117
- border-collapse: collapse;
118
- }
119
- .rtb-view-bookings-table,
120
- .rtb-view-bookings-table tr,
121
- .rtb-view-bookings-table th,
122
- .rtb-view-bookings-table td {
123
- border: 1px solid #ccc;
124
- }
125
- .rtb-view-bookings-table th {
126
- font-weight: bold;
127
- text-align: center;
128
- }
129
-
130
- @media screen and (max-width: 900px) {
131
- .rtb-view-bookings-table th:nth-of-type(n+6),
132
- .rtb-view-bookings-table td:nth-of-type(n+6) {
133
- display: none;
134
- }
135
- }
136
- @media screen and (max-width: 600px) {
137
- .rtb-view-bookings-table th:nth-of-type(n+5),
138
- .rtb-view-bookings-table td:nth-of-type(n+5) {
139
- display: none;
140
- }
141
- }
142
-
143
- .rtb-edit-view-booking {
144
- width: 20px;
145
- height: 20px;
146
- margin-left: calc(50% - 10px);
147
- }
148
- .rtb-edit-view-booking[disabled] {
149
- opacity: .75;
150
- }
151
-
152
- .rtb-view-bookings-form-confirmation-background-div {
153
- position: fixed;
154
- top: 0;
155
- left: 0;
156
- width: 100%;
157
- height: 100%;
158
- z-index: 999999;
159
- background: rgba(0,0,0,0.5);
160
- }
161
- .rtb-view-bookings-form-confirmation-div {
162
- position: fixed;
163
- top: 200px;
164
- width: 480px;
165
- left: calc(50% - 240px);
166
- z-index: 1000000;
167
- margin: 0;
168
- background: #fff;
169
- font-size: 18px;
170
- text-align: center;
171
- border-radius: 2px;
172
- box-shadow: 0 0 4px #555;
173
- }
174
- .rtb-view-bookings-form-confirmation-div-inside {
175
- position: relative;
176
- float: left;
177
- width: 100%;
178
- }
179
- .rtb-view-bookings-form-confirmation-div-title {
180
- position: relative;
181
- float: left;
182
- width: 90%;
183
- margin: 24px 5%;
184
- color: #333;
185
- }
186
- .rtb-view-bookings-form-confirmation-accept {
187
- box-sizing: border-box;
188
- position: relative;
189
- float: left;
190
- width: 32%;
191
- margin: 24px 34% 0;
192
- padding: 8px 0;
193
- background: #222;
194
- color: #fff;
195
- border-radius: 2px;
196
- cursor: pointer;
197
- transition: background .35s;
198
- }
199
- .rtb-view-bookings-form-confirmation-accept:hover {
200
- background: #555;
201
- }
202
- .rtb-view-bookings-form-confirmation-decline {
203
- box-sizing: border-box;
204
- position: relative;
205
- float: left;
206
- width: 32%;
207
- margin: 8px 34% 24px;
208
- padding: 6px 0;
209
- background: transparent;
210
- color: #222;
211
- border: 2px solid #222;
212
- border-radius: 2px;
213
- cursor: pointer;
214
- transition: background .35s, border-color .35s;
215
- }
216
- .rtb-view-bookings-form-confirmation-decline:hover {
217
- background: #555;
218
- color: #fff;
219
- border-color: #555;
220
- }
221
-
222
- #rtb-view-bookings-form-close {
223
- position: absolute;
224
- display: flex;
225
- justify-content: center;
226
- align-items: center;
227
- top: 0;
228
- right: 0;
229
- width: 32px;
230
- height: 32px;
231
- background: #555;
232
- color: #fff;
233
- border-bottom-left-radius: 2px;
234
- font-size: 15px;
235
- cursor: pointer;
236
- transition: background .35s;
237
- }
238
- #rtb-view-bookings-form-close:hover {
239
- background: #222;
240
- }
241
-
242
- @media screen and (max-width: 568px) {
243
- .rtb-view-bookings-form-confirmation-div {
244
- top: 100px;
245
- width: 300px;
246
- left: calc(50% - 150px);
247
- }
248
- }
249
-
250
-
251
- /*CANCEL LINK*/
252
- .rtb-modification-toggle {
253
- position: relative;
254
- float: left;
255
- padding: 10px 15px;
256
- margin-bottom: 24px;
257
- background: #444;
258
- color: #fff;
259
- border-radius: 3px;
260
- cursor: pointer;
261
- }
262
- label[for="rtb-modification-email"],
263
- input[name="modification"] {
264
- float: left;
265
- margin-top: 20px;
266
- }
267
- label[for="rtb-modification-email"] {
268
- margin-right: 12px;
269
- }
270
- .rtb-find-reservation-button {
271
- position: relative;
272
- float: left;
273
- padding: 10px 15px;
274
- margin-top: 24px;
275
- background: #444;
276
- color: #fff;
277
- border-radius: 3px;
278
- cursor: pointer;
279
- }
280
-
281
- .rtb-bookings-results {
282
- position: relative;
283
- float: left;
284
- width: 100%;
285
- margin-top: 16px;
286
- }
287
- .rtb-cancel-booking-div {
288
- border: 1px solid #ddd;
289
- margin-bottom: 8px;
290
- }
291
- .rtb-cancel-booking-div + .alert {
292
- margin: 0;
293
- }
294
- .rtb-cancel-booking-div + .alert.error {
295
- color: #f24a4d;
296
- background: #f24a4d47;
297
- }
298
- .rtb-cancel-booking-div *:not(:first-child) {
299
- margin-left: -5px;
300
- }
301
-
302
- .rtb-cancel-booking {
303
- text-align: center;
304
- padding: 10px 0;
305
- background: #fe4e4e;
306
- color: #fff;
307
- cursor: pointer;
308
- display: inline-block;
309
- max-width: 100px;
310
- min-width: 100px;
311
- width: 100%;
312
- }
313
- .rtb-cancel-booking:hover {
314
- background: #ff6b6b;
315
- color: #fff;
316
- }
317
- .rtb-cancel-booking.cancelled {
318
- background: #24b124;
319
- }
320
-
321
- .rtb-deposit-booking {
322
- text-align: center;
323
- padding: 10px 0;
324
- background: green;
325
- color: #fff;
326
- cursor: pointer;
327
- display: inline-block;
328
- max-width: 100px;
329
- min-width: 100px;
330
- width: 100%;
331
- }
332
- .rtb-deposit-booking:hover {
333
- background: #008000cf;
334
- color: #fff;
335
- }
336
-
337
- .rtb-booking-information {
338
- padding-left: 5px;
339
- display: inline-block;
340
- }
341
-
342
- #rtb_recaptcha {
343
- position: relative;
344
- float: left;
345
- width: 100%;
346
- margin: 16px 0;
347
- }
348
-
349
- .stripe-payment-help-text {
350
- display: none;
351
- }
352
- .payment-errors {
353
- margin: 32px 0;
354
- border-left: 4px solid #000;
355
- padding-left: 16px;
356
- }
357
- :is(.rtb-booking-form, #stripe-booking-form) button:disabled {
358
- background-color: gray;
359
- }
360
- :is(.rtb-booking-form, #stripe-booking-form) button:disabled:hover {
361
- text-decoration: none;
362
- }
363
-
364
- /* Payment Detail Summary */
365
- .booking-payment-wrapper .summary-title {
366
- width: 100%;
367
- display: block;
368
- }
369
- .booking-payment-wrapper dl.summary-detail {
370
- box-sizing: border-box;
371
- width: 100%;
372
- display: block;
373
- padding: 12px 16px;
374
- background: #fafafa;
375
- border: 1px solid #ddd;
376
- border-radius: 2px;
377
- }
378
- .booking-payment-wrapper dl.summary-detail dt {
379
- width: 120px;
380
- padding-right: 20px;
381
- display: inline-block;
382
- margin: 0 0 8px;
383
- }
384
- .booking-payment-wrapper dl.summary-detail dd {
385
- width: calc(100% - 140px);
386
- display: inline-block;
387
- margin: 0 0 8px;
388
- }
389
- .booking-payment-wrapper dl.summary-detail dt:last-of-type,
390
- .booking-payment-wrapper dl.summary-detail dd:last-of-type {
391
- margin-bottom: 0;
 
 
 
 
392
  }
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
+ /* VIEW BOOKINGS TABLE AND ARRIVAL LIGHTBOX */
114
+
115
+ .rtb-view-bookings-table {
116
+ margin-top: 24px;
117
+ border-collapse: collapse;
118
+ }
119
+ .rtb-view-bookings-table,
120
+ .rtb-view-bookings-table tr,
121
+ .rtb-view-bookings-table th,
122
+ .rtb-view-bookings-table td {
123
+ border: 1px solid #ccc;
124
+ }
125
+ .rtb-view-bookings-table th {
126
+ font-weight: bold;
127
+ text-align: center;
128
+ }
129
+
130
+ .rtb-view-booking-details-label {
131
+ font-weight: 600;
132
+ }
133
+
134
+ @media screen and (max-width: 900px) {
135
+ .rtb-view-bookings-table th:nth-of-type(n+6),
136
+ .rtb-view-bookings-table td:nth-of-type(n+6) {
137
+ display: none;
138
+ }
139
+ }
140
+ @media screen and (max-width: 600px) {
141
+ .rtb-view-bookings-table th:nth-of-type(n+5),
142
+ .rtb-view-bookings-table td:nth-of-type(n+5) {
143
+ display: none;
144
+ }
145
+ }
146
+
147
+ .rtb-edit-view-booking {
148
+ width: 20px;
149
+ height: 20px;
150
+ margin-left: calc(50% - 10px);
151
+ }
152
+ .rtb-edit-view-booking[disabled] {
153
+ opacity: .75;
154
+ }
155
+
156
+ .rtb-view-bookings-form-confirmation-background-div {
157
+ position: fixed;
158
+ top: 0;
159
+ left: 0;
160
+ width: 100%;
161
+ height: 100%;
162
+ z-index: 999999;
163
+ background: rgba(0,0,0,0.5);
164
+ }
165
+ .rtb-view-bookings-form-confirmation-div {
166
+ position: fixed;
167
+ top: 200px;
168
+ width: 480px;
169
+ left: calc(50% - 240px);
170
+ z-index: 1000000;
171
+ margin: 0;
172
+ background: #fff;
173
+ font-size: 18px;
174
+ text-align: center;
175
+ border-radius: 2px;
176
+ box-shadow: 0 0 4px #555;
177
+ }
178
+ .rtb-view-bookings-form-confirmation-div-inside {
179
+ position: relative;
180
+ float: left;
181
+ width: 100%;
182
+ }
183
+ .rtb-view-bookings-form-confirmation-div-title {
184
+ position: relative;
185
+ float: left;
186
+ width: 90%;
187
+ margin: 24px 5%;
188
+ color: #333;
189
+ }
190
+ .rtb-view-bookings-form-confirmation-accept {
191
+ box-sizing: border-box;
192
+ position: relative;
193
+ float: left;
194
+ width: 32%;
195
+ margin: 24px 34% 0;
196
+ padding: 8px 0;
197
+ background: #222;
198
+ color: #fff;
199
+ border-radius: 2px;
200
+ cursor: pointer;
201
+ transition: background .35s;
202
+ }
203
+ .rtb-view-bookings-form-confirmation-accept:hover {
204
+ background: #555;
205
+ }
206
+ .rtb-view-bookings-form-confirmation-decline {
207
+ box-sizing: border-box;
208
+ position: relative;
209
+ float: left;
210
+ width: 32%;
211
+ margin: 8px 34% 24px;
212
+ padding: 6px 0;
213
+ background: transparent;
214
+ color: #222;
215
+ border: 2px solid #222;
216
+ border-radius: 2px;
217
+ cursor: pointer;
218
+ transition: background .35s, border-color .35s;
219
+ }
220
+ .rtb-view-bookings-form-confirmation-decline:hover {
221
+ background: #555;
222
+ color: #fff;
223
+ border-color: #555;
224
+ }
225
+
226
+ #rtb-view-bookings-form-close {
227
+ position: absolute;
228
+ display: flex;
229
+ justify-content: center;
230
+ align-items: center;
231
+ top: 0;
232
+ right: 0;
233
+ width: 32px;
234
+ height: 32px;
235
+ background: #555;
236
+ color: #fff;
237
+ border-bottom-left-radius: 2px;
238
+ font-size: 15px;
239
+ cursor: pointer;
240
+ transition: background .35s;
241
+ }
242
+ #rtb-view-bookings-form-close:hover {
243
+ background: #222;
244
+ }
245
+
246
+ @media screen and (max-width: 568px) {
247
+ .rtb-view-bookings-form-confirmation-div {
248
+ top: 100px;
249
+ width: 300px;
250
+ left: calc(50% - 150px);
251
+ }
252
+ }
253
+
254
+
255
+ /*CANCEL LINK*/
256
+ .rtb-modification-toggle {
257
+ position: relative;
258
+ float: left;
259
+ padding: 10px 15px;
260
+ margin-bottom: 24px;
261
+ background: #444;
262
+ color: #fff;
263
+ border-radius: 3px;
264
+ cursor: pointer;
265
+ }
266
+ label[for="rtb-modification-email"],
267
+ input[name="modification"] {
268
+ float: left;
269
+ margin-top: 20px;
270
+ }
271
+ label[for="rtb-modification-email"] {
272
+ margin-right: 12px;
273
+ }
274
+ .rtb-find-reservation-button {
275
+ position: relative;
276
+ float: left;
277
+ padding: 10px 15px;
278
+ margin-top: 24px;
279
+ background: #444;
280
+ color: #fff;
281
+ border-radius: 3px;
282
+ cursor: pointer;
283
+ }
284
+
285
+ .rtb-bookings-results {
286
+ position: relative;
287
+ float: left;
288
+ width: 100%;
289
+ margin-top: 16px;
290
+ }
291
+ .rtb-cancel-booking-div {
292
+ border: 1px solid #ddd;
293
+ margin-bottom: 8px;
294
+ }
295
+ .rtb-cancel-booking-div + .alert {
296
+ margin: 0;
297
+ }
298
+ .rtb-cancel-booking-div + .alert.error {
299
+ color: #f24a4d;
300
+ background: #f24a4d47;
301
+ }
302
+ .rtb-cancel-booking-div *:not(:first-child) {
303
+ margin-left: -5px;
304
+ }
305
+
306
+ .rtb-cancel-booking {
307
+ text-align: center;
308
+ padding: 10px 0;
309
+ background: #fe4e4e;
310
+ color: #fff;
311
+ cursor: pointer;
312
+ display: inline-block;
313
+ max-width: 100px;
314
+ min-width: 100px;
315
+ width: 100%;
316
+ }
317
+ .rtb-cancel-booking:hover {
318
+ background: #ff6b6b;
319
+ color: #fff;
320
+ }
321
+ .rtb-cancel-booking.cancelled {
322
+ background: #24b124;
323
+ }
324
+
325
+ .rtb-deposit-booking {
326
+ text-align: center;
327
+ padding: 10px 0;
328
+ background: green;
329
+ color: #fff;
330
+ cursor: pointer;
331
+ display: inline-block;
332
+ max-width: 100px;
333
+ min-width: 100px;
334
+ width: 100%;
335
+ }
336
+ .rtb-deposit-booking:hover {
337
+ background: #008000cf;
338
+ color: #fff;
339
+ }
340
+
341
+ .rtb-booking-information {
342
+ padding-left: 5px;
343
+ display: inline-block;
344
+ }
345
+
346
+ #rtb_recaptcha {
347
+ position: relative;
348
+ float: left;
349
+ width: 100%;
350
+ margin: 16px 0;
351
+ }
352
+
353
+ .stripe-payment-help-text {
354
+ display: none;
355
+ }
356
+ .payment-errors {
357
+ margin: 32px 0;
358
+ border-left: 4px solid #000;
359
+ padding-left: 16px;
360
+ }
361
+ :is(.rtb-booking-form, #stripe-booking-form) button:disabled {
362
+ background-color: gray;
363
+ }
364
+ :is(.rtb-booking-form, #stripe-booking-form) button:disabled:hover {
365
+ text-decoration: none;
366
+ }
367
+
368
+ /* Payment Detail Summary */
369
+ .booking-payment-wrapper .summary-title {
370
+ width: 100%;
371
+ display: block;
372
+ }
373
+ .booking-payment-wrapper dl.summary-detail {
374
+ box-sizing: border-box;
375
+ width: 100%;
376
+ display: block;
377
+ padding: 12px 16px;
378
+ background: #fafafa;
379
+ border: 1px solid #ddd;
380
+ border-radius: 2px;
381
+ }
382
+ .booking-payment-wrapper dl.summary-detail dt {
383
+ width: 120px;
384
+ padding-right: 20px;
385
+ display: inline-block;
386
+ margin: 0 0 8px;
387
+ }
388
+ .booking-payment-wrapper dl.summary-detail dd {
389
+ width: calc(100% - 140px);
390
+ display: inline-block;
391
+ margin: 0 0 8px;
392
+ }
393
+ .booking-payment-wrapper dl.summary-detail dt:last-of-type,
394
+ .booking-payment-wrapper dl.summary-detail dd:last-of-type {
395
+ margin-bottom: 0;
396
  }
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/helper-install-notice.css CHANGED
@@ -1,19 +1,19 @@
1
- .rtb-clear {
2
- clear: both;
3
- }
4
-
5
- .rtb-helper-install-notice {
6
- display: inline-block;
7
- clear: both;
8
- }
9
-
10
- .rtb-helper-install-notice-img,
11
- .rtb-helper-install-notice-txt
12
- {
13
- float: left;
14
- padding: 8px 8px 8px 0px;
15
- }
16
-
17
- .rtb-helper-install-notice div img {
18
- max-height: 36px;
19
- }
1
+ .rtb-clear {
2
+ clear: both;
3
+ }
4
+
5
+ .rtb-helper-install-notice {
6
+ display: inline-block;
7
+ clear: both;
8
+ }
9
+
10
+ .rtb-helper-install-notice-img,
11
+ .rtb-helper-install-notice-txt
12
+ {
13
+ float: left;
14
+ padding: 8px 8px 8px 0px;
15
+ }
16
+
17
+ .rtb-helper-install-notice div img {
18
+ max-height: 36px;
19
+ }
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,93 +1,93 @@
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 params = {};
21
-
22
- params.action = 'rtb-welcome-add-menu-page';
23
- params.nonce = rtb_getting_started.nonce;
24
- params.reservations_page_title = reservations_page_title;
25
-
26
- var data = jQuery.param( params );
27
- jQuery.post(ajaxurl, data, function(response) {});
28
-
29
- var section = jQuery(this).data('nextaction');
30
- rtb_toggle_section(section);
31
- });
32
-
33
- jQuery('.rtb-welcome-screen-save-schedule-open-button').on('click', function() {
34
-
35
- var schedule_open = [];
36
-
37
- jQuery('.sap-scheduler-rule').each(function() {
38
- var weekdays ={};
39
-
40
- jQuery(this).find('.sap-scheduler-weekdays input[type="checkbox"]').each(function() {
41
- if ( jQuery(this).is(':checked') ) { weekdays[jQuery(this).data('day')] = "1" ; }
42
- });
43
-
44
- var start = jQuery(this).find('.sap-scheduler-time-input .start input').first().val();
45
- var end = jQuery(this).find('.sap-scheduler-time-input .end input').first().val();
46
-
47
- schedule_open.push({'weekdays': weekdays, 'time': {'start': start, 'end': end }});
48
- });
49
-
50
- var params = {};
51
-
52
- params.action = 'rtb-welcome-set-schedule';
53
- params.nonce = rtb_getting_started.nonce;
54
- params.schedule_open = schedule_open;
55
-
56
- var data = jQuery.param( params );
57
- jQuery.post(ajaxurl, data, function(response) {
58
-
59
- 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>' );
60
- jQuery( '.rtb-save-message' ).delay( 2000 ).fadeOut( 400, function() { jQuery( '.rtb-save-message' ).remove(); } );
61
- });
62
- });
63
-
64
- jQuery('.rtb-welcome-screen-save-options-button').on('click', function() {
65
- var party_size_min = jQuery('select[name="min-party-size"]').val();
66
- var party_size = jQuery('select[name="party-size"]').val();
67
- var early_bookings = jQuery('select[name="early-bookings"]').val();
68
- var late_bookings = jQuery('select[name="late-bookings"]').val();
69
- var time_interval = jQuery('select[name="time-interval"]').val();
70
-
71
- var params = {};
72
-
73
- params.action = 'rtb-welcome-set-options';
74
- params.nonce = rtb_getting_started.nonce;
75
- params.party_size_min = party_size_min;
76
- params.party_size = party_size;
77
- params.early_bookings = early_bookings;
78
- params.late_bookings = late_bookings;
79
- params.time_interval = time_interval;
80
-
81
- var data = jQuery.param( params );
82
- jQuery.post(ajaxurl, data, function(response) {
83
-
84
- 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>' );
85
- jQuery( '.rtb-save-message' ).delay( 2000 ).fadeOut( 400, function() { jQuery( '.rtb-save-message' ).remove(); } );
86
- });
87
- });
88
- });
89
-
90
- function rtb_toggle_section(page) {
91
- jQuery('.rtb-welcome-screen-box').removeClass('rtb-welcome-screen-open');
92
- jQuery('.rtb-welcome-screen-' + page).addClass('rtb-welcome-screen-open');
93
  }
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 params = {};
21
+
22
+ params.action = 'rtb-welcome-add-menu-page';
23
+ params.nonce = rtb_getting_started.nonce;
24
+ params.reservations_page_title = reservations_page_title;
25
+
26
+ var data = jQuery.param( params );
27
+ jQuery.post(ajaxurl, data, function(response) {});
28
+
29
+ var section = jQuery(this).data('nextaction');
30
+ rtb_toggle_section(section);
31
+ });
32
+
33
+ jQuery('.rtb-welcome-screen-save-schedule-open-button').on('click', function() {
34
+
35
+ var schedule_open = [];
36
+
37
+ jQuery('.sap-scheduler-rule').each(function() {
38
+ var weekdays ={};
39
+
40
+ jQuery(this).find('.sap-scheduler-weekdays input[type="checkbox"]').each(function() {
41
+ if ( jQuery(this).is(':checked') ) { weekdays[jQuery(this).data('day')] = "1" ; }
42
+ });
43
+
44
+ var start = jQuery(this).find('.sap-scheduler-time-input .start input').first().val();
45
+ var end = jQuery(this).find('.sap-scheduler-time-input .end input').first().val();
46
+
47
+ schedule_open.push({'weekdays': weekdays, 'time': {'start': start, 'end': end }});
48
+ });
49
+
50
+ var params = {};
51
+
52
+ params.action = 'rtb-welcome-set-schedule';
53
+ params.nonce = rtb_getting_started.nonce;
54
+ params.schedule_open = schedule_open;
55
+
56
+ var data = jQuery.param( params );
57
+ jQuery.post(ajaxurl, data, function(response) {
58
+
59
+ 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>' );
60
+ jQuery( '.rtb-save-message' ).delay( 2000 ).fadeOut( 400, function() { jQuery( '.rtb-save-message' ).remove(); } );
61
+ });
62
+ });
63
+
64
+ jQuery('.rtb-welcome-screen-save-options-button').on('click', function() {
65
+ var party_size_min = jQuery('select[name="min-party-size"]').val();
66
+ var party_size = jQuery('select[name="party-size"]').val();
67
+ var early_bookings = jQuery('select[name="early-bookings"]').val();
68
+ var late_bookings = jQuery('select[name="late-bookings"]').val();
69
+ var time_interval = jQuery('select[name="time-interval"]').val();
70
+
71
+ var params = {};
72
+
73
+ params.action = 'rtb-welcome-set-options';
74
+ params.nonce = rtb_getting_started.nonce;
75
+ params.party_size_min = party_size_min;
76
+ params.party_size = party_size;
77
+ params.early_bookings = early_bookings;
78
+ params.late_bookings = late_bookings;
79
+ params.time_interval = time_interval;
80
+
81
+ var data = jQuery.param( params );
82
+ jQuery.post(ajaxurl, data, function(response) {
83
+
84
+ 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>' );
85
+ jQuery( '.rtb-save-message' ).delay( 2000 ).fadeOut( 400, function() { jQuery( '.rtb-save-message' ).remove(); } );
86
+ });
87
+ });
88
+ });
89
+
90
+ function rtb_toggle_section(page) {
91
+ jQuery('.rtb-welcome-screen-box').removeClass('rtb-welcome-screen-open');
92
+ jQuery('.rtb-welcome-screen-' + page).addClass('rtb-welcome-screen-open');
93
  }
assets/js/admin-settings.js CHANGED
@@ -1,76 +1,76 @@
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' ).each(function(){
15
-
16
- var table = jQuery( this );
17
- var action = 'hide';
18
-
19
- if( table.find( '> tbody > tr:not(.sap-hidden)' ).length ) {
20
- action = 'show';
21
- }
22
-
23
- table[action]();
24
- if( ( ovrly = table.prev( '.sap-premium-options-table-overlay' ) ).length ) {
25
- ovrly[action]();
26
- ovrly.prev( 'h2' )[action]();
27
- }
28
- else {
29
- table.prev( 'h2' )[action]();
30
- }
31
- });
32
- }
33
-
34
-
35
- jQuery(document).ready(function() {
36
- jQuery('.rtb-spectrum').spectrum({
37
- showInput: true,
38
- showInitial: true,
39
- preferredFormat: "hex",
40
- allowEmpty: true
41
- });
42
-
43
- jQuery('.rtb-spectrum').css('display', 'inline');
44
-
45
- jQuery('.rtb-spectrum').on('change', function() {
46
- if (jQuery(this).val() != "") {
47
- jQuery(this).css('background', jQuery(this).val());
48
- var rgb = RTB_hexToRgb(jQuery(this).val());
49
- var Brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
50
- if (Brightness < 100) {jQuery(this).css('color', '#ffffff');}
51
- else {jQuery(this).css('color', '#000000');}
52
- }
53
- else {
54
- jQuery(this).css('background', 'none');
55
- }
56
- });
57
-
58
- jQuery('.rtb-spectrum').each(function() {
59
- if (jQuery(this).val() != "") {
60
- jQuery(this).css('background', jQuery(this).val());
61
- var rgb = RTB_hexToRgb(jQuery(this).val());
62
- var Brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
63
- if (Brightness < 100) {jQuery(this).css('color', '#ffffff');}
64
- else {jQuery(this).css('color', '#000000');}
65
- }
66
- });
67
- });
68
-
69
- function RTB_hexToRgb(hex) {
70
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
71
- return result ? {
72
- r: parseInt(result[1], 16),
73
- g: parseInt(result[2], 16),
74
- b: parseInt(result[3], 16)
75
- } : null;
76
- }
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' ).each(function(){
15
+
16
+ var table = jQuery( this );
17
+ var action = 'hide';
18
+
19
+ if( table.find( '> tbody > tr:not(.sap-hidden)' ).length ) {
20
+ action = 'show';
21
+ }
22
+
23
+ table[action]();
24
+ if( ( ovrly = table.prev( '.sap-premium-options-table-overlay' ) ).length ) {
25
+ ovrly[action]();
26
+ ovrly.prev( 'h2' )[action]();
27
+ }
28
+ else {
29
+ table.prev( 'h2' )[action]();
30
+ }
31
+ });
32
+ }
33
+
34
+
35
+ jQuery(document).ready(function() {
36
+ jQuery('.rtb-spectrum').spectrum({
37
+ showInput: true,
38
+ showInitial: true,
39
+ preferredFormat: "hex",
40
+ allowEmpty: true
41
+ });
42
+
43
+ jQuery('.rtb-spectrum').css('display', 'inline');
44
+
45
+ jQuery('.rtb-spectrum').on('change', function() {
46
+ if (jQuery(this).val() != "") {
47
+ jQuery(this).css('background', jQuery(this).val());
48
+ var rgb = RTB_hexToRgb(jQuery(this).val());
49
+ var Brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
50
+ if (Brightness < 100) {jQuery(this).css('color', '#ffffff');}
51
+ else {jQuery(this).css('color', '#000000');}
52
+ }
53
+ else {
54
+ jQuery(this).css('background', 'none');
55
+ }
56
+ });
57
+
58
+ jQuery('.rtb-spectrum').each(function() {
59
+ if (jQuery(this).val() != "") {
60
+ jQuery(this).css('background', jQuery(this).val());
61
+ var rgb = RTB_hexToRgb(jQuery(this).val());
62
+ var Brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
63
+ if (Brightness < 100) {jQuery(this).css('color', '#ffffff');}
64
+ else {jQuery(this).css('color', '#000000');}
65
+ }
66
+ });
67
+ });
68
+
69
+ function RTB_hexToRgb(hex) {
70
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
71
+ return result ? {
72
+ r: parseInt(result[1], 16),
73
+ g: parseInt(result[2], 16),
74
+ b: parseInt(result[3], 16)
75
+ } : null;
76
+ }
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,983 +1,983 @@
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
- today: rtb_pickadate.date_today_label,
53
- clear: rtb_pickadate.date_clear_label,
54
- close: rtb_pickadate.date_close_label,
55
-
56
- onStart: function() {
57
-
58
- // Block dates beyond early bookings window
59
- if ( rtb_pickadate.early_bookings !== '' ) {
60
- this.set( 'max', parseInt( rtb_pickadate.early_bookings, 10 ) );
61
- }
62
-
63
- // Select the value when loaded if a value has been set
64
- if ( $date_input.val() !== '' ) {
65
- var date = new Date( $date_input.val() );
66
- if ( Object.prototype.toString.call( date ) === "[object Date]" ) {
67
- this.set( 'select', date );
68
- }
69
- }
70
- else if ( jQuery( '.rtb-selected-date' ).length ) {
71
- var date = new Date( jQuery( '.rtb-selected-date' ).val() );
72
- if ( Object.prototype.toString.call( date ) === "[object Date]" ) {
73
- this.set( 'select', date );
74
- }
75
- }
76
- }
77
- });
78
-
79
- rtb_booking_form.datepicker = date_input.pickadate( 'picker' );
80
- }
81
-
82
- // Declare timepicker
83
- var $time_input = $( '#rtb-time' );
84
- if ( $time_input.length ) {
85
- var time_input = $time_input.pickatime({
86
- format: rtb_pickadate.time_format,
87
- formatSubmit: 'h:i A',
88
- hiddenName: true,
89
- interval: parseInt( rtb_pickadate.time_interval, 10 ),
90
- container: 'body',
91
- clear: rtb_pickadate.time_clear_label,
92
-
93
- // Select the value when loaded if a value has been set
94
- onStart: function() {
95
- if ( $time_input.val() !== '' ) {
96
- var today = new Date();
97
- var today_date = today.getFullYear() + '/' + ( today.getMonth() + 1 ) + '/' + today.getDate();
98
- var time = new Date( today_date + ' ' + $time_input.val() );
99
- if ( Object.prototype.toString.call( time ) === "[object Date]" ) {
100
- this.set( 'select', time );
101
- }
102
-
103
- }
104
- }
105
- });
106
-
107
- rtb_booking_form.timepicker = time_input.pickatime( 'picker' );
108
- }
109
-
110
- // We need to check both to support different jQuery versions loaded
111
- // by older versions of WordPress. In jQuery v1.10.2, the property
112
- // is undefined. But in v1.11.3 it's set to null.
113
- if ( rtb_booking_form.datepicker === null || typeof rtb_booking_form.datepicker == 'undefined' ) {
114
- return;
115
- }
116
-
117
- // Update disabled dates
118
- rtb_booking_form.update_disabled_dates();
119
-
120
- if ( typeof rtb_pickadate.late_bookings === 'string' ) {
121
- if ( rtb_pickadate.late_bookings == 'same_day' ) {
122
- rtb_booking_form.datepicker.set( 'min', 1 );
123
- } else if ( rtb_pickadate.late_bookings !== '' ) {
124
- rtb_pickadate.late_bookings = parseInt( rtb_pickadate.late_bookings, 10 );
125
- if ( rtb_pickadate.late_bookings % 1 === 0 && rtb_pickadate.late_bookings >= 1440 ) {
126
- var min = Math.floor( rtb_pickadate.late_bookings / 1440 );
127
- rtb_booking_form.datepicker.set( 'min', min );
128
- }
129
- }
130
- }
131
-
132
- // If no date has been set, select today's date if it's a valid
133
- // date. User may opt not to do this in the settings.
134
- if ( $date_input.val() === '' && !$( '.rtb-booking-form .date .rtb-error' ).length ) {
135
-
136
- if ( rtb_pickadate.date_onload == 'soonest' ) {
137
- rtb_booking_form.datepicker.set( 'select', new Date() );
138
- } else if ( rtb_pickadate.date_onload !== 'empty' ) {
139
- var dateToVerify = rtb_booking_form.datepicker.component.create( new Date() );
140
- var isDisabled = rtb_booking_form.datepicker.component.disabled( dateToVerify );
141
- if ( !isDisabled ) {
142
- rtb_booking_form.datepicker.set( 'select', dateToVerify );
143
- }
144
- }
145
- }
146
-
147
- if ( rtb_booking_form.timepicker === null || typeof rtb_booking_form.timepicker == 'undefined' ) {
148
- return;
149
- }
150
-
151
- // Update timepicker on pageload and whenever the datepicker is closed
152
- rtb_booking_form.update_timepicker_range();
153
- rtb_booking_form.datepicker.on( {
154
- open: function () {
155
-
156
- rtb_booking_form.before_change_value = rtb_booking_form.datepicker.get();
157
- },
158
-
159
- close: function() {
160
-
161
- rtb_booking_form.after_change_value = rtb_booking_form.datepicker.get();
162
-
163
- if(rtb_booking_form.before_change_value != rtb_booking_form.after_change_value) {
164
- // clear time value if date changed
165
- rtb_booking_form.timepicker.clear();
166
- }
167
-
168
- rtb_booking_form.update_timepicker_range();
169
- rtb_booking_form.update_party_size_select();
170
- rtb_booking_form.update_possible_tables();
171
- }
172
- });
173
-
174
- rtb_booking_form.timepicker.on( {
175
- close: function() {
176
- rtb_booking_form.update_party_size_select();
177
- rtb_booking_form.update_possible_tables();
178
- }
179
- });
180
-
181
- $( '#rtb-party' ).on( 'change', function() {
182
- rtb_booking_form.update_possible_tables();
183
- });
184
-
185
- $( '#rtb-location' ).on( 'change', function() {
186
-
187
- if ( ! rtb_pickadate.multiple_locations_enabled ) { return; }
188
-
189
- rtb_booking_form.timepicker.clear();
190
- rtb_booking_form.datepicker.clear();
191
-
192
- rtb_booking_form.update_base_data_for_selected_location();
193
-
194
- rtb_booking_form.update_datepicker();
195
-
196
- rtb_booking_form.update_timepicker_range();
197
-
198
- rtb_booking_form.update_party_size_select();
199
- });
200
-
201
- rtb_booking_form.update_possible_tables();
202
- }
203
- };
204
-
205
- /**
206
- * Update base data for date/time picker as per the selected location
207
- * @return object
208
- */
209
- rtb_booking_form.update_base_data_for_selected_location = function () {
210
- const selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '';
211
-
212
- if( '' == selected_location ) {
213
- // Set global settings
214
- return Object.assign( rtb_pickadate, rtb_location_data.global );
215
- }
216
-
217
- return Object.assign( rtb_pickadate, rtb_location_data[selected_location] );
218
- }
219
-
220
- rtb_booking_form.update_datepicker = function () {
221
-
222
- // Reset enabled/disabled rules on this datepicker
223
- rtb_booking_form.datepicker.set( 'enable', false );
224
- rtb_booking_form.datepicker.set( 'disable', false );
225
-
226
- rtb_booking_form.update_disabled_dates();
227
- }
228
-
229
- /**
230
- * Update datepicker to change the disabled dates based on location
231
- */
232
- rtb_booking_form.update_disabled_dates = function() {
233
-
234
- // Pass conditional configuration parameters
235
- if ( rtb_pickadate.disable_dates.length ) {
236
-
237
- var disable_dates = jQuery.extend( true, [], rtb_pickadate.disable_dates );
238
-
239
- // Update weekday dates if start of the week has been modified
240
- if ( typeof rtb_booking_form.datepicker.component.settings.firstDay == 'number' ) {
241
- var weekday_num = 0;
242
- for ( var disable_key in rtb_pickadate.disable_dates ) {
243
- if ( typeof rtb_pickadate.disable_dates[disable_key] == 'number' ) {
244
- weekday_num = rtb_pickadate.disable_dates[disable_key] - rtb_booking_form.datepicker.component.settings.firstDay;
245
- if ( weekday_num < 1 ) {
246
- weekday_num = 7;
247
- }
248
- disable_dates[disable_key] = weekday_num;
249
- }
250
- }
251
- }
252
-
253
- rtb_booking_form.datepicker.set( 'disable', disable_dates );
254
-
255
- }
256
- }
257
-
258
- /**
259
- * Update the timepicker's range based on the currently selected date
260
- */
261
- rtb_booking_form.update_timepicker_range = function() {
262
-
263
- // Reset enabled/disabled rules on this timepicker
264
- rtb_booking_form.timepicker.set( 'enable', false );
265
- rtb_booking_form.timepicker.set( 'disable', false );
266
-
267
- if ( rtb_booking_form.datepicker.get() === '' ) {
268
- rtb_booking_form.timepicker.set( 'disable', true );
269
- return;
270
- }
271
-
272
- var selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
273
- selected_date_year = selected_date.getFullYear(),
274
- selected_date_month = selected_date.getMonth(),
275
- selected_date_date = selected_date.getDate(),
276
- current_date = new Date();
277
-
278
- selected_date.setHours(0, 0, 0), selected_date.setMilliseconds(100);
279
-
280
- // Declaring the first element true inverts the timepicker settings. All
281
- // times subsequently declared are valid. Any time that doesn't fall
282
- // within those declarations is invalid.
283
- // See: http://amsul.ca/pickadate.js/time/#disable-times-all
284
- var valid_times = [ rtb_booking_form.get_outer_time_range() ];
285
-
286
- if ( rtb_pickadate.enable_max_reservations || rtb_pickadate.multiple_locations_enabled ) {
287
- selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '';
288
-
289
- let hidden_location = jQuery('.rtb-booking-form-form input[name="rtb-location"]');
290
- if('' == selected_location && hidden_location.length ) {
291
- selected_location = hidden_location.val();
292
- }
293
-
294
- selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
295
- selected_date_date = ('0' + selected_date_date).slice(-2);
296
-
297
- var params = {};
298
-
299
- params.action = 'rtb_get_available_time_slots';
300
- params.nonce = rtb_booking_form_js_localize.nonce;
301
- params.year = selected_date_year;
302
- params.month = selected_date_month;
303
- params.day = selected_date_date;
304
- params.location = selected_location;
305
-
306
- var data = jQuery.param( params );
307
- jQuery.post( ajaxurl, data, function( response ) {
308
-
309
- if ( ! response ) {
310
- displayFieldError( 'date', rtb_booking_form_js_localize.error['smthng-wrng-cntct-us'] );
311
- rtb_booking_form.timepicker.set( 'disable', true );
312
-
313
- return;
314
- }
315
- else {
316
- clearPrevFieldError( 'date' );
317
- }
318
-
319
- var additional_valid_times = jQuery.parseJSON( response );
320
-
321
- // If today is all day open, only add one valid date/time rule
322
- let outer_range = valid_times[0];
323
- if(
324
- additional_valid_times.length == 1
325
- && additional_valid_times[0].from[0] == outer_range.from[0]
326
- && additional_valid_times[0].from[1] == outer_range.from[1]
327
- && additional_valid_times[0].to[0] == outer_range.to[0]
328
- && additional_valid_times[0].to[1] == outer_range.to[1] ) {
329
- var all_valid_times = [ additional_valid_times[0] ];
330
- }
331
- else {
332
- var all_valid_times = valid_times.concat( additional_valid_times );
333
- }
334
-
335
- if( !Array.isArray( additional_valid_times ) || 1 > additional_valid_times.length ) {
336
- displayFieldError( 'time', rtb_booking_form_js_localize.error['no-slots-available'] );
337
- }
338
- else {
339
- clearPrevFieldError( 'time' );
340
- }
341
-
342
- rtb_booking_form.timepicker.set( 'disable', all_valid_times );
343
- });
344
- }
345
-
346
- else {
347
- // Check if this date is an exception to the rules
348
- if ( typeof rtb_pickadate.schedule_closed !== 'undefined' ) {
349
-
350
- var excp_date = [];
351
- var excp_start_date = [];
352
- var excp_start_time = [];
353
- var excp_end_date = [];
354
- var excp_end_time = [];
355
- for ( var closed_key in rtb_pickadate.schedule_closed ) {
356
-
357
- let rule = rtb_pickadate.schedule_closed[closed_key];
358
- if( rule.hasOwnProperty('date_range') ) {
359
- let start = '' != rule.date_range.start ? new Date( rule.date_range.start ) : new Date();
360
- start.setHours(0, 0, 0), start.setMilliseconds(0);
361
- start = start.getTime();
362
-
363
- let end = '' != rule.date_range.end ? new Date( rule.date_range.end ) : 9999999999999;
364
- 'number' != typeof end && end.setHours(23, 59, 58) && end.setMilliseconds(0);
365
- end = 'number' != typeof end ? end.getTime() : end;
366
-
367
- if( start < selected_date.getTime() && selected_date.getTime() < end ) {
368
- excp_date = selected_date;
369
- }
370
- else {
371
- // Set anything to void this rule
372
- // Dates assign with copy, thus creating a new one
373
- excp_date = new Date( selected_date.getTime() );
374
- excp_date.setDate( selected_date_year + 1 );
375
- }
376
- }
377
- else {
378
- excp_date = new Date( rule.date );
379
- }
380
-
381
- if ( excp_date.getFullYear() == selected_date_year &&
382
- excp_date.getMonth() == selected_date_month &&
383
- excp_date.getDate() == selected_date_date
384
- ) {
385
-
386
- // Closed all day
387
- if ( typeof rtb_pickadate.schedule_closed[closed_key].time == 'undefined' ) {
388
- rtb_booking_form.timepicker.set( 'disable', [ true ] );
389
-
390
- return;
391
- }
392
-
393
- if ( typeof rtb_pickadate.schedule_closed[closed_key].time.start !== 'undefined' ) {
394
- excp_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.start );
395
- excp_start_time = [ excp_start_date.getHours(), excp_start_date.getMinutes() ];
396
- } else {
397
- excp_start_time = [ 0, 0 ]; // Start of the day
398
- }
399
-
400
- if ( typeof rtb_pickadate.schedule_closed[closed_key].time.end !== 'undefined' ) {
401
- excp_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.end );
402
- excp_end_time = [ excp_end_date.getHours(), excp_end_date.getMinutes() ];
403
- } else {
404
- excp_end_time = [ 24, 0 ]; // End of the day
405
- }
406
-
407
- excp_start_time = rtb_booking_form.get_earliest_time( excp_start_time, selected_date, current_date );
408
-
409
- valid_times.push( { from: excp_start_time, to: excp_end_time, inverted: true } );
410
- }
411
- }
412
-
413
- excp_date = excp_start_date = excp_start_time = excp_end_date = excp_end_time = null;
414
-
415
- // Exit early if this date is an exception
416
- if ( valid_times.length > 1 ) {
417
- rtb_booking_form.timepicker.set( 'disable', valid_times );
418
-
419
- return;
420
- }
421
- }
422
-
423
- // Get any rules which apply to this weekday
424
- if ( typeof rtb_pickadate.schedule_open != 'undefined' ) {
425
-
426
- var selected_date_weekday = selected_date.getDay();
427
-
428
- var weekdays = {
429
- sunday: 0,
430
- monday: 1,
431
- tuesday: 2,
432
- wednesday: 3,
433
- thursday: 4,
434
- friday: 5,
435
- saturday: 6,
436
- };
437
-
438
- var rule_start_date = [];
439
- var rule_start_time = [];
440
- var rule_end_date = [];
441
- var rule_end_time = [];
442
- for ( var open_key in rtb_pickadate.schedule_open ) {
443
-
444
- if ( typeof rtb_pickadate.schedule_open[open_key].weekdays !== 'undefined' ) {
445
- for ( var weekdays_key in rtb_pickadate.schedule_open[open_key].weekdays ) {
446
- if ( weekdays[weekdays_key] == selected_date_weekday ) {
447
-
448
- // Closed all day
449
- if ( typeof rtb_pickadate.schedule_open[open_key].time == 'undefined' ) {
450
- rtb_booking_form.timepicker.set( 'disable', [ true ] );
451
-
452
- return;
453
- }
454
-
455
- if ( typeof rtb_pickadate.schedule_open[open_key].time.start !== 'undefined' ) {
456
- rule_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.start );
457
- rule_start_time = [ rule_start_date.getHours(), rule_start_date.getMinutes() ];
458
- } else {
459
- rule_start_time = [ 0, 0 ]; // Start of the day
460
- }
461
-
462
- if ( typeof rtb_pickadate.schedule_open[open_key].time.end !== 'undefined' ) {
463
- rule_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.end );
464
- rule_end_time = rtb_booking_form.get_latest_viable_time( rule_end_date.getHours(), rule_end_date.getMinutes() );
465
- } else {
466
- rule_end_time = [ 24, 0 ]; // End of the day
467
- }
468
-
469
- rule_start_time = rtb_booking_form.get_earliest_time( rule_start_time, selected_date, current_date );
470
-
471
- valid_times.push( { from: rule_start_time, to: rule_end_time, inverted: true } );
472
-
473
- }
474
- }
475
- }
476
- }
477
-
478
- rule_start_date = rule_start_time = rule_end_date = rule_end_time = null;
479
-
480
- // Pass any valid times located
481
- if ( valid_times.length > 1 ) {
482
- rtb_booking_form.timepicker.set( 'disable', valid_times );
483
-
484
- return;
485
- }
486
-
487
- }
488
-
489
- // Set it to always open if no rules have been defined
490
- rtb_booking_form.timepicker.set( 'enable', true );
491
- rtb_booking_form.timepicker.set( 'disable', false );
492
- }
493
-
494
- return;
495
- };
496
-
497
- /**
498
- * Get the outer times to exclude based on the time interval
499
- *
500
- * This is a work-around for a bug in pickadate.js
501
- * See: https://github.com/amsul/pickadate.js/issues/614
502
- */
503
- rtb_booking_form.get_outer_time_range = function() {
504
-
505
- var interval = rtb_booking_form.timepicker.get( 'interval' );
506
-
507
- var hour = 24;
508
-
509
- while ( interval >= 60 ) {
510
- hour--;
511
- interval -= 60;
512
- }
513
-
514
- if ( interval > 0 ) {
515
- hour--;
516
- // All day open does not work when date-time-range and current open time are exact same
517
- interval = 60 - interval;
518
- }
519
-
520
- return { from: [0, 0], to: [ hour, interval ] };
521
- };
522
-
523
- /**
524
- * Get the latest working opening hour/minute value
525
- *
526
- * This is a workaround for a bug in pickadate.js. The end time of a valid
527
- * time value must NOT fall within the last timepicker interval and midnight
528
- * See: https://github.com/amsul/pickadate.js/issues/614
529
- */
530
- rtb_booking_form.get_latest_viable_time = function( hour, minute ) {
531
-
532
- var interval = rtb_booking_form.timepicker.get( 'interval' );
533
-
534
- var outer_time_range = this.get_outer_time_range();
535
-
536
- /*
537
- * Adjust the last time for wide intervals, so that the last time entered
538
- * corresponds to an interval time. A pickadate bug causes a later time to
539
- * be available for booking otherwise.
540
- */
541
- if ( interval > 60) {
542
-
543
- var last_hour = 0;
544
- var last_minute = 0;
545
- var last_time_minutes = 0;
546
-
547
- var end_time_minutes = 60 * hour + minute;
548
-
549
- while ( ( last_time_minutes + interval ) <= end_time_minutes ) {
550
-
551
- var remainder = interval + last_minute;
552
-
553
- while ( remainder >= 60 ) {
554
- last_hour++;
555
- remainder -= 60;
556
- }
557
-
558
- last_minute = remainder;
559
-
560
- last_time_minutes = 60 * last_hour + last_minute;
561
- }
562
-
563
- var long_interval_viable_time = [ last_hour, last_minute ];
564
- }
565
-
566
-
567
- if ( interval > 60 ) {
568
-
569
- return long_interval_viable_time;
570
- }
571
- else if ( hour > outer_time_range.to[0] || minute > outer_time_range.to[1] ) {
572
-
573
- return [ outer_time_range.to[0], outer_time_range.to[1] ];
574
- } else {
575
-
576
- return [ hour, minute ];
577
- }
578
- };
579
-
580
- /**
581
- * Get the earliest valid time
582
- *
583
- * This checks the valid time for the day and, if a current day, applies
584
- * any late booking restrictions. It also ensures that times in the past
585
- * are not availabe.
586
- *
587
- * @param array start_time
588
- * @param array selected_date
589
- * @param array current_date
590
- */
591
- rtb_booking_form.get_earliest_time = function( start_time, selected_date, current_date ) {
592
-
593
- // Only make adjustments for current day selections
594
- if ( selected_date.toDateString() !== current_date.toDateString() ) {
595
- return start_time;
596
- }
597
-
598
- // Get the number of minutes after midnight to compare
599
- var start_minutes = ( start_time[0] * 60 ) + start_time[1],
600
- current_minutes = ( current_date.getHours() * 60 ) + current_date.getMinutes(),
601
- late_booking_minutes;
602
-
603
- start_minutes = start_minutes > current_minutes ? start_minutes : current_minutes;
604
-
605
- if ( typeof rtb_pickadate.late_bookings === 'number' && rtb_pickadate.late_bookings % 1 === 0 ) {
606
- late_booking_minutes = current_minutes + rtb_pickadate.late_bookings;
607
- if ( late_booking_minutes > start_minutes ) {
608
- start_minutes = late_booking_minutes;
609
- }
610
- }
611
-
612
- start_time = [ Math.floor( start_minutes / 60 ), start_minutes % 60 ];
613
-
614
- return start_time;
615
- };
616
-
617
- rtb_booking_form.update_party_size_select = function() {
618
-
619
- if ( rtb_pickadate.enable_max_reservations && ( rtb_pickadate.max_people || rtb_pickadate.multiple_locations_enabled ) ) {
620
- var partySelect = $('#rtb-party'),
621
- selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '',
622
- selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
623
- selected_date_year = selected_date.getFullYear(),
624
- selected_date_month = selected_date.getMonth(),
625
- selected_date_date = selected_date.getDate(),
626
- selected_time = rtb_booking_form.timepicker.get('value');
627
-
628
- if ( ! selected_time ) { return; }
629
-
630
- selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
631
- selected_date_date = ('0' + selected_date_date).slice(-2);
632
-
633
- //reset party size
634
- partySelect.prop("selectedIndex", 0).change();
635
-
636
- var params = {};
637
-
638
- params.action = 'rtb_get_available_party_size';
639
- params.nonce = rtb_booking_form_js_localize.nonce;
640
- params.year = selected_date_year;
641
- params.month = selected_date_month;
642
- params.day = selected_date_date;
643
- params.time = selected_time;
644
- params.location = selected_location;
645
-
646
- var data = jQuery.param( params );
647
- jQuery.post( ajaxurl, data, function( response ) {
648
- if ( ! response ) {
649
- return;
650
- }
651
-
652
- response = jQuery.parseJSON(response);
653
-
654
- var available_spots = response.available_spots;
655
-
656
- partySelect.prop('disabled', false);
657
-
658
- partySelect.find('> option').each(function() {
659
- var that = $(this);
660
- if (this.value > available_spots) {
661
- that.prop('disabled', true);
662
- } else {
663
- that.prop('disabled', false);
664
- }
665
- });
666
- });
667
- }
668
- }
669
-
670
- rtb_booking_form.update_possible_tables = function() {
671
-
672
- if ( rtb_pickadate.enable_tables ) {
673
-
674
- var table_select = $('#rtb-table'),
675
- party = $('#rtb-party').val(),
676
- selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
677
- selected_date_year = selected_date.getFullYear(),
678
- selected_date_month = selected_date.getMonth(),
679
- selected_date_date = selected_date.getDate(),
680
- selected_time = rtb_booking_form.timepicker.get('value');
681
-
682
- if ( ! selected_time || ! party ) { return; }
683
-
684
- selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
685
- selected_date_date = ('0' + selected_date_date).slice(-2);
686
-
687
- //reset table selection
688
- table_select.prop("selectedIndex", 0).change();
689
-
690
- //remove table combinations
691
- table_select.find('> option').each( function() {
692
- if ( $( this ).val().indexOf( ',' ) !== -1 ) { $( this ).remove(); }
693
- });
694
-
695
- var booking_id = $( '.rtb-booking-form form input[name="ID"]').length ? $( '.rtb-booking-form form input[name="ID"]').val() : 0;
696
-
697
- var params = {};
698
-
699
- params.action = 'rtb_get_available_tables';
700
- params.nonce = rtb_booking_form_js_localize.nonce;
701
- params.year = selected_date_year;
702
- params.month = selected_date_month;
703
- params.day = selected_date_date;
704
- params.time = selected_time;
705
- params.party = party;
706
- params.booking_id = booking_id
707
-
708
- var data = jQuery.param( params );
709
- jQuery.post( ajaxurl, data, function( response ) {
710
- if ( ! response ) {
711
- return;
712
- }
713
-
714
- response = jQuery.parseJSON(response);
715
-
716
- var available_tables = response.available_tables;
717
-
718
- table_select.prop('disabled', false);
719
-
720
- table_select.find('> option').hide();
721
- table_select.find('> option').attr('disabled', 'disabled');
722
-
723
- if( 1 > available_tables.length ) {
724
- displayFieldError( 'table', rtb_booking_form_js_localize.error['no-table-available'] );
725
- }
726
- else {
727
- clearPrevFieldError( 'table' );
728
- }
729
-
730
- jQuery.each(available_tables, function(index, element) {
731
-
732
- if ( index.indexOf( ',' ) === -1 ) {
733
- table_select.find('> option[value="' + index + '"]').show();
734
- table_select.find('> option[value="' + index + '"]').removeAttr('disabled', 'disabled');
735
- }
736
- else {
737
- table_select.append( '<option value="' + index + '">' + element + '</option>' );
738
- }
739
-
740
- });
741
-
742
- if ( response.selected_table != -1 ) {
743
- table_select.val( response.selected_table );
744
- }
745
- else if( '' != table_select.data('selected') ) {
746
- table_select.val( table_select.data('selected') );
747
- }
748
-
749
- // pre-select table if it was selected before and is available
750
-
751
-
752
- });
753
- }
754
-
755
- }
756
-
757
- rtb_booking_form.init();
758
- });
759
-
760
- //Handle reservation modification
761
- jQuery(document).ready(function() {
762
- jQuery('.rtb-modification-toggle').on('click', function() {
763
- jQuery('.rtb-modification-form, .rtb-booking-form-form').toggleClass('rtb-hidden');
764
-
765
- if (jQuery('.rtb-modification-form').hasClass('rtb-hidden')) {
766
- jQuery('.rtb-modification-toggle').html(rtb_booking_form_js_localize.want_to_modify);
767
- }
768
- else {
769
- jQuery('.rtb-modification-toggle').html(rtb_booking_form_js_localize.make);
770
- }
771
- });
772
-
773
- var modify_booking = function(ev) {
774
- var booking_email = jQuery('input[name="rtb_modification_email"]').val();
775
-
776
- var params = {};
777
-
778
- params.action = 'rtb_find_reservations';
779
- params.nonce = rtb_booking_form_js_localize.nonce;
780
- params.booking_email = booking_email;
781
-
782
- var data = jQuery.param( params );
783
- jQuery.post(ajaxurl, data, function(response) {
784
-
785
- if (response.success) {
786
- var booking_html = '';
787
- var guest_txt = '';
788
- var pay_btn = '';
789
-
790
- jQuery(response.data.bookings).each(function( index, val) {
791
- pay_btn = '';
792
- guest_txt = val.party > 1 ? rtb_booking_form_js_localize.guests : rtb_booking_form_js_localize.guest;
793
-
794
- if('payment_pending' == val.status || 'payment_failed' == val.status) {
795
- pay_btn = `
796
- <div class="rtb-deposit-booking" data-bookingid="${val.ID}" data-bookingemail="${val.email}">
797
- ${rtb_booking_form_js_localize.deposit}
798
- </div>
799
- `;
800
- }
801
-
802
- booking_html += `
803
- <div class="rtb-cancel-booking-div">
804
- <div class="rtb-cancel-booking" data-bookingid="${val.ID}" data-bookingemail="${val.email}">
805
- ${rtb_booking_form_js_localize.cancel}
806
- </div>
807
- ${pay_btn}
808
- <div class="rtb-booking-information">${val.datetime} - ${val.party} ${guest_txt} (${val.status_lbl})</div>
809
- </div>
810
- `;
811
- });
812
-
813
- jQuery('.rtb-bookings-results').html(booking_html);
814
-
815
- cancellationHandler();
816
- delayedPaymentHandler();
817
- }
818
- else {jQuery('.rtb-bookings-results').html(response.data.msg);}
819
- });
820
- };
821
-
822
- jQuery(document).on('click', '.rtb-find-reservation-button', modify_booking);
823
- jQuery(document).on('keypress', '.rtb-modification-form input', function (ev) {
824
- // Capture enter key
825
- if(13 == ev.which) {
826
- ev.preventDefault();
827
- modify_booking(ev);
828
- }
829
- });
830
- });
831
-
832
- function cancellationHandler() {
833
- jQuery('.rtb-cancel-booking:not(.cancelled)').off('click');
834
- jQuery('.rtb-cancel-booking:not(.cancelled)').on('click', function() {
835
- var btn = jQuery(this);
836
-
837
- if(btn.hasClass('processing')) {
838
- return;
839
- }
840
-
841
- btn.addClass('processing');
842
-
843
- var booking_id = btn.data('bookingid');
844
- var booking_email = btn.data('bookingemail');
845
-
846
- var params = {};
847
-
848
- params.action = 'rtb_cancel_reservations';
849
- params.nonce = rtb_booking_form_js_localize.nonce;
850
- params.booking_id = booking_id;
851
- params.booking_email = booking_email;
852
-
853
- var data = jQuery.param( params );
854
- jQuery.post(ajaxurl, data, function(response) {
855
- if (response.success) {
856
- if (response.data.hasOwnProperty('cancelled_redirect')) {
857
- window.location.href = response.data.cancelled_redirect;
858
- }
859
- else {
860
- btn.off('click');
861
- btn.addClass('cancelled');
862
- btn.text(rtb_booking_form_js_localize.cancelled);
863
- }
864
- }
865
- else {
866
- btn.parent().after(`<p class="alert error">${response.data.msg}</p>`);
867
- }
868
-
869
- btn.removeClass('processing');
870
- });
871
- });
872
- }
873
-
874
- function delayedPaymentHandler() {
875
- jQuery('.rtb-deposit-booking').off('click');
876
- jQuery('.rtb-deposit-booking').on('click', function() {
877
- var btn = jQuery(this);
878
-
879
- if(btn.hasClass('processing')) {
880
- return;
881
- }
882
-
883
- btn.addClass('processing');
884
-
885
- var booking_id = btn.data('bookingid');
886
- var booking_email = btn.data('bookingemail');
887
-
888
- var data = {
889
- 'booking_id': booking_id,
890
- 'booking_email': booking_email,
891
- 'payment': 'rtb-delayed-deposit'
892
- };
893
-
894
- let current_loc = window.location;
895
- let params = new URLSearchParams();
896
- Object.keys( data ).map( function( param ) { params.append( param, data[ param ] ) } );
897
-
898
- window.location = current_loc.origin + current_loc.pathname + '?' + params.toString();
899
-
900
- });
901
- }
902
-
903
- function displayFieldError( field, message ) {
904
-
905
- const fieldSelector = '.'+field;
906
- var fieldElm = jQuery('form.rtb-booking-form-form '+fieldSelector);
907
-
908
- if( fieldElm.length ) {
909
-
910
- clearPrevFieldError( field );
911
-
912
- fieldElm.prepend(`
913
- <div class="rtb-error">${message}</div>
914
- `);
915
- }
916
- }
917
-
918
- function clearPrevFieldError( field ) {
919
- const fieldSelector = '.'+field;
920
- var errorElms = jQuery('form.rtb-booking-form-form ' + fieldSelector + ' .rtb-error');
921
-
922
- if( errorElms.length ) {
923
- errorElms.each((idx, x) => x.remove());
924
- }
925
- }
926
-
927
- // Functions for the 'View Bookings' shortcode
928
- jQuery(document).ready(function ($) {
929
- jQuery('.rtb-view-bookings-form-date-selector').on('change', function() {
930
- window.location.href = replaceUrlParam(window.location.href, 'date', jQuery(this).val());
931
- });
932
-
933
- jQuery('.rtb-edit-view-booking').on('click', function() {
934
- jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').removeClass('rtb-hidden');
935
-
936
- jQuery('.rtb-view-bookings-form-confirmation-div').data('bookingid', jQuery(this).data('bookingid'));
937
-
938
- jQuery(this).prop('checked', false);
939
- });
940
-
941
- jQuery('.rtb-view-bookings-form-confirmation-accept').on('click', function() {
942
- var booking_id = jQuery('.rtb-view-bookings-form-confirmation-div').data('bookingid');
943
-
944
- var params = {};
945
-
946
- params.action = 'rtb_set_reservation_arrived';
947
- params.nonce = rtb_booking_form_js_localize.nonce;
948
- params.booking = {
949
- 'ID': booking_id
950
- };
951
-
952
- var data = $.param( params );
953
-
954
- jQuery.post(ajaxurl, data, function(response) {
955
-
956
- if (response.success) {window.location.href = window.location.href}
957
- else {jQuery('.rtb-view-bookings-form-confirmation-div').html(response.data.msg);}
958
- });
959
- });
960
-
961
- jQuery('.rtb-view-bookings-form-confirmation-decline').on('click', function() {
962
- jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
963
- });
964
- jQuery('.rtb-view-bookings-form-confirmation-background-div').on('click', function() {
965
- jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
966
- });
967
- jQuery('#rtb-view-bookings-form-close').on('click', function() {
968
- jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
969
- });
970
- });
971
-
972
- function replaceUrlParam(url, paramName, paramValue)
973
- {
974
- if (paramValue == null) {
975
- paramValue = '';
976
- }
977
- var pattern = new RegExp('\\b('+paramName+'=).*?(&|#|$)');
978
- if (url.search(pattern)>=0) {
979
- return url.replace(pattern,'$1' + paramValue + '$2');
980
- }
981
- url = url.replace(/[?#]$/,'');
982
- return url + (url.indexOf('?')>0 ? '&' : '?') + paramName + '=' + paramValue;
983
  }
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
+ today: rtb_pickadate.date_today_label,
53
+ clear: rtb_pickadate.date_clear_label,
54
+ close: rtb_pickadate.date_close_label,
55
+
56
+ onStart: function() {
57
+
58
+ // Block dates beyond early bookings window
59
+ if ( rtb_pickadate.early_bookings !== '' ) {
60
+ this.set( 'max', parseInt( rtb_pickadate.early_bookings, 10 ) );
61
+ }
62
+
63
+ // Select the value when loaded if a value has been set
64
+ if ( $date_input.val() !== '' ) {
65
+ var date = new Date( $date_input.val() );
66
+ if ( Object.prototype.toString.call( date ) === "[object Date]" ) {
67
+ this.set( 'select', date );
68
+ }
69
+ }
70
+ else if ( jQuery( '.rtb-selected-date' ).length ) {
71
+ var date = new Date( jQuery( '.rtb-selected-date' ).val() );
72
+ if ( Object.prototype.toString.call( date ) === "[object Date]" ) {
73
+ this.set( 'select', date );
74
+ }
75
+ }
76
+ }
77
+ });
78
+
79
+ rtb_booking_form.datepicker = date_input.pickadate( 'picker' );
80
+ }
81
+
82
+ // Declare timepicker
83
+ var $time_input = $( '#rtb-time' );
84
+ if ( $time_input.length ) {
85
+ var time_input = $time_input.pickatime({
86
+ format: rtb_pickadate.time_format,
87
+ formatSubmit: 'h:i A',
88
+ hiddenName: true,
89
+ interval: parseInt( rtb_pickadate.time_interval, 10 ),
90
+ container: 'body',
91
+ clear: rtb_pickadate.time_clear_label,
92
+
93
+ // Select the value when loaded if a value has been set
94
+ onStart: function() {
95
+ if ( $time_input.val() !== '' ) {
96
+ var today = new Date();
97
+ var today_date = today.getFullYear() + '/' + ( today.getMonth() + 1 ) + '/' + today.getDate();
98
+ var time = new Date( today_date + ' ' + $time_input.val() );
99
+ if ( Object.prototype.toString.call( time ) === "[object Date]" ) {
100
+ this.set( 'select', time );
101
+ }
102
+
103
+ }
104
+ }
105
+ });
106
+
107
+ rtb_booking_form.timepicker = time_input.pickatime( 'picker' );
108
+ }
109
+
110
+ // We need to check both to support different jQuery versions loaded
111
+ // by older versions of WordPress. In jQuery v1.10.2, the property
112
+ // is undefined. But in v1.11.3 it's set to null.
113
+ if ( rtb_booking_form.datepicker === null || typeof rtb_booking_form.datepicker == 'undefined' ) {
114
+ return;
115
+ }
116
+
117
+ // Update disabled dates
118
+ rtb_booking_form.update_disabled_dates();
119
+
120
+ if ( typeof rtb_pickadate.late_bookings === 'string' ) {
121
+ if ( rtb_pickadate.late_bookings == 'same_day' ) {
122
+ rtb_booking_form.datepicker.set( 'min', 1 );
123
+ } else if ( rtb_pickadate.late_bookings !== '' ) {
124
+ rtb_pickadate.late_bookings = parseInt( rtb_pickadate.late_bookings, 10 );
125
+ if ( rtb_pickadate.late_bookings % 1 === 0 && rtb_pickadate.late_bookings >= 1440 ) {
126
+ var min = Math.floor( rtb_pickadate.late_bookings / 1440 );
127
+ rtb_booking_form.datepicker.set( 'min', min );
128
+ }
129
+ }
130
+ }
131
+
132
+ // If no date has been set, select today's date if it's a valid
133
+ // date. User may opt not to do this in the settings.
134
+ if ( $date_input.val() === '' && !$( '.rtb-booking-form .date .rtb-error' ).length ) {
135
+
136
+ if ( rtb_pickadate.date_onload == 'soonest' ) {
137
+ rtb_booking_form.datepicker.set( 'select', new Date() );
138
+ } else if ( rtb_pickadate.date_onload !== 'empty' ) {
139
+ var dateToVerify = rtb_booking_form.datepicker.component.create( new Date() );
140
+ var isDisabled = rtb_booking_form.datepicker.component.disabled( dateToVerify );
141
+ if ( !isDisabled ) {
142
+ rtb_booking_form.datepicker.set( 'select', dateToVerify );
143
+ }
144
+ }
145
+ }
146
+
147
+ if ( rtb_booking_form.timepicker === null || typeof rtb_booking_form.timepicker == 'undefined' ) {
148
+ return;
149
+ }
150
+
151
+ // Update timepicker on pageload and whenever the datepicker is closed
152
+ rtb_booking_form.update_timepicker_range();
153
+ rtb_booking_form.datepicker.on( {
154
+ open: function () {
155
+
156
+ rtb_booking_form.before_change_value = rtb_booking_form.datepicker.get();
157
+ },
158
+
159
+ close: function() {
160
+
161
+ rtb_booking_form.after_change_value = rtb_booking_form.datepicker.get();
162
+
163
+ if(rtb_booking_form.before_change_value != rtb_booking_form.after_change_value) {
164
+ // clear time value if date changed
165
+ rtb_booking_form.timepicker.clear();
166
+ }
167
+
168
+ rtb_booking_form.update_timepicker_range();
169
+ rtb_booking_form.update_party_size_select();
170
+ rtb_booking_form.update_possible_tables();
171
+ }
172
+ });
173
+
174
+ rtb_booking_form.timepicker.on( {
175
+ close: function() {
176
+ rtb_booking_form.update_party_size_select();
177
+ rtb_booking_form.update_possible_tables();
178
+ }
179
+ });
180
+
181
+ $( '#rtb-party' ).on( 'change', function() {
182
+ rtb_booking_form.update_possible_tables();
183
+ });
184
+
185
+ $( '#rtb-location' ).on( 'change', function() {
186
+
187
+ if ( ! rtb_pickadate.multiple_locations_enabled ) { return; }
188
+
189
+ rtb_booking_form.timepicker.clear();
190
+ rtb_booking_form.datepicker.clear();
191
+
192
+ rtb_booking_form.update_base_data_for_selected_location();
193
+
194
+ rtb_booking_form.update_datepicker();
195
+
196
+ rtb_booking_form.update_timepicker_range();
197
+
198
+ rtb_booking_form.update_party_size_select();
199
+ });
200
+
201
+ rtb_booking_form.update_possible_tables();
202
+ }
203
+ };
204
+
205
+ /**
206
+ * Update base data for date/time picker as per the selected location
207
+ * @return object
208
+ */
209
+ rtb_booking_form.update_base_data_for_selected_location = function () {
210
+ const selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '';
211
+
212
+ if( '' == selected_location ) {
213
+ // Set global settings
214
+ return Object.assign( rtb_pickadate, rtb_location_data.global );
215
+ }
216
+
217
+ return Object.assign( rtb_pickadate, rtb_location_data[selected_location] );
218
+ }
219
+
220
+ rtb_booking_form.update_datepicker = function () {
221
+
222
+ // Reset enabled/disabled rules on this datepicker
223
+ rtb_booking_form.datepicker.set( 'enable', false );
224
+ rtb_booking_form.datepicker.set( 'disable', false );
225
+
226
+ rtb_booking_form.update_disabled_dates();
227
+ }
228
+
229
+ /**
230
+ * Update datepicker to change the disabled dates based on location
231
+ */
232
+ rtb_booking_form.update_disabled_dates = function() {
233
+
234
+ // Pass conditional configuration parameters
235
+ if ( rtb_pickadate.disable_dates.length ) {
236
+
237
+ var disable_dates = jQuery.extend( true, [], rtb_pickadate.disable_dates );
238
+
239
+ // Update weekday dates if start of the week has been modified
240
+ if ( typeof rtb_booking_form.datepicker.component.settings.firstDay == 'number' ) {
241
+ var weekday_num = 0;
242
+ for ( var disable_key in rtb_pickadate.disable_dates ) {
243
+ if ( typeof rtb_pickadate.disable_dates[disable_key] == 'number' ) {
244
+ weekday_num = rtb_pickadate.disable_dates[disable_key] - rtb_booking_form.datepicker.component.settings.firstDay;
245
+ if ( weekday_num < 1 ) {
246
+ weekday_num = 7;
247
+ }
248
+ disable_dates[disable_key] = weekday_num;
249
+ }
250
+ }
251
+ }
252
+
253
+ rtb_booking_form.datepicker.set( 'disable', disable_dates );
254
+
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Update the timepicker's range based on the currently selected date
260
+ */
261
+ rtb_booking_form.update_timepicker_range = function() {
262
+
263
+ // Reset enabled/disabled rules on this timepicker
264
+ rtb_booking_form.timepicker.set( 'enable', false );
265
+ rtb_booking_form.timepicker.set( 'disable', false );
266
+
267
+ if ( rtb_booking_form.datepicker.get() === '' ) {
268
+ rtb_booking_form.timepicker.set( 'disable', true );
269
+ return;
270
+ }
271
+
272
+ var selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
273
+ selected_date_year = selected_date.getFullYear(),
274
+ selected_date_month = selected_date.getMonth(),
275
+ selected_date_date = selected_date.getDate(),
276
+ current_date = new Date();
277
+
278
+ selected_date.setHours(0, 0, 0), selected_date.setMilliseconds(100);
279
+
280
+ // Declaring the first element true inverts the timepicker settings. All
281
+ // times subsequently declared are valid. Any time that doesn't fall
282
+ // within those declarations is invalid.
283
+ // See: http://amsul.ca/pickadate.js/time/#disable-times-all
284
+ var valid_times = [ rtb_booking_form.get_outer_time_range() ];
285
+
286
+ if ( rtb_pickadate.enable_max_reservations || rtb_pickadate.multiple_locations_enabled ) {
287
+ selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '';
288
+
289
+ let hidden_location = jQuery('.rtb-booking-form-form input[name="rtb-location"]');
290
+ if('' == selected_location && hidden_location.length ) {
291
+ selected_location = hidden_location.val();
292
+ }
293
+
294
+ selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
295
+ selected_date_date = ('0' + selected_date_date).slice(-2);
296
+
297
+ var params = {};
298
+
299
+ params.action = 'rtb_get_available_time_slots';
300
+ params.nonce = rtb_booking_form_js_localize.nonce;
301
+ params.year = selected_date_year;
302
+ params.month = selected_date_month;
303
+ params.day = selected_date_date;
304
+ params.location = selected_location;
305
+
306
+ var data = jQuery.param( params );
307
+ jQuery.post( ajaxurl, data, function( response ) {
308
+
309
+ if ( ! response ) {
310
+ displayFieldError( 'date', rtb_booking_form_js_localize.error['smthng-wrng-cntct-us'] );
311
+ rtb_booking_form.timepicker.set( 'disable', true );
312
+
313
+ return;
314
+ }
315
+ else {
316
+ clearPrevFieldError( 'date' );
317
+ }
318
+
319
+ var additional_valid_times = jQuery.parseJSON( response );
320
+
321
+ // If today is all day open, only add one valid date/time rule
322
+ let outer_range = valid_times[0];
323
+ if(
324
+ additional_valid_times.length == 1
325
+ && additional_valid_times[0].from[0] == outer_range.from[0]
326
+ && additional_valid_times[0].from[1] == outer_range.from[1]
327
+ && additional_valid_times[0].to[0] == outer_range.to[0]
328
+ && additional_valid_times[0].to[1] == outer_range.to[1] ) {
329
+ var all_valid_times = [ additional_valid_times[0] ];
330
+ }
331
+ else {
332
+ var all_valid_times = valid_times.concat( additional_valid_times );
333
+ }
334
+
335
+ if( !Array.isArray( additional_valid_times ) || 1 > additional_valid_times.length ) {
336
+ displayFieldError( 'time', rtb_booking_form_js_localize.error['no-slots-available'] );
337
+ }
338
+ else {
339
+ clearPrevFieldError( 'time' );
340
+ }
341
+
342
+ rtb_booking_form.timepicker.set( 'disable', all_valid_times );
343
+ });
344
+ }
345
+
346
+ else {
347
+ // Check if this date is an exception to the rules
348
+ if ( typeof rtb_pickadate.schedule_closed !== 'undefined' ) {
349
+
350
+ var excp_date = [];
351
+ var excp_start_date = [];
352
+ var excp_start_time = [];
353
+ var excp_end_date = [];
354
+ var excp_end_time = [];
355
+ for ( var closed_key in rtb_pickadate.schedule_closed ) {
356
+
357
+ let rule = rtb_pickadate.schedule_closed[closed_key];
358
+ if( rule.hasOwnProperty('date_range') ) {
359
+ let start = '' != rule.date_range.start ? new Date( rule.date_range.start ) : new Date();
360
+ start.setHours(0, 0, 0), start.setMilliseconds(0);
361
+ start = start.getTime();
362
+
363
+ let end = '' != rule.date_range.end ? new Date( rule.date_range.end ) : 9999999999999;
364
+ 'number' != typeof end && end.setHours(23, 59, 58) && end.setMilliseconds(0);
365
+ end = 'number' != typeof end ? end.getTime() : end;
366
+
367
+ if( start < selected_date.getTime() && selected_date.getTime() < end ) {
368
+ excp_date = selected_date;
369
+ }
370
+ else {
371
+ // Set anything to void this rule
372
+ // Dates assign with copy, thus creating a new one
373
+ excp_date = new Date( selected_date.getTime() );
374
+ excp_date.setDate( selected_date_year + 1 );
375
+ }
376
+ }
377
+ else {
378
+ excp_date = new Date( rule.date );
379
+ }
380
+
381
+ if ( excp_date.getFullYear() == selected_date_year &&
382
+ excp_date.getMonth() == selected_date_month &&
383
+ excp_date.getDate() == selected_date_date
384
+ ) {
385
+
386
+ // Closed all day
387
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time == 'undefined' ) {
388
+ rtb_booking_form.timepicker.set( 'disable', [ true ] );
389
+
390
+ return;
391
+ }
392
+
393
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time.start !== 'undefined' ) {
394
+ excp_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.start );
395
+ excp_start_time = [ excp_start_date.getHours(), excp_start_date.getMinutes() ];
396
+ } else {
397
+ excp_start_time = [ 0, 0 ]; // Start of the day
398
+ }
399
+
400
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time.end !== 'undefined' ) {
401
+ excp_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.end );
402
+ excp_end_time = [ excp_end_date.getHours(), excp_end_date.getMinutes() ];
403
+ } else {
404
+ excp_end_time = [ 24, 0 ]; // End of the day
405
+ }
406
+
407
+ excp_start_time = rtb_booking_form.get_earliest_time( excp_start_time, selected_date, current_date );
408
+
409
+ valid_times.push( { from: excp_start_time, to: excp_end_time, inverted: true } );
410
+ }
411
+ }
412
+
413
+ excp_date = excp_start_date = excp_start_time = excp_end_date = excp_end_time = null;
414
+
415
+ // Exit early if this date is an exception
416
+ if ( valid_times.length > 1 ) {
417
+ rtb_booking_form.timepicker.set( 'disable', valid_times );
418
+
419
+ return;
420
+ }
421
+ }
422
+
423
+ // Get any rules which apply to this weekday
424
+ if ( typeof rtb_pickadate.schedule_open != 'undefined' ) {
425
+
426
+ var selected_date_weekday = selected_date.getDay();
427
+
428
+ var weekdays = {
429
+ sunday: 0,
430
+ monday: 1,
431
+ tuesday: 2,
432
+ wednesday: 3,
433
+ thursday: 4,
434
+ friday: 5,
435
+ saturday: 6,
436
+ };
437
+
438
+ var rule_start_date = [];
439
+ var rule_start_time = [];
440
+ var rule_end_date = [];
441
+ var rule_end_time = [];
442
+ for ( var open_key in rtb_pickadate.schedule_open ) {
443
+
444
+ if ( typeof rtb_pickadate.schedule_open[open_key].weekdays !== 'undefined' ) {
445
+ for ( var weekdays_key in rtb_pickadate.schedule_open[open_key].weekdays ) {
446
+ if ( weekdays[weekdays_key] == selected_date_weekday ) {
447
+
448
+ // Closed all day
449
+ if ( typeof rtb_pickadate.schedule_open[open_key].time == 'undefined' ) {
450
+ rtb_booking_form.timepicker.set( 'disable', [ true ] );
451
+
452
+ return;
453
+ }
454
+
455
+ if ( typeof rtb_pickadate.schedule_open[open_key].time.start !== 'undefined' ) {
456
+ rule_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.start );
457
+ rule_start_time = [ rule_start_date.getHours(), rule_start_date.getMinutes() ];
458
+ } else {
459
+ rule_start_time = [ 0, 0 ]; // Start of the day
460
+ }
461
+
462
+ if ( typeof rtb_pickadate.schedule_open[open_key].time.end !== 'undefined' ) {
463
+ rule_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.end );
464
+ rule_end_time = rtb_booking_form.get_latest_viable_time( rule_end_date.getHours(), rule_end_date.getMinutes() );
465
+ } else {
466
+ rule_end_time = [ 24, 0 ]; // End of the day
467
+ }
468
+
469
+ rule_start_time = rtb_booking_form.get_earliest_time( rule_start_time, selected_date, current_date );
470
+
471
+ valid_times.push( { from: rule_start_time, to: rule_end_time, inverted: true } );
472
+
473
+ }
474
+ }
475
+ }
476
+ }
477
+
478
+ rule_start_date = rule_start_time = rule_end_date = rule_end_time = null;
479
+
480
+ // Pass any valid times located
481
+ if ( valid_times.length > 1 ) {
482
+ rtb_booking_form.timepicker.set( 'disable', valid_times );
483
+
484
+ return;
485
+ }
486
+
487
+ }
488
+
489
+ // Set it to always open if no rules have been defined
490
+ rtb_booking_form.timepicker.set( 'enable', true );
491
+ rtb_booking_form.timepicker.set( 'disable', false );
492
+ }
493
+
494
+ return;
495
+ };
496
+
497
+ /**
498
+ * Get the outer times to exclude based on the time interval
499
+ *
500
+ * This is a work-around for a bug in pickadate.js
501
+ * See: https://github.com/amsul/pickadate.js/issues/614
502
+ */
503
+ rtb_booking_form.get_outer_time_range = function() {
504
+
505
+ var interval = rtb_booking_form.timepicker.get( 'interval' );
506
+
507
+ var hour = 24;
508
+
509
+ while ( interval >= 60 ) {
510
+ hour--;
511
+ interval -= 60;
512
+ }
513
+
514
+ if ( interval > 0 ) {
515
+ hour--;
516
+ // All day open does not work when date-time-range and current open time are exact same
517
+ interval = 60 - interval;
518
+ }
519
+
520
+ return { from: [0, 0], to: [ hour, interval ] };
521
+ };
522
+
523
+ /**
524
+ * Get the latest working opening hour/minute value
525
+ *
526
+ * This is a workaround for a bug in pickadate.js. The end time of a valid
527
+ * time value must NOT fall within the last timepicker interval and midnight
528
+ * See: https://github.com/amsul/pickadate.js/issues/614
529
+ */
530
+ rtb_booking_form.get_latest_viable_time = function( hour, minute ) {
531
+
532
+ var interval = rtb_booking_form.timepicker.get( 'interval' );
533
+
534
+ var outer_time_range = this.get_outer_time_range();
535
+
536
+ /*
537
+ * Adjust the last time for wide intervals, so that the last time entered
538
+ * corresponds to an interval time. A pickadate bug causes a later time to
539
+ * be available for booking otherwise.
540
+ */
541
+ if ( interval > 60) {
542
+
543
+ var last_hour = 0;
544
+ var last_minute = 0;
545
+ var last_time_minutes = 0;
546
+
547
+ var end_time_minutes = 60 * hour + minute;
548
+
549
+ while ( ( last_time_minutes + interval ) <= end_time_minutes ) {
550
+
551
+ var remainder = interval + last_minute;
552
+
553
+ while ( remainder >= 60 ) {
554
+ last_hour++;
555
+ remainder -= 60;
556
+ }
557
+
558
+ last_minute = remainder;
559
+
560
+ last_time_minutes = 60 * last_hour + last_minute;
561
+ }
562
+
563
+ var long_interval_viable_time = [ last_hour, last_minute ];
564
+ }
565
+
566
+
567
+ if ( interval > 60 ) {
568
+
569
+ return long_interval_viable_time;
570
+ }
571
+ else if ( hour > outer_time_range.to[0] || minute > outer_time_range.to[1] ) {
572
+
573
+ return [ outer_time_range.to[0], outer_time_range.to[1] ];
574
+ } else {
575
+
576
+ return [ hour, minute ];
577
+ }
578
+ };
579
+
580
+ /**
581
+ * Get the earliest valid time
582
+ *
583
+ * This checks the valid time for the day and, if a current day, applies
584
+ * any late booking restrictions. It also ensures that times in the past
585
+ * are not availabe.
586
+ *
587
+ * @param array start_time
588
+ * @param array selected_date
589
+ * @param array current_date
590
+ */
591
+ rtb_booking_form.get_earliest_time = function( start_time, selected_date, current_date ) {
592
+
593
+ // Only make adjustments for current day selections
594
+ if ( selected_date.toDateString() !== current_date.toDateString() ) {
595
+ return start_time;
596
+ }
597
+
598
+ // Get the number of minutes after midnight to compare
599
+ var start_minutes = ( start_time[0] * 60 ) + start_time[1],
600
+ current_minutes = ( current_date.getHours() * 60 ) + current_date.getMinutes(),
601
+ late_booking_minutes;
602
+
603
+ start_minutes = start_minutes > current_minutes ? start_minutes : current_minutes;
604
+
605
+ if ( typeof rtb_pickadate.late_bookings === 'number' && rtb_pickadate.late_bookings % 1 === 0 ) {
606
+ late_booking_minutes = current_minutes + rtb_pickadate.late_bookings;
607
+ if ( late_booking_minutes > start_minutes ) {
608
+ start_minutes = late_booking_minutes;
609
+ }
610
+ }
611
+
612
+ start_time = [ Math.floor( start_minutes / 60 ), start_minutes % 60 ];
613
+
614
+ return start_time;
615
+ };
616
+
617
+ rtb_booking_form.update_party_size_select = function() {
618
+
619
+ if ( rtb_pickadate.enable_max_reservations && ( rtb_pickadate.max_people || rtb_pickadate.multiple_locations_enabled ) ) {
620
+ var partySelect = $('#rtb-party'),
621
+ selected_location = jQuery( '#rtb-location' ).length ? jQuery( '#rtb-location' ).val() : '',
622
+ selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
623
+ selected_date_year = selected_date.getFullYear(),
624
+ selected_date_month = selected_date.getMonth(),
625
+ selected_date_date = selected_date.getDate(),
626
+ selected_time = rtb_booking_form.timepicker.get('value');
627
+
628
+ if ( ! selected_time ) { return; }
629
+
630
+ selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
631
+ selected_date_date = ('0' + selected_date_date).slice(-2);
632
+
633
+ //reset party size
634
+ partySelect.prop("selectedIndex", 0).change();
635
+
636
+ var params = {};
637
+
638
+ params.action = 'rtb_get_available_party_size';
639
+ params.nonce = rtb_booking_form_js_localize.nonce;
640
+ params.year = selected_date_year;
641
+ params.month = selected_date_month;
642
+ params.day = selected_date_date;
643
+ params.time = selected_time;
644
+ params.location = selected_location;
645
+
646
+ var data = jQuery.param( params );
647
+ jQuery.post( ajaxurl, data, function( response ) {
648
+ if ( ! response ) {
649
+ return;
650
+ }
651
+
652
+ response = jQuery.parseJSON(response);
653
+
654
+ var available_spots = response.available_spots;
655
+
656
+ partySelect.prop('disabled', false);
657
+
658
+ partySelect.find('> option').each(function() {
659
+ var that = $(this);
660
+ if (this.value > available_spots) {
661
+ that.prop('disabled', true);
662
+ } else {
663
+ that.prop('disabled', false);
664
+ }
665
+ });
666
+ });
667
+ }
668
+ }
669
+
670
+ rtb_booking_form.update_possible_tables = function() {
671
+
672
+ if ( rtb_pickadate.enable_tables ) {
673
+
674
+ var table_select = $('#rtb-table'),
675
+ party = $('#rtb-party').val(),
676
+ selected_date = new Date( rtb_booking_form.datepicker.get( 'select', 'yyyy/mm/dd' ) ),
677
+ selected_date_year = selected_date.getFullYear(),
678
+ selected_date_month = selected_date.getMonth(),
679
+ selected_date_date = selected_date.getDate(),
680
+ selected_time = rtb_booking_form.timepicker.get('value');
681
+
682
+ if ( ! selected_time || ! party ) { return; }
683
+
684
+ selected_date_month = ('0' + (selected_date_month + 1)).slice(-2);
685
+ selected_date_date = ('0' + selected_date_date).slice(-2);
686
+
687
+ //reset table selection
688
+ table_select.prop("selectedIndex", 0).change();
689
+
690
+ //remove table combinations
691
+ table_select.find('> option').each( function() {
692
+ if ( $( this ).val().indexOf( ',' ) !== -1 ) { $( this ).remove(); }
693
+ });
694
+
695
+ var booking_id = $( '.rtb-booking-form form input[name="ID"]').length ? $( '.rtb-booking-form form input[name="ID"]').val() : 0;
696
+
697
+ var params = {};
698
+
699
+ params.action = 'rtb_get_available_tables';
700
+ params.nonce = rtb_booking_form_js_localize.nonce;
701
+ params.year = selected_date_year;
702
+ params.month = selected_date_month;
703
+ params.day = selected_date_date;
704
+ params.time = selected_time;
705
+ params.party = party;
706
+ params.booking_id = booking_id
707
+
708
+ var data = jQuery.param( params );
709
+ jQuery.post( ajaxurl, data, function( response ) {
710
+ if ( ! response ) {
711
+ return;
712
+ }
713
+
714
+ response = jQuery.parseJSON(response);
715
+
716
+ var available_tables = response.available_tables;
717
+
718
+ table_select.prop('disabled', false);
719
+
720
+ table_select.find('> option').hide();
721
+ table_select.find('> option').attr('disabled', 'disabled');
722
+
723
+ if( 1 > available_tables.length ) {
724
+ displayFieldError( 'table', rtb_booking_form_js_localize.error['no-table-available'] );
725
+ }
726
+ else {
727
+ clearPrevFieldError( 'table' );
728
+ }
729
+
730
+ jQuery.each(available_tables, function(index, element) {
731
+
732
+ if ( index.indexOf( ',' ) === -1 ) {
733
+ table_select.find('> option[value="' + index + '"]').show();
734
+ table_select.find('> option[value="' + index + '"]').removeAttr('disabled', 'disabled');
735
+ }
736
+ else {
737
+ table_select.append( '<option value="' + index + '">' + element + '</option>' );
738
+ }
739
+
740
+ });
741
+
742
+ if ( response.selected_table != -1 ) {
743
+ table_select.val( response.selected_table );
744
+ }
745
+ else if( '' != table_select.data('selected') ) {
746
+ table_select.val( table_select.data('selected') );
747
+ }
748
+
749
+ // pre-select table if it was selected before and is available
750
+
751
+
752
+ });
753
+ }
754
+
755
+ }
756
+
757
+ rtb_booking_form.init();
758
+ });
759
+
760
+ //Handle reservation modification
761
+ jQuery(document).ready(function() {
762
+ jQuery('.rtb-modification-toggle').on('click', function() {
763
+ jQuery('.rtb-modification-form, .rtb-booking-form-form').toggleClass('rtb-hidden');
764
+
765
+ if (jQuery('.rtb-modification-form').hasClass('rtb-hidden')) {
766
+ jQuery('.rtb-modification-toggle').html(rtb_booking_form_js_localize.want_to_modify);
767
+ }
768
+ else {
769
+ jQuery('.rtb-modification-toggle').html(rtb_booking_form_js_localize.make);
770
+ }
771
+ });
772
+
773
+ var modify_booking = function(ev) {
774
+ var booking_email = jQuery('input[name="rtb_modification_email"]').val();
775
+
776
+ var params = {};
777
+
778
+ params.action = 'rtb_find_reservations';
779
+ params.nonce = rtb_booking_form_js_localize.nonce;
780
+ params.booking_email = booking_email;
781
+
782
+ var data = jQuery.param( params );
783
+ jQuery.post(ajaxurl, data, function(response) {
784
+
785
+ if (response.success) {
786
+ var booking_html = '';
787
+ var guest_txt = '';
788
+ var pay_btn = '';
789
+
790
+ jQuery(response.data.bookings).each(function( index, val) {
791
+ pay_btn = '';
792
+ guest_txt = val.party > 1 ? rtb_booking_form_js_localize.guests : rtb_booking_form_js_localize.guest;
793
+
794
+ if('payment_pending' == val.status || 'payment_failed' == val.status) {
795
+ pay_btn = `
796
+ <div class="rtb-deposit-booking" data-bookingid="${val.ID}" data-bookingemail="${val.email}">
797
+ ${rtb_booking_form_js_localize.deposit}
798
+ </div>
799
+ `;
800
+ }
801
+
802
+ booking_html += `
803
+ <div class="rtb-cancel-booking-div">
804
+ <div class="rtb-cancel-booking" data-bookingid="${val.ID}" data-bookingemail="${val.email}">
805
+ ${rtb_booking_form_js_localize.cancel}
806
+ </div>
807
+ ${pay_btn}
808
+ <div class="rtb-booking-information">${val.datetime} - ${val.party} ${guest_txt} (${val.status_lbl})</div>
809
+ </div>
810
+ `;
811
+ });
812
+
813
+ jQuery('.rtb-bookings-results').html(booking_html);
814
+
815
+ cancellationHandler();
816
+ delayedPaymentHandler();
817
+ }
818
+ else {jQuery('.rtb-bookings-results').html(response.data.msg);}
819
+ });
820
+ };
821
+
822
+ jQuery(document).on('click', '.rtb-find-reservation-button', modify_booking);
823
+ jQuery(document).on('keypress', '.rtb-modification-form input', function (ev) {
824
+ // Capture enter key
825
+ if(13 == ev.which) {
826
+ ev.preventDefault();
827
+ modify_booking(ev);
828
+ }
829
+ });
830
+ });
831
+
832
+ function cancellationHandler() {
833
+ jQuery('.rtb-cancel-booking:not(.cancelled)').off('click');
834
+ jQuery('.rtb-cancel-booking:not(.cancelled)').on('click', function() {
835
+ var btn = jQuery(this);
836
+
837
+ if(btn.hasClass('processing')) {
838
+ return;
839
+ }
840
+
841
+ btn.addClass('processing');
842
+
843
+ var booking_id = btn.data('bookingid');
844
+ var booking_email = btn.data('bookingemail');
845
+
846
+ var params = {};
847
+
848
+ params.action = 'rtb_cancel_reservations';
849
+ params.nonce = rtb_booking_form_js_localize.nonce;
850
+ params.booking_id = booking_id;
851
+ params.booking_email = booking_email;
852
+
853
+ var data = jQuery.param( params );
854
+ jQuery.post(ajaxurl, data, function(response) {
855
+ if (response.success) {
856
+ if (response.data.hasOwnProperty('cancelled_redirect')) {
857
+ window.location.href = response.data.cancelled_redirect;
858
+ }
859
+ else {
860
+ btn.off('click');
861
+ btn.addClass('cancelled');
862
+ btn.text(rtb_booking_form_js_localize.cancelled);
863
+ }
864
+ }
865
+ else {
866
+ btn.parent().after(`<p class="alert error">${response.data.msg}</p>`);
867
+ }
868
+
869
+ btn.removeClass('processing');
870
+ });
871
+ });
872
+ }
873
+
874
+ function delayedPaymentHandler() {
875
+ jQuery('.rtb-deposit-booking').off('click');
876
+ jQuery('.rtb-deposit-booking').on('click', function() {
877
+ var btn = jQuery(this);
878
+
879
+ if(btn.hasClass('processing')) {
880
+ return;
881
+ }
882
+
883
+ btn.addClass('processing');
884
+
885
+ var booking_id = btn.data('bookingid');
886
+ var booking_email = btn.data('bookingemail');
887
+
888
+ var data = {
889
+ 'booking_id': booking_id,
890
+ 'booking_email': booking_email,
891
+ 'payment': 'rtb-delayed-deposit'
892
+ };
893
+
894
+ let current_loc = window.location;
895
+ let params = new URLSearchParams();
896
+ Object.keys( data ).map( function( param ) { params.append( param, data[ param ] ) } );
897
+
898
+ window.location = current_loc.origin + current_loc.pathname + '?' + params.toString();
899
+
900
+ });
901
+ }
902
+
903
+ function displayFieldError( field, message ) {
904
+
905
+ const fieldSelector = '.'+field;
906
+ var fieldElm = jQuery('form.rtb-booking-form-form '+fieldSelector);
907
+
908
+ if( fieldElm.length ) {
909
+
910
+ clearPrevFieldError( field );
911
+
912
+ fieldElm.prepend(`
913
+ <div class="rtb-error">${message}</div>
914
+ `);
915
+ }
916
+ }
917
+
918
+ function clearPrevFieldError( field ) {
919
+ const fieldSelector = '.'+field;
920
+ var errorElms = jQuery('form.rtb-booking-form-form ' + fieldSelector + ' .rtb-error');
921
+
922
+ if( errorElms.length ) {
923
+ errorElms.each((idx, x) => x.remove());
924
+ }
925
+ }
926
+
927
+ // Functions for the 'View Bookings' shortcode
928
+ jQuery(document).ready(function ($) {
929
+ jQuery('.rtb-view-bookings-form-date-selector').on('change', function() {
930
+ window.location.href = replaceUrlParam(window.location.href, 'date', jQuery(this).val());
931
+ });
932
+
933
+ jQuery('.rtb-edit-view-booking').on('click', function() {
934
+ jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').removeClass('rtb-hidden');
935
+
936
+ jQuery('.rtb-view-bookings-form-confirmation-div').data('bookingid', jQuery(this).data('bookingid'));
937
+
938
+ jQuery(this).prop('checked', false);
939
+ });
940
+
941
+ jQuery('.rtb-view-bookings-form-confirmation-accept').on('click', function() {
942
+ var booking_id = jQuery('.rtb-view-bookings-form-confirmation-div').data('bookingid');
943
+
944
+ var params = {};
945
+
946
+ params.action = 'rtb_set_reservation_arrived';
947
+ params.nonce = rtb_booking_form_js_localize.nonce;
948
+ params.booking = {
949
+ 'ID': booking_id
950
+ };
951
+
952
+ var data = $.param( params );
953
+
954
+ jQuery.post(ajaxurl, data, function(response) {
955
+
956
+ if (response.success) {window.location.href = window.location.href}
957
+ else {jQuery('.rtb-view-bookings-form-confirmation-div').html(response.data.msg);}
958
+ });
959
+ });
960
+
961
+ jQuery('.rtb-view-bookings-form-confirmation-decline').on('click', function() {
962
+ jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
963
+ });
964
+ jQuery('.rtb-view-bookings-form-confirmation-background-div').on('click', function() {
965
+ jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
966
+ });
967
+ jQuery('#rtb-view-bookings-form-close').on('click', function() {
968
+ jQuery('.rtb-view-bookings-form-confirmation-div, .rtb-view-bookings-form-confirmation-background-div').addClass('rtb-hidden');
969
+ });
970
+ });
971
+
972
+ function replaceUrlParam(url, paramName, paramValue)
973
+ {
974
+ if (paramValue == null) {
975
+ paramValue = '';
976
+ }
977
+ var pattern = new RegExp('\\b('+paramName+'=).*?(&|#|$)');
978
+ if (url.search(pattern)>=0) {
979
+ return url.replace(pattern,'$1' + paramValue + '$2');
980
+ }
981
+ url = url.replace(/[?#]$/,'');
982
+ return url + (url.indexOf('?')>0 ? '&' : '?') + paramName + '=' + paramValue;
983
  }
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,80 +1,80 @@
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 = rtb_hide_review_ask_params( 7 );
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 = rtb_hide_review_ask_params( 7 );
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 = rtb_hide_review_ask_params( 1000 );
31
- jQuery.post(ajaxurl, data, function() {});
32
- });
33
-
34
- jQuery('.rtb-review-ask-no-thanks').on('click', function() {
35
- var data = rtb_hide_review_ask_params( 1000 );
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 = rtb_hide_review_ask_params( 1000 );
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
-
53
- var params = {};
54
-
55
- params.action = 'rtb-send-feedback';
56
- params.nonce = rtb_review_ask.nonce;
57
- params.feedback = feedback;
58
- params.email_address = email_address;
59
-
60
- var data = jQuery.param( params );
61
- jQuery.post(ajaxurl, data, function() {});
62
-
63
- var data = rtb_hide_review_ask_params( 1000 );
64
- jQuery.post(ajaxurl, data, function() {});
65
-
66
- jQuery('.rtb-review-ask-feedback-form').addClass('rtb-hidden');
67
- jQuery('.rtb-review-ask-review-text').addClass('rtb-hidden');
68
- jQuery('.rtb-review-ask-thank-you-text').removeClass('rtb-hidden');
69
- });
70
-
71
- function rtb_hide_review_ask_params(ask_review_time = 7) {
72
- var params = {};
73
-
74
- params.action = 'rtb-hide-review-ask';
75
- params.nonce = rtb_review_ask.nonce;
76
- params.ask_review_time = ask_review_time;
77
-
78
- return jQuery.param( params );
79
- }
80
  });
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 = rtb_hide_review_ask_params( 7 );
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 = rtb_hide_review_ask_params( 7 );
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 = rtb_hide_review_ask_params( 1000 );
31
+ jQuery.post(ajaxurl, data, function() {});
32
+ });
33
+
34
+ jQuery('.rtb-review-ask-no-thanks').on('click', function() {
35
+ var data = rtb_hide_review_ask_params( 1000 );
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 = rtb_hide_review_ask_params( 1000 );
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
+
53
+ var params = {};
54
+
55
+ params.action = 'rtb-send-feedback';
56
+ params.nonce = rtb_review_ask.nonce;
57
+ params.feedback = feedback;
58
+ params.email_address = email_address;
59
+
60
+ var data = jQuery.param( params );
61
+ jQuery.post(ajaxurl, data, function() {});
62
+
63
+ var data = rtb_hide_review_ask_params( 1000 );
64
+ jQuery.post(ajaxurl, data, function() {});
65
+
66
+ jQuery('.rtb-review-ask-feedback-form').addClass('rtb-hidden');
67
+ jQuery('.rtb-review-ask-review-text').addClass('rtb-hidden');
68
+ jQuery('.rtb-review-ask-thank-you-text').removeClass('rtb-hidden');
69
+ });
70
+
71
+ function rtb_hide_review_ask_params(ask_review_time = 7) {
72
+ var params = {};
73
+
74
+ params.action = 'rtb-hide-review-ask';
75
+ params.nonce = rtb_review_ask.nonce;
76
+ params.ask_review_time = ask_review_time;
77
+
78
+ return jQuery.param( params );
79
+ }
80
  });
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/helper-install-notice.js CHANGED
@@ -1,11 +1,11 @@
1
- jQuery( document ).ready( function( $ ) {
2
-
3
- jQuery(document).on( 'click', '.rtb-helper-install-notice .notice-dismiss', function( event ) {
4
- var data = jQuery.param({
5
- action: 'rtb_hide_helper_notice',
6
- nonce: rtb_helper_notice.nonce
7
- });
8
-
9
- jQuery.post( ajaxurl, data, function() {} );
10
- });
11
  });
1
+ jQuery( document ).ready( function( $ ) {
2
+
3
+ jQuery(document).on( 'click', '.rtb-helper-install-notice .notice-dismiss', function( event ) {
4
+ var data = jQuery.param({
5
+ action: 'rtb_hide_helper_notice',
6
+ nonce: rtb_helper_notice.nonce
7
+ });
8
+
9
+ jQuery.post( ajaxurl, data, function() {} );
10
+ });
11
  });
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(".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()});
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,197 +1,197 @@
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
- 'nonce': rtb_stripe_payment.nonce,
100
- 'action': 'rtb_stripe_get_intent',
101
- 'booking_id': booking_id
102
- };
103
-
104
- $.post(ajaxurl, params, function(result) {
105
- result = JSON.parse(result);
106
- if( result.success ) {
107
-
108
- _stripe.confirmCardPayment(result.clientSecret, {
109
- payment_method: {
110
- card: cardElement,
111
- billing_details: {
112
- name: result.name,
113
- email: result.email
114
- }
115
- }
116
- }).then(function(result) {
117
- params = {
118
- nonce: rtb_stripe_payment.nonce,
119
- action: 'rtb_stripe_pmt_succeed',
120
- booking_id: booking_id
121
- };
122
-
123
- if (result.error) {
124
- // Show error to your customer (e.g., insufficient funds)
125
- params['success'] = false;
126
- params['message'] = result.error.message;
127
- error_handler(result.error.message);
128
- }
129
- else {
130
- var pi = result.paymentIntent;
131
-
132
- // The payment has been processed!
133
- if (pi.status === 'succeeded' || pi.status === 'requires_capture') {
134
- params['success'] = true;
135
- params['payment_amount'] = pi.amount;
136
- params['payment_id'] = pi.id;
137
- // params['payment_intent'] = pi;
138
- }
139
- else {
140
- params['success'] = false;
141
- params['message'] = 'Unknown error';
142
- }
143
- }
144
-
145
- $.post(ajaxurl, params, function (result) {
146
- result = JSON.parse(result);
147
-
148
- if(true == result.success) {
149
- var url = new URL(window.location.pathname, window.location.origin);
150
-
151
- for(const [key, value] of Object.entries(result.urlParams)) {
152
- url.searchParams.append(key, value);
153
- }
154
-
155
- window.location = url.href;
156
- }
157
- else {
158
- error_handler(result.message);
159
- console.log('RTB-Stripe error: ', result.message);
160
- }
161
- });
162
- });
163
- }
164
- else {
165
- error_handler(result.message);
166
- console.log('RTB-Stripe error: ', result.message);
167
- }
168
- });
169
- }
170
- else {
171
-
172
- let exp_month, exp_year;
173
-
174
- let single_field = $('#stripe-payment-form .single-masked').length;
175
- if(single_field) {
176
- let data = $('#stripe-payment-form .single-masked').val().split('/');
177
- exp_month = data[0];
178
- exp_year = data[1];
179
- }
180
- else {
181
- exp_month = $('input[data-stripe="exp_month"]').val();
182
- exp_year = $('input[data-stripe="exp_year"]').val();
183
- }
184
-
185
- Stripe.createToken({
186
- number: $('input[data-stripe="card_number"]').val(),
187
- cvc: $('input[data-stripe="card_cvc"]').val(),
188
- exp_month: exp_month,
189
- exp_year: exp_year,
190
- currency: $('input[data-stripe="currency"]').val()
191
- }, stripeResponseHandler);
192
- }
193
-
194
- // prevent the form from submitting with the default action
195
- return false;
196
- });
197
  });
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
+ 'nonce': rtb_stripe_payment.nonce,
100
+ 'action': 'rtb_stripe_get_intent',
101
+ 'booking_id': booking_id
102
+ };
103
+
104
+ $.post(ajaxurl, params, function(result) {
105
+ result = JSON.parse(result);
106
+ if( result.success ) {
107
+
108
+ _stripe.confirmCardPayment(result.clientSecret, {
109
+ payment_method: {
110
+ card: cardElement,
111
+ billing_details: {
112
+ name: result.name,
113
+ email: result.email
114
+ }
115
+ }
116
+ }).then(function(result) {
117
+ params = {
118
+ nonce: rtb_stripe_payment.nonce,
119
+ action: 'rtb_stripe_pmt_succeed',
120
+ booking_id: booking_id
121
+ };
122
+
123
+ if (result.error) {
124
+ // Show error to your customer (e.g., insufficient funds)
125
+ params['success'] = false;
126
+ params['message'] = result.error.message;
127
+ error_handler(result.error.message);
128
+ }
129
+ else {
130
+ var pi = result.paymentIntent;
131
+
132
+ // The payment has been processed!
133
+ if (pi.status === 'succeeded' || pi.status === 'requires_capture') {
134
+ params['success'] = true;
135
+ params['payment_amount'] = pi.amount;
136
+ params['payment_id'] = pi.id;
137
+ // params['payment_intent'] = pi;
138
+ }
139
+ else {
140
+ params['success'] = false;
141
+ params['message'] = 'Unknown error';
142
+ }
143
+ }
144
+
145
+ $.post(ajaxurl, params, function (result) {
146
+ result = JSON.parse(result);
147
+
148
+ if(true == result.success) {
149
+ var url = new URL(window.location.pathname, window.location.origin);
150
+
151
+ for(const [key, value] of Object.entries(result.urlParams)) {
152
+ url.searchParams.append(key, value);
153
+ }
154
+
155
+ window.location = url.href;
156
+ }
157
+ else {
158
+ error_handler(result.message);
159
+ console.log('RTB-Stripe error: ', result.message);
160
+ }
161
+ });
162
+ });
163
+ }
164
+ else {
165
+ error_handler(result.message);
166
+ console.log('RTB-Stripe error: ', result.message);
167
+ }
168
+ });
169
+ }
170
+ else {
171
+
172
+ let exp_month, exp_year;
173
+
174
+ let single_field = $('#stripe-payment-form .single-masked').length;
175
+ if(single_field) {
176
+ let data = $('#stripe-payment-form .single-masked').val().split('/');
177
+ exp_month = data[0];
178
+ exp_year = data[1];
179
+ }
180
+ else {
181
+ exp_month = $('input[data-stripe="exp_month"]').val();
182
+ exp_year = $('input[data-stripe="exp_year"]').val();
183
+ }
184
+
185
+ Stripe.createToken({
186
+ number: $('input[data-stripe="card_number"]').val(),
187
+ cvc: $('input[data-stripe="card_cvc"]').val(),
188
+ exp_month: exp_month,
189
+ exp_year: exp_year,
190
+ currency: $('input[data-stripe="currency"]').val()
191
+ }, stripeResponseHandler);
192
+ }
193
+
194
+ // prevent the form from submitting with the default action
195
+ return false;
196
+ });
197
  });
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/">
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/" 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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/">
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/" 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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/">
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/" 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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/">
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/" 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://www.fivestarplugins.com/">
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://www.fivestarplugins.com/" 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://www.fivestarplugins.com/<?php echo esc_url( $url_params ); ?>">Five Star Plugins</a>
133
- </span>
134
- </div>
135
- </div>
136
- </div>
137
- <div class="addon addon-themes">
138
- <a href="https://www.fivestarplugins.com/">
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://www.fivestarplugins.com/" 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://www.fivestarplugins.com/">Five Star Plugins</a>
152
- </span>
153
- </div>
154
- </div>
155
- </div>
156
- <div class="addon addon-themes">
157
- <a href="https://www.fivestarplugins.com/">
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://www.fivestarplugins.com/" 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://www.fivestarplugins.com/">Five Star Plugins</a>
171
- </span>
172
- </div>
173
- </div>
174
- </div>
175
- <div class="addon addon-themes">
176
- <a href="https://www.fivestarplugins.com/">
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://www.fivestarplugins.com/" 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://www.fivestarplugins.com/">Five Star Plugins</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 Five Star Plugins 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 %sFive Star Plugins%s on Facebook, or following %sFive Star Plugins%s on Twitter.', 'restaurant-reservations' ),
348
- '<a target="_blank" href="https://www.fivestarplugins.com/">',
349
- '</a>',
350
- '<a target="_blank" href="https://www.facebook.com/fivestarplugins/">',
351
- '</a>',
352
- '<a target="_blank" href="http://twitter.com/fivestarplugins">',
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/">
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/" 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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/">
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/" 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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/">
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/" 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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/">
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://www.fivestarplugins.com/plugins/five-star-restaurant-reservations/" 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://www.fivestarplugins.com/">
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://www.fivestarplugins.com/" 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://www.fivestarplugins.com/<?php echo esc_url( $url_params ); ?>">Five Star Plugins</a>
133
+ </span>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ <div class="addon addon-themes">
138
+ <a href="https://www.fivestarplugins.com/">
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://www.fivestarplugins.com/" 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://www.fivestarplugins.com/">Five Star Plugins</a>
152
+ </span>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ <div class="addon addon-themes">
157
+ <a href="https://www.fivestarplugins.com/">
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://www.fivestarplugins.com/" 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://www.fivestarplugins.com/">Five Star Plugins</a>
171
+ </span>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ <div class="addon addon-themes">
176
+ <a href="https://www.fivestarplugins.com/">
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://www.fivestarplugins.com/" 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://www.fivestarplugins.com/">Five Star Plugins</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 Five Star Plugins 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 %sFive Star Plugins%s on Facebook, or following %sFive Star Plugins%s on Twitter.', 'restaurant-reservations' ),
348
+ '<a target="_blank" href="https://www.fivestarplugins.com/">',
349
+ '</a>',
350
+ '<a target="_blank" href="https://www.facebook.com/fivestarplugins/">',
351
+ '</a>',
352
+ '<a target="_blank" href="http://twitter.com/fivestarplugins">',
353
+ '</a>'
354
+ );
355
+ ?>
356
+ </p>
357
+
358
+ <?php
359
+ }
360
+
361
+ }
362
+ } // endif;
includes/AdminBookings.class.php CHANGED
@@ -1,928 +1,928 @@
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', 'restaurant-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 esc_attr( $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 esc_attr( $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 esc_html( $title ); ?>
453
- <?php if ( !empty( $description ) ) : ?>
454
- <a href="#" class="rtb-description-prompt">
455
- <?php echo esc_html( $description['prompt'] ); ?>
456
- </a>
457
- <?php endif; ?>
458
- </label>
459
- <?php if ( !empty( $description ) ) : ?>
460
- <div class="rtb-description">
461
- <?php echo esc_html( $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'] != '' ) ? array_map( 'intval', 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_email( $_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
-
847
- // Authenticate request
848
- if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
849
- $this->nopriv_ajax();
850
- }
851
-
852
- $booking_id = isset($_POST['booking']) ? intval( $_POST['booking']['ID'] ) : 0;
853
-
854
- $booking_id = wp_update_post(array(
855
- 'ID' => $booking_id,
856
- 'post_status' => 'arrived'
857
- ) );
858
-
859
- if ( $booking_id ) {
860
- wp_send_json_success();
861
- }
862
- else {
863
- wp_send_json_error(
864
- array(
865
- 'error' => 'loggedout',
866
- 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( ) . '">', '</a>' ),
867
- )
868
- );
869
- }
870
- }
871
-
872
- /**
873
- * Validate post status and notification fields
874
- * @since 1.3
875
- */
876
- public function validate_admin_fields( $booking ) {
877
-
878
- // Only validate in the admin
879
- if ( !$_POST['action'] || $_POST['action'] !== 'admin_booking_request' ) {
880
- return;
881
- }
882
-
883
- global $rtb_controller;
884
-
885
- // Disable Notifications
886
- $booking->send_notifications = empty( $_POST['rtb-notifications'] ) ? false : true;
887
- }
888
-
889
- /**
890
- * Adjust post status when adding/editing a booking from the admin area
891
- * @since 1.3
892
- */
893
- public function insert_booking_data( $args, $booking ) {
894
-
895
- // Validate user request
896
- if ( empty( $_POST['action'] ) || $_POST['action'] !== 'admin_booking_request' || !current_user_can( 'manage_bookings' ) ) {
897
- return $args;
898
- }
899
-
900
- if ( !empty( $booking->post_status ) ) {
901
- $args['post_status'] = $booking->post_status;
902
- }
903
-
904
- return $args;
905
- }
906
-
907
- /**
908
- * Maybe disable notifications when adding/editing bookings from the
909
- * admin booking modal
910
- * @since 1.3
911
- */
912
- public function maybe_disable_notifications() {
913
-
914
- // Don't disable notifications if they have opted to send them
915
- if ( !empty( $_POST['rtb-notifications'] ) ) {
916
- return;
917
- }
918
-
919
- // Disable all notifications. This filter is here in case a
920
- // third-party sets up a notification that they don't want to be
921
- // disabled even if the user has opted not to send notifications
922
- // To exempt a notification, hook into the filter and copy it
923
- // from $rtb_notifications to the empty array.
924
- global $rtb_controller;
925
- $rtb_controller->notifications->notifications = apply_filters( 'rtb_admin_disabled_notifications_exemption', array(), $rtb_controller->notifications->notifications );
926
- }
927
- }
928
- } // 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', 'restaurant-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 esc_attr( $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 esc_attr( $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 esc_html( $title ); ?>
453
+ <?php if ( !empty( $description ) ) : ?>
454
+ <a href="#" class="rtb-description-prompt">
455
+ <?php echo esc_html( $description['prompt'] ); ?>
456
+ </a>
457
+ <?php endif; ?>
458
+ </label>
459
+ <?php if ( !empty( $description ) ) : ?>
460
+ <div class="rtb-description">
461
+ <?php echo esc_html( $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'] != '' ) ? array_map( 'intval', 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_email( $_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
+
847
+ // Authenticate request
848
+ if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
849
+ $this->nopriv_ajax();
850
+ }
851
+
852
+ $booking_id = isset($_POST['booking']) ? intval( $_POST['booking']['ID'] ) : 0;
853
+
854
+ $booking_id = wp_update_post(array(
855
+ 'ID' => $booking_id,
856
+ 'post_status' => 'arrived'
857
+ ) );
858
+
859
+ if ( $booking_id ) {
860
+ wp_send_json_success();
861
+ }
862
+ else {
863
+ wp_send_json_error(
864
+ array(
865
+ 'error' => 'loggedout',
866
+ 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( ) . '">', '</a>' ),
867
+ )
868
+ );
869
+ }
870
+ }
871
+
872
+ /**
873
+ * Validate post status and notification fields
874
+ * @since 1.3
875
+ */
876
+ public function validate_admin_fields( $booking ) {
877
+
878
+ // Only validate in the admin
879
+ if ( !$_POST['action'] || $_POST['action'] !== 'admin_booking_request' ) {
880
+ return;
881
+ }
882
+
883
+ global $rtb_controller;
884
+
885
+ // Disable Notifications
886
+ $booking->send_notifications = empty( $_POST['rtb-notifications'] ) ? false : true;
887
+ }
888
+
889
+ /**
890
+ * Adjust post status when adding/editing a booking from the admin area
891
+ * @since 1.3
892
+ */
893
+ public function insert_booking_data( $args, $booking ) {
894
+
895
+ // Validate user request
896
+ if ( empty( $_POST['action'] ) || $_POST['action'] !== 'admin_booking_request' || !current_user_can( 'manage_bookings' ) ) {
897
+ return $args;
898
+ }
899
+
900
+ if ( !empty( $booking->post_status ) ) {
901
+ $args['post_status'] = $booking->post_status;
902
+ }
903
+
904
+ return $args;
905
+ }
906
+
907
+ /**
908
+ * Maybe disable notifications when adding/editing bookings from the
909
+ * admin booking modal
910
+ * @since 1.3
911
+ */
912
+ public function maybe_disable_notifications() {
913
+
914
+ // Don't disable notifications if they have opted to send them
915
+ if ( !empty( $_POST['rtb-notifications'] ) ) {
916
+ return;
917
+ }
918
+
919
+ // Disable all notifications. This filter is here in case a
920
+ // third-party sets up a notification that they don't want to be
921
+ // disabled even if the user has opted not to send notifications
922
+ // To exempt a notification, hook into the filter and copy it
923
+ // from $rtb_notifications to the empty array.
924
+ global $rtb_controller;
925
+ $rtb_controller->notifications->notifications = apply_filters( 'rtb_admin_disabled_notifications_exemption', array(), $rtb_controller->notifications->notifications );
926
+ }
927
+ }
928
+ } // endif;
includes/AdminPageSettingLicenseKey.class.php CHANGED
@@ -1,429 +1,429 @@
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
-
212
- <input
213
- name="<?php echo esc_attr( $this->get_input_name().'[api_key]' ); ?>"
214
- type="text"
215
- id="<?php echo esc_attr( $this->get_input_name().'[api_key]' ); ?>"
216
- value="<?php echo esc_attr( $this->value['api_key'] ); ?>"
217
- <?php echo !empty( $this->placeholder ) ? ' placeholder="' . esc_attr( $this->placeholder ) . '"' : ''; ?>
218
- class="regular-text">
219
-
220
- <?php if ( !empty( $this->value['api_key'] ) ) : ?>
221
- <span class="status <?php echo $is_active ? 'valid' : 'inactive'; ?>">
222
- <?php echo esc_html( $status_string ); ?>
223
- </span>
224
-
225
- <a href="<?php echo esc_url( $url ); ?>" class="button">
226
- <?php echo $is_active ? $this->strings['deactivate'] : $this->strings['activate']; ?>
227
- </a>
228
-
229
- <span class="spinner"></span>
230
-
231
- <?php endif;
232
-
233
- $this->display_description();
234
-
235
- ?>
236
-
237
- </div>
238
-
239
- <?php
240
- }
241
-
242
- /**
243
- * Display a description for this setting
244
- *
245
- * @since 1.4.1
246
- */
247
- public function display_description() {
248
-
249
- if ( !empty( $this->description ) ) : ?>
250
-
251
- <p class="description"><?php echo $this->description; ?></p>
252
-
253
- <?php endif;
254
- }
255
-
256
- /**
257
- * Generate an option input field name, using the grouped schema.
258
- *
259
- * @since 1.4.1
260
- */
261
- public function get_input_name() {
262
- return esc_attr( $this->page ) . '[' . esc_attr( $this->id ) . ']';
263
- }
264
-
265
-
266
- /**
267
- * Sanitize the array of text inputs for this setting
268
- *
269
- * @since 1.4.1
270
- */
271
- public function sanitize_callback_wrapper( $values ) {
272
-
273
- $output = array(
274
- 'api_key' => '',
275
- 'status' => false,
276
- 'expiry' => false,
277
- );
278
-
279
- if ( empty( $values ) || empty( $values['api_key'] ) ) {
280
- return $output;
281
- }
282
-
283
- $output['api_key'] = trim( sanitize_text_field( $values['api_key'] ) );
284
-
285
- // Clear status and expiry when a license key has changed
286
- global $rtb_controller;
287
- $old = $rtb_controller->settings->get_setting( $this->id );
288
- if ( empty( $old['api_key'] ) || $old['api_key'] !== $output['api_key'] ) {
289
- return $output;
290
- }
291
-
292
- // Preserve old status values
293
- $output = array_merge( $old, $output );
294
-
295
- return $output;
296
- }
297
-
298
- /**
299
- * Add and register this setting
300
- *
301
- * @since 1.4.1
302
- */
303
- public function add_settings_field( $section_id ) {
304
-
305
- add_settings_field(
306
- $this->id,
307
- $this->title,
308
- array( $this, 'display_setting' ),
309
- $this->tab,
310
- $section_id
311
- );
312
-
313
- }
314
-
315
- /**
316
- * Set an error
317
- *
318
- * @since 1.4.1
319
- */
320
- public function set_error( $error ) {
321
- $this->errors[] = array_merge(
322
- $error,
323
- array(
324
- 'class' => get_class( $this ),
325
- 'id' => $this->id,
326
- 'backtrace' => debug_backtrace()
327
- )
328
- );
329
- }
330
-
331
- /**
332
- * Process a license activation if requested
333
- *
334
- * @since 1.4.1
335
- */
336
- public function process_action() {
337
-
338
- if ( !current_user_can( 'manage_options' ) || empty( $_GET['tab'] ) || $_GET['tab'] !== 'rtb-licenses' || empty( $_GET['action'] ) || empty( $_GET['id'] ) || $_GET['id'] !== $this->id ) {
339
- return;
340
- }
341
-
342
- $params = array();
343
- $params['edd_action'] = $_GET['action'] === 'activate' ? 'activate_license' : 'deactivate_license';
344
- $params['license'] = sanitize_text_field( $this->value['api_key'] );
345
- $params['item_name'] = urlencode( $this->product );
346
-
347
- $response = wp_remote_get( add_query_arg( $params, $this->store_url ), array( 'timeout' => 15, 'sslverify' => false ) );
348
-
349
- if ( is_wp_error( $response ) ) {
350
- $url = remove_query_arg( array( 'id', 'action' ) );
351
- $url = add_query_arg( 'license_result', 'response_wp_error', $url );
352
- header( 'Location: ' . esc_url_raw( $url ) );
353
- }
354
-
355
- $license_data = json_decode( wp_remote_retrieve_body( $response ) );
356
-
357
-
358
- if ( $params['edd_action'] == 'activate' ) {
359
- $result = $this->process_activation_response( $license_data );
360
- } else {
361
- $result = $this->process_activation_response( $license_data );
362
- }
363
-
364
- // Construct a URL to redirect back to the tab
365
- $url = remove_query_arg( array( 'id', 'action' ) );
366
- $url = add_query_arg(
367
- array(
368
- 'license_result' => $result ? 1 : 0,
369
- 'action' => $_GET['action'] == 'activate' ? 'activate' : 'deactivate',
370
- ),
371
- $url
372
- );
373
-
374
- // If the result failed maybe add note on why
375
- if ( !$result && !empty( $license_data->error ) ) {
376
- $url = add_query_arg( 'result_error', $license_data->error, $url );
377
- }
378
-
379
- header( 'Location: ' . esc_url_raw( $url ) );
380
-
381
- }
382
-
383
- /**
384
- * Process the response to an activation request
385
- *
386
- * @since 1.4.1
387
- */
388
- public function process_activation_response( $license_data ) {
389
-
390
- if ( ( !empty( $license_data->error ) && ( $license_data->error == 'missing' || $license_data->error == 'item_name_mismatch' ) ) || $license_data->license == 'invalid' ) {
391
- $this->value['status'] = 'invalid';
392
- $this->value['expiry'] = false;
393
- } else {
394
- $this->value['status'] = $license_data->license;
395
- $this->value['expiry'] = $license_data->expires;
396
- }
397
-
398
- $rtb_settings = get_option( $this->page );
399
- $rtb_settings[ $this->id ] = $this->value;
400
-
401
- update_option( $this->page, $rtb_settings );
402
-
403
- return $license_data->license == 'valid' || $license_data->license == 'deactivated';
404
- }
405
-
406
- /**
407
- * Process the response to an deactivation request
408
- *
409
- * @since 1.4.1
410
- */
411
- public function process_deactivation_response( $license_data ) {
412
-
413
- if ( $license_data->license !== 'deactivated' ) {
414
- return false;
415
- } else {
416
- $this->value['status'] = false;
417
- $this->value['expiry'] = false;
418
- }
419
-
420
- $rtb_settings = get_option( $this->page );
421
- $rtb_settings[ $this->id ] = $this->value;
422
-
423
- update_option( $this->page, $rtb_settings );
424
-
425
- return true;
426
- }
427
-
428
- }
429
- } // 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
+
212
+ <input
213
+ name="<?php echo esc_attr( $this->get_input_name().'[api_key]' ); ?>"
214
+ type="text"
215
+ id="<?php echo esc_attr( $this->get_input_name().'[api_key]' ); ?>"
216
+ value="<?php echo esc_attr( $this->value['api_key'] ); ?>"
217
+ <?php echo !empty( $this->placeholder ) ? ' placeholder="' . esc_attr( $this->placeholder ) . '"' : ''; ?>
218
+ class="regular-text">
219
+
220
+ <?php if ( !empty( $this->value['api_key'] ) ) : ?>
221
+ <span class="status <?php echo $is_active ? 'valid' : 'inactive'; ?>">
222
+ <?php echo esc_html( $status_string ); ?>
223
+ </span>
224
+
225
+ <a href="<?php echo esc_url( $url ); ?>" class="button">
226
+ <?php echo $is_active ? $this->strings['deactivate'] : $this->strings['activate']; ?>
227
+ </a>
228
+
229
+ <span class="spinner"></span>
230
+
231
+ <?php endif;
232
+
233
+ $this->display_description();
234
+
235
+ ?>
236
+
237
+ </div>
238
+
239
+ <?php
240
+ }
241
+
242
+ /**
243
+ * Display a description for this setting
244
+ *
245
+ * @since 1.4.1
246
+ */
247
+ public function display_description() {
248
+
249
+ if ( !empty( $this->description ) ) : ?>
250
+
251
+ <p class="description"><?php echo $this->description; ?></p>
252
+
253
+ <?php endif;
254
+ }
255
+
256
+ /**
257
+ * Generate an option input field name, using the grouped schema.
258
+ *
259
+ * @since 1.4.1
260
+ */
261
+ public function get_input_name() {
262
+ return esc_attr( $this->page ) . '[' . esc_attr( $this->id ) . ']';
263
+ }
264
+
265
+
266
+ /**
267
+ * Sanitize the array of text inputs for this setting
268
+ *
269
+ * @since 1.4.1
270
+ */
271
+ public function sanitize_callback_wrapper( $values ) {
272
+
273
+ $output = array(
274
+ 'api_key' => '',
275
+ 'status' => false,
276
+ 'expiry' => false,
277
+ );
278
+
279
+ if ( empty( $values ) || empty( $values['api_key'] ) ) {
280
+ return $output;
281
+ }
282
+
283
+ $output['api_key'] = trim( sanitize_text_field( $values['api_key'] ) );
284
+
285
+ // Clear status and expiry when a license key has changed
286
+ global $rtb_controller;
287
+ $old = $rtb_controller->settings->get_setting( $this->id );
288
+ if ( empty( $old['api_key'] ) || $old['api_key'] !== $output['api_key'] ) {
289
+ return $output;
290
+ }
291
+
292
+ // Preserve old status values
293
+ $output = array_merge( $old, $output );
294
+
295
+ return $output;
296
+ }
297
+
298
+ /**
299
+ * Add and register this setting
300
+ *
301
+ * @since 1.4.1
302
+ */
303
+ public function add_settings_field( $section_id ) {
304
+
305
+ add_settings_field(
306
+ $this->id,
307
+ $this->title,
308
+ array( $this, 'display_setting' ),
309
+ $this->tab,
310
+ $section_id
311
+ );
312
+
313
+ }
314
+
315
+ /**
316
+ * Set an error
317
+ *
318
+ * @since 1.4.1
319
+ */
320
+ public function set_error( $error ) {
321
+ $this->errors[] = array_merge(
322
+ $error,
323
+ array(
324
+ 'class' => get_class( $this ),
325
+ 'id' => $this->id,
326
+ 'backtrace' => debug_backtrace()
327
+ )
328
+ );
329
+ }
330
+
331
+ /**
332
+ * Process a license activation if requested
333
+ *
334
+ * @since 1.4.1
335
+ */
336
+ public function process_action() {
337
+
338
+ if ( !current_user_can( 'manage_options' ) || empty( $_GET['tab'] ) || $_GET['tab'] !== 'rtb-licenses' || empty( $_GET['action'] ) || empty( $_GET['id'] ) || $_GET['id'] !== $this->id ) {
339
+ return;
340
+ }
341
+
342
+ $params = array();
343
+ $params['edd_action'] = $_GET['action'] === 'activate' ? 'activate_license' : 'deactivate_license';
344
+ $params['license'] = sanitize_text_field( $this->value['api_key'] );
345
+ $params['item_name'] = urlencode( $this->product );
346
+
347
+ $response = wp_remote_get( add_query_arg( $params, $this->store_url ), array( 'timeout' => 15, 'sslverify' => false ) );
348
+
349
+ if ( is_wp_error( $response ) ) {
350
+ $url = remove_query_arg( array( 'id', 'action' ) );
351
+ $url = add_query_arg( 'license_result', 'response_wp_error', $url );
352
+ header( 'Location: ' . esc_url_raw( $url ) );
353
+ }
354
+
355
+ $license_data = json_decode( wp_remote_retrieve_body( $response ) );
356
+
357
+
358
+ if ( $params['edd_action'] == 'activate' ) {
359
+ $result = $this->process_activation_response( $license_data );
360
+ } else {
361
+ $result = $this->process_activation_response( $license_data );
362
+ }
363
+
364
+ // Construct a URL to redirect back to the tab
365
+ $url = remove_query_arg( array( 'id', 'action' ) );
366
+ $url = add_query_arg(
367
+ array(
368
+ 'license_result' => $result ? 1 : 0,
369
+ 'action' => $_GET['action'] == 'activate' ? 'activate' : 'deactivate',
370
+ ),
371
+ $url
372
+ );
373
+
374
+ // If the result failed maybe add note on why
375
+ if ( !$result && !empty( $license_data->error ) ) {
376
+ $url = add_query_arg( 'result_error', $license_data->error, $url );
377
+ }
378
+
379
+ header( 'Location: ' . esc_url_raw( $url ) );
380
+
381
+ }
382
+
383
+ /**
384
+ * Process the response to an activation request
385
+ *
386
+ * @since 1.4.1
387
+ */
388
+ public function process_activation_response( $license_data ) {
389
+
390
+ if ( ( !empty( $license_data->error ) && ( $license_data->error == 'missing' || $license_data->error == 'item_name_mismatch' ) ) || $license_data->license == 'invalid' ) {
391
+ $this->value['status'] = 'invalid';
392
+ $this->value['expiry'] = false;
393
+ } else {
394
+ $this->value['status'] = $license_data->license;
395
+ $this->value['expiry'] = $license_data->expires;
396
+ }
397
+
398
+ $rtb_settings = get_option( $this->page );
399
+ $rtb_settings[ $this->id ] = $this->value;
400
+
401
+ update_option( $this->page, $rtb_settings );
402
+
403
+ return $license_data->license == 'valid' || $license_data->license == 'deactivated';
404
+ }
405
+
406
+ /**
407
+ * Process the response to an deactivation request
408
+ *
409
+ * @since 1.4.1
410
+ */
411
+ public function process_deactivation_response( $license_data ) {
412
+
413
+ if ( $license_data->license !== 'deactivated' ) {
414
+ return false;
415
+ } else {
416
+ $this->value['status'] = false;
417
+ $this->value['expiry'] = false;
418
+ }
419
+
420
+ $rtb_settings = get_option( $this->page );
421
+ $rtb_settings[ $this->id ] = $this->value;
422
+
423
+ update_option( $this->page, $rtb_settings );
424
+
425
+ return true;
426
+ }
427
+
428
+ }
429
+ } // endif;
includes/Ajax.class.php CHANGED
@@ -1,951 +1,951 @@
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
- if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
74
- rtbHelper::bad_nonce_ajax();
75
- }
76
-
77
- $email = isset($_POST['booking_email']) ? sanitize_email( $_POST['booking_email'] ) : '';
78
-
79
- if ( ! $email ) {
80
- wp_send_json_error(
81
- array(
82
- 'error' => 'noemail',
83
- 'msg' => __( 'The email you entered is not valid.', 'restaurant-reservations' ),
84
- )
85
- );
86
- }
87
-
88
- $booking_status_lbls = $rtb_controller->cpts->booking_statuses;
89
-
90
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
91
-
92
- $bookings = array();
93
- $booking_ids = $wpdb->get_results(
94
- $wpdb->prepare("
95
- SELECT `post_id` FROM `{$wpdb->postmeta}` WHERE `meta_key` = 'rtb' AND `meta_value` LIKE %s",
96
- '%' . $email . '%'
97
- )
98
- );
99
-
100
- foreach ( $booking_ids as $booking_id ) {
101
- $booking = new rtbBooking();
102
- if ( $booking->load_post( $booking_id->post_id ) ) {
103
- $booking_date = (new DateTime($booking->date, wp_timezone()))->format('U');
104
- if ( in_array($booking->post_status, ['pending', 'payment_pending', 'payment_failed', 'confirmed'] ) and time() < $booking_date ) {
105
- $bookings[] = array(
106
- 'ID' => $booking->ID,
107
- 'email' => $booking->email,
108
- 'datetime' => $booking->format_date( $booking->date ),
109
- 'party' => $booking->party,
110
- 'status' => $booking->post_status,
111
- 'status_lbl' => $booking_status_lbls[$booking->post_status]['label']
112
- );
113
- }
114
- }
115
- }
116
-
117
- if ( ! empty( $bookings ) ) {
118
- wp_send_json_success(
119
- array(
120
- 'bookings' => $bookings
121
- )
122
- );
123
- }
124
- else {
125
- wp_send_json_error(
126
- array(
127
- 'error' => 'nobookings',
128
- 'msg' => esc_html( $rtb_controller->settings->get_setting( 'label-modify-no-bookings-found' ) ),
129
- )
130
- );
131
- }
132
-
133
- die();
134
- }
135
-
136
- /**
137
- * Cancel a reservation based on its ID, with the email address used for confirmation
138
- * @since 2.1.0
139
- */
140
- public function cancel_reservation( $ajax = true ) {
141
- global $rtb_controller;
142
-
143
- if ( $ajax && !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
144
- rtbHelper::bad_nonce_ajax();
145
- }
146
-
147
- $cancelled_redirect = $rtb_controller->settings->get_setting( 'cancelled-redirect-page' );
148
-
149
- $booking_id = isset($_REQUEST['booking_id']) ? absint( $_REQUEST['booking_id'] ) : '';
150
- $booking_email = isset($_REQUEST['booking_email']) ? sanitize_email( $_REQUEST['booking_email'] ) : '';
151
-
152
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
153
-
154
- $success = false;
155
- $error = array(
156
- 'error' => 'unknown',
157
- 'msg' => __( 'Unkown error. Please try again', 'restaurant-reservations' )
158
- );
159
-
160
- $booking = new rtbBooking();
161
- if ( $booking->load_post( $booking_id ) ) {
162
- if ( $booking_email == $booking->email ) {
163
- wp_update_post( array( 'ID' => $booking->ID, 'post_status' => 'cancelled' ) );
164
-
165
- $success = true;
166
- }
167
- else {
168
- $error = array(
169
- 'error' => 'invalidemail',
170
- 'msg' => __( 'No booking matches the information that was sent.', 'restaurant-reservations' ),
171
- );
172
- }
173
- }
174
- else {
175
- $error = array(
176
- 'error' => 'invalidid',
177
- 'msg' => __( 'No booking matches the information that was sent.', 'restaurant-reservations' ),
178
- );
179
- }
180
-
181
- if ( $ajax ) {
182
- if ( $success ) {
183
-
184
- $response = array( 'booking_id' => $booking_id );
185
-
186
- if( '' != $cancelled_redirect ) {
187
- $response['cancelled_redirect'] = $cancelled_redirect;
188
- }
189
-
190
- wp_send_json_success( $response );
191
- }
192
- else {
193
- wp_send_json_error( $error );
194
- }
195
- }
196
- else {
197
- $redirect_url = '';
198
-
199
- if( '' != $cancelled_redirect && $success ) {
200
- $redirect_url = $cancelled_redirect;
201
- }
202
- else {
203
- $booking_page_id = $rtb_controller->settings->get_setting( 'booking-page' );
204
- $booking_page_url = get_permalink( $booking_page_id );
205
-
206
- $redirect_url = add_query_arg(
207
- array(
208
- 'bookingCancelled' => $success ? 'success' : 'fail'
209
- ),
210
- $booking_page_url
211
- );
212
- }
213
-
214
- if( wp_redirect( $redirect_url ) ) {
215
- exit;
216
- }
217
-
218
- header( "Location: {$redirect_url}", true, 302 );
219
- exit;
220
- }
221
- }
222
-
223
- /**
224
- * Get available timeslots when "Max Reservations" or "Max People" is enabled
225
- * @since 2.0.0
226
- */
227
- public function get_time_slots() {
228
- global $rtb_controller;
229
-
230
- if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
231
- rtbHelper::bad_nonce_ajax();
232
- }
233
-
234
- $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables' );
235
-
236
- // proessing request for this date
237
- $this->location = ! empty( $_POST['location'] ) ? get_term( intval( $_POST['location'] ) ) : false;
238
-
239
- $this->year = sanitize_text_field( $_POST['year'] );
240
- $this->month = sanitize_text_field( $_POST['month'] );
241
- $this->day = sanitize_text_field( $_POST['day'] );
242
-
243
- $interval = $rtb_controller->settings->get_setting( 'time-interval' ) * 60;
244
-
245
- // Helper functions
246
- $finalize_response = function ( $open_close_pair_list = array() ) {
247
-
248
- $valid_times = [];
249
-
250
- if ( ! empty( $open_close_pair_list ) ) {
251
-
252
- foreach ( $open_close_pair_list as $pair ) {
253
-
254
- $valid_times[] = array(
255
- 'from' => $this->format_pickadate_time( $pair['from'] ),
256
- 'to' => $this->format_pickadate_time( $pair['to'] ),
257
- 'inverted' => true
258
- );
259
- }
260
- }
261
-
262
- echo json_encode( $valid_times );
263
-
264
- die();
265
- };
266
-
267
- $all_slots_for = function( $pairs ) use( $interval ) {
268
- $all_possible_slots = [];
269
-
270
- if( !is_array( $pairs ) ) return $pairs;
271
-
272
- foreach ( $pairs as $pair ) {
273
- $all_possible_slots[] = $pair['from'];
274
- $next = $pair['from'] + $interval;
275
- while ( $next <= $pair['to'] ) {
276
- $all_possible_slots[] = $next;
277
- $next += $interval;
278
- }
279
- }
280
-
281
- return $all_possible_slots;
282
- };
283
-
284
- $consolidating_timeslots_to_timeframes = function( $slots ) use( $interval ) {
285
- $timeframe = [];
286
-
287
- $slots_count = count( $slots );
288
- if( 0 < $slots_count ) {
289
-
290
- $current_pair = [ 'from' => $slots[ 0 ] ];
291
-
292
- for ( $i = 1; $i < $slots_count; $i++) {
293
- if( $slots[ $i ] - $slots[ $i - 1 ] !== $interval ) {
294
- $current_pair[ 'to' ] = $slots[ $i - 1 ];
295
- $timeframe[] = $current_pair;
296
-
297
- $current_pair = [ 'from' => $slots[ $i ] ];
298
- }
299
- }
300
-
301
- $current_pair[ 'to' ] = $slots[ $i - 1 ];
302
- $timeframe[] = $current_pair;
303
- }
304
-
305
- return $timeframe;
306
- };
307
-
308
- // Get opening/closing times for this particular day
309
- $hours = $this->get_opening_hours();
310
-
311
- // If the restaurant is closed that day
312
- // If Enable Max Reservation not set
313
- if ( 1 > count( $hours ) || ! $max_reservations_enabled ) {
314
- $slots = $all_slots_for( $hours );
315
-
316
- sort( $slots, SORT_NUMERIC );
317
-
318
- $timeframes = $consolidating_timeslots_to_timeframes( $slots );
319
-
320
- $finalize_response( $timeframes );
321
- }
322
-
323
- $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
324
-
325
- $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60;
326
-
327
- $min_party_size = (int) $rtb_controller->settings->get_setting( 'party-size-min' );
328
-
329
- $max_reservations = (int) $rtb_controller->settings->get_setting( 'rtb-max-tables-count', $location_slug );
330
-
331
- $max_people = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
332
-
333
- $all_possible_slots = $all_slots_for( $hours );
334
-
335
- // Get all current bookings sorted by date
336
- $args = array(
337
- 'posts_per_page' => -1,
338
- 'date_range' => 'dates',
339
- 'start_date' => $this->year . '-' . $this->month . '-' . $this->day,
340
- 'end_date' => $this->year . '-' . $this->month . '-' . $this->day,
341
- 'post_status' => ['pending', 'payment_pending', 'confirmed', 'arrived']
342
- );
343
-
344
- // If there are multiple locations, a location is selected, and
345
- // max reservations and/or seats has been enabled for this specific location
346
- 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 ) ) ) {
347
-
348
- $tax_query = array(
349
- array(
350
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
351
- 'field' => 'term_id',
352
- 'terms' => $this->location->term_id
353
- )
354
- );
355
-
356
- $args['tax_query'] = $tax_query;
357
- }
358
-
359
- $query = new rtbQuery( $args, 'ajax-get-time-slots' );
360
- $query->prepare_args();
361
- $bookings = $query->get_bookings();
362
-
363
- // This array holds bookings for all slots by expanding the booking by
364
- // dining block length to help finding the overlapped bookings for time-slots
365
- $all_bookings_by_slots = [];
366
- foreach ( $bookings as $key => $booking ) {
367
- // Convert booking date to seconds from UNIX
368
- $booking_time = ( new DateTime( $booking->date , wp_timezone() ) )->format( 'U' );
369
- if( ! array_key_exists( $booking_time, $all_bookings_by_slots ) ) {
370
- $all_bookings_by_slots[$booking_time] = [
371
- 'total_bookings' => 0,
372
- 'total_guest' => 0,
373
- 'overlapped' => false
374
- ];
375
- }
376
- $all_bookings_by_slots[$booking_time]['total_bookings']++;
377
- $all_bookings_by_slots[$booking_time]['total_guest'] += intval( $booking->party );
378
-
379
- /**
380
- * Expanding bookings
381
- * Example: If I have someone booked at 1pm who will be in the restaurant for 120 minutes,
382
- * that means they will be in the restaurant until 3pm. There is another booking at 2pm.
383
- * That means, from 2pm to 3pm, there are already two separate reservations in the restaurant.
384
- */
385
- $end = $booking_time + $dining_block_seconds;
386
- $next = $booking_time + $interval;
387
- while($next < $end) {
388
- if( ! array_key_exists( $next, $all_bookings_by_slots ) ) {
389
- $all_bookings_by_slots[$next] = [
390
- 'total_bookings' => 0,
391
- 'total_guest' => 0,
392
- 'overlapped' => false
393
- ];
394
- }
395
- $all_bookings_by_slots[$next]['overlapped'] = true;
396
- $all_bookings_by_slots[$next]['total_bookings']++;
397
- $all_bookings_by_slots[$next]['total_guest'] += intval( $booking->party );
398
- $next += $interval;
399
- }
400
- }
401
-
402
- $all_blocked_slots = [];
403
-
404
- // Go through all bookings and figure out when we're at or above the
405
- // max reservation or max people and mark that slot as blocked
406
- if ( isset( $max_reservations ) and $max_reservations > 0 ) {
407
- foreach ( $all_bookings_by_slots as $slot => $data ) {
408
- if( $max_reservations <= $data['total_bookings'] ) {
409
- $all_blocked_slots[] = $slot;
410
- }
411
- }
412
- }
413
- else if ( isset( $max_people ) and $max_people > 0 ) {
414
- /**
415
- * min_party_size = 10, max_people = 100, 6 bookings of total 91 guests
416
- * Now, if anybody wants to book for at least 10 people, it is not possible
417
- * because the total will surpass the max_people (100)
418
- * thus reducing min_party_size from max_people
419
- *
420
- * $max_people can be zero when min_party_size is same as max_people
421
- */
422
- $max_people = $max_people - $min_party_size;
423
-
424
- foreach ( $all_bookings_by_slots as $slot => $data ) {
425
- if( $max_people < $data['total_guest'] ) {
426
- $all_blocked_slots[] = $slot;
427
- }
428
- }
429
- }
430
-
431
- // Mark slots unavailable, due to dinning block length
432
- $additional_blocked_slots = [];
433
- foreach ($all_blocked_slots as $slot) {
434
- // blocking before this slot
435
- $begin = $slot - $dining_block_seconds;
436
- /**
437
- * interval 30 minutes, dinning_length 120 minutes, slot 10:00am
438
- * additional blockings before shall be 8:30am,9:00am and 9:30am
439
- * thus skipping 8:00am which is valid
440
- *
441
- * @var unix timestamp
442
- */
443
- $next = $begin + $interval;
444
- while($next < $slot) {
445
- $additional_blocked_slots[] = $next;
446
- $next += $interval;
447
- }
448
-
449
- // block after this slot only when this slot is not overlapped
450
- // Overlapped slots should block only backwards, but not afterward
451
- if( $all_bookings_by_slots[$slot]['overlapped'] ) {
452
- continue;
453
- }
454
-
455
- // blocking after this slot
456
- $end = $slot + $dining_block_seconds;
457
- /**
458
- * interval 30 minutes, dinning_length 120 minutes, slot 10:00am
459
- * additional blockings after shall be 10:30am,11:00am and 1130am
460
- * thus skipping 12:00pm which is valid
461
- *
462
- * @var unix timestamp
463
- */
464
- $next = $slot + $interval;
465
- while($next < $end) {
466
- $additional_blocked_slots[] = $next;
467
- $next += $interval;
468
- }
469
- }
470
-
471
- $all_blocked_slots = array_unique(
472
- array_merge( $all_blocked_slots, $additional_blocked_slots ),
473
- SORT_NUMERIC
474
- );
475
-
476
- sort( $all_blocked_slots, SORT_NUMERIC );
477
-
478
- // remove blocked slots from available slots
479
- $available_slots = array_diff( $all_possible_slots, $all_blocked_slots );
480
- sort( $available_slots, SORT_NUMERIC );
481
-
482
- // consolidating timeslots to timeframes
483
- $timeframes = $consolidating_timeslots_to_timeframes( $available_slots );
484
-
485
- $finalize_response( $timeframes );
486
- }
487
-
488
- public function get_opening_hours() {
489
- global $rtb_controller;
490
-
491
- $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
492
-
493
- $schedule_closed = $rtb_controller->settings->get_setting( 'schedule-closed', $location_slug );
494
- $schedule_closed = is_array( $schedule_closed ) ? $schedule_closed : array();
495
-
496
- $valid_times = array();
497
-
498
- // Check if this date is an exception to the rules
499
- if ( $schedule_closed !== 'undefined' ) {
500
-
501
- $selected_date = ( new DateTime('now', wp_timezone() ) )
502
- ->setTime(0, 0, 2)
503
- ->setDate( $this->year, $this->month, $this->day );
504
-
505
- foreach ( $schedule_closed as $ids => $closing ) {
506
- if( array_key_exists( 'date_range', $closing ) ) {
507
-
508
- $start = ! empty( $closing['date_range']['start'] )
509
- ? new DateTime( $closing['date_range']['start'], wp_timezone() )
510
- : new DateTime( 'now', wp_timezone() );
511
- $start->setTime(0, 0);
512
-
513
- $end = !empty( $closing['date_range']['end'] )
514
- ? new DateTime( $closing['date_range']['end'], wp_timezone() )
515
- : ( new DateTime( 'now', wp_timezone() ) )->add( new DateInterval( 'P10Y' ) );
516
- $end->setTime(23, 59, 58);
517
-
518
- if( $start < $selected_date && $selected_date < $end ) {
519
- $exception = clone $selected_date;
520
- }
521
- else {
522
- // Set anything to void this rule
523
- $exception = clone $selected_date;
524
- $exception->add( new DateInterval( 'P1Y' ) );
525
- }
526
- }
527
- else {
528
- $exception = ( new DateTime( $closing['date'], wp_timezone() ) )->setTime(0, 0, 2);
529
- }
530
-
531
- if ( $exception == $selected_date ) {
532
-
533
- // Closed all day
534
- if ( ! isset( $closing['time'] ) || $closing['time'] == 'undefined' ) {
535
- return false;
536
- }
537
-
538
- if ( $closing['time']['start'] !== 'undefined' ) {
539
- $open_time = ( new DateTime( $exception->format( 'Y-m-d' ) . ' ' . $closing['time']['start'], wp_timezone() ) )->format( 'U' );
540
- }
541
- else {
542
-
543
- // Start of the day
544
- $open_time = ( new DateTime( $exception->format( 'Y-m-d' ), wp_timezone() ) )->format('U');
545
- }
546
-
547
- if ( $closing['time']['end'] !== 'undefined' ) {
548
- $close_time = ( new DateTime( $exception->format( 'Y-m-d' ) . ' ' . $closing['time']['end'], wp_timezone() ) )->format( 'U' );
549
- }
550
- else {
551
-
552
- // End of the day
553
- $close_time = ( new DateTime( $exception->format( 'Y-m-d' ) . ' 23:59:59', wp_timezone() ) )->format( 'U' );
554
- }
555
-
556
- $open_time = $this->get_earliest_time( $open_time );
557
-
558
- if ( $open_time <= $close_time ) {
559
- $valid_times[] = ['from' => $open_time, 'to' => $close_time];
560
- }
561
- }
562
- }
563
-
564
- // Exit early if this date is an exception
565
- if ( isset( $open_time ) ) {
566
- return $valid_times;
567
- }
568
- }
569
-
570
- $schedule_open = $rtb_controller->settings->get_setting( 'schedule-open', $location_slug );
571
- $schedule_open = is_array( $schedule_open ) ? $schedule_open : array();
572
-
573
- // Get any rules which apply to this weekday
574
- $day_of_week = strtolower(
575
- ( new DateTime( $this->year . '-' . $this->month . '-' . $this->day . ' 1:00:00', wp_timezone() ) )->format( 'l' )
576
- );
577
-
578
- $selected_date = ( new DateTime('now', wp_timezone() ) )
579
- ->setTime(0, 0, 2)
580
- ->setDate( $this->year, $this->month, $this->day );
581
-
582
- foreach ( $schedule_open as $opening ) {
583
-
584
- if ( $opening['weekdays'] !== 'undefined' ) {
585
-
586
- foreach ( $opening['weekdays'] as $weekday => $value ) {
587
-
588
- if ( $weekday == $day_of_week ) {
589
-
590
- if ( isset( $opening['time'] ) && $opening['time']['start'] !== 'undefined' ) {
591
-
592
- $open_time = ( new DateTime( $selected_date->format( 'Y-m-d' ) .' '. $opening['time']['start'], wp_timezone() ) )->format( 'U' );
593
- }
594
- else {
595
-
596
- // Start of the day
597
- $open_time = ( new DateTime( $selected_date->format( 'Y-m-d' ), wp_timezone() ) )->format('U');
598
- }
599
-
600
- if ( isset( $opening['time'] ) && $opening['time']['end'] !== 'undefined' ) {
601
-
602
- $close_time = ( new DateTime( $selected_date->format( 'Y-m-d' ) .' '. $opening['time']['end'], wp_timezone() ) )->format( 'U' );
603
- }
604
- else {
605
-
606
- // End of the day
607
- $close_time = ( new DateTime( $selected_date->format( 'Y-m-d' ) . ' 23:59:59', wp_timezone() ) )->format( 'U' );
608
- }
609
-
610
- $open_time = $this->get_earliest_time( $open_time );
611
-
612
- if ( $open_time <= $close_time ) {
613
-
614
- $valid_times[] = ['from' => $open_time, 'to' => $close_time];
615
- }
616
- }
617
- }
618
- }
619
- }
620
-
621
- // Pass any valid times located
622
- return $valid_times;
623
- }
624
-
625
- public function get_earliest_time( $open_time ) {
626
- global $rtb_controller;
627
-
628
- $interval = $rtb_controller->settings->get_setting( 'time-interval' ) * 60;
629
-
630
- $selected_date = ( new DateTime('now', wp_timezone() ) )
631
- ->setTime(0, 0, 2)
632
- ->setDate( $this->year, $this->month, $this->day );
633
-
634
- // adjust open time with respect to the current time of the day for upcoming timeslots
635
- $current_time = ( new DateTime( 'now', wp_timezone() ) )->format( 'U' );
636
-
637
- // Only make adjustments for current day selections
638
- if ( $selected_date->format('Y-m-d') !== ( new DateTime( 'today', wp_timezone() ) )->format('Y-m-d') ) {
639
- return $open_time;
640
- }
641
-
642
- $late_bookings = ( is_admin() && current_user_can( 'manage_bookings' ) ) ? '' : $rtb_controller->settings->get_setting( 'late-bookings' );
643
-
644
- if( $current_time > $open_time ) {
645
- while( $current_time > $open_time ) {
646
- $open_time += $interval;
647
- }
648
- }
649
-
650
- // adjust the open time for the Late Bookings option
651
- if ( is_numeric($late_bookings) && $late_bookings % 1 === 0 ) {
652
- $time_calc = ( new DateTime( 'now', wp_timezone() ) )->format( 'U' ) + ( $late_bookings * 60 );
653
- while ($time_calc > $open_time) {
654
- $open_time = $open_time + $interval;
655
- }
656
- }
657
-
658
- return $open_time;
659
- }
660
-
661
- /**
662
- * Get number of seats remaining avilable to be booked
663
- * @since 2.1.5
664
- */
665
- public function get_available_party_size() {
666
- global $rtb_controller;
667
-
668
- if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
669
- rtbHelper::bad_nonce_ajax();
670
- }
671
-
672
- $this->location = ! empty( $_POST['location'] ) ? get_term( intval( $_POST['location'] ) ) : false;
673
- $this->year = sanitize_text_field( $_POST['year'] );
674
- $this->month = sanitize_text_field( $_POST['month'] );
675
- $this->day = sanitize_text_field( $_POST['day'] );
676
- $this->time = sanitize_text_field( $_POST['time'] );
677
-
678
- $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
679
-
680
- $max_people = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
681
-
682
- $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
683
-
684
- // Get opening/closing times for this particular day
685
- $hours = $this->get_opening_hours();
686
-
687
- // If the restaurant is closed that day, return false
688
- if ( 1 > count( $hours ) ) { die(); }
689
-
690
- // If no time is selected, return false
691
- if ( ! $this->time ) { die(); }
692
-
693
- $args = array(
694
- 'posts_per_page' => -1,
695
- 'date_range' => 'dates',
696
- 'start_date' => $this->year . '-' . $this->month . '-' . $this->day,
697
- 'end_date' => $this->year . '-' . $this->month . '-' . $this->day
698
- );
699
-
700
- // If there are multiple locations, a location is selected, and
701
- // max seats has been enabled for this specific location
702
- if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) {
703
-
704
- $tax_query = array(
705
- array(
706
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
707
- 'field' => 'term_id',
708
- 'terms' => $this->location->term_id
709
- )
710
- );
711
-
712
- $args['tax_query'] = $tax_query;
713
- }
714
-
715
- $query = new rtbQuery( $args );
716
- $query->prepare_args();
717
-
718
- // Get all current bookings sorted by date
719
- $bookings = $query->get_bookings();
720
-
721
- $tmzn = wp_timezone();
722
-
723
- $selected_date_time = ( new DateTime( $this->year . '-' . $this->month . '-' . $this->day . ' ' . $this->time, $tmzn ) )->format( 'U' );
724
- $selected_date_time_start = $selected_date_time - $dining_block_seconds;
725
- $selected_date_time_end = $selected_date_time + $dining_block_seconds;
726
- $party_sizes = [];
727
-
728
- if ($max_people != 'undefined' and $max_people != 0) {
729
-
730
- $max_time_size = 0;
731
- $current_times = array();
732
- $party_sizes = array();
733
-
734
- // Go through all current booking and collect the total party size
735
- foreach ( $bookings as $key => $booking ) {
736
-
737
- // Convert booking date to seconds from UNIX
738
- $booking_time = ( new DateTime( $booking->date, $tmzn ) )->format( 'U' );
739
-
740
- // Ignore bookings outside of our time range
741
- if ($booking_time < $selected_date_time_start or $booking_time > $selected_date_time_end) { continue; }
742
-
743
- $current_times[] = $booking_time;
744
- $party_sizes[] = (int) $booking->party;
745
-
746
- while ( sizeOf( $current_times ) > 0 and reset( $current_times ) < $booking_time - $dining_block_seconds ) {
747
- //save the time to know when the blocking potentially ends
748
- $removed_time = reset( $current_times );
749
-
750
- // remove the expired time and party size
751
- array_shift( $current_times );
752
- array_shift( $party_sizes );
753
- }
754
-
755
- $max_time_size = max( $max_time_size, array_sum( $party_sizes ) );
756
- }
757
-
758
- $response = (object) array( 'available_spots' => $max_people - $max_time_size);
759
-
760
- echo json_encode($response);
761
-
762
- die();
763
- } else {
764
- return false;
765
- }
766
- }
767
-
768
- /**
769
- * Get tables available to be booked at a specific time and party size
770
- * @since 2.1.7
771
- */
772
- public function get_available_tables() {
773
- global $rtb_controller;
774
-
775
- if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
776
- rtbHelper::bad_nonce_ajax();
777
- }
778
-
779
- $tables = $rtb_controller->settings->get_sorted_tables();
780
-
781
- $this->booking_id = isset( $_POST['booking_id'] ) ? intval( $_POST['booking_id'] ) : 0;
782
- $this->year = isset( $_POST['year'] ) ? sanitize_text_field( $_POST['year'] ) : false;
783
- $this->month = isset( $_POST['month'] ) ? sanitize_text_field( $_POST['month'] ) : false;
784
- $this->day = isset( $_POST['day'] ) ? sanitize_text_field( $_POST['day'] ) : false;
785
- $this->time = isset( $_POST['time'] ) ? sanitize_text_field( $_POST['time'] ) : false;
786
- $this->party = isset( $_POST['party'] ) ? sanitize_text_field( $_POST['party'] ) : false;
787
-
788
- /*$this->year = 2020;
789
- $this->month = 06;
790
- $this->day = 12;
791
- $this->time = '02:15 PM';
792
- $this->party = 12;*/
793
-
794
- if ( ! isset( $this->year ) or ! isset( $this->month ) or ! isset( $this->day ) or ! isset( $this->time ) ) { return false; }
795
-
796
- $datetime = $this->year . '-' . $this->month . '-' . $this->day . ' ' . $this->time;
797
-
798
- $valid_tables = rtb_get_valid_tables( $datetime );
799
-
800
- if ( $this->booking_id ) {
801
-
802
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
803
-
804
- $current_booking = new rtbBooking();
805
- $current_booking->load_post( $this->booking_id );
806
-
807
- if ( $current_booking->table ) { $valid_tables = array_merge( $valid_tables, $current_booking->table ); }
808
- }
809
-
810
- $return_tables = array();
811
-
812
- if ( isset( $this->party ) ) {
813
-
814
- $possible_combinations = array();
815
- foreach ( $valid_tables as $valid_table ) {
816
-
817
- // If the party size is between the min and max for the table, great
818
- if ( $tables[ $valid_table ]->min_people <= $this->party and $tables[ $valid_table ]->max_people >= $this->party ) {
819
-
820
- $possible_combinations[] = $valid_table;
821
- }
822
- // If the party is above the minimum for the table, look to see if combinations could work
823
- elseif ( $tables[ $valid_table ]->min_people <= $this->party ) {
824
-
825
- $combination = $this->get_combinations_chain( $tables, $valid_tables, $valid_table, $tables[ $valid_table ]->max_people, $this->party );
826
-
827
- if ( $combination ) {
828
- $possible_combinations[] = $combination;
829
- }
830
- }
831
-
832
- $return_tables = $this->format_tables( $possible_combinations );
833
- }
834
- }
835
- else {
836
- $return_tables = $this->format_tables( $valid_tables );
837
- }
838
-
839
- $selected_table = ( isset( $current_booking ) and $current_booking->table ) ? implode(',', $current_booking->table ) : -1;
840
-
841
- $response = (object) array( 'available_tables' => $return_tables, 'selected_table' => $selected_table );
842
-
843
- echo json_encode($response);
844
-
845
- die();
846
- }
847
-
848
- /**
849
- * Recursively go through table combinations to find one that has enough seats
850
- * @since 2.1.7
851
- */
852
- public function get_combinations_chain(
853
- $tables,
854
- $valid_tables,
855
- $current_table,
856
- $current_size,
857
- $needed_size
858
- ) {
859
- $table_chain[] = $current_table;
860
-
861
- // No combination specified
862
- if ( ! $tables[ $current_table ]->combinations ) {
863
- return false;
864
- }
865
-
866
- $possible_tables = explode( ',', $tables[ $current_table ]->combinations );
867
-
868
- foreach ( $possible_tables as $possible_table ) {
869
-
870
- // If the table has already been booked, continue
871
- if ( !in_array( $possible_table, $valid_tables) ) {
872
- continue;
873
- }
874
-
875
- // If the table can hold the group on its own, continue
876
- if ( $tables[ $possible_table ]->max_people >= $needed_size ) {
877
- continue;
878
- }
879
-
880
- $current_size += $tables[ $possible_table ]->max_people;
881
- $table_chain[] = $possible_table;
882
-
883
- if ( $current_size >= $needed_size ) {
884
- return implode(',', $table_chain);
885
- }
886
- }
887
-
888
- //no viable combination found
889
- return false;
890
- }
891
-
892
- /**
893
- * Format the tables available to be booked as number(s)_string => human_value pairs
894
- * @since 2.1.7
895
- */
896
- public function format_tables ( $table_numbers ) {
897
- global $rtb_controller;
898
-
899
- $formatted_tables = array();
900
-
901
- $tables = json_decode( html_entity_decode( $rtb_controller->settings->get_setting( 'rtb-tables' ) ) );
902
- $tables = is_array( $tables ) ? $tables : array();
903
-
904
- foreach ( $table_numbers as $table_number ) {
905
-
906
- $table_parts = explode( ',', $table_number );
907
-
908
- $table_values = array(
909
- 'numbers' => '',
910
- 'min_people' => 0,
911
- 'max_people' => 0
912
- );
913
-
914
- foreach ( $tables as $table ) {
915
- if ( in_array($table->number, $table_parts) ) {
916
- $table_values['numbers'] .= ( strlen( $table_values['numbers'] ) ? ', ' : '' ) . $table->number;
917
- $table_values['min_people'] += $table->min_people;
918
- $table_values['max_people'] += $table->max_people;
919
-
920
- if ( ! isset( $section_name ) ) { $section_name = $this->get_section_name( $table->section ); }
921
- }
922
- }
923
-
924
- $formatted_tables[ $table_values['numbers'] ] = $table_values['numbers'] . ' - ' . $section_name . ' (min. ' . $table_values['min_people'] . '/max. ' . $table_values['max_people'] . ')';
925
-
926
- unset( $section_name );
927
- }
928
-
929
- return $formatted_tables;
930
- }
931
-
932
- public function get_section_name( $section_id ) {
933
- global $rtb_controller;
934
-
935
- $sections = json_decode( html_entity_decode( $rtb_controller->settings->get_setting( 'rtb-table-sections' ) ) );
936
- $sections = is_array( $sections ) ? $sections : array();
937
-
938
- foreach ( $sections as $section ) {
939
-
940
- if ( $section->section_id == $section_id ) { return $section->name; }
941
- }
942
-
943
- return false;
944
- }
945
-
946
- public function format_pickadate_time( $time ) {
947
- $obj = ( new DateTime('now' ) )->setTimestamp( $time )->setTimezone( wp_timezone() );
948
- return array( $obj->format( 'G' ), $obj->format( 'i' ) );
949
- }
950
- }
951
  }
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
+ if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
74
+ rtbHelper::bad_nonce_ajax();
75
+ }
76
+
77
+ $email = isset($_POST['booking_email']) ? sanitize_email( $_POST['booking_email'] ) : '';
78
+
79
+ if ( ! $email ) {
80
+ wp_send_json_error(
81
+ array(
82
+ 'error' => 'noemail',
83
+ 'msg' => __( 'The email you entered is not valid.', 'restaurant-reservations' ),
84
+ )
85
+ );
86
+ }
87
+
88
+ $booking_status_lbls = $rtb_controller->cpts->booking_statuses;
89
+
90
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
91
+
92
+ $bookings = array();
93
+ $booking_ids = $wpdb->get_results(
94
+ $wpdb->prepare("
95
+ SELECT `post_id` FROM `{$wpdb->postmeta}` WHERE `meta_key` = 'rtb' AND `meta_value` LIKE %s",
96
+ '%' . $email . '%'
97
+ )
98
+ );
99
+
100
+ foreach ( $booking_ids as $booking_id ) {
101
+ $booking = new rtbBooking();
102
+ if ( $booking->load_post( $booking_id->post_id ) ) {
103
+ $booking_date = (new DateTime($booking->date, wp_timezone()))->format('U');
104
+ if ( in_array($booking->post_status, ['pending', 'payment_pending', 'payment_failed', 'confirmed'] ) and time() < $booking_date ) {
105
+ $bookings[] = array(
106
+ 'ID' => $booking->ID,
107
+ 'email' => $booking->email,
108
+ 'datetime' => $booking->format_date( $booking->date ),
109
+ 'party' => $booking->party,
110
+ 'status' => $booking->post_status,
111
+ 'status_lbl' => $booking_status_lbls[$booking->post_status]['label']
112
+ );
113
+ }
114
+ }
115
+ }
116
+
117
+ if ( ! empty( $bookings ) ) {
118
+ wp_send_json_success(
119
+ array(
120
+ 'bookings' => $bookings
121
+ )
122
+ );
123
+ }
124
+ else {
125
+ wp_send_json_error(
126
+ array(
127
+ 'error' => 'nobookings',
128
+ 'msg' => esc_html( $rtb_controller->settings->get_setting( 'label-modify-no-bookings-found' ) ),
129
+ )
130
+ );
131
+ }
132
+
133
+ die();
134
+ }
135
+
136
+ /**
137
+ * Cancel a reservation based on its ID, with the email address used for confirmation
138
+ * @since 2.1.0
139
+ */
140
+ public function cancel_reservation( $ajax = true ) {
141
+ global $rtb_controller;
142
+
143
+ if ( $ajax && !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
144
+ rtbHelper::bad_nonce_ajax();
145
+ }
146
+
147
+ $cancelled_redirect = $rtb_controller->settings->get_setting( 'cancelled-redirect-page' );
148
+
149
+ $booking_id = isset($_REQUEST['booking_id']) ? absint( $_REQUEST['booking_id'] ) : '';
150
+ $booking_email = isset($_REQUEST['booking_email']) ? sanitize_email( $_REQUEST['booking_email'] ) : '';
151
+
152
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
153
+
154
+ $success = false;
155
+ $error = array(
156
+ 'error' => 'unknown',
157
+ 'msg' => __( 'Unkown error. Please try again', 'restaurant-reservations' )
158
+ );
159
+
160
+ $booking = new rtbBooking();
161
+ if ( $booking->load_post( $booking_id ) ) {
162
+ if ( $booking_email == $booking->email ) {
163
+ wp_update_post( array( 'ID' => $booking->ID, 'post_status' => 'cancelled' ) );
164
+
165
+ $success = true;
166
+ }
167
+ else {
168
+ $error = array(
169
+ 'error' => 'invalidemail',
170
+ 'msg' => __( 'No booking matches the information that was sent.', 'restaurant-reservations' ),
171
+ );
172
+ }
173
+ }
174
+ else {
175
+ $error = array(
176
+ 'error' => 'invalidid',
177
+ 'msg' => __( 'No booking matches the information that was sent.', 'restaurant-reservations' ),
178
+ );
179
+ }
180
+
181
+ if ( $ajax ) {
182
+ if ( $success ) {
183
+
184
+ $response = array( 'booking_id' => $booking_id );
185
+
186
+ if( '' != $cancelled_redirect ) {
187
+ $response['cancelled_redirect'] = $cancelled_redirect;
188
+ }
189
+
190
+ wp_send_json_success( $response );
191
+ }
192
+ else {
193
+ wp_send_json_error( $error );
194
+ }
195
+ }
196
+ else {
197
+ $redirect_url = '';
198
+
199
+ if( '' != $cancelled_redirect && $success ) {
200
+ $redirect_url = $cancelled_redirect;
201
+ }
202
+ else {
203
+ $booking_page_id = $rtb_controller->settings->get_setting( 'booking-page' );
204
+ $booking_page_url = get_permalink( $booking_page_id );
205
+
206
+ $redirect_url = add_query_arg(
207
+ array(
208
+ 'bookingCancelled' => $success ? 'success' : 'fail'
209
+ ),
210
+ $booking_page_url
211
+ );
212
+ }
213
+
214
+ if( wp_redirect( $redirect_url ) ) {
215
+ exit;
216
+ }
217
+
218
+ header( "Location: {$redirect_url}", true, 302 );
219
+ exit;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Get available timeslots when "Max Reservations" or "Max People" is enabled
225
+ * @since 2.0.0
226
+ */
227
+ public function get_time_slots() {
228
+ global $rtb_controller;
229
+
230
+ if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
231
+ rtbHelper::bad_nonce_ajax();
232
+ }
233
+
234
+ $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables' );
235
+
236
+ // proessing request for this date
237
+ $this->location = ! empty( $_POST['location'] ) ? get_term( intval( $_POST['location'] ) ) : false;
238
+
239
+ $this->year = sanitize_text_field( $_POST['year'] );
240
+ $this->month = sanitize_text_field( $_POST['month'] );
241
+ $this->day = sanitize_text_field( $_POST['day'] );
242
+
243
+ $interval = $rtb_controller->settings->get_setting( 'time-interval' ) * 60;
244
+
245
+ // Helper functions
246
+ $finalize_response = function ( $open_close_pair_list = array() ) {
247
+
248
+ $valid_times = [];
249
+
250
+ if ( ! empty( $open_close_pair_list ) ) {
251
+
252
+ foreach ( $open_close_pair_list as $pair ) {
253
+
254
+ $valid_times[] = array(
255
+ 'from' => $this->format_pickadate_time( $pair['from'] ),
256
+ 'to' => $this->format_pickadate_time( $pair['to'] ),
257
+ 'inverted' => true
258
+ );
259
+ }
260
+ }
261
+
262
+ echo json_encode( $valid_times );
263
+
264
+ die();
265
+ };
266
+
267
+ $all_slots_for = function( $pairs ) use( $interval ) {
268
+ $all_possible_slots = [];
269
+
270
+ if( !is_array( $pairs ) ) return $pairs;
271
+
272
+ foreach ( $pairs as $pair ) {
273
+ $all_possible_slots[] = $pair['from'];
274
+ $next = $pair['from'] + $interval;
275
+ while ( $next <= $pair['to'] ) {
276
+ $all_possible_slots[] = $next;
277
+ $next += $interval;
278
+ }
279
+ }
280
+
281
+ return $all_possible_slots;
282
+ };
283
+
284
+ $consolidating_timeslots_to_timeframes = function( $slots ) use( $interval ) {
285
+ $timeframe = [];
286
+
287
+ $slots_count = count( $slots );
288
+ if( 0 < $slots_count ) {
289
+
290
+ $current_pair = [ 'from' => $slots[ 0 ] ];
291
+
292
+ for ( $i = 1; $i < $slots_count; $i++) {
293
+ if( $slots[ $i ] - $slots[ $i - 1 ] !== $interval ) {
294
+ $current_pair[ 'to' ] = $slots[ $i - 1 ];
295
+ $timeframe[] = $current_pair;
296
+
297
+ $current_pair = [ 'from' => $slots[ $i ] ];
298
+ }
299
+ }
300
+
301
+ $current_pair[ 'to' ] = $slots[ $i - 1 ];
302
+ $timeframe[] = $current_pair;
303
+ }
304
+
305
+ return $timeframe;
306
+ };
307
+
308
+ // Get opening/closing times for this particular day
309
+ $hours = $this->get_opening_hours();
310
+
311
+ // If the restaurant is closed that day
312
+ // If Enable Max Reservation not set
313
+ if ( 1 > count( $hours ) || ! $max_reservations_enabled ) {
314
+ $slots = $all_slots_for( $hours );
315
+
316
+ sort( $slots, SORT_NUMERIC );
317
+
318
+ $timeframes = $consolidating_timeslots_to_timeframes( $slots );
319
+
320
+ $finalize_response( $timeframes );
321
+ }
322
+
323
+ $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
324
+
325
+ $dining_block_seconds = (int) $rtb_controller->settings->get_setting( 'rtb-dining-block-length' ) * 60;
326
+
327
+ $min_party_size = (int) $rtb_controller->settings->get_setting( 'party-size-min' );
328
+
329
+ $max_reservations = (int) $rtb_controller->settings->get_setting( 'rtb-max-tables-count', $location_slug );
330
+
331
+ $max_people = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
332
+
333
+ $all_possible_slots = $all_slots_for( $hours );
334
+
335
+ // Get all current bookings sorted by date
336
+ $args = array(
337
+ 'posts_per_page' => -1,
338
+ 'date_range' => 'dates',
339
+ 'start_date' => $this->year . '-' . $this->month . '-' . $this->day,
340
+ 'end_date' => $this->year . '-' . $this->month . '-' . $this->day,
341
+ 'post_status' => ['pending', 'payment_pending', 'confirmed', 'arrived']
342
+ );
343
+
344
+ // If there are multiple locations, a location is selected, and
345
+ // max reservations and/or seats has been enabled for this specific location
346
+ 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 ) ) ) {
347
+
348
+ $tax_query = array(
349
+ array(
350
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
351
+ 'field' => 'term_id',
352
+ 'terms' => $this->location->term_id
353
+ )
354
+ );
355
+
356
+ $args['tax_query'] = $tax_query;
357
+ }
358
+
359
+ $query = new rtbQuery( $args, 'ajax-get-time-slots' );
360
+ $query->prepare_args();
361
+ $bookings = $query->get_bookings();
362
+
363
+ // This array holds bookings for all slots by expanding the booking by
364
+ // dining block length to help finding the overlapped bookings for time-slots
365
+ $all_bookings_by_slots = [];
366
+ foreach ( $bookings as $key => $booking ) {
367
+ // Convert booking date to seconds from UNIX
368
+ $booking_time = ( new DateTime( $booking->date , wp_timezone() ) )->format( 'U' );
369
+ if( ! array_key_exists( $booking_time, $all_bookings_by_slots ) ) {
370
+ $all_bookings_by_slots[$booking_time] = [
371
+ 'total_bookings' => 0,
372
+ 'total_guest' => 0,
373
+ 'overlapped' => false
374
+ ];
375
+ }
376
+ $all_bookings_by_slots[$booking_time]['total_bookings']++;
377
+ $all_bookings_by_slots[$booking_time]['total_guest'] += intval( $booking->party );
378
+
379
+ /**
380
+ * Expanding bookings
381
+ * Example: If I have someone booked at 1pm who will be in the restaurant for 120 minutes,
382
+ * that means they will be in the restaurant until 3pm. There is another booking at 2pm.
383
+ * That means, from 2pm to 3pm, there are already two separate reservations in the restaurant.
384
+ */
385
+ $end = $booking_time + $dining_block_seconds;
386
+ $next = $booking_time + $interval;
387
+ while($next < $end) {
388
+ if( ! array_key_exists( $next, $all_bookings_by_slots ) ) {
389
+ $all_bookings_by_slots[$next] = [
390
+ 'total_bookings' => 0,
391
+ 'total_guest' => 0,
392
+ 'overlapped' => false
393
+ ];
394
+ }
395
+ $all_bookings_by_slots[$next]['overlapped'] = true;
396
+ $all_bookings_by_slots[$next]['total_bookings']++;
397
+ $all_bookings_by_slots[$next]['total_guest'] += intval( $booking->party );
398
+ $next += $interval;
399
+ }
400
+ }
401
+
402
+ $all_blocked_slots = [];
403
+
404
+ // Go through all bookings and figure out when we're at or above the
405
+ // max reservation or max people and mark that slot as blocked
406
+ if ( isset( $max_reservations ) and $max_reservations > 0 ) {
407
+ foreach ( $all_bookings_by_slots as $slot => $data ) {
408
+ if( $max_reservations <= $data['total_bookings'] ) {
409
+ $all_blocked_slots[] = $slot;
410
+ }
411
+ }
412
+ }
413
+ else if ( isset( $max_people ) and $max_people > 0 ) {
414
+ /**
415
+ * min_party_size = 10, max_people = 100, 6 bookings of total 91 guests
416
+ * Now, if anybody wants to book for at least 10 people, it is not possible
417
+ * because the total will surpass the max_people (100)
418
+ * thus reducing min_party_size from max_people
419
+ *
420
+ * $max_people can be zero when min_party_size is same as max_people
421
+ */
422
+ $max_people = $max_people - $min_party_size;
423
+
424
+ foreach ( $all_bookings_by_slots as $slot => $data ) {
425
+ if( $max_people < $data['total_guest'] ) {
426
+ $all_blocked_slots[] = $slot;
427
+ }
428
+ }
429
+ }
430
+
431
+ // Mark slots unavailable, due to dinning block length
432
+ $additional_blocked_slots = [];
433
+ foreach ($all_blocked_slots as $slot) {
434
+ // blocking before this slot
435
+ $begin = $slot - $dining_block_seconds;
436
+ /**
437
+ * interval 30 minutes, dinning_length 120 minutes, slot 10:00am
438
+ * additional blockings before shall be 8:30am,9:00am and 9:30am
439
+ * thus skipping 8:00am which is valid
440
+ *
441
+ * @var unix timestamp
442
+ */
443
+ $next = $begin + $interval;
444
+ while($next < $slot) {
445
+ $additional_blocked_slots[] = $next;
446
+ $next += $interval;
447
+ }
448
+
449
+ // block after this slot only when this slot is not overlapped
450
+ // Overlapped slots should block only backwards, but not afterward
451
+ if( $all_bookings_by_slots[$slot]['overlapped'] ) {
452
+ continue;
453
+ }
454
+
455
+ // blocking after this slot
456
+ $end = $slot + $dining_block_seconds;
457
+ /**
458
+ * interval 30 minutes, dinning_length 120 minutes, slot 10:00am
459
+ * additional blockings after shall be 10:30am,11:00am and 1130am
460
+ * thus skipping 12:00pm which is valid
461
+ *
462
+ * @var unix timestamp
463
+ */
464
+ $next = $slot + $interval;
465
+ while($next < $end) {
466
+ $additional_blocked_slots[] = $next;
467
+ $next += $interval;
468
+ }
469
+ }
470
+
471
+ $all_blocked_slots = array_unique(
472
+ array_merge( $all_blocked_slots, $additional_blocked_slots ),
473
+ SORT_NUMERIC
474
+ );
475
+
476
+ sort( $all_blocked_slots, SORT_NUMERIC );
477
+
478
+ // remove blocked slots from available slots
479
+ $available_slots = array_diff( $all_possible_slots, $all_blocked_slots );
480
+ sort( $available_slots, SORT_NUMERIC );
481
+
482
+ // consolidating timeslots to timeframes
483
+ $timeframes = $consolidating_timeslots_to_timeframes( $available_slots );
484
+
485
+ $finalize_response( $timeframes );
486
+ }
487
+
488
+ public function get_opening_hours() {
489
+ global $rtb_controller;
490
+
491
+ $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
492
+
493
+ $schedule_closed = $rtb_controller->settings->get_setting( 'schedule-closed', $location_slug );
494
+ $schedule_closed = is_array( $schedule_closed ) ? $schedule_closed : array();
495
+
496
+ $valid_times = array();
497
+
498
+ // Check if this date is an exception to the rules
499
+ if ( $schedule_closed !== 'undefined' ) {
500
+
501
+ $selected_date = ( new DateTime('now', wp_timezone() ) )
502
+ ->setTime(0, 0, 2)
503
+ ->setDate( $this->year, $this->month, $this->day );
504
+
505
+ foreach ( $schedule_closed as $ids => $closing ) {
506
+ if( array_key_exists( 'date_range', $closing ) ) {
507
+
508
+ $start = ! empty( $closing['date_range']['start'] )
509
+ ? new DateTime( $closing['date_range']['start'], wp_timezone() )
510
+ : new DateTime( 'now', wp_timezone() );
511
+ $start->setTime(0, 0);
512
+
513
+ $end = !empty( $closing['date_range']['end'] )
514
+ ? new DateTime( $closing['date_range']['end'], wp_timezone() )
515
+ : ( new DateTime( 'now', wp_timezone() ) )->add( new DateInterval( 'P10Y' ) );
516
+ $end->setTime(23, 59, 58);
517
+
518
+ if( $start < $selected_date && $selected_date < $end ) {
519
+ $exception = clone $selected_date;
520
+ }
521
+ else {
522
+ // Set anything to void this rule
523
+ $exception = clone $selected_date;
524
+ $exception->add( new DateInterval( 'P1Y' ) );
525
+ }
526
+ }
527
+ else {
528
+ $exception = ( new DateTime( $closing['date'], wp_timezone() ) )->setTime(0, 0, 2);
529
+ }
530
+
531
+ if ( $exception == $selected_date ) {
532
+
533
+ // Closed all day
534
+ if ( ! isset( $closing['time'] ) || $closing['time'] == 'undefined' ) {
535
+ return false;
536
+ }
537
+
538
+ if ( $closing['time']['start'] !== 'undefined' ) {
539
+ $open_time = ( new DateTime( $exception->format( 'Y-m-d' ) . ' ' . $closing['time']['start'], wp_timezone() ) )->format( 'U' );
540
+ }
541
+ else {
542
+
543
+ // Start of the day
544
+ $open_time = ( new DateTime( $exception->format( 'Y-m-d' ), wp_timezone() ) )->format('U');
545
+ }
546
+
547
+ if ( $closing['time']['end'] !== 'undefined' ) {
548
+ $close_time = ( new DateTime( $exception->format( 'Y-m-d' ) . ' ' . $closing['time']['end'], wp_timezone() ) )->format( 'U' );
549
+ }
550
+ else {
551
+
552
+ // End of the day
553
+ $close_time = ( new DateTime( $exception->format( 'Y-m-d' ) . ' 23:59:59', wp_timezone() ) )->format( 'U' );
554
+ }
555
+
556
+ $open_time = $this->get_earliest_time( $open_time );
557
+
558
+ if ( $open_time <= $close_time ) {
559
+ $valid_times[] = ['from' => $open_time, 'to' => $close_time];
560
+ }
561
+ }
562
+ }
563
+
564
+ // Exit early if this date is an exception
565
+ if ( isset( $open_time ) ) {
566
+ return $valid_times;
567
+ }
568
+ }
569
+
570
+ $schedule_open = $rtb_controller->settings->get_setting( 'schedule-open', $location_slug );
571
+ $schedule_open = is_array( $schedule_open ) ? $schedule_open : array();
572
+
573
+ // Get any rules which apply to this weekday
574
+ $day_of_week = strtolower(
575
+ ( new DateTime( $this->year . '-' . $this->month . '-' . $this->day . ' 1:00:00', wp_timezone() ) )->format( 'l' )
576
+ );
577
+
578
+ $selected_date = ( new DateTime('now', wp_timezone() ) )
579
+ ->setTime(0, 0, 2)
580
+ ->setDate( $this->year, $this->month, $this->day );
581
+
582
+ foreach ( $schedule_open as $opening ) {
583
+
584
+ if ( $opening['weekdays'] !== 'undefined' ) {
585
+
586
+ foreach ( $opening['weekdays'] as $weekday => $value ) {
587
+
588
+ if ( $weekday == $day_of_week ) {
589
+
590
+ if ( isset( $opening['time'] ) && $opening['time']['start'] !== 'undefined' ) {
591
+
592
+ $open_time = ( new DateTime( $selected_date->format( 'Y-m-d' ) .' '. $opening['time']['start'], wp_timezone() ) )->format( 'U' );
593
+ }
594
+ else {
595
+
596
+ // Start of the day
597
+ $open_time = ( new DateTime( $selected_date->format( 'Y-m-d' ), wp_timezone() ) )->format('U');
598
+ }
599
+
600
+ if ( isset( $opening['time'] ) && $opening['time']['end'] !== 'undefined' ) {
601
+
602
+ $close_time = ( new DateTime( $selected_date->format( 'Y-m-d' ) .' '. $opening['time']['end'], wp_timezone() ) )->format( 'U' );
603
+ }
604
+ else {
605
+
606
+ // End of the day
607
+ $close_time = ( new DateTime( $selected_date->format( 'Y-m-d' ) . ' 23:59:59', wp_timezone() ) )->format( 'U' );
608
+ }
609
+
610
+ $open_time = $this->get_earliest_time( $open_time );
611
+
612
+ if ( $open_time <= $close_time ) {
613
+
614
+ $valid_times[] = ['from' => $open_time, 'to' => $close_time];
615
+ }
616
+ }
617
+ }
618
+ }
619
+ }
620
+
621
+ // Pass any valid times located
622
+ return $valid_times;
623
+ }
624
+
625
+ public function get_earliest_time( $open_time ) {
626
+ global $rtb_controller;
627
+
628
+ $interval = $rtb_controller->settings->get_setting( 'time-interval' ) * 60;
629
+
630
+ $selected_date = ( new DateTime('now', wp_timezone() ) )
631
+ ->setTime(0, 0, 2)
632
+ ->setDate( $this->year, $this->month, $this->day );
633
+
634
+ // adjust open time with respect to the current time of the day for upcoming timeslots
635
+ $current_time = ( new DateTime( 'now', wp_timezone() ) )->format( 'U' );
636
+
637
+ // Only make adjustments for current day selections
638
+ if ( $selected_date->format('Y-m-d') !== ( new DateTime( 'today', wp_timezone() ) )->format('Y-m-d') ) {
639
+ return $open_time;
640
+ }
641
+
642
+ $late_bookings = ( is_admin() && current_user_can( 'manage_bookings' ) ) ? '' : $rtb_controller->settings->get_setting( 'late-bookings' );
643
+
644
+ if( $current_time > $open_time ) {
645
+ while( $current_time > $open_time ) {
646
+ $open_time += $interval;
647
+ }
648
+ }
649
+
650
+ // adjust the open time for the Late Bookings option
651
+ if ( is_numeric($late_bookings) && $late_bookings % 1 === 0 ) {
652
+ $time_calc = ( new DateTime( 'now', wp_timezone() ) )->format( 'U' ) + ( $late_bookings * 60 );
653
+ while ($time_calc > $open_time) {
654
+ $open_time = $open_time + $interval;
655
+ }
656
+ }
657
+
658
+ return $open_time;
659
+ }
660
+
661
+ /**
662
+ * Get number of seats remaining avilable to be booked
663
+ * @since 2.1.5
664
+ */
665
+ public function get_available_party_size() {
666
+ global $rtb_controller;
667
+
668
+ if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
669
+ rtbHelper::bad_nonce_ajax();
670
+ }
671
+
672
+ $this->location = ! empty( $_POST['location'] ) ? get_term( intval( $_POST['location'] ) ) : false;
673
+ $this->year = sanitize_text_field( $_POST['year'] );
674
+ $this->month = sanitize_text_field( $_POST['month'] );
675
+ $this->day = sanitize_text_field( $_POST['day'] );
676
+ $this->time = sanitize_text_field( $_POST['time'] );
677
+
678
+ $location_slug = ! empty( $this->location ) ? $this->location->slug : false;
679
+
680
+ $max_people = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
681
+
682
+ $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
683
+
684
+ // Get opening/closing times for this particular day
685
+ $hours = $this->get_opening_hours();
686
+
687
+ // If the restaurant is closed that day, return false
688
+ if ( 1 > count( $hours ) ) { die(); }
689
+
690
+ // If no time is selected, return false
691
+ if ( ! $this->time ) { die(); }
692
+
693
+ $args = array(
694
+ 'posts_per_page' => -1,
695
+ 'date_range' => 'dates',
696
+ 'start_date' => $this->year . '-' . $this->month . '-' . $this->day,
697
+ 'end_date' => $this->year . '-' . $this->month . '-' . $this->day
698
+ );
699
+
700
+ // If there are multiple locations, a location is selected, and
701
+ // max seats has been enabled for this specific location
702
+ if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) {
703
+
704
+ $tax_query = array(
705
+ array(
706
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
707
+ 'field' => 'term_id',
708
+ 'terms' => $this->location->term_id
709
+ )
710
+ );
711
+
712
+ $args['tax_query'] = $tax_query;
713
+ }
714
+
715
+ $query = new rtbQuery( $args );
716
+ $query->prepare_args();
717
+
718
+ // Get all current bookings sorted by date
719
+ $bookings = $query->get_bookings();
720
+
721
+ $tmzn = wp_timezone();
722
+
723
+ $selected_date_time = ( new DateTime( $this->year . '-' . $this->month . '-' . $this->day . ' ' . $this->time, $tmzn ) )->format( 'U' );
724
+ $selected_date_time_start = $selected_date_time - $dining_block_seconds;
725
+ $selected_date_time_end = $selected_date_time + $dining_block_seconds;
726
+ $party_sizes = [];
727
+
728
+ if ($max_people != 'undefined' and $max_people != 0) {
729
+
730
+ $max_time_size = 0;
731
+ $current_times = array();
732
+ $party_sizes = array();
733
+
734
+ // Go through all current booking and collect the total party size
735
+ foreach ( $bookings as $key => $booking ) {
736
+
737
+ // Convert booking date to seconds from UNIX
738
+ $booking_time = ( new DateTime( $booking->date, $tmzn ) )->format( 'U' );
739
+
740
+ // Ignore bookings outside of our time range
741
+ if ($booking_time < $selected_date_time_start or $booking_time > $selected_date_time_end) { continue; }
742
+
743
+ $current_times[] = $booking_time;
744
+ $party_sizes[] = (int) $booking->party;
745
+
746
+ while ( sizeOf( $current_times ) > 0 and reset( $current_times ) < $booking_time - $dining_block_seconds ) {
747
+ //save the time to know when the blocking potentially ends
748
+ $removed_time = reset( $current_times );
749
+
750
+ // remove the expired time and party size
751
+ array_shift( $current_times );
752
+ array_shift( $party_sizes );
753
+ }
754
+
755
+ $max_time_size = max( $max_time_size, array_sum( $party_sizes ) );
756
+ }
757
+
758
+ $response = (object) array( 'available_spots' => $max_people - $max_time_size);
759
+
760
+ echo json_encode($response);
761
+
762
+ die();
763
+ } else {
764
+ return false;
765
+ }
766
+ }
767
+
768
+ /**
769
+ * Get tables available to be booked at a specific time and party size
770
+ * @since 2.1.7
771
+ */
772
+ public function get_available_tables() {
773
+ global $rtb_controller;
774
+
775
+ if ( !check_ajax_referer( 'rtb-booking-form', 'nonce' ) ) {
776
+ rtbHelper::bad_nonce_ajax();
777
+ }
778
+
779
+ $tables = $rtb_controller->settings->get_sorted_tables();
780
+
781
+ $this->booking_id = isset( $_POST['booking_id'] ) ? intval( $_POST['booking_id'] ) : 0;
782
+ $this->year = isset( $_POST['year'] ) ? sanitize_text_field( $_POST['year'] ) : false;
783
+ $this->month = isset( $_POST['month'] ) ? sanitize_text_field( $_POST['month'] ) : false;
784
+ $this->day = isset( $_POST['day'] ) ? sanitize_text_field( $_POST['day'] ) : false;
785
+ $this->time = isset( $_POST['time'] ) ? sanitize_text_field( $_POST['time'] ) : false;
786
+ $this->party = isset( $_POST['party'] ) ? sanitize_text_field( $_POST['party'] ) : false;
787
+
788
+ /*$this->year = 2020;
789
+ $this->month = 06;
790
+ $this->day = 12;
791
+ $this->time = '02:15 PM';
792
+ $this->party = 12;*/
793
+
794
+ if ( ! isset( $this->year ) or ! isset( $this->month ) or ! isset( $this->day ) or ! isset( $this->time ) ) { return false; }
795
+
796
+ $datetime = $this->year . '-' . $this->month . '-' . $this->day . ' ' . $this->time;
797
+
798
+ $valid_tables = rtb_get_valid_tables( $datetime );
799
+
800
+ if ( $this->booking_id ) {
801
+
802
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
803
+
804
+ $current_booking = new rtbBooking();
805
+ $current_booking->load_post( $this->booking_id );
806
+
807
+ if ( $current_booking->table ) { $valid_tables = array_merge( $valid_tables, $current_booking->table ); }
808
+ }
809
+
810
+ $return_tables = array();
811
+
812
+ if ( isset( $this->party ) ) {
813
+
814
+ $possible_combinations = array();
815
+ foreach ( $valid_tables as $valid_table ) {
816
+
817
+ // If the party size is between the min and max for the table, great
818
+ if ( $tables[ $valid_table ]->min_people <= $this->party and $tables[ $valid_table ]->max_people >= $this->party ) {
819
+
820
+ $possible_combinations[] = $valid_table;
821
+ }
822
+ // If the party is above the minimum for the table, look to see if combinations could work
823
+ elseif ( $tables[ $valid_table ]->min_people <= $this->party ) {
824
+
825
+ $combination = $this->get_combinations_chain( $tables, $valid_tables, $valid_table, $tables[ $valid_table ]->max_people, $this->party );
826
+
827
+ if ( $combination ) {
828
+ $possible_combinations[] = $combination;
829
+ }
830
+ }
831
+
832
+ $return_tables = $this->format_tables( $possible_combinations );
833
+ }
834
+ }
835
+ else {
836
+ $return_tables = $this->format_tables( $valid_tables );
837
+ }
838
+
839
+ $selected_table = ( isset( $current_booking ) and $current_booking->table ) ? implode(',', $current_booking->table ) : -1;
840
+
841
+ $response = (object) array( 'available_tables' => $return_tables, 'selected_table' => $selected_table );
842
+
843
+ echo json_encode($response);
844
+
845
+ die();
846
+ }
847
+
848
+ /**
849
+ * Recursively go through table combinations to find one that has enough seats
850
+ * @since 2.1.7
851
+ */
852
+ public function get_combinations_chain(
853
+ $tables,
854
+ $valid_tables,
855
+ $current_table,
856
+ $current_size,
857
+ $needed_size
858
+ ) {
859
+ $table_chain[] = $current_table;
860
+
861
+ // No combination specified
862
+ if ( ! $tables[ $current_table ]->combinations ) {
863
+ return false;
864
+ }
865
+
866
+ $possible_tables = explode( ',', $tables[ $current_table ]->combinations );
867
+
868
+ foreach ( $possible_tables as $possible_table ) {
869
+
870
+ // If the table has already been booked, continue
871
+ if ( !in_array( $possible_table, $valid_tables) ) {
872
+ continue;
873
+ }
874
+
875
+ // If the table can hold the group on its own, continue
876
+ if ( $tables[ $possible_table ]->max_people >= $needed_size ) {
877
+ continue;
878
+ }
879
+
880
+ $current_size += $tables[ $possible_table ]->max_people;
881
+ $table_chain[] = $possible_table;
882
+
883
+ if ( $current_size >= $needed_size ) {
884
+ return implode(',', $table_chain);
885
+ }
886
+ }
887
+
888
+ //no viable combination found
889
+ return false;
890
+ }
891
+
892
+ /**
893
+ * Format the tables available to be booked as number(s)_string => human_value pairs
894
+ * @since 2.1.7
895
+ */
896
+ public function format_tables ( $table_numbers ) {
897
+ global $rtb_controller;
898
+
899
+ $formatted_tables = array();
900
+
901
+ $tables = json_decode( html_entity_decode( $rtb_controller->settings->get_setting( 'rtb-tables' ) ) );
902
+ $tables = is_array( $tables ) ? $tables : array();
903
+
904
+ foreach ( $table_numbers as $table_number ) {
905
+
906
+ $table_parts = explode( ',', $table_number );
907
+
908
+ $table_values = array(
909
+ 'numbers' => '',
910
+ 'min_people' => 0,
911
+ 'max_people' => 0
912
+ );
913
+
914
+ foreach ( $tables as $table ) {
915
+ if ( in_array($table->number, $table_parts) ) {
916
+ $table_values['numbers'] .= ( strlen( $table_values['numbers'] ) ? ', ' : '' ) . $table->number;
917
+ $table_values['min_people'] += $table->min_people;
918
+ $table_values['max_people'] += $table->max_people;
919
+
920
+ if ( ! isset( $section_name ) ) { $section_name = $this->get_section_name( $table->section ); }
921
+ }
922
+ }
923
+
924
+ $formatted_tables[ $table_values['numbers'] ] = $table_values['numbers'] . ' - ' . $section_name . ' (min. ' . $table_values['min_people'] . '/max. ' . $table_values['max_people'] . ')';
925
+
926
+ unset( $section_name );
927
+ }
928
+
929
+ return $formatted_tables;
930
+ }
931
+
932
+ public function get_section_name( $section_id ) {
933
+ global $rtb_controller;
934
+
935
+ $sections = json_decode( html_entity_decode( $rtb_controller->settings->get_setting( 'rtb-table-sections' ) ) );
936
+ $sections = is_array( $sections ) ? $sections : array();
937
+
938
+ foreach ( $sections as $section ) {
939
+
940
+ if ( $section->section_id == $section_id ) { return $section->name; }
941
+ }
942
+
943
+ return false;
944
+ }
945
+
946
+ public function format_pickadate_time( $time ) {
947
+ $obj = ( new DateTime('now' ) )->setTimestamp( $time )->setTimezone( wp_timezone() );
948
+ return array( $obj->format( 'G' ), $obj->format( 'i' ) );
949
+ }
950
+ }
951
  }
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,1510 +1,1510 @@
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 ( is_object( $post ) && 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
- // Store post for access to other data if needed by extensions
64
- $this->post = $post;
65
-
66
- $this->ID = $post->ID;
67
- $this->name = $post->post_title;
68
- $this->date = $post->post_date;
69
- $this->message = $post->post_content;
70
- $this->post_status = $post->post_status;
71
-
72
- $this->load_post_metadata();
73
-
74
- do_action( 'rtb_booking_load_post_data', $this, $post );
75
- }
76
-
77
- /**
78
- * Store metadata for post
79
- * @since 0.0.1
80
- */
81
- public function load_post_metadata() {
82
- global $rtb_controller;
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
- 'mc_optin' => false
99
- );
100
-
101
- $meta_defaults = apply_filters( 'rtb_booking_metadata_defaults', $meta_defaults );
102
-
103
- if ( is_array( $meta = get_post_meta( $this->ID, 'rtb', true ) ) ) {
104
- $meta = array_merge( $meta_defaults, get_post_meta( $this->ID, 'rtb', true ) );
105
- } else {
106
- $meta = $meta_defaults;
107
- }
108
-
109
- $this->party = $meta['party'];
110
- $this->email = $meta['email'];
111
- $this->phone = $meta['phone'];
112
- $this->date_submission = $meta['date_submission'];
113
- $this->logs = $meta['logs'];
114
- $this->ip = $meta['ip'];
115
- $this->consent_acquired = $meta['consent_acquired'];
116
- $this->deposit = $meta['deposit'];
117
- $this->table = $meta['table'];
118
- $this->payment_failure_message = $meta['payment_failure_message'];
119
- $this->receipt_id = $meta['receipt_id'];
120
- $this->late_arrival_sent = $meta['late_arrival_sent'];
121
- $this->reminder_sent = $meta['reminder_sent'];
122
-
123
- // Did they opt out?
124
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
125
- // Because mcfrtbInit::reload_booking_meta() does not fire when needed for unknown reason
126
- if ( $optout != 'no' ) {
127
- $this->mc_optin = $meta['mc_optin'];
128
- }
129
- }
130
-
131
- /**
132
- * Prepare booking data loaded from the database for display in a booking
133
- * form as request fields. This is used, eg, for splitting datetime values
134
- * into date and time fields.
135
- * @since 1.3
136
- */
137
- public function prepare_request_data() {
138
-
139
- // Split $date to $request_date and $request_time
140
- if ( empty( $this->request_date ) || empty( $this->request_time ) ) {
141
- $date = new DateTime( $this->date, wp_timezone() );
142
- $this->request_date = $date->format( 'Y/m/d' );
143
- $this->request_time = $date->format( 'h:i A' );
144
- }
145
- }
146
-
147
- /**
148
- * Format date
149
- * @since 0.0.1
150
- */
151
- public function format_date( $date ) {
152
- $date = mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $date);
153
- return apply_filters( 'get_the_date', $date );
154
- }
155
-
156
- /**
157
- * Format a timestamp into a human-readable date
158
- *
159
- * @since 1.7.1
160
- */
161
- public function format_timestamp( $timestamp ) {
162
- $timestamp = $timestamp instanceof DateTime ? $timestamp->format('U') : $timestamp;
163
- $time = date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp );
164
- return $time;
165
- }
166
-
167
- /**
168
- * Calculates the deposit required for a reservation, if any
169
- *
170
- * @since 2.1.0
171
- */
172
- public function calculate_deposit() {
173
- global $rtb_controller;
174
-
175
- $deposit = $rtb_controller->settings->get_setting( 'rtb-deposit-amount' );
176
-
177
- if ( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ) == 'size_based' ) {
178
-
179
- $deposit = $this->party < $rtb_controller->settings->get_setting( 'rtb-deposit-min-party-size' ) ? 0 : $deposit;
180
- }
181
-
182
- if ( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ) == 'time_based' ) {
183
-
184
- $deposit = empty( $this->is_time_based_deposit_applicable() ) ? 0 : $deposit;
185
- }
186
-
187
- if ( $rtb_controller->settings->get_setting( 'rtb-deposit-type' ) == 'guest' ) { $deposit = $deposit * $this->party; }
188
-
189
- return $deposit;
190
- }
191
-
192
-
193
- /**
194
- * Insert a new booking submission into the database
195
- *
196
- * Validates the data, adds it to the database and executes notifications
197
- * @since 0.0.1
198
- */
199
- public function insert_booking($by_admin = false) {
200
-
201
- // Check if this request has already been processed. If multiple forms
202
- // exist on the same page, this prevents a single submission from
203
- // being added twice.
204
- if ( $this->request_processed === true ) {
205
- return true;
206
- }
207
-
208
- $this->request_processed = true;
209
-
210
- if ( empty( $this->ID ) ) {
211
- $action = 'insert';
212
- } else {
213
- $action = 'update';
214
- }
215
-
216
- $this->validate_submission($action, $by_admin);
217
- if ( $this->is_valid_submission() === false ) {
218
- return false;
219
- }
220
-
221
- if ( $this->insert_post_data() === false ) {
222
- return false;
223
- } else {
224
- $this->request_inserted = true;
225
- }
226
-
227
- do_action( 'rtb_' . $action . '_booking', $this );
228
-
229
- return true;
230
- }
231
-
232
- /**
233
- * Validate submission data. Expects to find data in $_POST.
234
- *
235
- * ************************** NOTE **************************
236
- * This function also create and assign all the required member variable with
237
- * the acurate values which will be insreted in the DB. One special member,
238
- * raw_input of type array holds the exact copy of $_POST
239
- *
240
- * Example:
241
- * class a {
242
- * public function a() {
243
- * $this->name = 'John Doe';
244
- * }
245
- * public function b() {
246
- * echo $this->name;
247
- * }
248
- * }
249
- *
250
- * $a = new a();
251
- *
252
- * var_dump($a);
253
- * object(a)#1 (0) {
254
- * }
255
- *
256
- * $a->a();
257
- *
258
- * var_dump($a);
259
- * object(a)#1 (1) {
260
- * ["name"]=>
261
- * string(8) "John Doe"
262
- * }
263
- *
264
- * $a->b();
265
- * John Doe
266
- *
267
- * @since 0.0.1
268
- */
269
- public function validate_submission($action = null, $by_admin = false) {
270
-
271
- global $rtb_controller;
272
-
273
- $this->validation_errors = array();
274
- /**
275
- * Raw, unprocessed value so that it can be used to preselect the form
276
- * field values, eg. table and pass the value with the request. This way,
277
- * hooked code doesn't have to check $_POST or $_GET for the data and can
278
- * access everything posted from aw_input.
279
- *
280
- * Its name implies the requirement of sanitization explicitly
281
- */
282
- $this->raw_input =& $_POST;
283
-
284
- do_action( 'rtb_pre_validate_booking_submission', $this );
285
-
286
- // Date
287
- $date = empty( $_POST['rtb-date'] ) ? false : sanitize_text_field( $_POST['rtb-date'] );
288
- if ( $date === false ) {
289
- $this->validation_errors[] = array(
290
- 'field' => 'date',
291
- 'error_msg' => 'Booking request missing date',
292
- 'message' => __( 'Please enter the date you would like to book.', 'restaurant-reservations' ),
293
- );
294
-
295
- } else {
296
- try {
297
- $date = new DateTime( sanitize_text_field( $_POST['rtb-date'] ), wp_timezone() );
298
- } catch ( Exception $e ) {
299
- $this->validation_errors[] = array(
300
- 'field' => 'date',
301
- 'error_msg' => $e->getMessage(),
302
- 'message' => __( 'The date you entered is not valid. Please select from one of the dates in the calendar.', 'restaurant-reservations' ),
303
- );
304
- }
305
- }
306
-
307
- // Time
308
- $time = empty( $_POST['rtb-time'] ) ? false : sanitize_text_field( $_POST['rtb-time'] );
309
- if ( $time === false ) {
310
- $this->validation_errors[] = array(
311
- 'field' => 'time',
312
- 'error_msg' => 'Booking request missing time',
313
- 'message' => __( 'Please enter the time you would like to book.', 'restaurant-reservations' ),
314
- );
315
-
316
- } else {
317
- try {
318
- $time = new DateTime( sanitize_text_field( $_POST['rtb-time'] ), wp_timezone() );
319
- } catch ( Exception $e ) {
320
- $this->validation_errors[] = array(
321
- 'field' => 'time',
322
- 'error_msg' => $e->getMessage(),
323
- 'message' => __( 'The time you entered is not valid. Please select from one of the times provided.', 'restaurant-reservations' ),
324
- );
325
- }
326
- }
327
-
328
- // Check against valid open dates/times
329
- if ( is_object( $time ) && is_object( $date ) ) {
330
-
331
- $request = new DateTime( $date->format( 'Y-m-d' ) . ' ' . $time->format( 'H:i:s' ), wp_timezone() );
332
- $this->date_submission = new DateTime( 'now', wp_timezone() );
333
-
334
- // Exempt Bookings Managers from the early and late bookings restrictions
335
- if ( !current_user_can( 'manage_bookings' ) ) {
336
-
337
- $early_bookings = $rtb_controller->settings->get_setting( 'early-bookings' );
338
- if ( !empty( $early_bookings ) && is_numeric( $early_bookings ) ) {
339
- $uppar_bound = ( new DateTime( 'now', wp_timezone() ) )->setTime( 23, 59 );
340
- $uppar_bound->add( new DateInterval( "P{$early_bookings}D" ) );
341
-
342
- if ( $request > $uppar_bound ) {
343
- $this->validation_errors[] = array(
344
- 'field' => 'time',
345
- 'error_msg' => 'Booking request too far in the future',
346
- 'message' => sprintf( __( 'Sorry, bookings can not be made more than %s days in advance.', 'restaurant-reservations' ), $early_bookings ),
347
- );
348
- }
349
- }
350
-
351
- $late_bookings = $rtb_controller->settings->get_setting( 'late-bookings' );
352
- if ( empty( $late_bookings ) ) {
353
- if ( $request->format( 'U' ) < $this->date_submission->format( 'U' ) ) {
354
- $this->validation_errors[] = array(
355
- 'field' => 'time',
356
- 'error_msg' => 'Booking request in the past',
357
- 'message' => __( 'Sorry, bookings can not be made in the past.', 'restaurant-reservations' ),
358
- );
359
- }
360
-
361
- } elseif ( $late_bookings === 'same_day' ) {
362
- if ( $request->format( 'Y-m-d' ) == $this->date_submission->format( 'Y-m-d' ) ) {
363
- $this->validation_errors[] = array(
364
- 'field' => 'time',
365
- 'error_msg' => 'Booking request made on same day',
366
- 'message' => __( 'Sorry, bookings can not be made for the same day.', 'restaurant-reservations' ),
367
- );
368
- }
369
-
370
- } elseif( is_numeric( $late_bookings ) ) {
371
- $late_bookings_seconds = $late_bookings * 60; // Late bookings allowance in seconds
372
- if ( $request->format( 'U' ) < ( $this->date_submission->format( 'U' ) + $late_bookings_seconds ) ) {
373
- if ( $late_bookings >= 1440 ) {
374
- $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s days in advance.', 'restaurant-reservations' ), $late_bookings / 1440 );
375
- } elseif ( $late_bookings >= 60 ) {
376
- $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s hours in advance.', 'restaurant-reservations' ), $late_bookings / 60 );
377
- } else {
378
- $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s minutes in advance.', 'restaurant-reservations' ), $late_bookings );
379
- }
380
- $this->validation_errors[] = array(
381
- 'field' => 'time',
382
- 'error_msg' => 'Booking request made too close to the reserved time',
383
- 'message' => $late_bookings_message,
384
- );
385
- }
386
- }
387
- }
388
-
389
- // Check against scheduling exception rules
390
- $exception_rules = $rtb_controller->settings->get_setting( 'schedule-closed' );
391
- $exception_is_active = false;
392
- if (
393
- empty( $this->validation_errors )
394
- && !empty( $exception_rules )
395
- && !current_user_can( 'manage_bookings' )
396
- ) {
397
-
398
- /**
399
- * We are checking the booking againt exceptions which consists blacklist and whitelist rules
400
- * - Any rule without time is a blacklist entry
401
- * - Any rule with time is a modified white entry
402
- * Thus consider the request as legit by default and terminate the loop when we hit first
403
- * blacklisted rule which applies to the request
404
- *
405
- * $exception_is_active This prevent validation againt normal open rules
406
- * $datetime_is_valid This is to throw error as soon as we encounter a blacklisted exception
407
- */
408
- $exception_is_active = false;
409
- $datetime_is_valid = true;
410
-
411
- foreach( $exception_rules as $excp_rule ) {
412
-
413
- if( array_key_exists( 'date_range', $excp_rule ) )
414
- {
415
- $start = ! empty( $excp_rule['date_range']['start'] )
416
- ? new DateTime( $excp_rule['date_range']['start'], wp_timezone() )
417
- : new DateTime( 'now', wp_timezone() );
418
- $start->setTime(0, 0);
419
-
420
- $end = !empty( $excp_rule['date_range']['end'] )
421
- ? new DateTime( $excp_rule['date_range']['end'], wp_timezone() )
422
- : ( new DateTime( 'now', wp_timezone() ) )->add( new DateInterval( 'P10Y' ) );
423
- $end->setTime(23, 59, 58);
424
-
425
- if( $start < $request && $request < $end ) {
426
- $excp_rule_obj = clone $request;
427
- }
428
- else {
429
- // Set anything to void this rule for following check
430
- $excp_rule_obj = clone $request;
431
- $excp_rule_obj->add( new DateInterval( 'P1Y' ) );
432
- }
433
- }
434
- else {
435
- $excp_rule_obj = ( new DateTime( $excp_rule['date'], wp_timezone() ) )->setTime(0, 0, 2);
436
- }
437
-
438
- if ( $excp_rule_obj->format( 'Y-m-d' ) == $request->format( 'Y-m-d' ) ) {
439
- // This rule applies so far, thus consider this request under exception
440
- // whielist or blacklist, yet to be determined
441
- $exception_is_active = true;
442
-
443
- // Closed all day
444
- // Request denied, falls under blacklist, terminate loop
445
- if ( empty( $excp_rule['time'] ) ) {
446
- $datetime_is_valid = false;
447
- break;
448
- }
449
-
450
- $excp_start_time = empty( $excp_rule['time']['start'] )
451
- ? $request
452
- : new DateTime( $excp_rule_obj->format( 'Y-m-d' ) . ' ' . $excp_rule['time']['start'], wp_timezone() );
453
-
454
- $excp_end_time = empty( $excp_rule['time']['end'] )
455
- ? $request
456
- : new DateTime( $excp_rule_obj->format( 'Y-m-d' ) . ' ' . $excp_rule['time']['end'], wp_timezone() );
457
-
458
- if (
459
- $excp_start_time->format( 'U' )
460
- <= $request->format( 'U' ) && $request->format( 'U' ) <=
461
- $excp_end_time->format( 'U' )
462
- ) {
463
- // If we reach here, means request is under modified whitelist rules
464
- $datetime_is_valid = true;
465
- break;
466
- }
467
- else {
468
- // else this request falls in blacklisted area based on modified whitelist rules.
469
- $datetime_is_valid = false;
470
- }
471
- }
472
- }
473
-
474
- if ( $exception_is_active && !$datetime_is_valid ) {
475
- $this->validation_errors[] = array(
476
- 'field' => 'date',
477
- 'error_msg' => 'Booking request made on invalid date or time in an exception rule',
478
- 'message' => __( 'Sorry, no bookings are being accepted then.', 'restaurant-reservations' ),
479
- );
480
- }
481
- }
482
-
483
- // Check against weekly scheduling rules
484
- $rules = $rtb_controller->settings->get_setting( 'schedule-open' );
485
-
486
- // Order of conditions in if matters to prevent unnacessary warnings
487
- if (
488
- empty( $this->validation_errors )
489
- && !empty( $rules )
490
- && !current_user_can( 'manage_bookings' )
491
- && !$exception_is_active
492
- ) {
493
- $request_weekday = strtolower( $request->format( 'l' ) );
494
- $time_is_valid = null;
495
- $day_is_valid = null;
496
- foreach( $rules as $rule ) {
497
-
498
- if ( !empty( $rule['weekdays'][ $request_weekday ] ) ) {
499
- $day_is_valid = true;
500
-
501
- if ( empty( $rule['time'] ) ) {
502
- $time_is_valid = true; // Days with no time values are open all day
503
- break;
504
- }
505
-
506
- $too_early = true;
507
- $too_late = true;
508
-
509
- // Too early
510
- if ( !empty( $rule['time']['start'] ) ) {
511
- $rule_start_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['start'], wp_timezone() );
512
- if ( $rule_start_time->format( 'U' ) <= $request->format( 'U' ) ) {
513
- $too_early = false;
514
- }
515
- }
516
-
517
- // Too late
518
- if ( !empty( $rule['time']['end'] ) ) {
519
- $rule_end_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['end'], wp_timezone() );
520
- if ( $rule_end_time->format( 'U' ) >= $request->format( 'U' ) ) {
521
- $too_late = false;
522
- }
523
- }
524
-
525
- // Valid time found
526
- if ( $too_early === false && $too_late === false) {
527
- $time_is_valid = true;
528
- break;
529
- }
530
- }
531
- }
532
-
533
- if ( !$day_is_valid ) {
534
- $this->validation_errors[] = array(
535
- 'field' => 'date',
536
- 'error_msg' => 'Booking request made on an invalid date',
537
- 'message' => __( 'Sorry, no bookings are being accepted on that date.', 'restaurant-reservations' ),
538
- );
539
- } elseif ( !$time_is_valid ) {
540
- $this->validation_errors[] = array(
541
- 'field' => 'time',
542
- 'error_msg' => 'Booking request made at an invalid time',
543
- 'message' => __( 'Sorry, no bookings are being accepted at that time.', 'restaurant-reservations' ),
544
- );
545
- }
546
- }
547
-
548
- // Accept the date if it has passed validation
549
- if ( empty( $this->validation_errors ) ) {
550
- $this->date = $request->format( 'Y-m-d H:i:s' );
551
- }
552
- }
553
-
554
- // Save requested date/time values in case they need to be
555
- // printed in the form again
556
- $this->request_date = empty( $_POST['rtb-date'] ) ? '' : sanitize_text_field( $_POST['rtb-date'] );
557
- $this->request_time = empty( $_POST['rtb-time'] ) ? '' : sanitize_text_field( $_POST['rtb-time'] );
558
-
559
- // Name
560
- $this->name = empty( $_POST['rtb-name'] ) ? '' : wp_strip_all_tags( sanitize_text_field( $_POST['rtb-name'] ), true ); // @todo should I limit length?
561
- if ( empty( $this->name ) ) {
562
- $this->validation_errors[] = array(
563
- 'field' => 'name',
564
- 'post_variable' => $this->name,
565
- 'message' => __( 'Please enter a name for this booking.', 'restaurant-reservations' ),
566
- );
567
- }
568
-
569
- // Party
570
- $this->party = empty( $_POST['rtb-party'] ) ? '' : absint( $_POST['rtb-party'] );
571
- if ( empty( $this->party ) ) {
572
- $this->validation_errors[] = array(
573
- 'field' => 'party',
574
- 'post_variable' => $this->party,
575
- 'message' => __( 'Please let us know how many people will be in your party.', 'restaurant-reservations' ),
576
- );
577
-
578
- // Check party size
579
- } else {
580
- $party_size = $rtb_controller->settings->get_setting( 'party-size' );
581
- if ( ! empty( $party_size ) && $party_size < $this->party ) {
582
- $this->validation_errors[] = array(
583
- 'field' => 'party',
584
- 'post_variable' => $this->party,
585
- 'message' => sprintf( __( 'We only accept bookings for parties of up to %d people.', 'restaurant-reservations' ), $party_size ),
586
- );
587
- }
588
- $party_size_min = $rtb_controller->settings->get_setting( 'party-size-min' );
589
- if ( ! empty( $party_size_min ) && $party_size_min > $this->party ) {
590
- $this->validation_errors[] = array(
591
- 'field' => 'party',
592
- 'post_variable' => $this->party,
593
- 'message' => sprintf( __( 'We only accept bookings for parties of more than %d people.', 'restaurant-reservations' ), $party_size_min ),
594
- );
595
- }
596
- }
597
-
598
- // Email
599
- $this->email = empty( $_POST['rtb-email'] ) ? '' : sanitize_email( $_POST['rtb-email'] ); // @todo email validation? send notification back to form on bad email address.
600
- if ( empty( $this->email ) ) {
601
- $this->validation_errors[] = array(
602
- 'field' => 'email',
603
- 'post_variable' => $this->email,
604
- 'message' => __( 'Please enter an email address so we can confirm your booking.', 'restaurant-reservations' ),
605
- );
606
- } elseif ( !is_email( $this->email ) && apply_filters( 'rtb_require_valid_email', true ) ) {
607
- $this->validation_errors[] = array(
608
- 'field' => 'email',
609
- 'post_variable' => $this->email,
610
- 'message' => __( 'Please enter a valid email address so we can confirm your booking.', 'restaurant-reservations' ),
611
- );
612
- }
613
-
614
- // Phone
615
- $this->phone = empty( $_POST['rtb-phone'] ) ? '' : sanitize_text_field( $_POST['rtb-phone'] );
616
- $phone_required = $rtb_controller->settings->get_setting( 'require-phone' );
617
- if ( $phone_required && empty( $this->phone ) ) {
618
- $this->validation_errors[] = array(
619
- 'field' => 'phone',
620
- 'post_variable' => $this->phone,
621
- 'message' => __( 'Please provide a phone number so we can confirm your booking.', 'restaurant-reservations' ),
622
- );
623
- }
624
-
625
- // Table
626
- $table = empty( $_POST['rtb-table'] ) ? array() : explode( ',', sanitize_text_field( $_POST['rtb-table'] ) );
627
- $this->table = is_array( $table ) ? array_map( 'sanitize_text_field', $table ) : array();
628
-
629
- $table_required = $rtb_controller->settings->get_setting( 'require-table' );
630
- if ( $table_required && empty( $this->table ) ) {
631
- $this->validation_errors[] = array(
632
- 'field' => 'table',
633
- 'post_variable' => $this->table,
634
- 'message' => __( 'Please select a table for your booking.', 'restaurant-reservations' ),
635
- );
636
- }
637
-
638
- // check whether there is a time conflict for a particular table
639
- $valid_table = $this->table ? $this->is_valid_table() : true;
640
- if ( ! $valid_table ) {
641
- $this->validation_errors[] = array(
642
- 'field' => 'table',
643
- 'post_variable' => $this->table,
644
- 'message' => __( 'Please select a valid table for your booking.', 'restaurant-reservations' ),
645
- );
646
- }
647
-
648
- // reCAPTCHA
649
- if ( $rtb_controller->settings->get_setting( 'enable-captcha' ) && !is_admin() ) {
650
- if ( ! isset($_POST['g-recaptcha-response']) ) {
651
- $this->validation_errors[] = array(
652
- 'field' => 'recaptcha',
653
- 'error_msg' => 'No reCAPTCHA code',
654
- 'message' => __( 'Please fill out the reCAPTCHA box before submitting.', 'restaurant-reservations' ),
655
- );
656
- }
657
- else {
658
- $secret_key = $rtb_controller->settings->get_setting( 'captcha-secret-key' );
659
- $captcha = $_POST['g-recaptcha-response'];
660
-
661
- $url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . urlencode($secret_key) . '&response=' . urlencode($captcha);
662
- $json_response = file_get_contents( $url );
663
- $response = json_decode( $json_response );
664
-
665
- $reCaptcha_error = false;
666
- if(json_last_error() != JSON_ERROR_NONE) {
667
- $response = new stdClass();
668
- $response->success = false;
669
- $reCaptcha_error = true;
670
- if(defined('WP_DEBUG') && WP_DEBUG) {
671
- error_log('RTB reCAPTCHA error. Raw respose: '.print_r([$json_response], true));
672
- }
673
- }
674
-
675
- if ( ! $response->success ) {
676
- $message = __( 'Please fill out the reCAPTCHA box again and re-submit.', 'restaurant-reservations' );
677
- if($reCaptcha_error) {
678
- $message .= __( ' If you encounter reCAPTCHA error multiple times, please contact us.', 'restaurant-reservations' );
679
- }
680
- $this->validation_errors[] = array(
681
- 'field' => 'recaptcha',
682
- 'error_msg' => 'Invalid reCAPTCHA code',
683
- 'message' => $message,
684
- );
685
- }
686
- }
687
- }
688
-
689
- // Message
690
- $this->message = empty( $_POST['rtb-message'] ) ? '' : sanitize_text_field( nl2br( $_POST['rtb-message'] ) );
691
-
692
- // Post Status (define a default post status if none passed)
693
- $this->determine_status();
694
-
695
- // Consent
696
- $require_consent = $rtb_controller->settings->get_setting( 'require-consent' );
697
- $consent_statement = $rtb_controller->settings->get_setting( 'consent-statement' );
698
- if ( $require_consent && $consent_statement ) {
699
- // Don't change consent status once initial consent has been collected
700
- if ( empty( $this->consent_acquired ) ) {
701
- $this->consent_acquired = !empty( $_POST['rtb-consent-statement'] );
702
- }
703
- }
704
-
705
- // Check if any required fields are empty
706
- $required_fields = $rtb_controller->settings->get_required_fields();
707
- foreach( $required_fields as $slug => $field ) {
708
- if ( !$this->field_has_error( $slug ) && $this->is_field_empty( $slug ) ) {
709
- $this->validation_errors[] = array(
710
- 'field' => $slug,
711
- 'post_variable' => '',
712
- 'message' => __( 'Please complete this field to request a booking.', 'restaurant-reservations' ),
713
- );
714
- }
715
- }
716
-
717
- // Check if the email or IP is banned
718
- if ( !current_user_can( 'manage_bookings' ) ) {
719
- $ip = $_SERVER['REMOTE_ADDR'];
720
- if ( !$this->is_valid_ip( $ip ) || !$this->is_valid_email( $this->email ) ) {
721
- $this->validation_errors[] = array(
722
- 'field' => 'date',
723
- 'post_variable' => $ip,
724
- 'message' => __( 'Your booking has been rejected. Please call us if you would like to make a booking.', 'restaurant-reservations' ),
725
- );
726
- } elseif ( empty( $this->ip ) and ! $rtb_controller->settings->get_setting( 'disable-ip-capture' ) ) {
727
- $this->ip = sanitize_text_field( $ip );
728
- }
729
- } elseif ( empty( $this->ip ) and ! $rtb_controller->settings->get_setting( 'disable-ip-capture' ) ) {
730
- $this->ip = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
731
- }
732
-
733
- // Check to make sure that the maximum number of reservations has not already been made
734
- if ( ! $this->is_under_max_reservations() ){
735
- $this->validation_errors[] = array(
736
- 'field' => 'time',
737
- 'error_msg' => 'maximum reservations exceeded',
738
- 'message' => __( 'The maximum number of reservations for that timeslot has been reached. Please select a different timeslot.', 'restaurant-reservations' ),
739
- );
740
- }
741
-
742
- // Check to make sure that the maximum number of seats has not already been made
743
- if ( ! $this->is_under_max_seats() ){
744
- $this->validation_errors[] = array(
745
- 'field' => 'time',
746
- 'error_msg' => 'maximum seats exceeded',
747
- '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' ),
748
- );
749
- }
750
-
751
- // Check if there is a booking already made with the exact same information, to prevent double bookings on refresh
752
- if ( (!$by_admin || $by_admin && $action !== 'update') && $this->is_duplicate_booking() ) {
753
- $this->validation_errors[] = array(
754
- 'field' => 'date',
755
- 'error_msg' => 'duplicate booking',
756
- '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' ),
757
- );
758
- }
759
-
760
- do_action( 'rtb_validate_booking_submission', $this );
761
-
762
- }
763
-
764
- /**
765
- * Check if submission is valid
766
- *
767
- * @since 0.0.1
768
- */
769
- public function is_valid_submission() {
770
-
771
- if ( !count( $this->validation_errors ) ) {
772
- return true;
773
- }
774
-
775
- return false;
776
- }
777
-
778
- /**
779
- * Check if a field already has an error attached to it
780
- *
781
- * @field string Field slug
782
- * @since 1.3
783
- */
784
- public function field_has_error( $field_slug ) {
785
-
786
- foreach( $this->validation_errors as $error ) {
787
- if ( $error['field'] == $field_slug ) {
788
- return true;
789
- }
790
- }
791
-
792
- return false;
793
- }
794
-
795
- /**
796
- * Check if a field is missing
797
- *
798
- * Checks for empty strings and arrays, but accepts '0'
799
- * @since 0.1
800
- */
801
- public function is_field_empty( $slug ) {
802
-
803
- $field_key = 'rtb-' . $slug;
804
-
805
- if (
806
- ! isset( $_POST[ $field_key ] )
807
- || ( is_string( $_POST[ $field_key ] ) && trim( $_POST[ $field_key ] ) == '' )
808
- || ( is_array( $_POST[ $field_key ] ) && empty( $_POST[ $field_key ] ) )
809
- )
810
- {
811
- return true;
812
- }
813
-
814
- return false;
815
- }
816
-
817
- /**
818
- * Check if an IP address has been banned
819
- *
820
- * @param string $ip
821
- * @return bool
822
- * @since 1.7
823
- */
824
- public function is_valid_ip( $ip = null ) {
825
-
826
- if ( is_null( $ip ) ) {
827
- $ip = isset( $this->ip ) ? $this->ip : null;
828
- if ( is_null( $ip ) ) {
829
- return false;
830
- }
831
- }
832
-
833
- global $rtb_controller;
834
-
835
- $banned_ips = array_filter( explode( "\n", $rtb_controller->settings->get_setting( 'ban-ips' ) ) );
836
-
837
- foreach( $banned_ips as $banned_ip ) {
838
- if ( $ip == trim( $banned_ip ) ) {
839
- return false;
840
- }
841
- }
842
-
843
- return true;
844
- }
845
-
846
- /**
847
- * Check if an email address has been banned
848
- *
849
- * @param string $email
850
- * @return bool
851
- * @since 1.7
852
- */
853
- public function is_valid_email( $email = null ) {
854
-
855
- if ( is_null( $email ) ) {
856
- $email = isset( $this->email ) ? $this->email : null;
857
- if ( is_null( $email ) ) {
858
- return false;
859
- }
860
- }
861
-
862
- global $rtb_controller;
863
-
864
- $banned_emails = array_filter( explode( "\n", $rtb_controller->settings->get_setting( 'ban-emails' ) ) );
865
-
866
- foreach( $banned_emails as $banned_email ) {
867
- if ( $email == trim( $banned_email ) ) {
868
- return false;
869
- }
870
- }
871
-
872
- return true;
873
- }
874
-
875
- /**
876
- * Check if a table(s) is valid (not already taken during a specific timeslot)
877
- *
878
- * @return bool
879
- * @since 2.1.7
880
- */
881
- public function is_valid_table() {
882
- global $rtb_controller;
883
-
884
- if ( ! $this->table or ! is_array( $this->table ) ) { return false; }
885
-
886
- $valid_tables = rtb_get_valid_tables( $this->date );
887
-
888
- if ( isset( $this->ID ) ) {
889
-
890
- $post_meta = get_post_meta( $this->ID, 'rtb', true );
891
-
892
- if ( isset( $post_meta['table'] ) and is_array( $post_meta['table'] ) ) { $valid_tables = array_merge( $valid_tables, $post_meta['table'] ); }
893
- }
894
-
895
- return $this->table == array_intersect( $this->table, $valid_tables );
896
- }
897
-
898
- /**
899
- * Check if this booking would put the restaurant over the maximum, if set
900
- *
901
- * @return bool
902
- * @since 2.1.20
903
- */
904
- public function is_under_max_reservations() {
905
- global $rtb_controller;
906
-
907
- $location = ( ! empty( $this->location ) and term_exists( $this->location ) ) ? get_term( $this->location ) : false;
908
- $location_slug = ! empty( $location ) ? $location->slug : false;
909
-
910
- $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $location_slug );
911
-
912
- if ( ! $max_reservations_enabled ) { return true; }
913
-
914
- $max_reservations = (int) $rtb_controller->settings->get_setting( 'rtb-max-tables-count', $location_slug );
915
-
916
- if ( $max_reservations == 'undefined' or ! $max_reservations ) { return true; }
917
-
918
- $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
919
-
920
- $tmp = ( new DateTime( $this->date, wp_timezone() ) )->format( 'U' );
921
- $after_time = $tmp - $dining_block_seconds;
922
- $before_time = $tmp + $dining_block_seconds;
923
-
924
- $args = array(
925
- 'posts_per_page' => -1,
926
- 'post_status' => array( 'pending', 'payment_pending', 'confirmed', 'arrived' ),
927
- 'date_query' => array(
928
- 'before' => date( 'c', $before_time ),
929
- 'after' => date( 'c', $after_time )
930
- )
931
- );
932
-
933
- // If there are multiple locations, a location is selected, and
934
- // max seats has been enabled for this specific location
935
- if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-tables-count', $location_slug ) ) {
936
-
937
- $tax_query = array(
938
- array(
939
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
940
- 'field' => 'term_id',
941
- 'terms' => $location->term_id
942
- )
943
- );
944
-
945
- $args['tax_query'] = $tax_query;
946
- }
947
-
948
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
949
- $query = new rtbQuery( $args );
950
- $query->prepare_args();
951
-
952
- $tmzn = wp_timezone();
953
-
954
- $times = array();
955
- foreach ( $query->get_bookings() as $booking ) {
956
-
957
- if ( isset( $this->ID ) and $booking->ID == $this->ID ) { continue; }
958
-
959
- $times[] = ( new DateTime( $booking->date, $tmzn ) )->format( 'U' );
960
- }
961
-
962
- sort( $times );
963
-
964
- $current_times = array();
965
- foreach ( $times as $time ) {
966
-
967
- $current_times[] = $time;
968
-
969
- if ( reset( $current_times ) < ( $time - $dining_block_seconds ) ) { array_shift( $current_times ); }
970
-
971
- // Check if we go above the max confirmation number
972
- if ( sizeOf( $current_times ) + 1 > $max_reservations ) { return false; }
973
- }
974
-
975
- return true;
976
- }
977
-
978
- /**
979
- * Check if this booking would put the restaurant over the maximum number of people, if set
980
- *
981
- * @return bool
982
- * @since 2.1.20
983
- */
984
- public function is_under_max_seats() {
985
- global $rtb_controller;
986
-
987
- $location = ( ! empty( $this->location ) and term_exists( $this->location ) ) ? get_term( $this->location ) : false;
988
- $location_slug = ! empty( $location ) ? $location->slug : false;
989
-
990
- $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $location_slug );
991
-
992
- if ( ! $max_reservations_enabled ) { return true; }
993
-
994
- $max_seats = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
995
-
996
- if ( $max_seats == 'undefined' or ! $max_seats ) { return true; }
997
- if ( $this->party > $max_seats ) { return false; }
998
-
999
- $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
1000
-
1001
- $tmp = ( new DateTime( $this->date, wp_timezone() ) )->format( 'U' );
1002
- $after_time = $tmp - $dining_block_seconds;
1003
- $before_time = $tmp + $dining_block_seconds;
1004
-
1005
- $args = array(
1006
- 'posts_per_page' => -1,
1007
- 'post_status' => array( 'pending', 'payment_pending', 'confirmed', 'arrived' ),
1008
- 'date_query' => array(
1009
- 'before' => date( 'c', $before_time ),
1010
- 'after' => date( 'c', $after_time )
1011
- )
1012
- );
1013
-
1014
- // If there are multiple locations, a location is selected, and
1015
- // max seats has been enabled for this specific location
1016
- if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) {
1017
-
1018
- $tax_query = array(
1019
- array(
1020
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
1021
- 'field' => 'term_id',
1022
- 'terms' => $location->term_id
1023
- )
1024
- );
1025
-
1026
- $args['tax_query'] = $tax_query;
1027
- }
1028
-
1029
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1030
- $query = new rtbQuery( $args );
1031
-
1032
- $tmzn = wp_timezone();
1033
- $times = array();
1034
- foreach ( $query->get_bookings() as $booking ) {
1035
-
1036
- if ( isset( $this->ID ) and $booking->ID == $this->ID ) { continue; }
1037
-
1038
- $booking_time = (new DateTime( $booking->date, $tmzn ) )->format( 'U' );
1039
- if ( isset( $times[$booking_time] ) ) { $times[$booking_time] += intval( $booking->party ); }
1040
- else { $times[$booking_time] = (int) $booking->party; }
1041
- }
1042
-
1043
- ksort( $times );
1044
-
1045
- $current_seats = array();
1046
- foreach ( $times as $time => $seats ) {
1047
-
1048
- $current_seats[$time] = $seats;
1049
-
1050
- reset( $current_seats );
1051
-
1052
- if ( key ( $current_seats ) < $time - $dining_block_seconds ) { array_shift( $current_seats ); }
1053
-
1054
- // Check if adding the current party puts us above the max confirmation number
1055
- if ( array_sum( $current_seats ) + $this->party > $max_seats ) { return false; }
1056
- }
1057
-
1058
- return true;
1059
-
1060
- }
1061
-
1062
- /**
1063
- * Check if the information in a booking exactly matches another booking
1064
- *
1065
- * @return bool
1066
- * @since 2.1.20
1067
- */
1068
- public function is_duplicate_booking() {
1069
- global $wpdb, $rtb_controller;
1070
-
1071
- if( 0 < count($this->validation_errors) ) {
1072
- /**
1073
- * Do not run this check if there is an error already
1074
- * There could abe a moment when someminfo could be missing, which is required
1075
- * for this qurey to function.
1076
- */
1077
- return null;
1078
- }
1079
-
1080
- $valid_status = ['confirmed', 'pending'];
1081
-
1082
- // This is an intermediate status when payment is pending
1083
- if ( $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
1084
- $valid_status = array_merge($valid_status, ['payment_pending']);
1085
- }
1086
-
1087
- $args = array_merge(
1088
- array(
1089
- RTB_BOOKING_POST_TYPE,
1090
- $this->date,
1091
- $this->name
1092
- ),
1093
- $valid_status
1094
- );
1095
-
1096
- $status_placeholder = implode( ',', array_fill( 0, count( $valid_status ), '%s' ) );
1097
-
1098
- $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type=%s AND post_date=%s AND post_title=%s AND post_status IN ({$status_placeholder})";
1099
-
1100
- if ( isset( $this->ID ) ) {
1101
- $sql .= ' AND ID!=%d';
1102
- $args[] = $this->ID;
1103
- }
1104
-
1105
- $booking_result = $wpdb->get_row( $wpdb->prepare( $sql, $args ) );
1106
-
1107
- if ( $booking_result ) {
1108
- $meta = get_post_meta( $booking_result->ID, 'rtb', true );
1109
- $meta = is_array( $meta ) ? $meta : array();
1110
-
1111
- if ( $this->party == $meta['party'] and $this->email == $meta['email'] and $this->phone == $meta['phone'] ) {
1112
-
1113
- return true;
1114
- }
1115
- }
1116
-
1117
- return false;
1118
- }
1119
-
1120
- /**
1121
- * Should we ask for deposit based on required minimum party size?
1122
- * @param string $value [description]
1123
- */
1124
- public function is_size_based_deposit_applicable() {
1125
- global $rtb_controller;
1126
-
1127
- if ($this->party < $rtb_controller->settings->get_setting( 'rtb-deposit-min-party-size' ) ) {
1128
- return false;
1129
- }
1130
-
1131
- return true;
1132
- }
1133
-
1134
- /**
1135
- * Shoudl we ask for deposit or not based on the given schedule?
1136
- *
1137
- * @since 2.0.0
1138
- */
1139
- public function is_time_based_deposit_applicable() {
1140
- global $rtb_controller;
1141
-
1142
- $deposit_applicable = is_array( $rtb_controller->settings->get_setting( 'rtb-deposit-schedule' ) )
1143
- ? $rtb_controller->settings->get_setting( 'rtb-deposit-schedule' )
1144
- : array();
1145
-
1146
- // Get any rules which apply to this weekday
1147
- if ( $deposit_applicable != 'undefined' ) {
1148
-
1149
- $tmzn = wp_timezone();
1150
- $date_object = new DateTime( $this->date, $tmzn );
1151
-
1152
- $time = $date_object->format( 'U' );
1153
-
1154
- $day_of_week = strtolower( $date_object->format( 'l' ) );
1155
-
1156
- foreach ( $deposit_applicable as $applicable ) {
1157
-
1158
- if ( $applicable['weekdays'] !== 'undefined' ) {
1159
-
1160
- foreach ( $applicable['weekdays'] as $weekday => $value ) {
1161
-
1162
- if ( $weekday == $day_of_week ) {
1163
-
1164
- // applicable all day
1165
- if ( !isset( $applicable['time'] ) || $applicable['time'] == 'undefined' ) {
1166
-
1167
- return true;
1168
- }
1169
-
1170
- if ( $applicable['time']['start'] !== 'undefined' ) {
1171
-
1172
- $applicable_start_time = ( new DateTime( $date_object->format( 'Y-m-d' ) . ' ' . $applicable['time']['start'], $tmzn ) )->format( 'U' );
1173
- }
1174
- else {
1175
-
1176
- $applicable_start_time = ( new DateTime( $date_object->format( 'Y-m-d' ), $tmzn ) )->format( 'U' );
1177
- }
1178
-
1179
- if ( $applicable['time']['end'] !== 'undefined' ) {
1180
-
1181
- $applicable_end_time = ( new DateTime( $date_object->format( 'Y-m-d' ) . ' ' . $applicable['time']['end'], $tmzn ) )->format( 'U' );
1182
- }
1183
- else {
1184
- // End of the day
1185
- $applicable_end_time = ( new DateTime( $date_object->format( 'Y-m-d' ) . ' 23:59:59', $tmzn ) )->format( 'U' );
1186
- }
1187
-
1188
- if ( $time > $applicable_start_time and $time < $applicable_end_time ) {
1189
-
1190
- return true;
1191
- }
1192
- }
1193
- }
1194
- }
1195
- }
1196
- }
1197
-
1198
- return false;
1199
- }
1200
-
1201
- /**
1202
- * Check whether the number of reservations occurring at the same time is below the threshold
1203
- * where reservations get automatically confirmed
1204
- *
1205
- * @since 2.0.0
1206
- */
1207
- public function under_max_confirm_reservations() {
1208
- global $rtb_controller;
1209
-
1210
- $max_reservations = (int) $rtb_controller->settings->get_setting( 'auto-confirm-max-reservations' );
1211
-
1212
- if ( $max_reservations == 'undefined' or $max_reservations <= 1 ) { return false; }
1213
-
1214
- $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
1215
-
1216
- $tmp = (new DateTime( $this->date, wp_timezone() ) )->format( 'U' );
1217
- $after_time = $tmp - $dining_block_seconds;
1218
- $before_time = $tmp + $dining_block_seconds;
1219
-
1220
- $args = array(
1221
- 'posts_per_page' => -1,
1222
- 'post_status' => ['confirmed', 'arrived'],
1223
- 'date_query' => array(
1224
- 'before' => date( 'c', $before_time ),
1225
- 'after' => date( 'c', $after_time )
1226
- )
1227
- );
1228
-
1229
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1230
- $query = new rtbQuery( $args );
1231
- $query->prepare_args();
1232
-
1233
- $tmzn = wp_timezone();
1234
- $times = array();
1235
- foreach ( $query->get_bookings() as $booking ) {
1236
- $times[] = (new DateTime( $booking->date, $tmzn ) )->format( "U" );
1237
- }
1238
-
1239
- sort( $times );
1240
-
1241
- $auto_confirm = true;
1242
- $current_times = array();
1243
- foreach ( $times as $time ) {
1244
- $current_times[] = $time;
1245
-
1246
- if ( reset( $current_times ) < ($time - $dining_block_seconds) ) { array_shift( $current_times ); }
1247
-
1248
- // Check if we've reached 1 below the max confirmation number, since adding the current booking will put us at the threshold
1249
- if ( sizeOf( $current_times ) + 1 >= $max_reservations ) { $auto_confirm = false; break; }
1250
- }
1251
-
1252
- return $auto_confirm;
1253
- }
1254
-
1255
- /**
1256
- * Check whether the number of seats occurring at the same time is below the threshold
1257
- * where reservations get automatically confirmed
1258
- *
1259
- * @since 2.0.0
1260
- */
1261
- public function under_max_confirm_seats() {
1262
- global $rtb_controller;
1263
-
1264
- $max_seats = (int) $rtb_controller->settings->get_setting( 'auto-confirm-max-seats' );
1265
-
1266
- if ( $max_seats == 'undefined' or $max_seats < 2 or $this->party >= $max_seats ) { return false; }
1267
-
1268
- $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
1269
-
1270
- $tmp = (new DateTime( $this->date, wp_timezone() ) )->format( 'U' );
1271
- $after_time = $tmp - $dining_block_seconds;
1272
- $before_time = $tmp + $dining_block_seconds;
1273
-
1274
- $args = array(
1275
- 'posts_per_page' => -1,
1276
- 'post_status' => ['confirmed', 'arrived'],
1277
- 'date_query' => array(
1278
- 'before' => date( 'c', $before_time ),
1279
- 'after' => date( 'c', $after_time )
1280
- )
1281
- );
1282
-
1283
- require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1284
- $query = new rtbQuery( $args );
1285
-
1286
- $tmzn = wp_timezone();
1287
- $times = array();
1288
- foreach ( $query->get_bookings() as $booking ) {
1289
- $booking_time = ( new DateTime( $booking->date, $tmzn ) )->format( 'U' );
1290
- if ( isset( $times[$booking_time] ) ) { $times[$booking_time] += $booking->party; }
1291
- else { $times[$booking_time] = $booking->party; }
1292
- }
1293
-
1294
- ksort( $times );
1295
-
1296
- $auto_confirm = true;
1297
- $current_seats = array();
1298
- foreach ( $times as $time => $seats ) {
1299
- $current_seats[$time] = $seats;
1300
-
1301
- reset( $current_seats );
1302
-
1303
- if ( key ( $current_seats ) < $time - $dining_block_seconds ) { array_shift( $current_seats ); }
1304
-
1305
- // Check if adding the current party puts us at or above the max confirmation number
1306
- if ( array_sum( $current_seats ) + $this->party >= $max_seats ) { $auto_confirm = false; break; }
1307
- }
1308
-
1309
- return $auto_confirm;
1310
- }
1311
-
1312
- /**
1313
- * Determine what status a booking should have
1314
- *
1315
- * @since 2.1.0
1316
- */
1317
- public function determine_status( $payment_made = false ) {
1318
- global $rtb_controller;
1319
-
1320
- if ( !empty( $_POST['rtb-post-status'] ) && array_key_exists( $_POST['rtb-post-status'], $rtb_controller->cpts->booking_statuses ) ) {
1321
- $post_status = sanitize_text_field( $_POST['rtb-post-status'] );
1322
- } elseif (
1323
- (
1324
- $rtb_controller->settings->get_setting( 'require-deposit' )
1325
- && ! $payment_made
1326
- && ! $this->is_conditional_deposit_enabled()
1327
- )
1328
- ||
1329
- (
1330
- $rtb_controller->settings->get_setting( 'require-deposit' )
1331
- && ! $payment_made
1332
- && $this->is_conditional_deposit_enabled()
1333
- &&
1334
- (
1335
- ( $this->is_time_based_deposit_enabled() && $this->is_time_based_deposit_applicable() )
1336
- ||
1337
- ( $this->is_size_based_deposit_enabled() && $this->is_size_based_deposit_applicable() )
1338
- )
1339
- )
1340
- ) {
1341
- $post_status = 'payment_pending';
1342
- } elseif ( $this->party < $rtb_controller->settings->get_setting( 'auto-confirm-max-party-size' ) ) {
1343
- $post_status = 'confirmed';
1344
- } elseif ($rtb_controller->settings->get_setting( 'auto-confirm-max-reservations' ) and $this->under_max_confirm_reservations() ) {
1345
- $post_status = 'confirmed';
1346
- } elseif ( $rtb_controller->settings->get_setting( 'auto-confirm-max-seats' ) and $this->under_max_confirm_seats() ) {
1347
- $post_status = 'confirmed';
1348
- } else {
1349
- $post_status = 'pending';
1350
- }
1351
-
1352
- $this->post_status = apply_filters( 'rtb_determine_booking_status', $post_status, $this );
1353
- }
1354
-
1355
- /**
1356
- * Add a log entry to the booking
1357
- *
1358
- * @since 1.3.1
1359
- */
1360
- public function add_log( $type, $title, $message = '', $datetime = null ) {
1361
-
1362
- if ( empty( $datetime ) ) {
1363
- $datetime = date( 'Y-m-d H:i:s');
1364
- }
1365
-
1366
- if ( empty( $this->logs ) ) {
1367
- $this->logs = array();
1368
- }
1369
-
1370
- array_push( $this->logs, array( $type, $title, $message, $datetime ) );
1371
- }
1372
-
1373
- /**
1374
- * Insert post data for a new booking or update a booking
1375
- * @since 0.0.1
1376
- */
1377
- public function insert_post_data() {
1378
-
1379
- $args = array(
1380
- 'post_type' => RTB_BOOKING_POST_TYPE,
1381
- 'post_title' => $this->name,
1382
- 'post_content' => $this->message,
1383
- 'post_date' => $this->date,
1384
- 'post_date_gmt' => get_gmt_from_date( $this->date ), // fix for post_date_gmt not being set for some bookings
1385
- 'post_status' => $this->post_status,
1386
- );
1387
-
1388
- if ( !empty( $this->ID ) ) {
1389
- $args['ID'] = $this->ID;
1390
- }
1391
-
1392
- $args = apply_filters( 'rtb_insert_booking_data', $args, $this );
1393
-
1394
- // When updating a booking, we need to update the metadata first, so that
1395
- // notifications hooked to the status changes go out with the new metadata.
1396
- // If we're inserting a new booking, we have to insert it before we can
1397
- // add metadata, and the default notifications don't fire until it's all done.
1398
- if ( !empty( $this->ID ) ) {
1399
- $this->insert_post_meta();
1400
- $id = wp_insert_post( $args );
1401
- } else {
1402
- $id = wp_insert_post( $args );
1403
- if ( $id && !is_wp_error( $id ) ) {
1404
- $this->ID = $id;
1405
- $this->insert_post_meta();
1406
- }
1407
- }
1408
-
1409
- return !is_wp_error( $id ) && $id !== false;
1410
- }
1411
-
1412
- /**
1413
- * Insert the post metadata for a new booking or when updating a booking
1414
- * @since 1.7.7
1415
- */
1416
- public function insert_post_meta() {
1417
-
1418
- $meta = array(
1419
- 'party' => $this->party,
1420
- 'email' => $this->email,
1421
- 'phone' => $this->phone,
1422
- );
1423
-
1424
- if ( !empty( $this->ip ) ) {
1425
- $meta['ip'] = $this->ip;
1426
- }
1427
-
1428
- if ( empty( $this->date_submission ) ) {
1429
- $meta['date_submission'] = (new DateTime( 'now', wp_timezone() ))->format( 'U' );
1430
- } else {
1431
- $meta['date_submission'] = $this->date_submission instanceof DateTime
1432
- ? $this->date_submission->format('U')
1433
- : $this->date_submission;
1434
- }
1435
-
1436
- if ( !empty( $this->consent_acquired ) ) {
1437
- $meta['consent_acquired'] = $this->consent_acquired;
1438
- }
1439
-
1440
- if ( !empty( $this->logs ) ) {
1441
- $meta['logs'] = $this->logs;
1442
- }
1443
-
1444
- if ( !empty( $this->table ) ) {
1445
- $meta['table'] = $this->table;
1446
- }
1447
-
1448
- if ( !empty( $this->deposit ) ) {
1449
- $meta['deposit'] = $this->deposit;
1450
- }
1451
-
1452
- if ( !empty( $this->payment_failure_message ) ) {
1453
- $meta['payment_failure_message'] = $this->payment_failure_message;
1454
- }
1455
-
1456
- if ( !empty( $this->receipt_id ) ) {
1457
- $meta['receipt_id'] = $this->receipt_id;
1458
- }
1459
-
1460
- if ( !empty( $this->reminder_sent ) ) {
1461
- $meta['reminder_sent'] = $this->reminder_sent;
1462
- }
1463
-
1464
- if ( !empty( $this->late_arrival_sent ) ) {
1465
- $meta['late_arrival_sent'] = $this->late_arrival_sent;
1466
- }
1467
-
1468
- $meta = apply_filters( 'rtb_insert_booking_metadata', $meta, $this );
1469
-
1470
- return update_post_meta( $this->ID, 'rtb', $meta );
1471
- }
1472
-
1473
- public function payment_paid()
1474
- {
1475
- if( isset( $this->ID ) ) {
1476
- $this->determine_status( true );
1477
-
1478
- $this->insert_post_data();
1479
-
1480
- do_action( 'rtb_booking_paid', $this );
1481
- }
1482
- }
1483
-
1484
- public function payment_failed( $message = '' )
1485
- {
1486
- $this->post_status = 'payment_failed';
1487
- $this->payment_failure_message = $message;
1488
-
1489
- $this->insert_post_data();
1490
-
1491
- do_action( 'rtb_booking_paid', $this );
1492
- }
1493
-
1494
- function is_conditional_deposit_enabled() {
1495
- global $rtb_controller;
1496
- return in_array( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ), ['time_based', 'size_based'] );
1497
- }
1498
-
1499
- function is_time_based_deposit_enabled() {
1500
- global $rtb_controller;
1501
- return 'time_based' === $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' );
1502
- }
1503
-
1504
- function is_size_based_deposit_enabled() {
1505
- global $rtb_controller;
1506
- return 'size_based' === $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' );
1507
- }
1508
-
1509
- }
1510
- } // 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 ( is_object( $post ) && 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
+ // Store post for access to other data if needed by extensions
64
+ $this->post = $post;
65
+
66
+ $this->ID = $post->ID;
67
+ $this->name = $post->post_title;
68
+ $this->date = $post->post_date;
69
+ $this->message = $post->post_content;
70
+ $this->post_status = $post->post_status;
71
+
72
+ $this->load_post_metadata();
73
+
74
+ do_action( 'rtb_booking_load_post_data', $this, $post );
75
+ }
76
+
77
+ /**
78
+ * Store metadata for post
79
+ * @since 0.0.1
80
+ */
81
+ public function load_post_metadata() {
82
+ global $rtb_controller;
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
+ 'mc_optin' => false
99
+ );
100
+
101
+ $meta_defaults = apply_filters( 'rtb_booking_metadata_defaults', $meta_defaults );
102
+
103
+ if ( is_array( $meta = get_post_meta( $this->ID, 'rtb', true ) ) ) {
104
+ $meta = array_merge( $meta_defaults, get_post_meta( $this->ID, 'rtb', true ) );
105
+ } else {
106
+ $meta = $meta_defaults;
107
+ }
108
+
109
+ $this->party = $meta['party'];
110
+ $this->email = $meta['email'];
111
+ $this->phone = $meta['phone'];
112
+ $this->date_submission = $meta['date_submission'];
113
+ $this->logs = $meta['logs'];
114
+ $this->ip = $meta['ip'];
115
+ $this->consent_acquired = $meta['consent_acquired'];
116
+ $this->deposit = $meta['deposit'];
117
+ $this->table = $meta['table'];
118
+ $this->payment_failure_message = $meta['payment_failure_message'];
119
+ $this->receipt_id = $meta['receipt_id'];
120
+ $this->late_arrival_sent = $meta['late_arrival_sent'];
121
+ $this->reminder_sent = $meta['reminder_sent'];
122
+
123
+ // Did they opt out?
124
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
125
+ // Because mcfrtbInit::reload_booking_meta() does not fire when needed for unknown reason
126
+ if ( $optout != 'no' ) {
127
+ $this->mc_optin = $meta['mc_optin'];
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Prepare booking data loaded from the database for display in a booking
133
+ * form as request fields. This is used, eg, for splitting datetime values
134
+ * into date and time fields.
135
+ * @since 1.3
136
+ */
137
+ public function prepare_request_data() {
138
+
139
+ // Split $date to $request_date and $request_time
140
+ if ( empty( $this->request_date ) || empty( $this->request_time ) ) {
141
+ $date = new DateTime( $this->date, wp_timezone() );
142
+ $this->request_date = $date->format( 'Y/m/d' );
143
+ $this->request_time = $date->format( 'h:i A' );
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Format date
149
+ * @since 0.0.1
150
+ */
151
+ public function format_date( $date ) {
152
+ $date = mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $date);
153
+ return apply_filters( 'get_the_date', $date );
154
+ }
155
+
156
+ /**
157
+ * Format a timestamp into a human-readable date
158
+ *
159
+ * @since 1.7.1
160
+ */
161
+ public function format_timestamp( $timestamp ) {
162
+ $timestamp = $timestamp instanceof DateTime ? $timestamp->format('U') : $timestamp;
163
+ $time = date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp );
164
+ return $time;
165
+ }
166
+
167
+ /**
168
+ * Calculates the deposit required for a reservation, if any
169
+ *
170
+ * @since 2.1.0
171
+ */
172
+ public function calculate_deposit() {
173
+ global $rtb_controller;
174
+
175
+ $deposit = $rtb_controller->settings->get_setting( 'rtb-deposit-amount' );
176
+
177
+ if ( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ) == 'size_based' ) {
178
+
179
+ $deposit = $this->party < $rtb_controller->settings->get_setting( 'rtb-deposit-min-party-size' ) ? 0 : $deposit;
180
+ }
181
+
182
+ if ( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ) == 'time_based' ) {
183
+
184
+ $deposit = empty( $this->is_time_based_deposit_applicable() ) ? 0 : $deposit;
185
+ }
186
+
187
+ if ( $rtb_controller->settings->get_setting( 'rtb-deposit-type' ) == 'guest' ) { $deposit = $deposit * $this->party; }
188
+
189
+ return $deposit;
190
+ }
191
+
192
+
193
+ /**
194
+ * Insert a new booking submission into the database
195
+ *
196
+ * Validates the data, adds it to the database and executes notifications
197
+ * @since 0.0.1
198
+ */
199
+ public function insert_booking($by_admin = false) {
200
+
201
+ // Check if this request has already been processed. If multiple forms
202
+ // exist on the same page, this prevents a single submission from
203
+ // being added twice.
204
+ if ( $this->request_processed === true ) {
205
+ return true;
206
+ }
207
+
208
+ $this->request_processed = true;
209
+
210
+ if ( empty( $this->ID ) ) {
211
+ $action = 'insert';
212
+ } else {
213
+ $action = 'update';
214
+ }
215
+
216
+ $this->validate_submission($action, $by_admin);
217
+ if ( $this->is_valid_submission() === false ) {
218
+ return false;
219
+ }
220
+
221
+ if ( $this->insert_post_data() === false ) {
222
+ return false;
223
+ } else {
224
+ $this->request_inserted = true;
225
+ }
226
+
227
+ do_action( 'rtb_' . $action . '_booking', $this );
228
+
229
+ return true;
230
+ }
231
+
232
+ /**
233
+ * Validate submission data. Expects to find data in $_POST.
234
+ *
235
+ * ************************** NOTE **************************
236
+ * This function also create and assign all the required member variable with
237
+ * the acurate values which will be insreted in the DB. One special member,
238
+ * raw_input of type array holds the exact copy of $_POST
239
+ *
240
+ * Example:
241
+ * class a {
242
+ * public function a() {
243
+ * $this->name = 'John Doe';
244
+ * }
245
+ * public function b() {
246
+ * echo $this->name;
247
+ * }
248
+ * }
249
+ *
250
+ * $a = new a();
251
+ *
252
+ * var_dump($a);
253
+ * object(a)#1 (0) {
254
+ * }
255
+ *
256
+ * $a->a();
257
+ *
258
+ * var_dump($a);
259
+ * object(a)#1 (1) {
260
+ * ["name"]=>
261
+ * string(8) "John Doe"
262
+ * }
263
+ *
264
+ * $a->b();
265
+ * John Doe
266
+ *
267
+ * @since 0.0.1
268
+ */
269
+ public function validate_submission($action = null, $by_admin = false) {
270
+
271
+ global $rtb_controller;
272
+
273
+ $this->validation_errors = array();
274
+ /**
275
+ * Raw, unprocessed value so that it can be used to preselect the form
276
+ * field values, eg. table and pass the value with the request. This way,
277
+ * hooked code doesn't have to check $_POST or $_GET for the data and can
278
+ * access everything posted from aw_input.
279
+ *
280
+ * Its name implies the requirement of sanitization explicitly
281
+ */
282
+ $this->raw_input =& $_POST;
283
+
284
+ do_action( 'rtb_pre_validate_booking_submission', $this );
285
+
286
+ // Date
287
+ $date = empty( $_POST['rtb-date'] ) ? false : sanitize_text_field( $_POST['rtb-date'] );
288
+ if ( $date === false ) {
289
+ $this->validation_errors[] = array(
290
+ 'field' => 'date',
291
+ 'error_msg' => 'Booking request missing date',
292
+ 'message' => __( 'Please enter the date you would like to book.', 'restaurant-reservations' ),
293
+ );
294
+
295
+ } else {
296
+ try {
297
+ $date = new DateTime( sanitize_text_field( $_POST['rtb-date'] ), wp_timezone() );
298
+ } catch ( Exception $e ) {
299
+ $this->validation_errors[] = array(
300
+ 'field' => 'date',
301
+ 'error_msg' => $e->getMessage(),
302
+ 'message' => __( 'The date you entered is not valid. Please select from one of the dates in the calendar.', 'restaurant-reservations' ),
303
+ );
304
+ }
305
+ }
306
+
307
+ // Time
308
+ $time = empty( $_POST['rtb-time'] ) ? false : sanitize_text_field( $_POST['rtb-time'] );
309
+ if ( $time === false ) {
310
+ $this->validation_errors[] = array(
311
+ 'field' => 'time',
312
+ 'error_msg' => 'Booking request missing time',
313
+ 'message' => __( 'Please enter the time you would like to book.', 'restaurant-reservations' ),
314
+ );
315
+
316
+ } else {
317
+ try {
318
+ $time = new DateTime( sanitize_text_field( $_POST['rtb-time'] ), wp_timezone() );
319
+ } catch ( Exception $e ) {
320
+ $this->validation_errors[] = array(
321
+ 'field' => 'time',
322
+ 'error_msg' => $e->getMessage(),
323
+ 'message' => __( 'The time you entered is not valid. Please select from one of the times provided.', 'restaurant-reservations' ),
324
+ );
325
+ }
326
+ }
327
+
328
+ // Check against valid open dates/times
329
+ if ( is_object( $time ) && is_object( $date ) ) {
330
+
331
+ $request = new DateTime( $date->format( 'Y-m-d' ) . ' ' . $time->format( 'H:i:s' ), wp_timezone() );
332
+ $this->date_submission = new DateTime( 'now', wp_timezone() );
333
+
334
+ // Exempt Bookings Managers from the early and late bookings restrictions
335
+ if ( !current_user_can( 'manage_bookings' ) ) {
336
+
337
+ $early_bookings = $rtb_controller->settings->get_setting( 'early-bookings' );
338
+ if ( !empty( $early_bookings ) && is_numeric( $early_bookings ) ) {
339
+ $uppar_bound = ( new DateTime( 'now', wp_timezone() ) )->setTime( 23, 59 );
340
+ $uppar_bound->add( new DateInterval( "P{$early_bookings}D" ) );
341
+
342
+ if ( $request > $uppar_bound ) {
343
+ $this->validation_errors[] = array(
344
+ 'field' => 'time',
345
+ 'error_msg' => 'Booking request too far in the future',
346
+ 'message' => sprintf( __( 'Sorry, bookings can not be made more than %s days in advance.', 'restaurant-reservations' ), $early_bookings ),
347
+ );
348
+ }
349
+ }
350
+
351
+ $late_bookings = $rtb_controller->settings->get_setting( 'late-bookings' );
352
+ if ( empty( $late_bookings ) ) {
353
+ if ( $request->format( 'U' ) < $this->date_submission->format( 'U' ) ) {
354
+ $this->validation_errors[] = array(
355
+ 'field' => 'time',
356
+ 'error_msg' => 'Booking request in the past',
357
+ 'message' => __( 'Sorry, bookings can not be made in the past.', 'restaurant-reservations' ),
358
+ );
359
+ }
360
+
361
+ } elseif ( $late_bookings === 'same_day' ) {
362
+ if ( $request->format( 'Y-m-d' ) == $this->date_submission->format( 'Y-m-d' ) ) {
363
+ $this->validation_errors[] = array(
364
+ 'field' => 'time',
365
+ 'error_msg' => 'Booking request made on same day',
366
+ 'message' => __( 'Sorry, bookings can not be made for the same day.', 'restaurant-reservations' ),
367
+ );
368
+ }
369
+
370
+ } elseif( is_numeric( $late_bookings ) ) {
371
+ $late_bookings_seconds = $late_bookings * 60; // Late bookings allowance in seconds
372
+ if ( $request->format( 'U' ) < ( $this->date_submission->format( 'U' ) + $late_bookings_seconds ) ) {
373
+ if ( $late_bookings >= 1440 ) {
374
+ $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s days in advance.', 'restaurant-reservations' ), $late_bookings / 1440 );
375
+ } elseif ( $late_bookings >= 60 ) {
376
+ $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s hours in advance.', 'restaurant-reservations' ), $late_bookings / 60 );
377
+ } else {
378
+ $late_bookings_message = sprintf( __( 'Sorry, bookings must be made more than %s minutes in advance.', 'restaurant-reservations' ), $late_bookings );
379
+ }
380
+ $this->validation_errors[] = array(
381
+ 'field' => 'time',
382
+ 'error_msg' => 'Booking request made too close to the reserved time',
383
+ 'message' => $late_bookings_message,
384
+ );
385
+ }
386
+ }
387
+ }
388
+
389
+ // Check against scheduling exception rules
390
+ $exception_rules = $rtb_controller->settings->get_setting( 'schedule-closed' );
391
+ $exception_is_active = false;
392
+ if (
393
+ empty( $this->validation_errors )
394
+ && !empty( $exception_rules )
395
+ && !current_user_can( 'manage_bookings' )
396
+ ) {
397
+
398
+ /**
399
+ * We are checking the booking againt exceptions which consists blacklist and whitelist rules
400
+ * - Any rule without time is a blacklist entry
401
+ * - Any rule with time is a modified white entry
402
+ * Thus consider the request as legit by default and terminate the loop when we hit first
403
+ * blacklisted rule which applies to the request
404
+ *
405
+ * $exception_is_active This prevent validation againt normal open rules
406
+ * $datetime_is_valid This is to throw error as soon as we encounter a blacklisted exception
407
+ */
408
+ $exception_is_active = false;
409
+ $datetime_is_valid = true;
410
+
411
+ foreach( $exception_rules as $excp_rule ) {
412
+
413
+ if( array_key_exists( 'date_range', $excp_rule ) )
414
+ {
415
+ $start = ! empty( $excp_rule['date_range']['start'] )
416
+ ? new DateTime( $excp_rule['date_range']['start'], wp_timezone() )
417
+ : new DateTime( 'now', wp_timezone() );
418
+ $start->setTime(0, 0);
419
+
420
+ $end = !empty( $excp_rule['date_range']['end'] )
421
+ ? new DateTime( $excp_rule['date_range']['end'], wp_timezone() )
422
+ : ( new DateTime( 'now', wp_timezone() ) )->add( new DateInterval( 'P10Y' ) );
423
+ $end->setTime(23, 59, 58);
424
+
425
+ if( $start < $request && $request < $end ) {
426
+ $excp_rule_obj = clone $request;
427
+ }
428
+ else {
429
+ // Set anything to void this rule for following check
430
+ $excp_rule_obj = clone $request;
431
+ $excp_rule_obj->add( new DateInterval( 'P1Y' ) );
432
+ }
433
+ }
434
+ else {
435
+ $excp_rule_obj = ( new DateTime( $excp_rule['date'], wp_timezone() ) )->setTime(0, 0, 2);
436
+ }
437
+
438
+ if ( $excp_rule_obj->format( 'Y-m-d' ) == $request->format( 'Y-m-d' ) ) {
439
+ // This rule applies so far, thus consider this request under exception
440
+ // whielist or blacklist, yet to be determined
441
+ $exception_is_active = true;
442
+
443
+ // Closed all day
444
+ // Request denied, falls under blacklist, terminate loop
445
+ if ( empty( $excp_rule['time'] ) ) {
446
+ $datetime_is_valid = false;
447
+ break;
448
+ }
449
+
450
+ $excp_start_time = empty( $excp_rule['time']['start'] )
451
+ ? $request
452
+ : new DateTime( $excp_rule_obj->format( 'Y-m-d' ) . ' ' . $excp_rule['time']['start'], wp_timezone() );
453
+
454
+ $excp_end_time = empty( $excp_rule['time']['end'] )
455
+ ? $request
456
+ : new DateTime( $excp_rule_obj->format( 'Y-m-d' ) . ' ' . $excp_rule['time']['end'], wp_timezone() );
457
+
458
+ if (
459
+ $excp_start_time->format( 'U' )
460
+ <= $request->format( 'U' ) && $request->format( 'U' ) <=
461
+ $excp_end_time->format( 'U' )
462
+ ) {
463
+ // If we reach here, means request is under modified whitelist rules
464
+ $datetime_is_valid = true;
465
+ break;
466
+ }
467
+ else {
468
+ // else this request falls in blacklisted area based on modified whitelist rules.
469
+ $datetime_is_valid = false;
470
+ }
471
+ }
472
+ }
473
+
474
+ if ( $exception_is_active && !$datetime_is_valid ) {
475
+ $this->validation_errors[] = array(
476
+ 'field' => 'date',
477
+ 'error_msg' => 'Booking request made on invalid date or time in an exception rule',
478
+ 'message' => __( 'Sorry, no bookings are being accepted then.', 'restaurant-reservations' ),
479
+ );
480
+ }
481
+ }
482
+
483
+ // Check against weekly scheduling rules
484
+ $rules = $rtb_controller->settings->get_setting( 'schedule-open' );
485
+
486
+ // Order of conditions in if matters to prevent unnacessary warnings
487
+ if (
488
+ empty( $this->validation_errors )
489
+ && !empty( $rules )
490
+ && !current_user_can( 'manage_bookings' )
491
+ && !$exception_is_active
492
+ ) {
493
+ $request_weekday = strtolower( $request->format( 'l' ) );
494
+ $time_is_valid = null;
495
+ $day_is_valid = null;
496
+ foreach( $rules as $rule ) {
497
+
498
+ if ( !empty( $rule['weekdays'][ $request_weekday ] ) ) {
499
+ $day_is_valid = true;
500
+
501
+ if ( empty( $rule['time'] ) ) {
502
+ $time_is_valid = true; // Days with no time values are open all day
503
+ break;
504
+ }
505
+
506
+ $too_early = true;
507
+ $too_late = true;
508
+
509
+ // Too early
510
+ if ( !empty( $rule['time']['start'] ) ) {
511
+ $rule_start_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['start'], wp_timezone() );
512
+ if ( $rule_start_time->format( 'U' ) <= $request->format( 'U' ) ) {
513
+ $too_early = false;
514
+ }
515
+ }
516
+
517
+ // Too late
518
+ if ( !empty( $rule['time']['end'] ) ) {
519
+ $rule_end_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['end'], wp_timezone() );
520
+ if ( $rule_end_time->format( 'U' ) >= $request->format( 'U' ) ) {
521
+ $too_late = false;
522
+ }
523
+ }
524
+
525
+ // Valid time found
526
+ if ( $too_early === false && $too_late === false) {
527
+ $time_is_valid = true;
528
+ break;
529
+ }
530
+ }
531
+ }
532
+
533
+ if ( !$day_is_valid ) {
534
+ $this->validation_errors[] = array(
535
+ 'field' => 'date',
536
+ 'error_msg' => 'Booking request made on an invalid date',
537
+ 'message' => __( 'Sorry, no bookings are being accepted on that date.', 'restaurant-reservations' ),
538
+ );
539
+ } elseif ( !$time_is_valid ) {
540
+ $this->validation_errors[] = array(
541
+ 'field' => 'time',
542
+ 'error_msg' => 'Booking request made at an invalid time',
543
+ 'message' => __( 'Sorry, no bookings are being accepted at that time.', 'restaurant-reservations' ),
544
+ );
545
+ }
546
+ }
547
+
548
+ // Accept the date if it has passed validation
549
+ if ( empty( $this->validation_errors ) ) {
550
+ $this->date = $request->format( 'Y-m-d H:i:s' );
551
+ }
552
+ }
553
+
554
+ // Save requested date/time values in case they need to be
555
+ // printed in the form again
556
+ $this->request_date = empty( $_POST['rtb-date'] ) ? '' : sanitize_text_field( $_POST['rtb-date'] );
557
+ $this->request_time = empty( $_POST['rtb-time'] ) ? '' : sanitize_text_field( $_POST['rtb-time'] );
558
+
559
+ // Name
560
+ $this->name = empty( $_POST['rtb-name'] ) ? '' : wp_strip_all_tags( sanitize_text_field( $_POST['rtb-name'] ), true ); // @todo should I limit length?
561
+ if ( empty( $this->name ) ) {
562
+ $this->validation_errors[] = array(
563
+ 'field' => 'name',
564
+ 'post_variable' => $this->name,
565
+ 'message' => __( 'Please enter a name for this booking.', 'restaurant-reservations' ),
566
+ );
567
+ }
568
+
569
+ // Party
570
+ $this->party = empty( $_POST['rtb-party'] ) ? '' : absint( $_POST['rtb-party'] );
571
+ if ( empty( $this->party ) ) {
572
+ $this->validation_errors[] = array(
573
+ 'field' => 'party',
574
+ 'post_variable' => $this->party,
575
+ 'message' => __( 'Please let us know how many people will be in your party.', 'restaurant-reservations' ),
576
+ );
577
+
578
+ // Check party size
579
+ } else {
580
+ $party_size = $rtb_controller->settings->get_setting( 'party-size' );
581
+ if ( ! empty( $party_size ) && $party_size < $this->party ) {
582
+ $this->validation_errors[] = array(
583
+ 'field' => 'party',
584
+ 'post_variable' => $this->party,
585
+ 'message' => sprintf( __( 'We only accept bookings for parties of up to %d people.', 'restaurant-reservations' ), $party_size ),
586
+ );
587
+ }
588
+ $party_size_min = $rtb_controller->settings->get_setting( 'party-size-min' );
589
+ if ( ! empty( $party_size_min ) && $party_size_min > $this->party ) {
590
+ $this->validation_errors[] = array(
591
+ 'field' => 'party',
592
+ 'post_variable' => $this->party,
593
+ 'message' => sprintf( __( 'We only accept bookings for parties of more than %d people.', 'restaurant-reservations' ), $party_size_min ),
594
+ );
595
+ }
596
+ }
597
+
598
+ // Email
599
+ $this->email = empty( $_POST['rtb-email'] ) ? '' : sanitize_email( $_POST['rtb-email'] ); // @todo email validation? send notification back to form on bad email address.
600
+ if ( empty( $this->email ) ) {
601
+ $this->validation_errors[] = array(
602
+ 'field' => 'email',
603
+ 'post_variable' => $this->email,
604
+ 'message' => __( 'Please enter an email address so we can confirm your booking.', 'restaurant-reservations' ),
605
+ );
606
+ } elseif ( !is_email( $this->email ) && apply_filters( 'rtb_require_valid_email', true ) ) {
607
+ $this->validation_errors[] = array(
608
+ 'field' => 'email',
609
+ 'post_variable' => $this->email,
610
+ 'message' => __( 'Please enter a valid email address so we can confirm your booking.', 'restaurant-reservations' ),
611
+ );
612
+ }
613
+
614
+ // Phone
615
+ $this->phone = empty( $_POST['rtb-phone'] ) ? '' : sanitize_text_field( $_POST['rtb-phone'] );
616
+ $phone_required = $rtb_controller->settings->get_setting( 'require-phone' );
617
+ if ( $phone_required && empty( $this->phone ) ) {
618
+ $this->validation_errors[] = array(
619
+ 'field' => 'phone',
620
+ 'post_variable' => $this->phone,
621
+ 'message' => __( 'Please provide a phone number so we can confirm your booking.', 'restaurant-reservations' ),
622
+ );
623
+ }
624
+
625
+ // Table
626
+ $table = empty( $_POST['rtb-table'] ) ? array() : explode( ',', sanitize_text_field( $_POST['rtb-table'] ) );
627
+ $this->table = is_array( $table ) ? array_map( 'sanitize_text_field', $table ) : array();
628
+
629
+ $table_required = $rtb_controller->settings->get_setting( 'require-table' );
630
+ if ( $table_required && empty( $this->table ) ) {
631
+ $this->validation_errors[] = array(
632
+ 'field' => 'table',
633
+ 'post_variable' => $this->table,
634
+ 'message' => __( 'Please select a table for your booking.', 'restaurant-reservations' ),
635
+ );
636
+ }
637
+
638
+ // check whether there is a time conflict for a particular table
639
+ $valid_table = $this->table ? $this->is_valid_table() : true;
640
+ if ( ! $valid_table ) {
641
+ $this->validation_errors[] = array(
642
+ 'field' => 'table',
643
+ 'post_variable' => $this->table,
644
+ 'message' => __( 'Please select a valid table for your booking.', 'restaurant-reservations' ),
645
+ );
646
+ }
647
+
648
+ // reCAPTCHA
649
+ if ( $rtb_controller->settings->get_setting( 'enable-captcha' ) && !is_admin() ) {
650
+ if ( ! isset($_POST['g-recaptcha-response']) ) {
651
+ $this->validation_errors[] = array(
652
+ 'field' => 'recaptcha',
653
+ 'error_msg' => 'No reCAPTCHA code',
654
+ 'message' => __( 'Please fill out the reCAPTCHA box before submitting.', 'restaurant-reservations' ),
655
+ );
656
+ }
657
+ else {
658
+ $secret_key = $rtb_controller->settings->get_setting( 'captcha-secret-key' );
659
+ $captcha = $_POST['g-recaptcha-response'];
660
+
661
+ $url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . urlencode($secret_key) . '&response=' . urlencode($captcha);
662
+ $json_response = file_get_contents( $url );
663
+ $response = json_decode( $json_response );
664
+
665
+ $reCaptcha_error = false;
666
+ if(json_last_error() != JSON_ERROR_NONE) {
667
+ $response = new stdClass();
668
+ $response->success = false;
669
+ $reCaptcha_error = true;
670
+ if(defined('WP_DEBUG') && WP_DEBUG) {
671
+ error_log('RTB reCAPTCHA error. Raw respose: '.print_r([$json_response], true));
672
+ }
673
+ }
674
+
675
+ if ( ! $response->success ) {
676
+ $message = __( 'Please fill out the reCAPTCHA box again and re-submit.', 'restaurant-reservations' );
677
+ if($reCaptcha_error) {
678
+ $message .= __( ' If you encounter reCAPTCHA error multiple times, please contact us.', 'restaurant-reservations' );
679
+ }
680
+ $this->validation_errors[] = array(
681
+ 'field' => 'recaptcha',
682
+ 'error_msg' => 'Invalid reCAPTCHA code',
683
+ 'message' => $message,
684
+ );
685
+ }
686
+ }
687
+ }
688
+
689
+ // Message
690
+ $this->message = empty( $_POST['rtb-message'] ) ? '' : sanitize_text_field( nl2br( $_POST['rtb-message'] ) );
691
+
692
+ // Post Status (define a default post status if none passed)
693
+ $this->determine_status();
694
+
695
+ // Consent
696
+ $require_consent = $rtb_controller->settings->get_setting( 'require-consent' );
697
+ $consent_statement = $rtb_controller->settings->get_setting( 'consent-statement' );
698
+ if ( $require_consent && $consent_statement ) {
699
+ // Don't change consent status once initial consent has been collected
700
+ if ( empty( $this->consent_acquired ) ) {
701
+ $this->consent_acquired = !empty( $_POST['rtb-consent-statement'] );
702
+ }
703
+ }
704
+
705
+ // Check if any required fields are empty
706
+ $required_fields = $rtb_controller->settings->get_required_fields();
707
+ foreach( $required_fields as $slug => $field ) {
708
+ if ( !$this->field_has_error( $slug ) && $this->is_field_empty( $slug ) ) {
709
+ $this->validation_errors[] = array(
710
+ 'field' => $slug,
711
+ 'post_variable' => '',
712
+ 'message' => __( 'Please complete this field to request a booking.', 'restaurant-reservations' ),
713
+ );
714
+ }
715
+ }
716
+
717
+ // Check if the email or IP is banned
718
+ if ( !current_user_can( 'manage_bookings' ) ) {
719
+ $ip = $_SERVER['REMOTE_ADDR'];
720
+ if ( !$this->is_valid_ip( $ip ) || !$this->is_valid_email( $this->email ) ) {
721
+ $this->validation_errors[] = array(
722
+ 'field' => 'date',
723
+ 'post_variable' => $ip,
724
+ 'message' => __( 'Your booking has been rejected. Please call us if you would like to make a booking.', 'restaurant-reservations' ),
725
+ );
726
+ } elseif ( empty( $this->ip ) and ! $rtb_controller->settings->get_setting( 'disable-ip-capture' ) ) {
727
+ $this->ip = sanitize_text_field( $ip );
728
+ }
729
+ } elseif ( empty( $this->ip ) and ! $rtb_controller->settings->get_setting( 'disable-ip-capture' ) ) {
730
+ $this->ip = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
731
+ }
732
+
733
+ // Check to make sure that the maximum number of reservations has not already been made
734
+ if ( ! $this->is_under_max_reservations() ){
735
+ $this->validation_errors[] = array(
736
+ 'field' => 'time',
737
+ 'error_msg' => 'maximum reservations exceeded',
738
+ 'message' => __( 'The maximum number of reservations for that timeslot has been reached. Please select a different timeslot.', 'restaurant-reservations' ),
739
+ );
740
+ }
741
+
742
+ // Check to make sure that the maximum number of seats has not already been made
743
+ if ( ! $this->is_under_max_seats() ){
744
+ $this->validation_errors[] = array(
745
+ 'field' => 'time',
746
+ 'error_msg' => 'maximum seats exceeded',
747
+ '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' ),
748
+ );
749
+ }
750
+
751
+ // Check if there is a booking already made with the exact same information, to prevent double bookings on refresh
752
+ if ( (!$by_admin || $by_admin && $action !== 'update') && $this->is_duplicate_booking() ) {
753
+ $this->validation_errors[] = array(
754
+ 'field' => 'date',
755
+ 'error_msg' => 'duplicate booking',
756
+ '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' ),
757
+ );
758
+ }
759
+
760
+ do_action( 'rtb_validate_booking_submission', $this );
761
+
762
+ }
763
+
764
+ /**
765
+ * Check if submission is valid
766
+ *
767
+ * @since 0.0.1
768
+ */
769
+ public function is_valid_submission() {
770
+
771
+ if ( !count( $this->validation_errors ) ) {
772
+ return true;
773
+ }
774
+
775
+ return false;
776
+ }
777
+
778
+ /**
779
+ * Check if a field already has an error attached to it
780
+ *
781
+ * @field string Field slug
782
+ * @since 1.3
783
+ */
784
+ public function field_has_error( $field_slug ) {
785
+
786
+ foreach( $this->validation_errors as $error ) {
787
+ if ( $error['field'] == $field_slug ) {
788
+ return true;
789
+ }
790
+ }
791
+
792
+ return false;
793
+ }
794
+
795
+ /**
796
+ * Check if a field is missing
797
+ *
798
+ * Checks for empty strings and arrays, but accepts '0'
799
+ * @since 0.1
800
+ */
801
+ public function is_field_empty( $slug ) {
802
+
803
+ $field_key = 'rtb-' . $slug;
804
+
805
+ if (
806
+ ! isset( $_POST[ $field_key ] )
807
+ || ( is_string( $_POST[ $field_key ] ) && trim( $_POST[ $field_key ] ) == '' )
808
+ || ( is_array( $_POST[ $field_key ] ) && empty( $_POST[ $field_key ] ) )
809
+ )
810
+ {
811
+ return true;
812
+ }
813
+
814
+ return false;
815
+ }
816
+
817
+ /**
818
+ * Check if an IP address has been banned
819
+ *
820
+ * @param string $ip
821
+ * @return bool
822
+ * @since 1.7
823
+ */
824
+ public function is_valid_ip( $ip = null ) {
825
+
826
+ if ( is_null( $ip ) ) {
827
+ $ip = isset( $this->ip ) ? $this->ip : null;
828
+ if ( is_null( $ip ) ) {
829
+ return false;
830
+ }
831
+ }
832
+
833
+ global $rtb_controller;
834
+
835
+ $banned_ips = array_filter( explode( "\n", $rtb_controller->settings->get_setting( 'ban-ips' ) ) );
836
+
837
+ foreach( $banned_ips as $banned_ip ) {
838
+ if ( $ip == trim( $banned_ip ) ) {
839
+ return false;
840
+ }
841
+ }
842
+
843
+ return true;
844
+ }
845
+
846
+ /**
847
+ * Check if an email address has been banned
848
+ *
849
+ * @param string $email
850
+ * @return bool
851
+ * @since 1.7
852
+ */
853
+ public function is_valid_email( $email = null ) {
854
+
855
+ if ( is_null( $email ) ) {
856
+ $email = isset( $this->email ) ? $this->email : null;
857
+ if ( is_null( $email ) ) {
858
+ return false;
859
+ }
860
+ }
861
+
862
+ global $rtb_controller;
863
+
864
+ $banned_emails = array_filter( explode( "\n", $rtb_controller->settings->get_setting( 'ban-emails' ) ) );
865
+
866
+ foreach( $banned_emails as $banned_email ) {
867
+ if ( $email == trim( $banned_email ) ) {
868
+ return false;
869
+ }
870
+ }
871
+
872
+ return true;
873
+ }
874
+
875
+ /**
876
+ * Check if a table(s) is valid (not already taken during a specific timeslot)
877
+ *
878
+ * @return bool
879
+ * @since 2.1.7
880
+ */
881
+ public function is_valid_table() {
882
+ global $rtb_controller;
883
+
884
+ if ( ! $this->table or ! is_array( $this->table ) ) { return false; }
885
+
886
+ $valid_tables = rtb_get_valid_tables( $this->date );
887
+
888
+ if ( isset( $this->ID ) ) {
889
+
890
+ $post_meta = get_post_meta( $this->ID, 'rtb', true );
891
+
892
+ if ( isset( $post_meta['table'] ) and is_array( $post_meta['table'] ) ) { $valid_tables = array_merge( $valid_tables, $post_meta['table'] ); }
893
+ }
894
+
895
+ return $this->table == array_intersect( $this->table, $valid_tables );
896
+ }
897
+
898
+ /**
899
+ * Check if this booking would put the restaurant over the maximum, if set
900
+ *
901
+ * @return bool
902
+ * @since 2.1.20
903
+ */
904
+ public function is_under_max_reservations() {
905
+ global $rtb_controller;
906
+
907
+ $location = ( ! empty( $this->location ) and term_exists( $this->location ) ) ? get_term( $this->location ) : false;
908
+ $location_slug = ! empty( $location ) ? $location->slug : false;
909
+
910
+ $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $location_slug );
911
+
912
+ if ( ! $max_reservations_enabled ) { return true; }
913
+
914
+ $max_reservations = (int) $rtb_controller->settings->get_setting( 'rtb-max-tables-count', $location_slug );
915
+
916
+ if ( $max_reservations == 'undefined' or ! $max_reservations ) { return true; }
917
+
918
+ $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
919
+
920
+ $tmp = ( new DateTime( $this->date, wp_timezone() ) )->format( 'U' );
921
+ $after_time = $tmp - $dining_block_seconds;
922
+ $before_time = $tmp + $dining_block_seconds;
923
+
924
+ $args = array(
925
+ 'posts_per_page' => -1,
926
+ 'post_status' => array( 'pending', 'payment_pending', 'confirmed', 'arrived' ),
927
+ 'date_query' => array(
928
+ 'before' => date( 'c', $before_time ),
929
+ 'after' => date( 'c', $after_time )
930
+ )
931
+ );
932
+
933
+ // If there are multiple locations, a location is selected, and
934
+ // max seats has been enabled for this specific location
935
+ if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-tables-count', $location_slug ) ) {
936
+
937
+ $tax_query = array(
938
+ array(
939
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
940
+ 'field' => 'term_id',
941
+ 'terms' => $location->term_id
942
+ )
943
+ );
944
+
945
+ $args['tax_query'] = $tax_query;
946
+ }
947
+
948
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
949
+ $query = new rtbQuery( $args );
950
+ $query->prepare_args();
951
+
952
+ $tmzn = wp_timezone();
953
+
954
+ $times = array();
955
+ foreach ( $query->get_bookings() as $booking ) {
956
+
957
+ if ( isset( $this->ID ) and $booking->ID == $this->ID ) { continue; }
958
+
959
+ $times[] = ( new DateTime( $booking->date, $tmzn ) )->format( 'U' );
960
+ }
961
+
962
+ sort( $times );
963
+
964
+ $current_times = array();
965
+ foreach ( $times as $time ) {
966
+
967
+ $current_times[] = $time;
968
+
969
+ if ( reset( $current_times ) < ( $time - $dining_block_seconds ) ) { array_shift( $current_times ); }
970
+
971
+ // Check if we go above the max confirmation number
972
+ if ( sizeOf( $current_times ) + 1 > $max_reservations ) { return false; }
973
+ }
974
+
975
+ return true;
976
+ }
977
+
978
+ /**
979
+ * Check if this booking would put the restaurant over the maximum number of people, if set
980
+ *
981
+ * @return bool
982
+ * @since 2.1.20
983
+ */
984
+ public function is_under_max_seats() {
985
+ global $rtb_controller;
986
+
987
+ $location = ( ! empty( $this->location ) and term_exists( $this->location ) ) ? get_term( $this->location ) : false;
988
+ $location_slug = ! empty( $location ) ? $location->slug : false;
989
+
990
+ $max_reservations_enabled = $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $location_slug );
991
+
992
+ if ( ! $max_reservations_enabled ) { return true; }
993
+
994
+ $max_seats = (int) $rtb_controller->settings->get_setting( 'rtb-max-people-count', $location_slug );
995
+
996
+ if ( $max_seats == 'undefined' or ! $max_seats ) { return true; }
997
+ if ( $this->party > $max_seats ) { return false; }
998
+
999
+ $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
1000
+
1001
+ $tmp = ( new DateTime( $this->date, wp_timezone() ) )->format( 'U' );
1002
+ $after_time = $tmp - $dining_block_seconds;
1003
+ $before_time = $tmp + $dining_block_seconds;
1004
+
1005
+ $args = array(
1006
+ 'posts_per_page' => -1,
1007
+ 'post_status' => array( 'pending', 'payment_pending', 'confirmed', 'arrived' ),
1008
+ 'date_query' => array(
1009
+ 'before' => date( 'c', $before_time ),
1010
+ 'after' => date( 'c', $after_time )
1011
+ )
1012
+ );
1013
+
1014
+ // If there are multiple locations, a location is selected, and
1015
+ // max seats has been enabled for this specific location
1016
+ if ( ! empty( $location_slug ) and $rtb_controller->settings->is_location_setting_enabled( 'rtb-max-people-count', $location_slug ) ) {
1017
+
1018
+ $tax_query = array(
1019
+ array(
1020
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
1021
+ 'field' => 'term_id',
1022
+ 'terms' => $location->term_id
1023
+ )
1024
+ );
1025
+
1026
+ $args['tax_query'] = $tax_query;
1027
+ }
1028
+
1029
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1030
+ $query = new rtbQuery( $args );
1031
+
1032
+ $tmzn = wp_timezone();
1033
+ $times = array();
1034
+ foreach ( $query->get_bookings() as $booking ) {
1035
+
1036
+ if ( isset( $this->ID ) and $booking->ID == $this->ID ) { continue; }
1037
+
1038
+ $booking_time = (new DateTime( $booking->date, $tmzn ) )->format( 'U' );
1039
+ if ( isset( $times[$booking_time] ) ) { $times[$booking_time] += intval( $booking->party ); }
1040
+ else { $times[$booking_time] = (int) $booking->party; }
1041
+ }
1042
+
1043
+ ksort( $times );
1044
+
1045
+ $current_seats = array();
1046
+ foreach ( $times as $time => $seats ) {
1047
+
1048
+ $current_seats[$time] = $seats;
1049
+
1050
+ reset( $current_seats );
1051
+
1052
+ if ( key ( $current_seats ) < $time - $dining_block_seconds ) { array_shift( $current_seats ); }
1053
+
1054
+ // Check if adding the current party puts us above the max confirmation number
1055
+ if ( array_sum( $current_seats ) + $this->party > $max_seats ) { return false; }
1056
+ }
1057
+
1058
+ return true;
1059
+
1060
+ }
1061
+
1062
+ /**
1063
+ * Check if the information in a booking exactly matches another booking
1064
+ *
1065
+ * @return bool
1066
+ * @since 2.1.20
1067
+ */
1068
+ public function is_duplicate_booking() {
1069
+ global $wpdb, $rtb_controller;
1070
+
1071
+ if( 0 < count($this->validation_errors) ) {
1072
+ /**
1073
+ * Do not run this check if there is an error already
1074
+ * There could abe a moment when someminfo could be missing, which is required
1075
+ * for this qurey to function.
1076
+ */
1077
+ return null;
1078
+ }
1079
+
1080
+ $valid_status = ['confirmed', 'pending'];
1081
+
1082
+ // This is an intermediate status when payment is pending
1083
+ if ( $rtb_controller->settings->get_setting( 'require-deposit' ) ) {
1084
+ $valid_status = array_merge($valid_status, ['payment_pending']);
1085
+ }
1086
+
1087
+ $args = array_merge(
1088
+ array(
1089
+ RTB_BOOKING_POST_TYPE,
1090
+ $this->date,
1091
+ $this->name
1092
+ ),
1093
+ $valid_status
1094
+ );
1095
+
1096
+ $status_placeholder = implode( ',', array_fill( 0, count( $valid_status ), '%s' ) );
1097
+
1098
+ $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type=%s AND post_date=%s AND post_title=%s AND post_status IN ({$status_placeholder})";
1099
+
1100
+ if ( isset( $this->ID ) ) {
1101
+ $sql .= ' AND ID!=%d';
1102
+ $args[] = $this->ID;
1103
+ }
1104
+
1105
+ $booking_result = $wpdb->get_row( $wpdb->prepare( $sql, $args ) );
1106
+
1107
+ if ( $booking_result ) {
1108
+ $meta = get_post_meta( $booking_result->ID, 'rtb', true );
1109
+ $meta = is_array( $meta ) ? $meta : array();
1110
+
1111
+ if ( $this->party == $meta['party'] and $this->email == $meta['email'] and $this->phone == $meta['phone'] ) {
1112
+
1113
+ return true;
1114
+ }
1115
+ }
1116
+
1117
+ return false;
1118
+ }
1119
+
1120
+ /**
1121
+ * Should we ask for deposit based on required minimum party size?
1122
+ * @param string $value [description]
1123
+ */
1124
+ public function is_size_based_deposit_applicable() {
1125
+ global $rtb_controller;
1126
+
1127
+ if ($this->party < $rtb_controller->settings->get_setting( 'rtb-deposit-min-party-size' ) ) {
1128
+ return false;
1129
+ }
1130
+
1131
+ return true;
1132
+ }
1133
+
1134
+ /**
1135
+ * Shoudl we ask for deposit or not based on the given schedule?
1136
+ *
1137
+ * @since 2.0.0
1138
+ */
1139
+ public function is_time_based_deposit_applicable() {
1140
+ global $rtb_controller;
1141
+
1142
+ $deposit_applicable = is_array( $rtb_controller->settings->get_setting( 'rtb-deposit-schedule' ) )
1143
+ ? $rtb_controller->settings->get_setting( 'rtb-deposit-schedule' )
1144
+ : array();
1145
+
1146
+ // Get any rules which apply to this weekday
1147
+ if ( $deposit_applicable != 'undefined' ) {
1148
+
1149
+ $tmzn = wp_timezone();
1150
+ $date_object = new DateTime( $this->date, $tmzn );
1151
+
1152
+ $time = $date_object->format( 'U' );
1153
+
1154
+ $day_of_week = strtolower( $date_object->format( 'l' ) );
1155
+
1156
+ foreach ( $deposit_applicable as $applicable ) {
1157
+
1158
+ if ( $applicable['weekdays'] !== 'undefined' ) {
1159
+
1160
+ foreach ( $applicable['weekdays'] as $weekday => $value ) {
1161
+
1162
+ if ( $weekday == $day_of_week ) {
1163
+
1164
+ // applicable all day
1165
+ if ( !isset( $applicable['time'] ) || $applicable['time'] == 'undefined' ) {
1166
+
1167
+ return true;
1168
+ }
1169
+
1170
+ if ( $applicable['time']['start'] !== 'undefined' ) {
1171
+
1172
+ $applicable_start_time = ( new DateTime( $date_object->format( 'Y-m-d' ) . ' ' . $applicable['time']['start'], $tmzn ) )->format( 'U' );
1173
+ }
1174
+ else {
1175
+
1176
+ $applicable_start_time = ( new DateTime( $date_object->format( 'Y-m-d' ), $tmzn ) )->format( 'U' );
1177
+ }
1178
+
1179
+ if ( $applicable['time']['end'] !== 'undefined' ) {
1180
+
1181
+ $applicable_end_time = ( new DateTime( $date_object->format( 'Y-m-d' ) . ' ' . $applicable['time']['end'], $tmzn ) )->format( 'U' );
1182
+ }
1183
+ else {
1184
+ // End of the day
1185
+ $applicable_end_time = ( new DateTime( $date_object->format( 'Y-m-d' ) . ' 23:59:59', $tmzn ) )->format( 'U' );
1186
+ }
1187
+
1188
+ if ( $time > $applicable_start_time and $time < $applicable_end_time ) {
1189
+
1190
+ return true;
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ }
1197
+
1198
+ return false;
1199
+ }
1200
+
1201
+ /**
1202
+ * Check whether the number of reservations occurring at the same time is below the threshold
1203
+ * where reservations get automatically confirmed
1204
+ *
1205
+ * @since 2.0.0
1206
+ */
1207
+ public function under_max_confirm_reservations() {
1208
+ global $rtb_controller;
1209
+
1210
+ $max_reservations = (int) $rtb_controller->settings->get_setting( 'auto-confirm-max-reservations' );
1211
+
1212
+ if ( $max_reservations == 'undefined' or $max_reservations <= 1 ) { return false; }
1213
+
1214
+ $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
1215
+
1216
+ $tmp = (new DateTime( $this->date, wp_timezone() ) )->format( 'U' );
1217
+ $after_time = $tmp - $dining_block_seconds;
1218
+ $before_time = $tmp + $dining_block_seconds;
1219
+
1220
+ $args = array(
1221
+ 'posts_per_page' => -1,
1222
+ 'post_status' => ['confirmed', 'arrived'],
1223
+ 'date_query' => array(
1224
+ 'before' => date( 'c', $before_time ),
1225
+ 'after' => date( 'c', $after_time )
1226
+ )
1227
+ );
1228
+
1229
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1230
+ $query = new rtbQuery( $args );
1231
+ $query->prepare_args();
1232
+
1233
+ $tmzn = wp_timezone();
1234
+ $times = array();
1235
+ foreach ( $query->get_bookings() as $booking ) {
1236
+ $times[] = (new DateTime( $booking->date, $tmzn ) )->format( "U" );
1237
+ }
1238
+
1239
+ sort( $times );
1240
+
1241
+ $auto_confirm = true;
1242
+ $current_times = array();
1243
+ foreach ( $times as $time ) {
1244
+ $current_times[] = $time;
1245
+
1246
+ if ( reset( $current_times ) < ($time - $dining_block_seconds) ) { array_shift( $current_times ); }
1247
+
1248
+ // Check if we've reached 1 below the max confirmation number, since adding the current booking will put us at the threshold
1249
+ if ( sizeOf( $current_times ) + 1 >= $max_reservations ) { $auto_confirm = false; break; }
1250
+ }
1251
+
1252
+ return $auto_confirm;
1253
+ }
1254
+
1255
+ /**
1256
+ * Check whether the number of seats occurring at the same time is below the threshold
1257
+ * where reservations get automatically confirmed
1258
+ *
1259
+ * @since 2.0.0
1260
+ */
1261
+ public function under_max_confirm_seats() {
1262
+ global $rtb_controller;
1263
+
1264
+ $max_seats = (int) $rtb_controller->settings->get_setting( 'auto-confirm-max-seats' );
1265
+
1266
+ if ( $max_seats == 'undefined' or $max_seats < 2 or $this->party >= $max_seats ) { return false; }
1267
+
1268
+ $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
1269
+
1270
+ $tmp = (new DateTime( $this->date, wp_timezone() ) )->format( 'U' );
1271
+ $after_time = $tmp - $dining_block_seconds;
1272
+ $before_time = $tmp + $dining_block_seconds;
1273
+
1274
+ $args = array(
1275
+ 'posts_per_page' => -1,
1276
+ 'post_status' => ['confirmed', 'arrived'],
1277
+ 'date_query' => array(
1278
+ 'before' => date( 'c', $before_time ),
1279
+ 'after' => date( 'c', $after_time )
1280
+ )
1281
+ );
1282
+
1283
+ require_once( RTB_PLUGIN_DIR . '/includes/Query.class.php' );
1284
+ $query = new rtbQuery( $args );
1285
+
1286
+ $tmzn = wp_timezone();
1287
+ $times = array();
1288
+ foreach ( $query->get_bookings() as $booking ) {
1289
+ $booking_time = ( new DateTime( $booking->date, $tmzn ) )->format( 'U' );
1290
+ if ( isset( $times[$booking_time] ) ) { $times[$booking_time] += $booking->party; }
1291
+ else { $times[$booking_time] = $booking->party; }
1292
+ }
1293
+
1294
+ ksort( $times );
1295
+
1296
+ $auto_confirm = true;
1297
+ $current_seats = array();
1298
+ foreach ( $times as $time => $seats ) {
1299
+ $current_seats[$time] = $seats;
1300
+
1301
+ reset( $current_seats );
1302
+
1303
+ if ( key ( $current_seats ) < $time - $dining_block_seconds ) { array_shift( $current_seats ); }
1304
+
1305
+ // Check if adding the current party puts us at or above the max confirmation number
1306
+ if ( array_sum( $current_seats ) + $this->party >= $max_seats ) { $auto_confirm = false; break; }
1307
+ }
1308
+
1309
+ return $auto_confirm;
1310
+ }
1311
+
1312
+ /**
1313
+ * Determine what status a booking should have
1314
+ *
1315
+ * @since 2.1.0
1316
+ */
1317
+ public function determine_status( $payment_made = false ) {
1318
+ global $rtb_controller;
1319
+
1320
+ if ( !empty( $_POST['rtb-post-status'] ) && array_key_exists( $_POST['rtb-post-status'], $rtb_controller->cpts->booking_statuses ) ) {
1321
+ $post_status = sanitize_text_field( $_POST['rtb-post-status'] );
1322
+ } elseif (
1323
+ (
1324
+ $rtb_controller->settings->get_setting( 'require-deposit' )
1325
+ && ! $payment_made
1326
+ && ! $this->is_conditional_deposit_enabled()
1327
+ )
1328
+ ||
1329
+ (
1330
+ $rtb_controller->settings->get_setting( 'require-deposit' )
1331
+ && ! $payment_made
1332
+ && $this->is_conditional_deposit_enabled()
1333
+ &&
1334
+ (
1335
+ ( $this->is_time_based_deposit_enabled() && $this->is_time_based_deposit_applicable() )
1336
+ ||
1337
+ ( $this->is_size_based_deposit_enabled() && $this->is_size_based_deposit_applicable() )
1338
+ )
1339
+ )
1340
+ ) {
1341
+ $post_status = 'payment_pending';
1342
+ } elseif ( $this->party < $rtb_controller->settings->get_setting( 'auto-confirm-max-party-size' ) ) {
1343
+ $post_status = 'confirmed';
1344
+ } elseif ($rtb_controller->settings->get_setting( 'auto-confirm-max-reservations' ) and $this->under_max_confirm_reservations() ) {
1345
+ $post_status = 'confirmed';
1346
+ } elseif ( $rtb_controller->settings->get_setting( 'auto-confirm-max-seats' ) and $this->under_max_confirm_seats() ) {
1347
+ $post_status = 'confirmed';
1348
+ } else {
1349
+ $post_status = 'pending';
1350
+ }
1351
+
1352
+ $this->post_status = apply_filters( 'rtb_determine_booking_status', $post_status, $this );
1353
+ }
1354
+
1355
+ /**
1356
+ * Add a log entry to the booking
1357
+ *
1358
+ * @since 1.3.1
1359
+ */
1360
+ public function add_log( $type, $title, $message = '', $datetime = null ) {
1361
+
1362
+ if ( empty( $datetime ) ) {
1363
+ $datetime = date( 'Y-m-d H:i:s');
1364
+ }
1365
+
1366
+ if ( empty( $this->logs ) ) {
1367
+ $this->logs = array();
1368
+ }
1369
+
1370
+ array_push( $this->logs, array( $type, $title, $message, $datetime ) );
1371
+ }
1372
+
1373
+ /**
1374
+ * Insert post data for a new booking or update a booking
1375
+ * @since 0.0.1
1376
+ */
1377
+ public function insert_post_data() {
1378
+
1379
+ $args = array(
1380
+ 'post_type' => RTB_BOOKING_POST_TYPE,
1381
+ 'post_title' => $this->name,
1382
+ 'post_content' => $this->message,
1383
+ 'post_date' => $this->date,
1384
+ 'post_date_gmt' => get_gmt_from_date( $this->date ), // fix for post_date_gmt not being set for some bookings
1385
+ 'post_status' => $this->post_status,
1386
+ );
1387
+
1388
+ if ( !empty( $this->ID ) ) {
1389
+ $args['ID'] = $this->ID;
1390
+ }
1391
+
1392
+ $args = apply_filters( 'rtb_insert_booking_data', $args, $this );
1393
+
1394
+ // When updating a booking, we need to update the metadata first, so that
1395
+ // notifications hooked to the status changes go out with the new metadata.
1396
+ // If we're inserting a new booking, we have to insert it before we can
1397
+ // add metadata, and the default notifications don't fire until it's all done.
1398
+ if ( !empty( $this->ID ) ) {
1399
+ $this->insert_post_meta();
1400
+ $id = wp_insert_post( $args );
1401
+ } else {
1402
+ $id = wp_insert_post( $args );
1403
+ if ( $id && !is_wp_error( $id ) ) {
1404
+ $this->ID = $id;
1405
+ $this->insert_post_meta();
1406
+ }
1407
+ }
1408
+
1409
+ return !is_wp_error( $id ) && $id !== false;
1410
+ }
1411
+
1412
+ /**
1413
+ * Insert the post metadata for a new booking or when updating a booking
1414
+ * @since 1.7.7
1415
+ */
1416
+ public function insert_post_meta() {
1417
+
1418
+ $meta = array(
1419
+ 'party' => $this->party,
1420
+ 'email' => $this->email,
1421
+ 'phone' => $this->phone,
1422
+ );
1423
+
1424
+ if ( !empty( $this->ip ) ) {
1425
+ $meta['ip'] = $this->ip;
1426
+ }
1427
+
1428
+ if ( empty( $this->date_submission ) ) {
1429
+ $meta['date_submission'] = (new DateTime( 'now', wp_timezone() ))->format( 'U' );
1430
+ } else {
1431
+ $meta['date_submission'] = $this->date_submission instanceof DateTime
1432
+ ? $this->date_submission->format('U')
1433
+ : $this->date_submission;
1434
+ }
1435
+
1436
+ if ( !empty( $this->consent_acquired ) ) {
1437
+ $meta['consent_acquired'] = $this->consent_acquired;
1438
+ }
1439
+
1440
+ if ( !empty( $this->logs ) ) {
1441
+ $meta['logs'] = $this->logs;
1442
+ }
1443
+
1444
+ if ( !empty( $this->table ) ) {
1445
+ $meta['table'] = $this->table;
1446
+ }
1447
+
1448
+ if ( !empty( $this->deposit ) ) {
1449
+ $meta['deposit'] = $this->deposit;
1450
+ }
1451
+
1452
+ if ( !empty( $this->payment_failure_message ) ) {
1453
+ $meta['payment_failure_message'] = $this->payment_failure_message;
1454
+ }
1455
+
1456
+ if ( !empty( $this->receipt_id ) ) {
1457
+ $meta['receipt_id'] = $this->receipt_id;
1458
+ }
1459
+
1460
+ if ( !empty( $this->reminder_sent ) ) {
1461
+ $meta['reminder_sent'] = $this->reminder_sent;
1462
+ }
1463
+
1464
+ if ( !empty( $this->late_arrival_sent ) ) {
1465
+ $meta['late_arrival_sent'] = $this->late_arrival_sent;
1466
+ }
1467
+
1468
+ $meta = apply_filters( 'rtb_insert_booking_metadata', $meta, $this );
1469
+
1470
+ return update_post_meta( $this->ID, 'rtb', $meta );
1471
+ }
1472
+
1473
+ public function payment_paid()
1474
+ {
1475
+ if( isset( $this->ID ) ) {
1476
+ $this->determine_status( true );
1477
+
1478
+ $this->insert_post_data();
1479
+
1480
+ do_action( 'rtb_booking_paid', $this );
1481
+ }
1482
+ }
1483
+
1484
+ public function payment_failed( $message = '' )
1485
+ {
1486
+ $this->post_status = 'payment_failed';
1487
+ $this->payment_failure_message = $message;
1488
+
1489
+ $this->insert_post_data();
1490
+
1491
+ do_action( 'rtb_booking_paid', $this );
1492
+ }
1493
+
1494
+ function is_conditional_deposit_enabled() {
1495
+ global $rtb_controller;
1496
+ return in_array( $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' ), ['time_based', 'size_based'] );
1497
+ }
1498
+
1499
+ function is_time_based_deposit_enabled() {
1500
+ global $rtb_controller;
1501
+ return 'time_based' === $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' );
1502
+ }
1503
+
1504
+ function is_size_based_deposit_enabled() {
1505
+ global $rtb_controller;
1506
+ return 'size_based' === $rtb_controller->settings->get_setting( 'rtb-deposit-applicable' );
1507
+ }
1508
+
1509
+ }
1510
+ } // 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
- elseif ( $rtb_controller->settings->get_setting( 'late-notification-format' ) == 'email' ) {
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
- elseif ( $rtb_controller->settings->get_setting( 'reminder-notification-format' ) == 'email' ) {
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
+ elseif ( $rtb_controller->settings->get_setting( 'late-notification-format' ) == 'email' ) {
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
+ elseif ( $rtb_controller->settings->get_setting( 'reminder-notification-format' ) == 'email' ) {
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,445 +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
- '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;
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/Editor.class.php CHANGED
@@ -1,845 +1,845 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'cffrtbEditor' ) ) {
5
- /**
6
- * Class which builds the form editor for Custom Fields for Restaurant
7
- * Reservations
8
- *
9
- * @since 0.1
10
- */
11
- class cffrtbEditor {
12
-
13
- /**
14
- * Hook suffix for the page
15
- *
16
- * @since 0.1
17
- */
18
- public $hook_suffix;
19
-
20
- public function __construct() {
21
-
22
- // Add the admin menu
23
- add_action( 'admin_menu', array( $this, 'add_menu_page' ), 20 );
24
-
25
- // Handle ajax requests
26
- add_action( 'wp_ajax_nopriv_cffrtb-save-field' , array( $this , 'ajax_nopriv' ) );
27
- add_action( 'wp_ajax_cffrtb-save-field', array( $this, 'ajax_save_field' ) );
28
- add_action( 'wp_ajax_nopriv_cffrtb-save-order' , array( $this , 'ajax_nopriv' ) );
29
- add_action( 'wp_ajax_cffrtb-save-order', array( $this, 'ajax_save_order' ) );
30
- add_action( 'wp_ajax_nopriv_cffrtb-load-field' , array( $this , 'ajax_nopriv' ) );
31
- add_action( 'wp_ajax_cffrtb-load-field', array( $this, 'ajax_load_field' ) );
32
- add_action( 'wp_ajax_nopriv_cffrtb-delete-field' , array( $this , 'ajax_nopriv' ) );
33
- add_action( 'wp_ajax_cffrtb-delete-field', array( $this, 'ajax_delete_field' ) );
34
- add_action( 'wp_ajax_nopriv_cffrtb-enable-field' , array( $this , 'ajax_nopriv' ) );
35
- add_action( 'wp_ajax_cffrtb-enable-field', array( $this, 'ajax_enable_field' ) );
36
- add_action( 'wp_ajax_nopriv_cffrtb-reset-all' , array( $this , 'ajax_nopriv' ) );
37
- add_action( 'wp_ajax_cffrtb-reset-all', array( $this, 'ajax_reset_all' ) );
38
-
39
- // Load "pointers" (help tooltips on initial load)
40
- require_once( RTB_PLUGIN_DIR . '/includes/custom_fields_pointers.php' );
41
- }
42
-
43
- /**
44
- * Add the booking form editor page to the admin menu
45
- *
46
- * @since 0.1
47
- */
48
- public function add_menu_page() {
49
- $this->hook_suffix = add_submenu_page(
50
- 'rtb-bookings',
51
- _x( 'Custom Fields', 'Title of the Custom Fields editor page', 'restaurant-reservations' ),
52
- _x( 'Custom Fields', 'Title of Custom Fields editor link in the admin menu', 'restaurant-reservations' ),
53
- 'manage_options',
54
- 'cffrtb-editor',
55
- array( $this, 'display_editor_page' )
56
- );
57
-
58
- // Print the error modal and enqueue assets
59
- add_action( 'load-' . $this->hook_suffix, array( $this, 'enqueue_admin_assets' ) );
60
- add_action( 'admin_footer-' . $this->hook_suffix, array( $this, 'print_modals' ) );
61
- }
62
-
63
- /**
64
- * Enqueue assets on the editor page
65
- *
66
- * @since 0.1
67
- */
68
- public function enqueue_admin_assets() {
69
- global $rtb_controller;
70
-
71
- if ( ! $rtb_controller->permissions->check_permission( 'custom_fields' ) ) { return; }
72
-
73
- // Retrieve pointers (admin tooltips)
74
- $pointers = apply_filters( 'cffrtb_pointers', array() );
75
-
76
- // Determine editor dependencies
77
- $editor_css_deps = array( 'rtb-booking-form' );
78
- $editor_js_deps = array( 'jquery-ui-sortable', 'rtb-booking-form' );
79
- if ( !empty( $pointers ) ) {
80
- $editor_css_deps[] = 'wp-pointer';
81
- $editor_js_deps[] = 'wp-pointer';
82
- }
83
-
84
- // Booking form assets
85
- $rtb_controller->register_assets();
86
- rtb_enqueue_assets();
87
-
88
- // Editor assets
89
- wp_enqueue_style( 'cffrtb-editor', RTB_PLUGIN_URL . '/assets/css/editor.css', $editor_css_deps, false );
90
- wp_enqueue_script( 'cffrtb-editor', RTB_PLUGIN_URL . '/assets/js/editor.js', $editor_js_deps, false, true );
91
-
92
- // Pass the fields array to the script
93
- $field_controller = $rtb_controller->fields;
94
- $field_controller->get_valid_field_types();
95
- wp_localize_script(
96
- 'cffrtb-editor',
97
- 'cffrtb_editor',
98
- array(
99
- 'ajax_nonce' => wp_create_nonce( 'cffrtb-editor' ),
100
- 'default_type' => key( $field_controller->valid_field_types ),
101
- 'default_subtype' => key( $field_controller->valid_field_types[ key( $field_controller->valid_field_types ) ]['subtypes'] ),
102
- 'pointers' => $pointers,
103
- 'strings' => array(
104
- 'save' => __( 'Save', 'restaurant-reservations' ),
105
- 'editor_add_field' => __( 'Add Field', 'restaurant-reservations' ),
106
- 'editor_edit_field' => __( 'Edit Field', 'restaurant-reservations' ),
107
- 'editor_save_field' => __( 'Save Field', 'restaurant-reservations' ),
108
- 'editor_add_fieldset' => __( 'Add Fieldset', 'restaurant-reservations' ),
109
- 'editor_save_fieldset' => __( 'Save Fieldset', 'restaurant-reservations' ),
110
- 'field_missing_title' => __( 'Please enter a label for this field.', 'restaurant-reservations' ),
111
- 'field_missing_options' => __( 'To add an Option field you must add at least one option below.', 'restaurant-reservations' ),
112
- 'fieldset_not_empty' => __( 'This fieldset can not be deleted until all of its attached fields are removed or assigned to another fieldset.', 'restaurant-reservations' ),
113
- 'confirm_reset_all' => __( 'Are you sure you want to reset the booking form? All of your changes and custom fields will be removed. This action can not be undone.', 'restaurant-reservations' ),
114
- 'unknown_error' => __( 'An unspecified error occurred. Please try again. If the problem persists, try logging out and logging back in.', 'restaurant-reservations' ),
115
- ),
116
- )
117
- );
118
- }
119
-
120
- /**
121
- * Display the booking form editor page
122
- *
123
- * @since 0.1
124
- */
125
- public function display_editor_page() {
126
- global $rtb_controller;
127
-
128
- $custom_fields_permission = $rtb_controller->permissions->check_permission( 'custom_fields' )
129
-
130
- ?>
131
-
132
- <div class="wrap">
133
- <h2>
134
- <?php _e( 'Custom Fields Editor', 'restaurant-reservations' ); ?>
135
- <a href="#" class="add-new-h2 add-field">Add New</a>
136
- </h2>
137
- <?php if ( $custom_fields_permission ) { ?>
138
- <div id="cffrtb-editor">
139
- <?php $this->print_booking_form_fields(); ?>
140
- </div>
141
- <?php } else { ?>
142
- <div class='rtb-premium-locked'>
143
- <a href="https://www.fivestarplugins.com/license-payment/?Selected=RTB&Quantity=1" target="_blank">Upgrade</a> to the premium version to use this feature
144
- </div>
145
- <?php } ?>
146
- </div>
147
-
148
- <?php
149
- }
150
-
151
- /**
152
- * Print the booking form fields for editing
153
- *
154
- * @since 0.0.1
155
- */
156
- public function print_booking_form_fields() {
157
- global $rtb_controller;
158
-
159
- // Retrieve the form fields
160
- $fields = $rtb_controller->settings->get_booking_form_fields();
161
-
162
- // Retrieve system fields with limited editing abilities
163
- $field_controller = $rtb_controller->fields;
164
- $field_controller->get_system_fields();
165
- ?>
166
-
167
- <div class="cffrtb-lft">
168
-
169
- <ul id="cffrtb-list" class="cffrtb-list">
170
-
171
- <?php
172
- foreach( $fields as $fieldset => $contents ) {
173
- echo $this->print_field( $fieldset, $contents, 'fieldset' );
174
- }
175
- ?>
176
-
177
- </ul>
178
-
179
- <a href="#" class="add-field">
180
- <span class="dashicons dashicons-plus-alt"></span>
181
- <?php _e( 'Add New', 'restaurant-reservations' ); ?>
182
- </a>
183
-
184
- </div>
185
- <div class="cffrtb-rgt">
186
-
187
- <div id="cffrtb-disabled" class="cffrtb-list">
188
- <h3><?php _e( 'Disabled Fields', 'restaurant-reservations' ); ?></h3>
189
-
190
- <?php
191
- $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
192
- if ( $modified ) :
193
-
194
- $list = '';
195
- foreach( $modified as $slug => $field ) {
196
-
197
- if ( !empty( $field['disabled'] ) ) {
198
-
199
- $default_field = $slug == $field['fieldset'] ? $field_controller->default_fields[ $slug ] : $field_controller->get_nested_field( $slug, $field_controller->default_fields );
200
- if ( empty( $default_field ) || !is_array( $default_field ) ) {
201
- continue;
202
- }
203
-
204
- $type = $slug == $field['fieldset'] ? 'fieldset' : 'field';
205
-
206
- $list .= $this->print_field( $slug, array_merge( $default_field, $field ), $type );
207
- }
208
- }
209
-
210
- if ( !empty( $list ) ) :
211
- ?>
212
-
213
- <ul class="fields">
214
- <?php echo $list; ?>
215
- </ul>
216
-
217
- <?php else : ?>
218
-
219
- <p class="description no-disabled-fields"><?php _e( 'You have not disabled any default fields yet.', 'restaurant-reservations' ); ?></p>
220
-
221
- <?php
222
- endif;
223
- endif;
224
- ?>
225
-
226
- <div class="reset<?php echo !empty( $modified ) ? ' is-visible' : ''; ?>">
227
- <a href="#" class="button reset-all">
228
- <?php _e( 'Revert to default', 'restaurant-reservations' ); ?>
229
- </a>
230
- <a href="#" class="learn-more">
231
- <?php _e( 'Learn more', 'restaurant-reservations' ); ?>
232
- </a>
233
- <p class="description learn-more-details">
234
- <?php _e( 'All of your changes and custom fields will be discarded if you revert to default. This is not advised unless you want to remove all of your changes and restore the default booking form.', 'restaurant-reservations' ); ?>
235
- </p>
236
- </div>
237
- </div>
238
-
239
- </div>
240
-
241
- <?php
242
- }
243
-
244
- /**
245
- * Print a single field in the fields list
246
- *
247
- * @since 0.1
248
- */
249
- public function print_field( $slug, $field, $type = 'field' ) {
250
- global $rtb_controller;
251
-
252
- $field_controller = $rtb_controller->fields;
253
- if ( empty( $field_controller->system_fields ) ) {
254
- $field_controller->get_system_fields();
255
- }
256
-
257
- $title = '';
258
- if ( $type == 'fieldset' && !empty( $field['legend'] ) ) {
259
- $title = $field['legend'];
260
- } elseif ( !empty( $field['title'] ) ) {
261
- $title = $field['title'];
262
- }
263
-
264
- ob_start();
265
- ?>
266
-
267
- <li class="<?php echo $type; echo !empty( $field['disabled'] ) ? ' disabled' : ''; ?>" data-slug="<?php echo esc_attr( $slug ); ?>"<?php echo empty( $field['ID'] ) ? '' : ' data-id="' . (int) $field['ID'] . '"'; ?>>
268
-
269
- <div class="title">
270
- <div class="view">
271
- <span class="value">
272
- <?php echo esc_html( $title ); ?>
273
- </span>
274
-
275
- <div class="controls">
276
- <?php if ( empty( $field['disabled'] ) ) : ?>
277
- <a href="#" class="label" title="<?php _e( 'Edit title', 'restaurant-reservations' ); ?>">
278
- <span class="dashicons dashicons-edit"></span>
279
- </a>
280
- <?php endif; ?>
281
- <?php if ( !empty( $field['disabled'] ) ) : ?>
282
- <a href="#" class="enable" title="<?php _e( 'Enable field', 'restaurant-reservations' ); ?>">
283
- <span class="dashicons dashicons-visibility"></span>
284
- </a>
285
- <?php elseif ( ( $type == 'field' && !in_array( $slug, $field_controller->system_fields ) ) || ( $type == 'fieldset' && !in_array( $slug, $field_controller->system_fieldsets ) ) ) : ?>
286
- <?php if ( $type == 'field' && !empty( $field['ID'] ) ) : ?>
287
- <a href="#" class="options" title="<?php _e( 'Edit field', 'restaurant-reservations' ); ?>">
288
- <span class="dashicons dashicons-admin-tools"></span>
289
- </a>
290
- <?php endif; ?>
291
- <a href="#" class="delete" title="<?php _e( 'Delete field', 'restaurant-reservations' ); ?>">
292
- <span class="dashicons dashicons-no"></span>
293
- </a>
294
- <?php endif; ?>
295
- </div>
296
- </div>
297
-
298
- <?php if ( empty( $field['disabled'] ) ) : ?>
299
- <div class="edit">
300
- <input type="text" name="title" value="<?php echo esc_html( $title ); ?>" tabindex="-1">
301
-
302
- <div class="controls">
303
- <a href="#" class="save" tabindex="-1">
304
- <?php _e( 'Save', 'restaurant-reservations' ); ?>
305
- </a>
306
- </div>
307
-
308
- <div class="status">
309
- <span class="load-spinner"></span>
310
- </div>
311
- </div>
312
- <?php endif; ?>
313
-
314
- </div>
315
-
316
- <?php if ( $type == 'fieldset' && empty( $field['disabled'] ) && empty( $field['exclude_fields'] ) ) : ?>
317
- <ul class="fields">
318
- <?php
319
- if( !empty( $field['fields'] ) ) :
320
- foreach( $field['fields'] as $field_slug => $sub_field ) :
321
- echo $this->print_field( $field_slug, $sub_field );
322
- endforeach;
323
- endif;
324
- ?>
325
- </ul>
326
- <?php endif; ?>
327
- </li>
328
- <?php
329
-
330
- return ob_get_clean();
331
- }
332
-
333
- /**
334
- * Print a label field for the editing form
335
- *
336
- * @since 0.1
337
- */
338
- public function print_label_input( $slug, $label ) {
339
- ?>
340
-
341
- <div class="label">
342
- <label for="<?php echo esc_attr( $slug ); ?>_label">
343
- <?php _e( 'Label', 'restaurant-reservations' ); ?>
344
- </label>
345
- <input type="text" name="label" id="<?php echo esc_attr( $slug ); ?>_label" value="<?php echo esc_attr( $label ); ?>">
346
- </div>
347
-
348
- <?php
349
- }
350
-
351
- /**
352
- * Print the error modal in the footer. This re-uses the error modal
353
- * markup and styling from Restaurant Reservations
354
- *
355
- * @since 0.1
356
- */
357
- public function print_modals() {
358
- global $rtb_controller;
359
-
360
- $field_controller = $rtb_controller->fields;
361
- $field_controller->get_valid_field_types();
362
-
363
- $default_type = key( $field_controller->valid_field_types );
364
- $default_subtype = key( $field_controller->valid_field_types[ $default_type ]['subtypes'] );
365
- ?>
366
- <div id="cffrtb-field-editor" class="rtb-admin-modal">
367
-
368
- <form id="cffrtb-field-editor-form" class="rtb-container">
369
- <input type="hidden" name="id" value="">
370
- <input type="hidden" name="type" value="<?php echo esc_attr( $default_type ); ?>">
371
- <input type="hidden" name="subtype" value="<?php echo esc_attr( $default_type ); ?>">
372
-
373
- <div class="title">
374
- <h2><?php _e( 'Add Field', 'restaurant-reservations' ); ?></h2>
375
- </div>
376
-
377
- <div class="type">
378
- <label>
379
- <?php _e( 'Field Type', 'restaurant-reservations' ); ?>
380
- </label>
381
-
382
- <div class="selector">
383
- <ul class="types">
384
- <?php foreach( $field_controller->valid_field_types as $slug => $type ) : ?>
385
- <li>
386
- <a href="#" class="<?php echo esc_attr( $slug ); if ( $default_type == $slug ) : ?> current<?php endif; ?>" data-type="<?php echo esc_attr( $slug ); ?>">
387
- <?php echo $type['title']; ?>
388
- </a>
389
- </li>
390
- <?php endforeach; ?>
391
- </ul>
392
-
393
- <?php foreach( $field_controller->valid_field_types as $slug => $type ) : ?>
394
- <ul class="subtypes <?php echo $slug; if ( $default_type == $slug ) : ?> current<?php endif; ?>">
395
- <?php foreach( $type['subtypes'] as $sub_slug => $subtype ) : ?>
396
- <li>
397
- <a href="#" class="<?php echo esc_attr( $sub_slug ); if ( $default_type == $sub_slug ) : ?> current<?php endif; ?>" data-subtype="<?php echo esc_attr( $sub_slug ); ?>">
398
- <?php echo $subtype['title']; ?>
399
- </a>
400
- </li>
401
- <?php endforeach; ?>
402
- </ul>
403
- <?php endforeach; ?>
404
- </div>
405
- </div>
406
- <div class="settings">
407
-
408
- <div class="item">
409
- <label for="title">
410
- <?php _e( 'Label', 'restaurant-reservations' ); ?>
411
- </label>
412
- <input type="text" name="title" id="title">
413
- </div>
414
-
415
- <div class="settings-panel options">
416
- <div class="item">
417
- <label for="options-options">
418
- <?php _e( 'Options', 'restaurant-reservations' ); ?>
419
- </label>
420
- <div class="add">
421
- <input type="text" name="options" id="options-options">
422
- <a href="#">
423
- <span class="dashicons dashicons-plus-alt"></span>
424
- <?php _e( 'Add', 'restaurant-reservations' ); ?>
425
- </a>
426
- </div>
427
- <ul class="options">
428
- </ul>
429
- </div>
430
- </div>
431
-
432
- <?php do_action( 'cffrtb_field_editor_settings_panel' ); ?>
433
-
434
- </div>
435
-
436
- <div class="required">
437
- <label>
438
- <input type="checkbox" name="required" value="1">
439
- <?php _e( 'Required', 'restaurant-reservations' ); ?>
440
- </label>
441
- </div>
442
-
443
- <div class="actions">
444
- <a href="#" class="button-primary save">
445
- <?php _e( 'Add Field', 'restaurant-reservations' ); ?>
446
- </a>
447
- <a href="#" class="button cancel">
448
- <?php _e( 'Cancel', 'restaurant-reservations' ); ?>
449
- </a>
450
- <div class="status">
451
- <span class="load-spinner"></span>
452
- </div>
453
- </div>
454
- </form>
455
- </div>
456
-
457
- <div id="cffrtb-field-editor-option" class="rtb-admin-modal">
458
- <div class="rtb-container">
459
- <div class="option">
460
- <a href="#" class="field button-primary">
461
- <?php _e( 'Add Field', 'restaurant-reservations' ); ?>
462
- </a>
463
- <p class="description">
464
- <?php _e( 'Fields prompt the user to enter information or select from options.' ); ?>
465
- </p>
466
- </div>
467
- <div class="option">
468
- <a href="#" class="fieldset button">
469
- <?php _e( 'Add Fieldset', 'restaurant-reservations' ); ?>
470
- </a>
471
- <p class="description">
472
- <?php _e( 'Fieldsets group other fields under a common label.' ); ?>
473
- </p>
474
- </div>
475
- </div>
476
- </div>
477
-
478
- <div id="rtb-error-modal" class="rtb-admin-modal">
479
- <div class="rtb-error rtb-container"">
480
- <div class="rtb-error-msg"></div>
481
- <a href="#" class="button"><?php _e( 'Close', 'restaurant-reservations' ); ?></a>
482
- </div>
483
- </div>
484
- <?php
485
- }
486
-
487
- /**
488
- * Handle ajax requests from logged out users
489
- *
490
- * @since 0.1
491
- */
492
- public function ajax_nopriv() {
493
-
494
- wp_send_json_error(
495
- array(
496
- 'error' => 'loggedout',
497
- 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( admin_url( 'admin.php?page=cffrtb-editor' ) ) . '">', '</a>' ),
498
- )
499
- );
500
- }
501
-
502
- /**
503
- * Handle ajax request to save a field
504
- *
505
- * @since 0.1
506
- */
507
- public function ajax_save_field() {
508
- global $rtb_controller;
509
-
510
- // Authenticate request
511
- if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
512
- $this->nopriv_ajax();
513
- }
514
-
515
- // Missing data
516
- if ( empty( $_POST['field'] ) ) {
517
- wp_send_json_error(
518
- array(
519
- 'error' => 'no_field_data',
520
- 'msg' => __( 'No field data was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
521
- )
522
- );
523
- }
524
-
525
- // Missing request specification
526
- if ( empty( $_POST['request'] ) ) {
527
- wp_send_json_error(
528
- array(
529
- 'error' => 'no_request',
530
- 'msg' => __( 'Internal data that was supposed to be passed with your request was not received.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
531
- )
532
- );
533
- }
534
-
535
- $field = new cffrtbField( $_POST['field'] );
536
-
537
- // Just the label
538
- if ( $_POST['request'] == 'save_label' ) {
539
- $this->send_ajax_response( $field->save_label() );
540
-
541
- // The whole field
542
- } elseif ( $_POST['request'] == 'save_field' ) {
543
- $this->send_ajax_response( $field->save_field() );
544
- }
545
-
546
- wp_send_json_error(
547
- array(
548
- 'error' => 'unknown',
549
- 'msg' => __( 'An unknown error has occurred.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
550
- )
551
- );
552
- }
553
-
554
- /**
555
- * Handle ajax request to save the order of fields and fieldsets
556
- *
557
- * @since 0.1
558
- */
559
- public function ajax_save_order() {
560
- global $rtb_controller;
561
-
562
- // Authenticate request
563
- if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
564
- $this->nopriv_ajax();
565
- }
566
-
567
- // Missing data
568
- if ( empty( $_POST['order'] ) ) {
569
- wp_send_json_error(
570
- array(
571
- 'error' => 'no_fields_data',
572
- 'msg' => __( 'No fields data was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
573
- )
574
- );
575
- }
576
-
577
- $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
578
- $orig_modified = $modified;
579
-
580
- $custom_fields_error = array();
581
- $has_custom_fields = false;
582
- foreach( $_POST['order'] as $field ) {
583
-
584
- if ( !isset( $field['order'] ) || empty( $field['fieldset'] ) ) {
585
- continue; // @todo this indicates some kind of data error, though
586
- }
587
-
588
- // Default fields
589
- if ( empty( $field['ID'] ) ) {
590
-
591
- if ( empty( $modified[ $field['slug'] ] ) ) {
592
- $modified[ $field['slug'] ] = array();
593
- }
594
-
595
- // Skip if the order isn't changing
596
- if ( isset( $modified[ $field['slug'] ]['order'] ) && $modified[ $field['slug'] ]['order'] == $field['order'] && isset( $modified[ $field['slug'] ]['fieldset'] ) && $modified[ $field['slug'] ]['fieldset'] == $field['fieldset'] ) {
597
- continue;
598
- }
599
-
600
- $modified[ $field['slug'] ]['order'] = (int) $field['order'];
601
- $modified[ $field['slug'] ]['fieldset'] = sanitize_key( $field['fieldset'] );
602
-
603
- // Custom fields
604
- } else {
605
-
606
- $custom_field = new cffrtbField( $field );
607
- $result = $custom_field->save_field();
608
-
609
- if ( !$result[0] ) {
610
- array_push( $custom_fields_error, $result[1] );
611
- }
612
-
613
- $has_custom_fields = true;
614
- }
615
- }
616
-
617
- update_option( $rtb_controller->custom_fields->modified_option_key, $modified );
618
-
619
- if ( !$has_custom_fields || ( $has_custom_fields && empty( $custom_fields_error ) ) ) {
620
- wp_send_json_success();
621
-
622
- } else {
623
- wp_send_json_error(
624
- array(
625
- 'error' => 'save_order_failed',
626
- 'msg' => __( 'An error occurred while saving the new field order. Please try again.', 'restaurant-reservations' ),
627
- 'fields' => sanitize_text_field( $_POST['order'] ),
628
- 'custom_fields_error' => $custom_fields_error,
629
- )
630
- );
631
- }
632
-
633
- wp_send_json_error(
634
- array(
635
- 'error' => 'unknown',
636
- 'msg' => __( 'An unknown error has occurred.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
637
- )
638
- );
639
- }
640
-
641
- /**
642
- * Handle ajax request to load a field
643
- *
644
- * @since 0.1
645
- */
646
- public function ajax_load_field() {
647
- global $rtb_controller;
648
-
649
- // Authenticate request
650
- if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
651
- $this->nopriv_ajax();
652
- }
653
-
654
- // Missing data
655
- if ( empty( $_POST['ID'] ) ) {
656
- wp_send_json_error(
657
- array(
658
- 'error' => 'no_id',
659
- 'msg' => __( 'The requested field could not be loaded because no ID was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
660
- )
661
- );
662
- }
663
-
664
- $field = new cffrtbField( array( 'ID' => (int) $_POST['ID'] ) );
665
-
666
- wp_send_json_success(
667
- array(
668
- 'field' => $field,
669
- )
670
- );
671
- }
672
-
673
- /**
674
- * Handle ajax request to delete or disable a field
675
- *
676
- * @since 0.1
677
- */
678
- public function ajax_delete_field() {
679
- global $rtb_controller;
680
-
681
- // Authenticate request
682
- if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
683
- $this->nopriv_ajax();
684
- }
685
-
686
- // Missing data
687
- if ( empty( $_POST['ID'] ) && ( empty( $_POST['slug'] ) || empty( $_POST['fieldset'] ) ) ) {
688
- wp_send_json_error(
689
- array(
690
- 'error' => 'no_id',
691
- 'msg' => __( 'The requested field could not be deleted because no ID or slug was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
692
- )
693
- );
694
- }
695
-
696
- if ( !empty( $_POST['ID'] ) ) {
697
- $post = wp_delete_post( (int) $_POST['ID'] );
698
-
699
- if ( !$post ) {
700
- wp_send_json_error(
701
- array(
702
- 'error' => 'delete_field_failed',
703
- 'msg' => __( 'An error occurred while deleting this field.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
704
- )
705
- );
706
- }
707
-
708
- wp_send_json_success();
709
-
710
- } else {
711
- $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
712
- $slug = sanitize_key( $_POST['slug'] );
713
- $fieldset = sanitize_key( $_POST['fieldset'] );
714
- $modified[ $slug ]['disabled'] = 1;
715
- $modified[ $slug ]['fieldset'] = $fieldset;
716
- update_option( $rtb_controller->custom_fields->modified_option_key, $modified );
717
-
718
- global $rtb_controller;
719
- $fields = $rtb_controller->settings->get_booking_form_fields();
720
- $field = $slug == $fieldset ? $rtb_controller->fields->default_fields[ $slug ] : $rtb_controller->fields->get_nested_field( $slug, $rtb_controller->fields->default_fields );
721
- $field['disabled'] = 1;
722
-
723
- $type = $slug == $fieldset ? 'fieldset' : 'field';
724
-
725
- wp_send_json_success(
726
- array(
727
- 'field' => $this->print_field( $slug, $field, $type ),
728
- )
729
- );
730
- }
731
- }
732
-
733
- /**
734
- * Handle an ajax request to enable a field that has been disabled
735
- *
736
- * @since 0.1
737
- */
738
- public function ajax_enable_field() {
739
- global $rtb_controller;
740
-
741
- // Authenticate request
742
- if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
743
- $this->nopriv_ajax();
744
- }
745
-
746
- // Missing data
747
- if ( empty( $_POST['slug'] ) ) {
748
- wp_send_json_error(
749
- array(
750
- 'error' => 'no_id',
751
- 'msg' => __( 'The requested field could not be enabled because no identifying slug was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
752
- )
753
- );
754
- }
755
-
756
- $slug = sanitize_key( $_POST['slug'] );
757
- $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
758
-
759
- if ( empty( $modified ) || !is_array( $modified ) || !array_key_exists( $slug, $modified ) ) {
760
- wp_send_json_error(
761
- array(
762
- 'error' => 'field_not_found',
763
- 'msg' => __( 'The requested field could not be enabled because it did not appear to be disabled.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
764
- )
765
- );
766
- }
767
-
768
- if ( isset( $modified[ $slug ]['disabled'] ) ) {
769
- unset( $modified[ $slug ]['disabled'] );
770
- update_option( $rtb_controller->custom_fields->modified_option_key, $modified );
771
- }
772
-
773
- global $rtb_controller;
774
- $fields = $rtb_controller->settings->get_booking_form_fields();
775
- $field = $_POST['type'] == 'fieldset' ? $rtb_controller->fields->default_fields[ $slug ] : $rtb_controller->fields->get_nested_field( $slug, $fields );
776
-
777
- $type = $_POST['type'] == 'fieldset' ? 'fieldset' : 'field';
778
-
779
- if ( $type == 'fieldset' ) {
780
- $field['exclude_fields'] = true;
781
- }
782
-
783
- wp_send_json_success(
784
- array(
785
- 'field' => $this->print_field( $slug, $field, $type )
786
- )
787
- );
788
- }
789
-
790
- /**
791
- * Handle an ajax request to reset all changes and delete custom
792
- * fields
793
- *
794
- * @since 0.1
795
- */
796
- public function ajax_reset_all( $authorized = false) {
797
- global $rtb_controller;
798
-
799
- // Authenticate request
800
- if ( ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) and ! $authorized ) {
801
- $this->nopriv_ajax();
802
- }
803
-
804
- // Delete modifications to default fields
805
- delete_option( $rtb_controller->custom_fields->modified_option_key );
806
-
807
- // Delete all custom posts
808
- $posts = new WP_Query(
809
- array(
810
- 'post_type' => 'cffrtb_field',
811
- 'posts_per_page' => 1000, // Very large upper limit
812
- )
813
- );
814
-
815
- while( $posts->have_posts() ) {
816
- $posts->the_post();
817
- wp_delete_post( get_the_ID() );
818
- }
819
-
820
- wp_send_json_success();
821
- }
822
-
823
- /**
824
- * Send an ajax response
825
- *
826
- * This is a generic response sender which will search for an error
827
- * in the response and make the appropriate wp_send_json_*() call.
828
- *
829
- * @since 0.1
830
- */
831
- public function send_ajax_response( $response = array() ) {
832
-
833
- $response[1] = empty( $response[1] ) ? '' : $response[1];
834
-
835
- if ( !empty( $response[0] ) ) {
836
- wp_send_json_success( $response[1] );
837
-
838
- } else {
839
- wp_send_json_error( $response[1] );
840
- }
841
-
842
- }
843
-
844
- }
845
- } // endif;
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'cffrtbEditor' ) ) {
5
+ /**
6
+ * Class which builds the form editor for Custom Fields for Restaurant
7
+ * Reservations
8
+ *
9
+ * @since 0.1
10
+ */
11
+ class cffrtbEditor {
12
+
13
+ /**
14
+ * Hook suffix for the page
15
+ *
16
+ * @since 0.1
17
+ */
18
+ public $hook_suffix;
19
+
20
+ public function __construct() {
21
+
22
+ // Add the admin menu
23
+ add_action( 'admin_menu', array( $this, 'add_menu_page' ), 20 );
24
+
25
+ // Handle ajax requests
26
+ add_action( 'wp_ajax_nopriv_cffrtb-save-field' , array( $this , 'ajax_nopriv' ) );
27
+ add_action( 'wp_ajax_cffrtb-save-field', array( $this, 'ajax_save_field' ) );
28
+ add_action( 'wp_ajax_nopriv_cffrtb-save-order' , array( $this , 'ajax_nopriv' ) );
29
+ add_action( 'wp_ajax_cffrtb-save-order', array( $this, 'ajax_save_order' ) );
30
+ add_action( 'wp_ajax_nopriv_cffrtb-load-field' , array( $this , 'ajax_nopriv' ) );
31
+ add_action( 'wp_ajax_cffrtb-load-field', array( $this, 'ajax_load_field' ) );
32
+ add_action( 'wp_ajax_nopriv_cffrtb-delete-field' , array( $this , 'ajax_nopriv' ) );
33
+ add_action( 'wp_ajax_cffrtb-delete-field', array( $this, 'ajax_delete_field' ) );
34
+ add_action( 'wp_ajax_nopriv_cffrtb-enable-field' , array( $this , 'ajax_nopriv' ) );
35
+ add_action( 'wp_ajax_cffrtb-enable-field', array( $this, 'ajax_enable_field' ) );
36
+ add_action( 'wp_ajax_nopriv_cffrtb-reset-all' , array( $this , 'ajax_nopriv' ) );
37
+ add_action( 'wp_ajax_cffrtb-reset-all', array( $this, 'ajax_reset_all' ) );
38
+
39
+ // Load "pointers" (help tooltips on initial load)
40
+ require_once( RTB_PLUGIN_DIR . '/includes/custom_fields_pointers.php' );
41
+ }
42
+
43
+ /**
44
+ * Add the booking form editor page to the admin menu
45
+ *
46
+ * @since 0.1
47
+ */
48
+ public function add_menu_page() {
49
+ $this->hook_suffix = add_submenu_page(
50
+ 'rtb-bookings',
51
+ _x( 'Custom Fields', 'Title of the Custom Fields editor page', 'restaurant-reservations' ),
52
+ _x( 'Custom Fields', 'Title of Custom Fields editor link in the admin menu', 'restaurant-reservations' ),
53
+ 'manage_options',
54
+ 'cffrtb-editor',
55
+ array( $this, 'display_editor_page' )
56
+ );
57
+
58
+ // Print the error modal and enqueue assets
59
+ add_action( 'load-' . $this->hook_suffix, array( $this, 'enqueue_admin_assets' ) );
60
+ add_action( 'admin_footer-' . $this->hook_suffix, array( $this, 'print_modals' ) );
61
+ }
62
+
63
+ /**
64
+ * Enqueue assets on the editor page
65
+ *
66
+ * @since 0.1
67
+ */
68
+ public function enqueue_admin_assets() {
69
+ global $rtb_controller;
70
+
71
+ if ( ! $rtb_controller->permissions->check_permission( 'custom_fields' ) ) { return; }
72
+
73
+ // Retrieve pointers (admin tooltips)
74
+ $pointers = apply_filters( 'cffrtb_pointers', array() );
75
+
76
+ // Determine editor dependencies
77
+ $editor_css_deps = array( 'rtb-booking-form' );
78
+ $editor_js_deps = array( 'jquery-ui-sortable', 'rtb-booking-form' );
79
+ if ( !empty( $pointers ) ) {
80
+ $editor_css_deps[] = 'wp-pointer';
81
+ $editor_js_deps[] = 'wp-pointer';
82
+ }
83
+
84
+ // Booking form assets
85
+ $rtb_controller->register_assets();
86
+ rtb_enqueue_assets();
87
+
88
+ // Editor assets
89
+ wp_enqueue_style( 'cffrtb-editor', RTB_PLUGIN_URL . '/assets/css/editor.css', $editor_css_deps, false );
90
+ wp_enqueue_script( 'cffrtb-editor', RTB_PLUGIN_URL . '/assets/js/editor.js', $editor_js_deps, false, true );
91
+
92
+ // Pass the fields array to the script
93
+ $field_controller = $rtb_controller->fields;
94
+ $field_controller->get_valid_field_types();
95
+ wp_localize_script(
96
+ 'cffrtb-editor',
97
+ 'cffrtb_editor',
98
+ array(
99
+ 'ajax_nonce' => wp_create_nonce( 'cffrtb-editor' ),
100
+ 'default_type' => key( $field_controller->valid_field_types ),
101
+ 'default_subtype' => key( $field_controller->valid_field_types[ key( $field_controller->valid_field_types ) ]['subtypes'] ),
102
+ 'pointers' => $pointers,
103
+ 'strings' => array(
104
+ 'save' => __( 'Save', 'restaurant-reservations' ),
105
+ 'editor_add_field' => __( 'Add Field', 'restaurant-reservations' ),
106
+ 'editor_edit_field' => __( 'Edit Field', 'restaurant-reservations' ),
107
+ 'editor_save_field' => __( 'Save Field', 'restaurant-reservations' ),
108
+ 'editor_add_fieldset' => __( 'Add Fieldset', 'restaurant-reservations' ),
109
+ 'editor_save_fieldset' => __( 'Save Fieldset', 'restaurant-reservations' ),
110
+ 'field_missing_title' => __( 'Please enter a label for this field.', 'restaurant-reservations' ),
111
+ 'field_missing_options' => __( 'To add an Option field you must add at least one option below.', 'restaurant-reservations' ),
112
+ 'fieldset_not_empty' => __( 'This fieldset can not be deleted until all of its attached fields are removed or assigned to another fieldset.', 'restaurant-reservations' ),
113
+ 'confirm_reset_all' => __( 'Are you sure you want to reset the booking form? All of your changes and custom fields will be removed. This action can not be undone.', 'restaurant-reservations' ),
114
+ 'unknown_error' => __( 'An unspecified error occurred. Please try again. If the problem persists, try logging out and logging back in.', 'restaurant-reservations' ),
115
+ ),
116
+ )
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Display the booking form editor page
122
+ *
123
+ * @since 0.1
124
+ */
125
+ public function display_editor_page() {
126
+ global $rtb_controller;
127
+
128
+ $custom_fields_permission = $rtb_controller->permissions->check_permission( 'custom_fields' )
129
+
130
+ ?>
131
+
132
+ <div class="wrap">
133
+ <h2>
134
+ <?php _e( 'Custom Fields Editor', 'restaurant-reservations' ); ?>
135
+ <a href="#" class="add-new-h2 add-field">Add New</a>
136
+ </h2>
137
+ <?php if ( $custom_fields_permission ) { ?>
138
+ <div id="cffrtb-editor">
139
+ <?php $this->print_booking_form_fields(); ?>
140
+ </div>
141
+ <?php } else { ?>
142
+ <div class='rtb-premium-locked'>
143
+ <a href="https://www.fivestarplugins.com/license-payment/?Selected=RTB&Quantity=1" target="_blank">Upgrade</a> to the premium version to use this feature
144
+ </div>
145
+ <?php } ?>
146
+ </div>
147
+
148
+ <?php
149
+ }
150
+
151
+ /**
152
+ * Print the booking form fields for editing
153
+ *
154
+ * @since 0.0.1
155
+ */
156
+ public function print_booking_form_fields() {
157
+ global $rtb_controller;
158
+
159
+ // Retrieve the form fields
160
+ $fields = $rtb_controller->settings->get_booking_form_fields();
161
+
162
+ // Retrieve system fields with limited editing abilities
163
+ $field_controller = $rtb_controller->fields;
164
+ $field_controller->get_system_fields();
165
+ ?>
166
+
167
+ <div class="cffrtb-lft">
168
+
169
+ <ul id="cffrtb-list" class="cffrtb-list">
170
+
171
+ <?php
172
+ foreach( $fields as $fieldset => $contents ) {
173
+ echo $this->print_field( $fieldset, $contents, 'fieldset' );
174
+ }
175
+ ?>
176
+
177
+ </ul>
178
+
179
+ <a href="#" class="add-field">
180
+ <span class="dashicons dashicons-plus-alt"></span>
181
+ <?php _e( 'Add New', 'restaurant-reservations' ); ?>
182
+ </a>
183
+
184
+ </div>
185
+ <div class="cffrtb-rgt">
186
+
187
+ <div id="cffrtb-disabled" class="cffrtb-list">
188
+ <h3><?php _e( 'Disabled Fields', 'restaurant-reservations' ); ?></h3>
189
+
190
+ <?php
191
+ $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
192
+ if ( $modified ) :
193
+
194
+ $list = '';
195
+ foreach( $modified as $slug => $field ) {
196
+
197
+ if ( !empty( $field['disabled'] ) ) {
198
+
199
+ $default_field = $slug == $field['fieldset'] ? $field_controller->default_fields[ $slug ] : $field_controller->get_nested_field( $slug, $field_controller->default_fields );
200
+ if ( empty( $default_field ) || !is_array( $default_field ) ) {
201
+ continue;
202
+ }
203
+
204
+ $type = $slug == $field['fieldset'] ? 'fieldset' : 'field';
205
+
206
+ $list .= $this->print_field( $slug, array_merge( $default_field, $field ), $type );
207
+ }
208
+ }
209
+
210
+ if ( !empty( $list ) ) :
211
+ ?>
212
+
213
+ <ul class="fields">
214
+ <?php echo $list; ?>
215
+ </ul>
216
+
217
+ <?php else : ?>
218
+
219
+ <p class="description no-disabled-fields"><?php _e( 'You have not disabled any default fields yet.', 'restaurant-reservations' ); ?></p>
220
+
221
+ <?php
222
+ endif;
223
+ endif;
224
+ ?>
225
+
226
+ <div class="reset<?php echo !empty( $modified ) ? ' is-visible' : ''; ?>">
227
+ <a href="#" class="button reset-all">
228
+ <?php _e( 'Revert to default', 'restaurant-reservations' ); ?>
229
+ </a>
230
+ <a href="#" class="learn-more">
231
+ <?php _e( 'Learn more', 'restaurant-reservations' ); ?>
232
+ </a>
233
+ <p class="description learn-more-details">
234
+ <?php _e( 'All of your changes and custom fields will be discarded if you revert to default. This is not advised unless you want to remove all of your changes and restore the default booking form.', 'restaurant-reservations' ); ?>
235
+ </p>
236
+ </div>
237
+ </div>
238
+
239
+ </div>
240
+
241
+ <?php
242
+ }
243
+
244
+ /**
245
+ * Print a single field in the fields list
246
+ *
247
+ * @since 0.1
248
+ */
249
+ public function print_field( $slug, $field, $type = 'field' ) {
250
+ global $rtb_controller;
251
+
252
+ $field_controller = $rtb_controller->fields;
253
+ if ( empty( $field_controller->system_fields ) ) {
254
+ $field_controller->get_system_fields();
255
+ }
256
+
257
+ $title = '';
258
+ if ( $type == 'fieldset' && !empty( $field['legend'] ) ) {
259
+ $title = $field['legend'];
260
+ } elseif ( !empty( $field['title'] ) ) {
261
+ $title = $field['title'];
262
+ }
263
+
264
+ ob_start();
265
+ ?>
266
+
267
+ <li class="<?php echo $type; echo !empty( $field['disabled'] ) ? ' disabled' : ''; ?>" data-slug="<?php echo esc_attr( $slug ); ?>"<?php echo empty( $field['ID'] ) ? '' : ' data-id="' . (int) $field['ID'] . '"'; ?>>
268
+
269
+ <div class="title">
270
+ <div class="view">
271
+ <span class="value">
272
+ <?php echo esc_html( $title ); ?>
273
+ </span>
274
+
275
+ <div class="controls">
276
+ <?php if ( empty( $field['disabled'] ) ) : ?>
277
+ <a href="#" class="label" title="<?php _e( 'Edit title', 'restaurant-reservations' ); ?>">
278
+ <span class="dashicons dashicons-edit"></span>
279
+ </a>
280
+ <?php endif; ?>
281
+ <?php if ( !empty( $field['disabled'] ) ) : ?>
282
+ <a href="#" class="enable" title="<?php _e( 'Enable field', 'restaurant-reservations' ); ?>">
283
+ <span class="dashicons dashicons-visibility"></span>
284
+ </a>
285
+ <?php elseif ( ( $type == 'field' && !in_array( $slug, $field_controller->system_fields ) ) || ( $type == 'fieldset' && !in_array( $slug, $field_controller->system_fieldsets ) ) ) : ?>
286
+ <?php if ( $type == 'field' && !empty( $field['ID'] ) ) : ?>
287
+ <a href="#" class="options" title="<?php _e( 'Edit field', 'restaurant-reservations' ); ?>">
288
+ <span class="dashicons dashicons-admin-tools"></span>
289
+ </a>
290
+ <?php endif; ?>
291
+ <a href="#" class="delete" title="<?php _e( 'Delete field', 'restaurant-reservations' ); ?>">
292
+ <span class="dashicons dashicons-no"></span>
293
+ </a>
294
+ <?php endif; ?>
295
+ </div>
296
+ </div>
297
+
298
+ <?php if ( empty( $field['disabled'] ) ) : ?>
299
+ <div class="edit">
300
+ <input type="text" name="title" value="<?php echo esc_html( $title ); ?>" tabindex="-1">
301
+
302
+ <div class="controls">
303
+ <a href="#" class="save" tabindex="-1">
304
+ <?php _e( 'Save', 'restaurant-reservations' ); ?>
305
+ </a>
306
+ </div>
307
+
308
+ <div class="status">
309
+ <span class="load-spinner"></span>
310
+ </div>
311
+ </div>
312
+ <?php endif; ?>
313
+
314
+ </div>
315
+
316
+ <?php if ( $type == 'fieldset' && empty( $field['disabled'] ) && empty( $field['exclude_fields'] ) ) : ?>
317
+ <ul class="fields">
318
+ <?php
319
+ if( !empty( $field['fields'] ) ) :
320
+ foreach( $field['fields'] as $field_slug => $sub_field ) :
321
+ echo $this->print_field( $field_slug, $sub_field );
322
+ endforeach;
323
+ endif;
324
+ ?>
325
+ </ul>
326
+ <?php endif; ?>
327
+ </li>
328
+ <?php
329
+
330
+ return ob_get_clean();
331
+ }
332
+
333
+ /**
334
+ * Print a label field for the editing form
335
+ *
336
+ * @since 0.1
337
+ */
338
+ public function print_label_input( $slug, $label ) {
339
+ ?>
340
+
341
+ <div class="label">
342
+ <label for="<?php echo esc_attr( $slug ); ?>_label">
343
+ <?php _e( 'Label', 'restaurant-reservations' ); ?>
344
+ </label>
345
+ <input type="text" name="label" id="<?php echo esc_attr( $slug ); ?>_label" value="<?php echo esc_attr( $label ); ?>">
346
+ </div>
347
+
348
+ <?php
349
+ }
350
+
351
+ /**
352
+ * Print the error modal in the footer. This re-uses the error modal
353
+ * markup and styling from Restaurant Reservations
354
+ *
355
+ * @since 0.1
356
+ */
357
+ public function print_modals() {
358
+ global $rtb_controller;
359
+
360
+ $field_controller = $rtb_controller->fields;
361
+ $field_controller->get_valid_field_types();
362
+
363
+ $default_type = key( $field_controller->valid_field_types );
364
+ $default_subtype = key( $field_controller->valid_field_types[ $default_type ]['subtypes'] );
365
+ ?>
366
+ <div id="cffrtb-field-editor" class="rtb-admin-modal">
367
+
368
+ <form id="cffrtb-field-editor-form" class="rtb-container">
369
+ <input type="hidden" name="id" value="">
370
+ <input type="hidden" name="type" value="<?php echo esc_attr( $default_type ); ?>">
371
+ <input type="hidden" name="subtype" value="<?php echo esc_attr( $default_type ); ?>">
372
+
373
+ <div class="title">
374
+ <h2><?php _e( 'Add Field', 'restaurant-reservations' ); ?></h2>
375
+ </div>
376
+
377
+ <div class="type">
378
+ <label>
379
+ <?php _e( 'Field Type', 'restaurant-reservations' ); ?>
380
+ </label>
381
+
382
+ <div class="selector">
383
+ <ul class="types">
384
+ <?php foreach( $field_controller->valid_field_types as $slug => $type ) : ?>
385
+ <li>
386
+ <a href="#" class="<?php echo esc_attr( $slug ); if ( $default_type == $slug ) : ?> current<?php endif; ?>" data-type="<?php echo esc_attr( $slug ); ?>">
387
+ <?php echo $type['title']; ?>
388
+ </a>
389
+ </li>
390
+ <?php endforeach; ?>
391
+ </ul>
392
+
393
+ <?php foreach( $field_controller->valid_field_types as $slug => $type ) : ?>
394
+ <ul class="subtypes <?php echo $slug; if ( $default_type == $slug ) : ?> current<?php endif; ?>">
395
+ <?php foreach( $type['subtypes'] as $sub_slug => $subtype ) : ?>
396
+ <li>
397
+ <a href="#" class="<?php echo esc_attr( $sub_slug ); if ( $default_type == $sub_slug ) : ?> current<?php endif; ?>" data-subtype="<?php echo esc_attr( $sub_slug ); ?>">
398
+ <?php echo $subtype['title']; ?>
399
+ </a>
400
+ </li>
401
+ <?php endforeach; ?>
402
+ </ul>
403
+ <?php endforeach; ?>
404
+ </div>
405
+ </div>
406
+ <div class="settings">
407
+
408
+ <div class="item">
409
+ <label for="title">
410
+ <?php _e( 'Label', 'restaurant-reservations' ); ?>
411
+ </label>
412
+ <input type="text" name="title" id="title">
413
+ </div>
414
+
415
+ <div class="settings-panel options">
416
+ <div class="item">
417
+ <label for="options-options">
418
+ <?php _e( 'Options', 'restaurant-reservations' ); ?>
419
+ </label>
420
+ <div class="add">
421
+ <input type="text" name="options" id="options-options">
422
+ <a href="#">
423
+ <span class="dashicons dashicons-plus-alt"></span>
424
+ <?php _e( 'Add', 'restaurant-reservations' ); ?>
425
+ </a>
426
+ </div>
427
+ <ul class="options">
428
+ </ul>
429
+ </div>
430
+ </div>
431
+
432
+ <?php do_action( 'cffrtb_field_editor_settings_panel' ); ?>
433
+
434
+ </div>
435
+
436
+ <div class="required">
437
+ <label>
438
+ <input type="checkbox" name="required" value="1">
439
+ <?php _e( 'Required', 'restaurant-reservations' ); ?>
440
+ </label>
441
+ </div>
442
+
443
+ <div class="actions">
444
+ <a href="#" class="button-primary save">
445
+ <?php _e( 'Add Field', 'restaurant-reservations' ); ?>
446
+ </a>
447
+ <a href="#" class="button cancel">
448
+ <?php _e( 'Cancel', 'restaurant-reservations' ); ?>
449
+ </a>
450
+ <div class="status">
451
+ <span class="load-spinner"></span>
452
+ </div>
453
+ </div>
454
+ </form>
455
+ </div>
456
+
457
+ <div id="cffrtb-field-editor-option" class="rtb-admin-modal">
458
+ <div class="rtb-container">
459
+ <div class="option">
460
+ <a href="#" class="field button-primary">
461
+ <?php _e( 'Add Field', 'restaurant-reservations' ); ?>
462
+ </a>
463
+ <p class="description">
464
+ <?php _e( 'Fields prompt the user to enter information or select from options.' ); ?>
465
+ </p>
466
+ </div>
467
+ <div class="option">
468
+ <a href="#" class="fieldset button">
469
+ <?php _e( 'Add Fieldset', 'restaurant-reservations' ); ?>
470
+ </a>
471
+ <p class="description">
472
+ <?php _e( 'Fieldsets group other fields under a common label.' ); ?>
473
+ </p>
474
+ </div>
475
+ </div>
476
+ </div>
477
+
478
+ <div id="rtb-error-modal" class="rtb-admin-modal">
479
+ <div class="rtb-error rtb-container"">
480
+ <div class="rtb-error-msg"></div>
481
+ <a href="#" class="button"><?php _e( 'Close', 'restaurant-reservations' ); ?></a>
482
+ </div>
483
+ </div>
484
+ <?php
485
+ }
486
+
487
+ /**
488
+ * Handle ajax requests from logged out users
489
+ *
490
+ * @since 0.1
491
+ */
492
+ public function ajax_nopriv() {
493
+
494
+ wp_send_json_error(
495
+ array(
496
+ 'error' => 'loggedout',
497
+ 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( admin_url( 'admin.php?page=cffrtb-editor' ) ) . '">', '</a>' ),
498
+ )
499
+ );
500
+ }
501
+
502
+ /**
503
+ * Handle ajax request to save a field
504
+ *
505
+ * @since 0.1
506
+ */
507
+ public function ajax_save_field() {
508
+ global $rtb_controller;
509
+
510
+ // Authenticate request
511
+ if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
512
+ $this->nopriv_ajax();
513
+ }
514
+
515
+ // Missing data
516
+ if ( empty( $_POST['field'] ) ) {
517
+ wp_send_json_error(
518
+ array(
519
+ 'error' => 'no_field_data',
520
+ 'msg' => __( 'No field data was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
521
+ )
522
+ );
523
+ }
524
+
525
+ // Missing request specification
526
+ if ( empty( $_POST['request'] ) ) {
527
+ wp_send_json_error(
528
+ array(
529
+ 'error' => 'no_request',
530
+ 'msg' => __( 'Internal data that was supposed to be passed with your request was not received.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
531
+ )
532
+ );
533
+ }
534
+
535
+ $field = new cffrtbField( $_POST['field'] );
536
+
537
+ // Just the label
538
+ if ( $_POST['request'] == 'save_label' ) {
539
+ $this->send_ajax_response( $field->save_label() );
540
+
541
+ // The whole field
542
+ } elseif ( $_POST['request'] == 'save_field' ) {
543
+ $this->send_ajax_response( $field->save_field() );
544
+ }
545
+
546
+ wp_send_json_error(
547
+ array(
548
+ 'error' => 'unknown',
549
+ 'msg' => __( 'An unknown error has occurred.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
550
+ )
551
+ );
552
+ }
553
+
554
+ /**
555
+ * Handle ajax request to save the order of fields and fieldsets
556
+ *
557
+ * @since 0.1
558
+ */
559
+ public function ajax_save_order() {
560
+ global $rtb_controller;
561
+
562
+ // Authenticate request
563
+ if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
564
+ $this->nopriv_ajax();
565
+ }
566
+
567
+ // Missing data
568
+ if ( empty( $_POST['order'] ) ) {
569
+ wp_send_json_error(
570
+ array(
571
+ 'error' => 'no_fields_data',
572
+ 'msg' => __( 'No fields data was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
573
+ )
574
+ );
575
+ }
576
+
577
+ $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
578
+ $orig_modified = $modified;
579
+
580
+ $custom_fields_error = array();
581
+ $has_custom_fields = false;
582
+ foreach( $_POST['order'] as $field ) {
583
+
584
+ if ( !isset( $field['order'] ) || empty( $field['fieldset'] ) ) {
585
+ continue; // @todo this indicates some kind of data error, though
586
+ }
587
+
588
+ // Default fields
589
+ if ( empty( $field['ID'] ) ) {
590
+
591
+ if ( empty( $modified[ $field['slug'] ] ) ) {
592
+ $modified[ $field['slug'] ] = array();
593
+ }
594
+
595
+ // Skip if the order isn't changing
596
+ if ( isset( $modified[ $field['slug'] ]['order'] ) && $modified[ $field['slug'] ]['order'] == $field['order'] && isset( $modified[ $field['slug'] ]['fieldset'] ) && $modified[ $field['slug'] ]['fieldset'] == $field['fieldset'] ) {
597
+ continue;
598
+ }
599
+
600
+ $modified[ $field['slug'] ]['order'] = (int) $field['order'];
601
+ $modified[ $field['slug'] ]['fieldset'] = sanitize_key( $field['fieldset'] );
602
+
603
+ // Custom fields
604
+ } else {
605
+
606
+ $custom_field = new cffrtbField( $field );
607
+ $result = $custom_field->save_field();
608
+
609
+ if ( !$result[0] ) {
610
+ array_push( $custom_fields_error, $result[1] );
611
+ }
612
+
613
+ $has_custom_fields = true;
614
+ }
615
+ }
616
+
617
+ update_option( $rtb_controller->custom_fields->modified_option_key, $modified );
618
+
619
+ if ( !$has_custom_fields || ( $has_custom_fields && empty( $custom_fields_error ) ) ) {
620
+ wp_send_json_success();
621
+
622
+ } else {
623
+ wp_send_json_error(
624
+ array(
625
+ 'error' => 'save_order_failed',
626
+ 'msg' => __( 'An error occurred while saving the new field order. Please try again.', 'restaurant-reservations' ),
627
+ 'fields' => sanitize_text_field( $_POST['order'] ),
628
+ 'custom_fields_error' => $custom_fields_error,
629
+ )
630
+ );
631
+ }
632
+
633
+ wp_send_json_error(
634
+ array(
635
+ 'error' => 'unknown',
636
+ 'msg' => __( 'An unknown error has occurred.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
637
+ )
638
+ );
639
+ }
640
+
641
+ /**
642
+ * Handle ajax request to load a field
643
+ *
644
+ * @since 0.1
645
+ */
646
+ public function ajax_load_field() {
647
+ global $rtb_controller;
648
+
649
+ // Authenticate request
650
+ if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
651
+ $this->nopriv_ajax();
652
+ }
653
+
654
+ // Missing data
655
+ if ( empty( $_POST['ID'] ) ) {
656
+ wp_send_json_error(
657
+ array(
658
+ 'error' => 'no_id',
659
+ 'msg' => __( 'The requested field could not be loaded because no ID was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
660
+ )
661
+ );
662
+ }
663
+
664
+ $field = new cffrtbField( array( 'ID' => (int) $_POST['ID'] ) );
665
+
666
+ wp_send_json_success(
667
+ array(
668
+ 'field' => $field,
669
+ )
670
+ );
671
+ }
672
+
673
+ /**
674
+ * Handle ajax request to delete or disable a field
675
+ *
676
+ * @since 0.1
677
+ */
678
+ public function ajax_delete_field() {
679
+ global $rtb_controller;
680
+
681
+ // Authenticate request
682
+ if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
683
+ $this->nopriv_ajax();
684
+ }
685
+
686
+ // Missing data
687
+ if ( empty( $_POST['ID'] ) && ( empty( $_POST['slug'] ) || empty( $_POST['fieldset'] ) ) ) {
688
+ wp_send_json_error(
689
+ array(
690
+ 'error' => 'no_id',
691
+ 'msg' => __( 'The requested field could not be deleted because no ID or slug was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
692
+ )
693
+ );
694
+ }
695
+
696
+ if ( !empty( $_POST['ID'] ) ) {
697
+ $post = wp_delete_post( (int) $_POST['ID'] );
698
+
699
+ if ( !$post ) {
700
+ wp_send_json_error(
701
+ array(
702
+ 'error' => 'delete_field_failed',
703
+ 'msg' => __( 'An error occurred while deleting this field.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
704
+ )
705
+ );
706
+ }
707
+
708
+ wp_send_json_success();
709
+
710
+ } else {
711
+ $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
712
+ $slug = sanitize_key( $_POST['slug'] );
713
+ $fieldset = sanitize_key( $_POST['fieldset'] );
714
+ $modified[ $slug ]['disabled'] = 1;
715
+ $modified[ $slug ]['fieldset'] = $fieldset;
716
+ update_option( $rtb_controller->custom_fields->modified_option_key, $modified );
717
+
718
+ global $rtb_controller;
719
+ $fields = $rtb_controller->settings->get_booking_form_fields();
720
+ $field = $slug == $fieldset ? $rtb_controller->fields->default_fields[ $slug ] : $rtb_controller->fields->get_nested_field( $slug, $rtb_controller->fields->default_fields );
721
+ $field['disabled'] = 1;
722
+
723
+ $type = $slug == $fieldset ? 'fieldset' : 'field';
724
+
725
+ wp_send_json_success(
726
+ array(
727
+ 'field' => $this->print_field( $slug, $field, $type ),
728
+ )
729
+ );
730
+ }
731
+ }
732
+
733
+ /**
734
+ * Handle an ajax request to enable a field that has been disabled
735
+ *
736
+ * @since 0.1
737
+ */
738
+ public function ajax_enable_field() {
739
+ global $rtb_controller;
740
+
741
+ // Authenticate request
742
+ if ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) {
743
+ $this->nopriv_ajax();
744
+ }
745
+
746
+ // Missing data
747
+ if ( empty( $_POST['slug'] ) ) {
748
+ wp_send_json_error(
749
+ array(
750
+ 'error' => 'no_id',
751
+ 'msg' => __( 'The requested field could not be enabled because no identifying slug was received with your request.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
752
+ )
753
+ );
754
+ }
755
+
756
+ $slug = sanitize_key( $_POST['slug'] );
757
+ $modified = get_option( $rtb_controller->custom_fields->modified_option_key );
758
+
759
+ if ( empty( $modified ) || !is_array( $modified ) || !array_key_exists( $slug, $modified ) ) {
760
+ wp_send_json_error(
761
+ array(
762
+ 'error' => 'field_not_found',
763
+ 'msg' => __( 'The requested field could not be enabled because it did not appear to be disabled.', 'restaurant-reservations' ) . ' ' . $rtb_controller->custom_fields->common_error_msg
764
+ )
765
+ );
766
+ }
767
+
768
+ if ( isset( $modified[ $slug ]['disabled'] ) ) {
769
+ unset( $modified[ $slug ]['disabled'] );
770
+ update_option( $rtb_controller->custom_fields->modified_option_key, $modified );
771
+ }
772
+
773
+ global $rtb_controller;
774
+ $fields = $rtb_controller->settings->get_booking_form_fields();
775
+ $field = $_POST['type'] == 'fieldset' ? $rtb_controller->fields->default_fields[ $slug ] : $rtb_controller->fields->get_nested_field( $slug, $fields );
776
+
777
+ $type = $_POST['type'] == 'fieldset' ? 'fieldset' : 'field';
778
+
779
+ if ( $type == 'fieldset' ) {
780
+ $field['exclude_fields'] = true;
781
+ }
782
+
783
+ wp_send_json_success(
784
+ array(
785
+ 'field' => $this->print_field( $slug, $field, $type )
786
+ )
787
+ );
788
+ }
789
+
790
+ /**
791
+ * Handle an ajax request to reset all changes and delete custom
792
+ * fields
793
+ *
794
+ * @since 0.1
795
+ */
796
+ public function ajax_reset_all( $authorized = false) {
797
+ global $rtb_controller;
798
+
799
+ // Authenticate request
800
+ if ( ( !check_ajax_referer( 'cffrtb-editor', 'nonce' ) || !current_user_can( 'manage_options' ) ) and ! $authorized ) {
801
+ $this->nopriv_ajax();
802
+ }
803
+
804
+ // Delete modifications to default fields
805
+ delete_option( $rtb_controller->custom_fields->modified_option_key );
806
+
807
+ // Delete all custom posts
808
+ $posts = new WP_Query(
809
+ array(
810
+ 'post_type' => 'cffrtb_field',
811
+ 'posts_per_page' => 1000, // Very large upper limit
812
+ )
813
+ );
814
+
815
+ while( $posts->have_posts() ) {
816
+ $posts->the_post();
817
+ wp_delete_post( get_the_ID() );
818
+ }
819
+
820
+ wp_send_json_success();
821
+ }
822
+
823
+ /**
824
+ * Send an ajax response
825
+ *
826
+ * This is a generic response sender which will search for an error
827
+ * in the response and make the appropriate wp_send_json_*() call.
828
+ *
829
+ * @since 0.1
830
+ */
831
+ public function send_ajax_response( $response = array() ) {
832
+
833
+ $response[1] = empty( $response[1] ) ? '' : $response[1];
834
+
835
+ if ( !empty( $response[0] ) ) {
836
+ wp_send_json_success( $response[1] );
837
+
838
+ } else {
839
+ wp_send_json_error( $response[1] );
840
+ }
841
+
842
+ }
843
+
844
+ }
845
+ } // endif;
includes/Export.CSV.class.php CHANGED
@@ -1,381 +1,381 @@
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
- global $rtb_controller;
71
-
72
- $date_format = $rtb_controller->settings->get_setting( 'ebfrtb-csv-date-format' );
73
- $time_format = get_option( 'time_format' );
74
-
75
- // Compile bookings arrayarray headers
76
- $arr = apply_filters( 'ebfrtb_export_csv_booking_headers', array(
77
- array(
78
- 'ID' => __( 'Booking ID', 'restaurant-reservations' ),
79
- 'date' => __( 'Date', 'restaurant-reservations' ),
80
- 'time' => __( 'Time', 'restaurant-reservations' ),
81
- 'name' => __( 'Name', 'restaurant-reservations' ),
82
- 'party' => __( 'Party', 'restaurant-reservations' ),
83
- 'email' => __( 'Email', 'restaurant-reservations' ),
84
- 'phone' => __( 'Phone', 'restaurant-reservations' ),
85
- 'message' => __( 'Message', 'restaurant-reservations' ),
86
- 'date_submission' => __( 'Date the request was made', 'restaurant-reservations' ),
87
- 'status' => __( 'Booking Status', 'restaurant-reservations' ),
88
- )
89
- ) );
90
-
91
- $tmzn = wp_timezone();
92
-
93
- // Compile bookings array
94
- foreach( $this->bookings as $booking ) {
95
- $arr[] = apply_filters( 'ebfrtb_export_csv_booking', array(
96
- 'ID' => $booking->ID,
97
- 'date' => date_i18n( $date_format, ( new DateTime( $booking->date, $tmzn ) )->format( 'U' ) ),
98
- 'time' => date_i18n( $time_format, ( new DateTime( $booking->date, $tmzn ) )->format( 'U' ) ),
99
- 'name' => $booking->name,
100
- 'party' => $booking->party,
101
- 'email' => $booking->email,
102
- 'phone' => $booking->phone,
103
- 'message' => str_replace( array( "\r\n", "\n", "\r", '<br />', '<br>', '<br/>' ), ' ', $booking->message ),
104
- 'date_submission' => date_i18n( $date_format . ' ' . $time_format, $booking->date_submission ),
105
- 'status' => $booking->post_status,
106
- ), $booking );
107
- }
108
-
109
- $this->export = apply_filters( 'ebfrtb_export_csv_bookings', $arr );
110
-
111
- return $this->export;
112
- }
113
-
114
- /**
115
- * Deliver the CSV file to the browser
116
- *
117
- * @since 0.1
118
- */
119
- public function deliver() {
120
-
121
- // Generate the export if it's not been done yet
122
- if ( empty( $this->export ) ) {
123
- $this->export();
124
- }
125
-
126
- $filename = apply_filters( 'ebfrtb_export_csv_filename', sanitize_file_name( $this->get_date_phrase() ) . '.csv' );
127
- $delimiter = apply_filters( 'ebfrtb_export_csv_delimiter', ',' );
128
-
129
- header( 'Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' );
130
- header( 'Content-Description: File Transfer' );
131
- header( 'Content-type: text/csv' );
132
- header( 'Content-Disposition: attachment; filename=' . $filename );
133
- header( 'Expires: 0' );
134
- header( 'Pragma: no-cache' );
135
-
136
- $output = @fopen( 'php://output', 'w' );
137
-
138
- foreach( $this->export as $booking ) {
139
- fputcsv( $output, $booking, $delimiter );
140
- }
141
-
142
- fclose( $output );
143
-
144
- exit();
145
- }
146
-
147
- /**
148
- * Add a location header if locations are active
149
- *
150
- * @param array $headers Key/value of spreadsheet header rows id/label
151
- * @since 1.1
152
- */
153
- public function add_location_header( $headers ) {
154
-
155
- $headers[0] = array_merge(
156
- array( 'location' => __( 'Location', 'restaurant-reservations' ) ),
157
- $headers[0]
158
- );
159
-
160
- return $headers;
161
- }
162
-
163
- /**
164
- * Add location data from a booking to the array for conversion to csv
165
- *
166
- * @param array $arr Assoc array of booking data compiled for conversion to
167
- * csv
168
- * @param rtbBooking $booking Original booking object
169
- * @since 1.1
170
- */
171
- public function add_location( $arr, $booking ) {
172
-
173
- $location = '';
174
- if ( !empty( $booking->location ) ) {
175
- $term = get_term( $booking->location );
176
- if ( is_a( $term, 'WP_Term' ) ) {
177
- $location = $term->name;
178
- }
179
- }
180
-
181
- $arr = array_merge(
182
- array( 'location' => $location ),
183
- $arr
184
- );
185
-
186
- return $arr;
187
- }
188
-
189
- /**
190
- * Add a header for the privacy consent if it's active
191
- *
192
- * @param array $headers Key/value of spreadsheet header rows id/label
193
- * @since 1.1.1
194
- */
195
- public function add_privacy_header( $headers ) {
196
-
197
- $headers[0] = array_merge(
198
- $headers[0],
199
- array( 'consent_acquired' => __( 'Data Privacy Consent', 'restaurant-reservations' ) )
200
- );
201
-
202
- return $headers;
203
- }
204
-
205
- /**
206
- * Add privacy consent collection status to the array for conversion to csv
207
- *
208
- * @param array $arr Assoc array of booking data compiled for conversion to
209
- * csv
210
- * @param rtbBooking $booking Original booking object
211
- * @since 1.1
212
- */
213
- public function add_privacy( $arr, $booking ) {
214
-
215
- $consent_acquired = !empty( $booking->consent_acquired ) ? __( 'Yes', 'restaurant-reservations' ) : __( 'No', 'restaurant-reservations' );
216
- $arr = array_merge(
217
- $arr,
218
- array( 'consent_acquired' => $consent_acquired )
219
- );
220
-
221
- return $arr;
222
- }
223
-
224
- /**
225
- * Add a header for the table(s) feature if it's active
226
- *
227
- * @param array $headers Key/value of spreadsheet header rows id/label
228
- * @since 2.2.0
229
- */
230
- public function add_table_header( $headers ) {
231
-
232
- $headers[0] = array_merge(
233
- $headers[0],
234
- array( 'table' => __( 'Table(s)', 'restaurant-reservations' ) )
235
- );
236
-
237
- return $headers;
238
- }
239
-
240
- /**
241
- * Add the selected table(s) to the array for conversion to csv
242
- *
243
- * @param array $arr Assoc array of booking data compiled for conversion to
244
- * csv
245
- * @param rtbBooking $booking Original booking object
246
- * @since 2.2.0
247
- */
248
- public function add_table( $arr, $booking ) {
249
-
250
- $arr = array_merge(
251
- $arr,
252
- array( 'table' => implode( ',', $booking->table ) )
253
- );
254
-
255
- return $arr;
256
- }
257
-
258
- /**
259
- * Add a header for the payment(s) feature if it's active
260
- *
261
- * @param array $headers Key/value of spreadsheet header rows id/label
262
- * @since 2.2.8
263
- */
264
- public function add_deposit_header( $headers ) {
265
-
266
- $headers[0] = array_merge(
267
- $headers[0],
268
- array(
269
- 'deposit' => __( 'Deposit', 'restaurant-reservations' ),
270
- 'receipt_id' => __( 'Receipt ID', 'restaurant-reservations' )
271
- )
272
- );
273
-
274
- return $headers;
275
- }
276
-
277
- /**
278
- * Add the deposit(s) to the array for conversion to csv
279
- *
280
- * @param array $arr Assoc array of booking data compiled for conversion to
281
- * csv
282
- * @param rtbBooking $booking Original booking object
283
- * @since 2.2.0
284
- */
285
- public function add_deposit( $arr, $booking ) {
286
-
287
- global $rtb_controller;
288
-
289
- $arr = array_merge(
290
- $arr,
291
- array(
292
- 'deposit' => $rtb_controller->settings->get_setting( 'rtb-currency' ) .' '. $booking->deposit,
293
- 'receipt_id' => $booking->receipt_id
294
- )
295
- );
296
-
297
- return $arr;
298
- }
299
-
300
- /**
301
- * Add custom fields to CSV headers
302
- *
303
- * @param array $headers Key/value of spreadsheet header rows id/label
304
- * @since 2.0
305
- */
306
- public function add_custom_fields_header( $headers ) {
307
-
308
- $fields = rtb_get_custom_fields();
309
-
310
- foreach( $fields as $field ) {
311
-
312
- if ( $field->type == 'fieldset' ) {
313
- continue;
314
- }
315
-
316
- $headers[0][ 'cf-' . $field->slug ] = $field->title;
317
- }
318
-
319
- return $headers;
320
- }
321
-
322
- /**
323
- * Add custom fields to CSV headers
324
- *
325
- * @param array $headers Key/value of spreadsheet header rows id/label
326
- * @since 2.0
327
- */
328
- public function add_custom_fields( $row, $booking ) {
329
- global $rtb_controller;
330
-
331
- $fields = rtb_get_custom_fields();
332
-
333
- $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
334
-
335
- foreach( $fields as $field ) {
336
-
337
- if ( $field->type == 'fieldset' ) {
338
- continue;
339
- }
340
-
341
- $val = isset( $cf[ $field->slug ] ) ? $cf[ $field->slug ] : '';
342
- $display_val = apply_filters( 'cffrtb_display_value_csv', $rtb_controller->fields->get_display_value( $val, $field, '', false ), $val, $field, $booking );
343
-
344
- $row[ 'cf-' . $field->slug ] = $display_val;
345
- }
346
-
347
- return $row;
348
- }
349
-
350
- /**
351
- * Add custom fields to CSV data row
352
- *
353
- * @param array $row Assoc array of booking data compiled for conversion to
354
- * csv
355
- * @param rtbBooking $booking Original booking object
356
- * @since 2.0
357
- */
358
- public function cffrtb_ebfrtb_add_csv_row( $row, $booking ) {
359
- global $rtb_controller;
360
-
361
- $fields = rtb_get_custom_fields();
362
-
363
- $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
364
-
365
- foreach( $fields as $field ) {
366
-
367
- if ( $field->type == 'fieldset' ) {
368
- continue;
369
- }
370
-
371
- $val = isset( $cf[ $field->slug ] ) ? $cf[ $field->slug ] : '';
372
- $display_val = apply_filters( 'cffrtb_display_value_csv', $rtb_controller->fields->get_display_value( $val, $field, '', false ), $val, $field, $booking );
373
-
374
- $row[ 'cf-' . $field->slug ] = $display_val;
375
- }
376
-
377
- return $row;
378
- }
379
-
380
- }
381
- } // 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
+ global $rtb_controller;
71
+
72
+ $date_format = $rtb_controller->settings->get_setting( 'ebfrtb-csv-date-format' );
73
+ $time_format = get_option( 'time_format' );
74
+
75
+ // Compile bookings arrayarray headers
76
+ $arr = apply_filters( 'ebfrtb_export_csv_booking_headers', array(
77
+ array(
78
+ 'ID' => __( 'Booking ID', 'restaurant-reservations' ),
79
+ 'date' => __( 'Date', 'restaurant-reservations' ),
80
+ 'time' => __( 'Time', 'restaurant-reservations' ),
81
+ 'name' => __( 'Name', 'restaurant-reservations' ),
82
+ 'party' => __( 'Party', 'restaurant-reservations' ),
83
+ 'email' => __( 'Email', 'restaurant-reservations' ),
84
+ 'phone' => __( 'Phone', 'restaurant-reservations' ),
85
+ 'message' => __( 'Message', 'restaurant-reservations' ),
86
+ 'date_submission' => __( 'Date the request was made', 'restaurant-reservations' ),
87
+ 'status' => __( 'Booking Status', 'restaurant-reservations' ),
88
+ )
89
+ ) );
90
+
91
+ $tmzn = wp_timezone();
92
+
93
+ // Compile bookings array
94
+ foreach( $this->bookings as $booking ) {
95
+ $arr[] = apply_filters( 'ebfrtb_export_csv_booking', array(
96
+ 'ID' => $booking->ID,
97
+ 'date' => date_i18n( $date_format, ( new DateTime( $booking->date, $tmzn ) )->format( 'U' ) ),
98
+ 'time' => date_i18n( $time_format, ( new DateTime( $booking->date, $tmzn ) )->format( 'U' ) ),
99
+ 'name' => $booking->name,
100
+ 'party' => $booking->party,
101
+ 'email' => $booking->email,
102
+ 'phone' => $booking->phone,
103
+ 'message' => str_replace( array( "\r\n", "\n", "\r", '<br />', '<br>', '<br/>' ), ' ', $booking->message ),
104
+ 'date_submission' => date_i18n( $date_format . ' ' . $time_format, $booking->date_submission ),
105
+ 'status' => $booking->post_status,
106
+ ), $booking );
107
+ }
108
+
109
+ $this->export = apply_filters( 'ebfrtb_export_csv_bookings', $arr );
110
+
111
+ return $this->export;
112
+ }
113
+
114
+ /**
115
+ * Deliver the CSV file to the browser
116
+ *
117
+ * @since 0.1
118
+ */
119
+ public function deliver() {
120
+
121
+ // Generate the export if it's not been done yet
122
+ if ( empty( $this->export ) ) {
123
+ $this->export();
124
+ }
125
+
126
+ $filename = apply_filters( 'ebfrtb_export_csv_filename', sanitize_file_name( $this->get_date_phrase() ) . '.csv' );
127
+ $delimiter = apply_filters( 'ebfrtb_export_csv_delimiter', ',' );
128
+
129
+ header( 'Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' );
130
+ header( 'Content-Description: File Transfer' );
131
+ header( 'Content-type: text/csv' );
132
+ header( 'Content-Disposition: attachment; filename=' . $filename );
133
+ header( 'Expires: 0' );
134
+ header( 'Pragma: no-cache' );
135
+
136
+ $output = @fopen( 'php://output', 'w' );
137
+
138
+ foreach( $this->export as $booking ) {
139
+ fputcsv( $output, $booking, $delimiter );
140
+ }
141
+
142
+ fclose( $output );
143
+
144
+ exit();
145
+ }
146
+
147
+ /**
148
+ * Add a location header if locations are active
149
+ *
150
+ * @param array $headers Key/value of spreadsheet header rows id/label
151
+ * @since 1.1
152
+ */
153
+ public function add_location_header( $headers ) {
154
+
155
+ $headers[0] = array_merge(
156
+ array( 'location' => __( 'Location', 'restaurant-reservations' ) ),
157
+ $headers[0]
158
+ );
159
+
160
+ return $headers;
161
+ }
162
+
163
+ /**
164
+ * Add location data from a booking to the array for conversion to csv
165
+ *
166
+ * @param array $arr Assoc array of booking data compiled for conversion to
167
+ * csv
168
+ * @param rtbBooking $booking Original booking object
169
+ * @since 1.1
170
+ */
171
+ public function add_location( $arr, $booking ) {
172
+
173
+ $location = '';
174
+ if ( !empty( $booking->location ) ) {
175
+ $term = get_term( $booking->location );
176
+ if ( is_a( $term, 'WP_Term' ) ) {
177
+ $location = $term->name;
178
+ }
179
+ }
180
+
181
+ $arr = array_merge(
182
+ array( 'location' => $location ),
183
+ $arr
184
+ );
185
+
186
+ return $arr;
187
+ }
188
+
189
+ /**
190
+ * Add a header for the privacy consent if it's active
191
+ *
192
+ * @param array $headers Key/value of spreadsheet header rows id/label
193
+ * @since 1.1.1
194
+ */
195
+ public function add_privacy_header( $headers ) {
196
+
197
+ $headers[0] = array_merge(
198
+ $headers[0],
199
+ array( 'consent_acquired' => __( 'Data Privacy Consent', 'restaurant-reservations' ) )
200
+ );
201
+
202
+ return $headers;
203
+ }
204
+
205
+ /**
206
+ * Add privacy consent collection status to the array for conversion to csv
207
+ *
208
+ * @param array $arr Assoc array of booking data compiled for conversion to
209
+ * csv
210
+ * @param rtbBooking $booking Original booking object
211
+ * @since 1.1
212
+ */
213
+ public function add_privacy( $arr, $booking ) {
214
+
215
+ $consent_acquired = !empty( $booking->consent_acquired ) ? __( 'Yes', 'restaurant-reservations' ) : __( 'No', 'restaurant-reservations' );
216
+ $arr = array_merge(
217
+ $arr,
218
+ array( 'consent_acquired' => $consent_acquired )
219
+ );
220
+
221
+ return $arr;
222
+ }
223
+
224
+ /**
225
+ * Add a header for the table(s) feature if it's active
226
+ *
227
+ * @param array $headers Key/value of spreadsheet header rows id/label
228
+ * @since 2.2.0
229
+ */
230
+ public function add_table_header( $headers ) {
231
+
232
+ $headers[0] = array_merge(
233
+ $headers[0],
234
+ array( 'table' => __( 'Table(s)', 'restaurant-reservations' ) )
235
+ );
236
+
237
+ return $headers;
238
+ }
239
+
240
+ /**
241
+ * Add the selected table(s) to the array for conversion to csv
242
+ *
243
+ * @param array $arr Assoc array of booking data compiled for conversion to
244
+ * csv
245
+ * @param rtbBooking $booking Original booking object
246
+ * @since 2.2.0
247
+ */
248
+ public function add_table( $arr, $booking ) {
249
+
250
+ $arr = array_merge(
251
+ $arr,
252
+ array( 'table' => implode( ',', $booking->table ) )
253
+ );
254
+
255
+ return $arr;
256
+ }
257
+
258
+ /**
259
+ * Add a header for the payment(s) feature if it's active
260
+ *
261
+ * @param array $headers Key/value of spreadsheet header rows id/label
262
+ * @since 2.2.8
263
+ */
264
+ public function add_deposit_header( $headers ) {
265
+
266
+ $headers[0] = array_merge(
267
+ $headers[0],
268
+ array(
269
+ 'deposit' => __( 'Deposit', 'restaurant-reservations' ),
270
+ 'receipt_id' => __( 'Receipt ID', 'restaurant-reservations' )
271
+ )
272
+ );
273
+
274
+ return $headers;
275
+ }
276
+
277
+ /**
278
+ * Add the deposit(s) to the array for conversion to csv
279
+ *
280
+ * @param array $arr Assoc array of booking data compiled for conversion to
281
+ * csv
282
+ * @param rtbBooking $booking Original booking object
283
+ * @since 2.2.0
284
+ */
285
+ public function add_deposit( $arr, $booking ) {
286
+
287
+ global $rtb_controller;
288
+
289
+ $arr = array_merge(
290
+ $arr,
291
+ array(
292
+ 'deposit' => $rtb_controller->settings->get_setting( 'rtb-currency' ) .' '. $booking->deposit,
293
+ 'receipt_id' => $booking->receipt_id
294
+ )
295
+ );
296
+
297
+ return $arr;
298
+ }
299
+
300
+ /**
301
+ * Add custom fields to CSV headers
302
+ *
303
+ * @param array $headers Key/value of spreadsheet header rows id/label
304
+ * @since 2.0
305
+ */
306
+ public function add_custom_fields_header( $headers ) {
307
+
308
+ $fields = rtb_get_custom_fields();
309
+
310
+ foreach( $fields as $field ) {
311
+
312
+ if ( $field->type == 'fieldset' ) {
313
+ continue;
314
+ }
315
+
316
+ $headers[0][ 'cf-' . $field->slug ] = $field->title;
317
+ }
318
+
319
+ return $headers;
320
+ }
321
+
322
+ /**
323
+ * Add custom fields to CSV headers
324
+ *
325
+ * @param array $headers Key/value of spreadsheet header rows id/label
326
+ * @since 2.0
327
+ */
328
+ public function add_custom_fields( $row, $booking ) {
329
+ global $rtb_controller;
330
+
331
+ $fields = rtb_get_custom_fields();
332
+
333
+ $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
334
+
335
+ foreach( $fields as $field ) {
336
+
337
+ if ( $field->type == 'fieldset' ) {
338
+ continue;
339
+ }
340
+
341
+ $val = isset( $cf[ $field->slug ] ) ? $cf[ $field->slug ] : '';
342
+ $display_val = apply_filters( 'cffrtb_display_value_csv', $rtb_controller->fields->get_display_value( $val, $field, '', false ), $val, $field, $booking );
343
+
344
+ $row[ 'cf-' . $field->slug ] = $display_val;
345
+ }
346
+
347
+ return $row;
348
+ }
349
+
350
+ /**
351
+ * Add custom fields to CSV data row
352
+ *
353
+ * @param array $row Assoc array of booking data compiled for conversion to
354
+ * csv
355
+ * @param rtbBooking $booking Original booking object
356
+ * @since 2.0
357
+ */
358
+ public function cffrtb_ebfrtb_add_csv_row( $row, $booking ) {
359
+ global $rtb_controller;
360
+
361
+ $fields = rtb_get_custom_fields();
362
+
363
+ $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
364
+
365
+ foreach( $fields as $field ) {
366
+
367
+ if ( $field->type == 'fieldset' ) {
368
+ continue;
369
+ }
370
+
371
+ $val = isset( $cf[ $field->slug ] ) ? $cf[ $field->slug ] : '';
372
+ $display_val = apply_filters( 'cffrtb_display_value_csv', $rtb_controller->fields->get_display_value( $val, $field, '', false ), $val, $field, $booking );
373
+
374
+ $row[ 'cf-' . $field->slug ] = $display_val;
375
+ }
376
+
377
+ return $row;
378
+ }
379
+
380
+ }
381
+ } // endif
includes/Export.PDF.class.php CHANGED
@@ -1,365 +1,365 @@
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
- * The reservation and guest numbers by date
49
- *
50
- * @since 0.1
51
- */
52
- public $bookings_summary = array();
53
-
54
- /**
55
- * Insantiate the PDF export
56
- *
57
- * @since 0.1
58
- */
59
- public function __construct( $bookings, $args = array() ) {
60
-
61
- $this->bookings = $bookings;
62
-
63
- global $rtb_controller;
64
- $this->lib = !empty( $args['pdf-lib'] ) ? sanitize_key( $args['pdf-lib'] ) : $rtb_controller->settings->get_setting( 'ebfrtb-pdf-lib' );
65
-
66
- // Get paper size
67
- $this->paper_size = !empty( $args['paper-size'] ) ? sanitize_key( $args['paper-size'] ) : $rtb_controller->settings->get_setting( 'ebfrtb-paper-size' );
68
-
69
- // Query arguments
70
- if ( !empty( $args['query_args'] ) ) {
71
- $this->query_args = $args['query_args'];
72
- }
73
-
74
- // Maybe add table hooks
75
- add_filter( 'ebfrtb_mpdf_after_details', array($this, 'add_booking_table_to_pdf' ) );
76
- add_filter( 'ebfrtb_tcpdf_after_details', array($this, 'add_booking_table_to_pdf' ) );
77
-
78
- // Add custom field hooks
79
- add_filter( 'ebfrtb_mpdf_after_details', array($this, 'add_custom_fields_to_mpdf' ) );
80
- add_filter( 'ebfrtb_tcpdf_after_details', array($this, 'add_custom_fields_to_tcpdf' ) );
81
- }
82
-
83
- /**
84
- * Set document information
85
- *
86
- * Sets document meta data like title, creator, etc. Both
87
- * TCPDF and mPDF use the same methods for this.
88
- *
89
- * @since 0.1
90
- */
91
- public function set_doc_info( $pdf ) {
92
-
93
- $pdf->SetCreator( get_bloginfo( 'sitename' ) );
94
- $pdf->SetAuthor( get_bloginfo( 'sitename' ) );
95
- $pdf->SetTitle( sprintf( _x( 'Bookings at %s', 'Title of PDF documents', 'restaurant-reservations' ), get_bloginfo( 'sitename' ) ) );
96
- $pdf->SetSubject( $this->get_date_phrase() );
97
-
98
- return $pdf;
99
- }
100
-
101
- /**
102
- * Compile the PDF file
103
- *
104
- * This routes to the appropriate export method
105
- * depending on the PDF library being used.
106
- *
107
- * @since 0.1
108
- */
109
- public function export() {
110
-
111
- $this->create_bookings_summary();
112
-
113
- if ( $this->lib === 'tcpdf' ) {
114
- $this->export_tcpdf();
115
-
116
- } elseif ( $this->lib === 'mpdf' ) {
117
- $this->export_mpdf();
118
-
119
- } else {
120
- do_action( 'ebcfrtb_pdf_export_' . $this->lib, $this );
121
- }
122
-
123
- return $this->export;
124
- }
125
-
126
- /**
127
- * Deliver the PDF to the browser
128
- *
129
- * @since 0.1
130
- */
131
- public function deliver() {
132
-
133
- // Generate the export if it's not been done yet
134
- if ( empty( $this->export ) ) {
135
- $this->export();
136
- }
137
-
138
- $filename = apply_filters( 'ebfrtb_export_pdf_filename', sanitize_file_name( $this->get_date_phrase() ) . '.pdf' );
139
-
140
- // Clean any stray errors, warnings or notices that may have been
141
- // printed to the buffer
142
- ob_get_clean();
143
-
144
- if ( $this->lib === 'tcpdf' || $this->lib === 'mpdf' ) {
145
- $this->export->Output( $filename, 'I');
146
- exit();
147
-
148
- } else {
149
- do_action( 'ebcfrtb_pdf_deliver_' . $this->lib, $this );
150
- }
151
-
152
- // Just in case we forget...
153
- wp_die( __( 'An unexpected error occurred and your export request could not be fulfilled.', 'restaurant-reservations' ) );
154
- }
155
-
156
- /**
157
- * Compile PDF file with TCPDF library
158
- *
159
- * @since 0.1
160
- */
161
- public function export_tcpdf() {
162
-
163
- // Load TCPDF library
164
- require_once( RTB_PLUGIN_DIR . '/lib/tcpdf/config/tcpdf_config.php' );
165
- require_once( RTB_PLUGIN_DIR . '/lib/tcpdf/tcpdf.php' );
166
-
167
- // TCPDF uses a different identifier for U.S. Letter format
168
- if ( $this->paper_size === 'LETTER' ) {
169
- $this->paper_size = 'ANSI_A';
170
- }
171
-
172
- $tcpdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, $this->paper_size, true, 'UTF-8', false);
173
- $tcpdf = $this->set_doc_info( $tcpdf );
174
-
175
- // set default header data
176
- $header_title = get_bloginfo( 'sitename' );
177
- if ( !empty( $this->query_args['location'] ) ) {
178
- $term = get_term( $this->query_args['location'] );
179
- if ( is_a( $term, 'WP_Term' ) ) {
180
- $header_title = $term->name;
181
- }
182
- }
183
- $tcpdf->SetHeaderData('', '', $header_title, $this->get_date_phrase(), array( 117, 117, 117 ), array( 117, 117, 117 ) );
184
- $tcpdf->setFooterData(array(0,64,0), array(0,64,128));
185
-
186
- // set header and footer fonts
187
- $tcpdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
188
- $tcpdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
189
-
190
- // set margins
191
- $tcpdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
192
- $tcpdf->SetHeaderMargin(PDF_MARGIN_HEADER);
193
- $tcpdf->SetFooterMargin(PDF_MARGIN_FOOTER);
194
-
195
- // set auto page breaks
196
- $tcpdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
197
-
198
- // set image scale factor
199
- $tcpdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
200
-
201
- // Subset fonts for smaller doc sizes
202
- $tcpdf->setFontSubsetting(true);
203
-
204
- // Add a page
205
- $tcpdf->AddPage();
206
- $tcpdf->SetCellPadding(0);
207
-
208
- // Generate the bookings HTML
209
- ob_start();
210
- $this->include_template( 'tcpdf.php' );
211
- $html = ob_get_clean();
212
-
213
- // Print HTML
214
- $tcpdf->writeHTML($html, false, true, false, false, '');
215
-
216
- $this->export = $tcpdf;
217
-
218
- return $this->export;
219
- }
220
-
221
- /**
222
- *Maybe add the table for a booking to PDF output
223
- *
224
- * @since 2.2
225
- */
226
- public function add_booking_table_to_pdf( $booking ) {
227
- global $rtb_controller;
228
-
229
- if ( $rtb_controller->settings->get_setting( 'enable-tables' ) ) { ?>
230
-
231
- <p class="booking-table">
232
- <span class="label"><?php echo __( 'Table(s)' ) . esc_html_x( ': ', 'Appears after table label in PDF exports', 'custom-fields-for-rtb' ); ?></span>
233
- <?php echo implode( ',', $booking->table ); ?>
234
- </p>
235
-
236
- <?php }
237
- }
238
-
239
- /**
240
- * Add custom fields to TCPDF output
241
- *
242
- * @since 2.0
243
- */
244
- public function add_custom_fields_to_tcpdf( $booking ) {
245
- global $rtb_controller;
246
-
247
- $fields = rtb_get_custom_fields();
248
- $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
249
-
250
- foreach( $fields as $field ) {
251
-
252
- if ( $field->type == 'fieldset' || !isset( $cf[ $field->slug ] ) ) {
253
- continue;
254
- }
255
-
256
- $val = $cf[ $field->slug ];
257
- $display_val = apply_filters( 'cffrtb_display_value_tcpdf', $rtb_controller->fields->get_display_value( $val, $field, '', true ), $val, $field, $booking );
258
-
259
- ?>
260
-
261
- <div class="custom-field <?php echo esc_attr( $field->type . ' ' . $field->slug ); ?>">
262
- <?php echo esc_html( $field->title ) . esc_html_x( ': ', 'Appears after field label in TCPDF exports', 'custom-fields-for-rtb' ); ?>
263
- <?php echo $display_val ?>
264
- </div>
265
-
266
- <?php
267
- }
268
- }
269
-
270
- /**
271
- * Compile PDF file with mPDF library
272
- *
273
- * @since 0.1
274
- */
275
- public function export_mpdf() {
276
-
277
- if ( !extension_loaded( 'mbstring' ) ) {
278
- $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>';
279
- 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 ) );
280
- }
281
-
282
- // Load mPDF library
283
- require_once( RTB_PLUGIN_DIR . '/lib/mpdf/vendor/autoload.php' );
284
-
285
- $mpdf = new \Mpdf\Mpdf( [], $this->paper_size, '', '', 15, 15, 25 );
286
- $mpdf = $this->set_doc_info( $mpdf );
287
-
288
- // Support languages automatically
289
- $mpdf->autoScriptToLang = true;
290
- $mpdf->baseScript = 1;
291
- $mpdf->autoVietnamese = true;
292
- $mpdf->autoArabic = true;
293
- $mpdf->autoLangToFont = true;
294
-
295
- // Generate the bookings HTML
296
- ob_start();
297
- $this->include_template( 'mpdf.php' );
298
- $html = ob_get_clean();
299
-
300
- $mpdf->WriteHTML($html);
301
-
302
- $this->export = $mpdf;
303
-
304
- return $this->export;
305
- }
306
-
307
- /**
308
- * Add custom fields to mPDF output
309
- *
310
- * @since 2.0
311
- */
312
- public function add_custom_fields_to_mpdf( $booking ) {
313
- global $rtb_controller;
314
-
315
- $fields = rtb_get_custom_fields();
316
- $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
317
-
318
- foreach( $fields as $field ) {
319
-
320
- if ( $field->type == 'fieldset' || !isset( $cf[ $field->slug ] ) ) {
321
- continue;
322
- }
323
-
324
- $val = $cf[ $field->slug ];
325
- $display_val = apply_filters( 'cffrtb_display_value_mpdf', $rtb_controller->fields->get_display_value( $val, $field, '', true ), $val, $field, $booking );
326
-
327
- ?>
328
-
329
- <p class="custom-field <?php echo esc_attr( $field->type . ' ' . $field->slug ); ?>">
330
- <span class="label"><?php echo esc_html( $field->title ) . esc_html_x( ': ', 'Appears after field label in mPDF exports', 'custom-fields-for-rtb' ); ?></span>
331
- <?php echo $display_val ?>
332
- </p>
333
-
334
- <?php
335
- }
336
- }
337
-
338
- /**
339
- * Calcualte the reservation and guests number by date
340
- *
341
- * @since 2.5.2
342
- */
343
- public function create_bookings_summary() {
344
- global $rtb_controller;
345
-
346
- foreach( $this->bookings as $booking ) {
347
-
348
- $booking_date_idx = mysql2date( 'Y-m-d', $booking->date );
349
-
350
- if ( ! isset( $this->bookings_summary[ $booking_date_idx ] ) ) {
351
-
352
- $this->bookings_summary[ $booking_date_idx ] = array(
353
- 'reservations' => 0,
354
- 'seats' => 0
355
- );
356
- }
357
-
358
- $this->bookings_summary[ $booking_date_idx ]['reservations']++;
359
-
360
- $this->bookings_summary[ $booking_date_idx ]['seats'] += $booking->party;
361
- }
362
- }
363
-
364
- }
365
- } // 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
+ * The reservation and guest numbers by date
49
+ *
50
+ * @since 0.1
51
+ */
52
+ public $bookings_summary = array();
53
+
54
+ /**
55
+ * Insantiate the PDF export
56
+ *
57
+ * @since 0.1
58
+ */
59
+ public function __construct( $bookings, $args = array() ) {
60
+
61
+ $this->bookings = $bookings;
62
+
63
+ global $rtb_controller;
64
+ $this->lib = !empty( $args['pdf-lib'] ) ? sanitize_key( $args['pdf-lib'] ) : $rtb_controller->settings->get_setting( 'ebfrtb-pdf-lib' );
65
+
66
+ // Get paper size
67
+ $this->paper_size = !empty( $args['paper-size'] ) ? sanitize_key( $args['paper-size'] ) : $rtb_controller->settings->get_setting( 'ebfrtb-paper-size' );
68
+
69
+ // Query arguments
70
+ if ( !empty( $args['query_args'] ) ) {
71
+ $this->query_args = $args['query_args'];
72
+ }
73
+
74
+ // Maybe add table hooks
75
+ add_filter( 'ebfrtb_mpdf_after_details', array($this, 'add_booking_table_to_pdf' ) );
76
+ add_filter( 'ebfrtb_tcpdf_after_details', array($this, 'add_booking_table_to_pdf' ) );
77
+
78
+ // Add custom field hooks
79
+ add_filter( 'ebfrtb_mpdf_after_details', array($this, 'add_custom_fields_to_mpdf' ) );
80
+ add_filter( 'ebfrtb_tcpdf_after_details', array($this, 'add_custom_fields_to_tcpdf' ) );
81
+ }
82
+
83
+ /**
84
+ * Set document information
85
+ *
86
+ * Sets document meta data like title, creator, etc. Both
87
+ * TCPDF and mPDF use the same methods for this.
88
+ *
89
+ * @since 0.1
90
+ */
91
+ public function set_doc_info( $pdf ) {
92
+
93
+ $pdf->SetCreator( get_bloginfo( 'sitename' ) );
94
+ $pdf->SetAuthor( get_bloginfo( 'sitename' ) );
95
+ $pdf->SetTitle( sprintf( _x( 'Bookings at %s', 'Title of PDF documents', 'restaurant-reservations' ), get_bloginfo( 'sitename' ) ) );
96
+ $pdf->SetSubject( $this->get_date_phrase() );
97
+
98
+ return $pdf;
99
+ }
100
+
101
+ /**
102
+ * Compile the PDF file
103
+ *
104
+ * This routes to the appropriate export method
105
+ * depending on the PDF library being used.
106
+ *
107
+ * @since 0.1
108
+ */
109
+ public function export() {
110
+
111
+ $this->create_bookings_summary();
112
+
113
+ if ( $this->lib === 'tcpdf' ) {
114
+ $this->export_tcpdf();
115
+
116
+ } elseif ( $this->lib === 'mpdf' ) {
117
+ $this->export_mpdf();
118
+
119
+ } else {
120
+ do_action( 'ebcfrtb_pdf_export_' . $this->lib, $this );
121
+ }
122
+
123
+ return $this->export;
124
+ }
125
+
126
+ /**
127
+ * Deliver the PDF to the browser
128
+ *
129
+ * @since 0.1
130
+ */
131
+ public function deliver() {
132
+
133
+ // Generate the export if it's not been done yet
134
+ if ( empty( $this->export ) ) {
135
+ $this->export();
136
+ }
137
+
138
+ $filename = apply_filters( 'ebfrtb_export_pdf_filename', sanitize_file_name( $this->get_date_phrase() ) . '.pdf' );
139
+
140
+ // Clean any stray errors, warnings or notices that may have been
141
+ // printed to the buffer
142
+ ob_get_clean();
143
+
144
+ if ( $this->lib === 'tcpdf' || $this->lib === 'mpdf' ) {
145
+ $this->export->Output( $filename, 'I');
146
+ exit();
147
+
148
+ } else {
149
+ do_action( 'ebcfrtb_pdf_deliver_' . $this->lib, $this );
150
+ }
151
+
152
+ // Just in case we forget...
153
+ wp_die( __( 'An unexpected error occurred and your export request could not be fulfilled.', 'restaurant-reservations' ) );
154
+ }
155
+
156
+ /**
157
+ * Compile PDF file with TCPDF library
158
+ *
159
+ * @since 0.1
160
+ */
161
+ public function export_tcpdf() {
162
+
163
+ // Load TCPDF library
164
+ require_once( RTB_PLUGIN_DIR . '/lib/tcpdf/config/tcpdf_config.php' );
165
+ require_once( RTB_PLUGIN_DIR . '/lib/tcpdf/tcpdf.php' );
166
+
167
+ // TCPDF uses a different identifier for U.S. Letter format
168
+ if ( $this->paper_size === 'LETTER' ) {
169
+ $this->paper_size = 'ANSI_A';
170
+ }
171
+
172
+ $tcpdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, $this->paper_size, true, 'UTF-8', false);
173
+ $tcpdf = $this->set_doc_info( $tcpdf );
174
+
175
+ // set default header data
176
+ $header_title = get_bloginfo( 'sitename' );
177
+ if ( !empty( $this->query_args['location'] ) ) {
178
+ $term = get_term( $this->query_args['location'] );
179
+ if ( is_a( $term, 'WP_Term' ) ) {
180
+ $header_title = $term->name;
181
+ }
182
+ }
183
+ $tcpdf->SetHeaderData('', '', $header_title, $this->get_date_phrase(), array( 117, 117, 117 ), array( 117, 117, 117 ) );
184
+ $tcpdf->setFooterData(array(0,64,0), array(0,64,128));
185
+
186
+ // set header and footer fonts
187
+ $tcpdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
188
+ $tcpdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
189
+
190
+ // set margins
191
+ $tcpdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
192
+ $tcpdf->SetHeaderMargin(PDF_MARGIN_HEADER);
193
+ $tcpdf->SetFooterMargin(PDF_MARGIN_FOOTER);
194
+
195
+ // set auto page breaks
196
+ $tcpdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
197
+
198
+ // set image scale factor
199
+ $tcpdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
200
+
201
+ // Subset fonts for smaller doc sizes
202
+ $tcpdf->setFontSubsetting(true);
203
+
204
+ // Add a page
205
+ $tcpdf->AddPage();
206
+ $tcpdf->SetCellPadding(0);
207
+
208
+ // Generate the bookings HTML
209
+ ob_start();
210
+ $this->include_template( 'tcpdf.php' );
211
+ $html = ob_get_clean();
212
+
213
+ // Print HTML
214
+ $tcpdf->writeHTML($html, false, true, false, false, '');
215
+
216
+ $this->export = $tcpdf;
217
+
218
+ return $this->export;
219
+ }
220
+
221
+ /**
222
+ *Maybe add the table for a booking to PDF output
223
+ *
224
+ * @since 2.2
225
+ */
226
+ public function add_booking_table_to_pdf( $booking ) {
227
+ global $rtb_controller;
228
+
229
+ if ( $rtb_controller->settings->get_setting( 'enable-tables' ) ) { ?>
230
+
231
+ <p class="booking-table">
232
+ <span class="label"><?php echo __( 'Table(s)' ) . esc_html_x( ': ', 'Appears after table label in PDF exports', 'custom-fields-for-rtb' ); ?></span>
233
+ <?php echo implode( ',', $booking->table ); ?>
234
+ </p>
235
+
236
+ <?php }
237
+ }
238
+
239
+ /**
240
+ * Add custom fields to TCPDF output
241
+ *
242
+ * @since 2.0
243
+ */
244
+ public function add_custom_fields_to_tcpdf( $booking ) {
245
+ global $rtb_controller;
246
+
247
+ $fields = rtb_get_custom_fields();
248
+ $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
249
+
250
+ foreach( $fields as $field ) {
251
+
252
+ if ( $field->type == 'fieldset' || !isset( $cf[ $field->slug ] ) ) {
253
+ continue;
254
+ }
255
+
256
+ $val = $cf[ $field->slug ];
257
+ $display_val = apply_filters( 'cffrtb_display_value_tcpdf', $rtb_controller->fields->get_display_value( $val, $field, '', true ), $val, $field, $booking );
258
+
259
+ ?>
260
+
261
+ <div class="custom-field <?php echo esc_attr( $field->type . ' ' . $field->slug ); ?>">
262
+ <?php echo esc_html( $field->title ) . esc_html_x( ': ', 'Appears after field label in TCPDF exports', 'custom-fields-for-rtb' ); ?>
263
+ <?php echo $display_val ?>
264
+ </div>
265
+
266
+ <?php
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Compile PDF file with mPDF library
272
+ *
273
+ * @since 0.1
274
+ */
275
+ public function export_mpdf() {
276
+
277
+ if ( !extension_loaded( 'mbstring' ) ) {
278
+ $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>';
279
+ 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 ) );
280
+ }
281
+
282
+ // Load mPDF library
283
+ require_once( RTB_PLUGIN_DIR . '/lib/mpdf/vendor/autoload.php' );
284
+
285
+ $mpdf = new \Mpdf\Mpdf( [], $this->paper_size, '', '', 15, 15, 25 );
286
+ $mpdf = $this->set_doc_info( $mpdf );
287
+
288
+ // Support languages automatically
289
+ $mpdf->autoScriptToLang = true;
290
+ $mpdf->baseScript = 1;
291
+ $mpdf->autoVietnamese = true;
292
+ $mpdf->autoArabic = true;
293
+ $mpdf->autoLangToFont = true;
294
+
295
+ // Generate the bookings HTML
296
+ ob_start();
297
+ $this->include_template( 'mpdf.php' );
298
+ $html = ob_get_clean();
299
+
300
+ $mpdf->WriteHTML($html);
301
+
302
+ $this->export = $mpdf;
303
+
304
+ return $this->export;
305
+ }
306
+
307
+ /**
308
+ * Add custom fields to mPDF output
309
+ *
310
+ * @since 2.0
311
+ */
312
+ public function add_custom_fields_to_mpdf( $booking ) {
313
+ global $rtb_controller;
314
+
315
+ $fields = rtb_get_custom_fields();
316
+ $cf = isset( $booking->custom_fields ) ? $booking->custom_fields : array();
317
+
318
+ foreach( $fields as $field ) {
319
+
320
+ if ( $field->type == 'fieldset' || !isset( $cf[ $field->slug ] ) ) {
321
+ continue;
322
+ }
323
+
324
+ $val = $cf[ $field->slug ];
325
+ $display_val = apply_filters( 'cffrtb_display_value_mpdf', $rtb_controller->fields->get_display_value( $val, $field, '', true ), $val, $field, $booking );
326
+
327
+ ?>
328
+
329
+ <p class="custom-field <?php echo esc_attr( $field->type . ' ' . $field->slug ); ?>">
330
+ <span class="label"><?php echo esc_html( $field->title ) . esc_html_x( ': ', 'Appears after field label in mPDF exports', 'custom-fields-for-rtb' ); ?></span>
331
+ <?php echo $display_val ?>
332
+ </p>
333
+
334
+ <?php
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Calcualte the reservation and guests number by date
340
+ *
341
+ * @since 2.5.2
342
+ */
343
+ public function create_bookings_summary() {
344
+ global $rtb_controller;
345
+
346
+ foreach( $this->bookings as $booking ) {
347
+
348
+ $booking_date_idx = mysql2date( 'Y-m-d', $booking->date );
349
+
350
+ if ( ! isset( $this->bookings_summary[ $booking_date_idx ] ) ) {
351
+
352
+ $this->bookings_summary[ $booking_date_idx ] = array(
353
+ 'reservations' => 0,
354
+ 'seats' => 0
355
+ );
356
+ }
357
+
358
+ $this->bookings_summary[ $booking_date_idx ]['reservations']++;
359
+
360
+ $this->bookings_summary[ $booking_date_idx ]['seats'] += $booking->party;
361
+ }
362
+ }
363
+
364
+ }
365
+ } // endif
includes/ExportHandler.class.php CHANGED
@@ -1,309 +1,309 @@
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
- $args = array(
132
- 'orderby' => 'date',
133
- 'order' => 'ASC',
134
- );
135
-
136
- $query = new rtbQuery( $args, 'export' );
137
- $query->parse_request_args();
138
- $query->prepare_args();
139
- $query->args['posts_per_page'] = -1;
140
-
141
- // Show an error if they forgot to enter dates
142
- if ( isset( $query->args['date_range'] ) && $query->args['date_range'] == 'dates' && !isset( $query->args['start_date'] ) && empty( $query->args['end_date'] ) ) {
143
- 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' ) );
144
- }
145
-
146
- // Retrieve bookings
147
- $bookings = $query->get_bookings();
148
-
149
- if ( empty( $bookings ) ) {
150
- wp_die( __( 'There are no bookings which match your export request.', 'restaurant-reservations' ) );
151
- }
152
-
153
- $export = new $export_class( $bookings, array( 'query_args' => $query->args ) );
154
- $export->deliver(); // calls wp_die()
155
- }
156
-
157
- /**
158
- * Print the export button above and below the table
159
- *
160
- * @since 0.1.0
161
- */
162
- public function print_button( $pos ) {
163
- global $rtb_controller;
164
-
165
- if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
166
-
167
- ?>
168
-
169
- <div class="alignleft actions ebfrtb-actions">
170
- <a href="#" class="button ebfrtb-export-button">
171
- <span class="dashicons dashicons-media-spreadsheet"></span>
172
- <?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?>
173
- </a>
174
- </div>
175
-
176
- <?php
177
- }
178
-
179
- /**
180
- * Print the export options modal in the footer
181
- * of the bookings page
182
- *
183
- * @since 0.1
184
- */
185
- public function print_export_options_modal() {
186
-
187
- global $rtb_controller;
188
-
189
- if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
190
- ?>
191
-
192
- <!-- Export bookings options modal -->
193
- <div id="ebfrtb-options-modal" class="rtb-admin-modal">
194
- <div class="ebfrtb-options-form rtb-container">
195
- <form>
196
-
197
- <div class="title">
198
- <h2>
199
- <?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?>
200
- </h2>
201
- </div>
202
-
203
- <fieldset>
204
- <div class="type">
205
- <label for="type" class="hidden-label">
206
- <?php esc_html_e( 'Type', 'restaurant-reservations' ); ?>
207
- </label>
208
- <select name="type">
209
- <?php foreach( $this->export_types as $type => $export ) : ?>
210
- <option value="<?php echo esc_attr( $type ); ?>">
211
- <?php echo esc_html( $export['label'] ); ?>
212
- </option>
213
- <?php endforeach; ?>
214
- </select>
215
- </div>
216
-
217
- <?php if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) : ?>
218
- <div class="location">
219
- <label for="location" class="hidden-label">
220
- <?php esc_html_e( 'Location', 'restaurant-reservations' ); ?>
221
- </label>
222
- <select name="location">
223
- <option value=""><?php esc_html_e( 'All locations', 'restaurant-reservations' ); ?></option>
224
- <?php
225
- $locations = $rtb_controller->locations->get_location_options();
226
- foreach( $locations as $id => $name ) :
227
- ?>
228
- <option value="<?php echo absint( $id ); ?>"><?php echo esc_attr( $name ); ?></option>
229
- <?php
230
- endforeach;
231
- ?>
232
- </select>
233
- </div>
234
- <?php endif; ?>
235
-
236
- <div class="date-range">
237
- <input type="hidden" name="date_range">
238
- <label for="date-range" class="hidden-label">
239
- <?php esc_html_e( 'Date Range', 'restaurant-reservations' ); ?>
240
- </label>
241
- <div class="selector">
242
- <ul class="options">
243
- <li>
244
- <a href="#" data-type="today">
245
- <?php esc_html_e( 'Today', 'restaurant-reservations' ); ?>
246
- </a>
247
- </li>
248
- <li>
249
- <a href="#" data-type="upcoming">
250
- <?php esc_html_e( 'Upcoming', 'restaurant-reservations' ); ?>
251
- </a>
252
- </li>
253
- <li>
254
- <a href="#" data-type="dates">
255
- <?php esc_html_e( 'Date Range', 'restaurant-reservations' ); ?>
256
- </a>
257
- </li>
258
- </ul>
259
- <div class="today">
260
- <?php esc_html_e( "Today's bookings", 'restaurant-reservations' ); ?>
261
- </div>
262
- <div class="upcoming">
263
- <?php esc_html_e( 'All upcoming bookings', 'restaurant-reservations' ); ?>
264
- </div>
265
- <div class="dates">
266
- <div class="date-start">
267
- <label for="ebfrtb-start-date">
268
- <?php esc_html_e( 'Start', 'restaurant-reservations' ); ?>
269
- </label>
270
- <input type="text" name="start_date" id="ebfrtb-start-date" value="<?php echo ! empty( $_GET['start_date'] ) ? esc_attr( $_GET['start_date'] ) : ''; ?>">
271
- </div>
272
- <div class="date-end">
273
- <label for="ebfrtb-end-date">
274
- <?php esc_html_e( 'End', 'restaurant-reservations' ); ?>
275
- </label>
276
- <input type="text" name="end_date" id="ebfrtb-end-date" value="<?php echo ! empty( $_GET['end_date'] ) ? esc_attr( $_GET['end_date'] ) : ''; ?>">
277
- </div>
278
- </div>
279
- </div>
280
- </div>
281
-
282
- <div class="status">
283
- <?php foreach( $rtb_controller->cpts->booking_statuses as $key => $status ) : ?>
284
- <label>
285
- <input type="checkbox" name="status" value="<?php echo esc_attr( $key ); ?>" <?php checked( $key, 'confirmed' ); ?>>
286
- <?php echo esc_html( $status['label'] ); ?>
287
- </label>
288
- <?php endforeach; ?>
289
- </div>
290
-
291
- </fieldset>
292
- <button type="submit" class="button button-primary">
293
- <?php esc_html_e( 'Export', 'restaurant-reservations' ); ?>
294
- </button>
295
- <a href="#" class="button" id="ebfrtb-cancel-export-modal">
296
- <?php esc_html_e( 'Cancel', 'restaurant-reservations' ); ?>
297
- </a>
298
- <a href="<?php echo admin_url( 'admin.php?page=rtb-settings&tab=rtb-export-tab' ); ?>" class="settings">
299
- <?php esc_html_e( 'Settings', 'restaurant-reservations' ); ?>
300
- </a>
301
- </form>
302
- </div>
303
- </div>
304
-
305
- <?php
306
- }
307
-
308
- }
309
- } // 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
+ $args = array(
132
+ 'orderby' => 'date',
133
+ 'order' => 'ASC',
134
+ );
135
+
136
+ $query = new rtbQuery( $args, 'export' );
137
+ $query->parse_request_args();
138
+ $query->prepare_args();
139
+ $query->args['posts_per_page'] = -1;
140
+
141
+ // Show an error if they forgot to enter dates
142
+ if ( isset( $query->args['date_range'] ) && $query->args['date_range'] == 'dates' && !isset( $query->args['start_date'] ) && empty( $query->args['end_date'] ) ) {
143
+ 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' ) );
144
+ }
145
+
146
+ // Retrieve bookings
147
+ $bookings = $query->get_bookings();
148
+
149
+ if ( empty( $bookings ) ) {
150
+ wp_die( __( 'There are no bookings which match your export request.', 'restaurant-reservations' ) );
151
+ }
152
+
153
+ $export = new $export_class( $bookings, array( 'query_args' => $query->args ) );
154
+ $export->deliver(); // calls wp_die()
155
+ }
156
+
157
+ /**
158
+ * Print the export button above and below the table
159
+ *
160
+ * @since 0.1.0
161
+ */
162
+ public function print_button( $pos ) {
163
+ global $rtb_controller;
164
+
165
+ if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
166
+
167
+ ?>
168
+
169
+ <div class="alignleft actions ebfrtb-actions">
170
+ <a href="#" class="button ebfrtb-export-button">
171
+ <span class="dashicons dashicons-media-spreadsheet"></span>
172
+ <?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?>
173
+ </a>
174
+ </div>
175
+
176
+ <?php
177
+ }
178
+
179
+ /**
180
+ * Print the export options modal in the footer
181
+ * of the bookings page
182
+ *
183
+ * @since 0.1
184
+ */
185
+ public function print_export_options_modal() {
186
+
187
+ global $rtb_controller;
188
+
189
+ if (! $rtb_controller->permissions->check_permission( 'export' ) ) { return; }
190
+ ?>
191
+
192
+ <!-- Export bookings options modal -->
193
+ <div id="ebfrtb-options-modal" class="rtb-admin-modal">
194
+ <div class="ebfrtb-options-form rtb-container">
195
+ <form>
196
+
197
+ <div class="title">
198
+ <h2>
199
+ <?php esc_html_e( 'Export Bookings', 'restaurant-reservations' ); ?>
200
+ </h2>
201
+ </div>
202
+
203
+ <fieldset>
204
+ <div class="type">
205
+ <label for="type" class="hidden-label">
206
+ <?php esc_html_e( 'Type', 'restaurant-reservations' ); ?>
207
+ </label>
208
+ <select name="type">
209
+ <?php foreach( $this->export_types as $type => $export ) : ?>
210
+ <option value="<?php echo esc_attr( $type ); ?>">
211
+ <?php echo esc_html( $export['label'] ); ?>
212
+ </option>
213
+ <?php endforeach; ?>
214
+ </select>
215
+ </div>
216
+
217
+ <?php if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) : ?>
218
+ <div class="location">
219
+ <label for="location" class="hidden-label">
220
+ <?php esc_html_e( 'Location', 'restaurant-reservations' ); ?>
221
+ </label>
222
+ <select name="location">
223
+ <option value=""><?php esc_html_e( 'All locations', 'restaurant-reservations' ); ?></option>
224
+ <?php
225
+ $locations = $rtb_controller->locations->get_location_options();
226
+ foreach( $locations as $id => $name ) :
227
+ ?>
228
+ <option value="<?php echo absint( $id ); ?>"><?php echo esc_attr( $name ); ?></option>
229
+ <?php
230
+ endforeach;
231
+ ?>
232
+ </select>
233
+ </div>
234
+ <?php endif; ?>
235
+
236
+ <div class="date-range">
237
+ <input type="hidden" name="date_range">
238
+ <label for="date-range" class="hidden-label">
239
+ <?php esc_html_e( 'Date Range', 'restaurant-reservations' ); ?>
240
+ </label>
241
+ <div class="selector">
242
+ <ul class="options">
243
+ <li>
244
+ <a href="#" data-type="today">
245
+ <?php esc_html_e( 'Today', 'restaurant-reservations' ); ?>
246
+ </a>
247
+ </li>
248
+ <li>
249
+ <a href="#" data-type="upcoming">
250
+ <?php esc_html_e( 'Upcoming', 'restaurant-reservations' ); ?>
251
+ </a>
252
+ </li>
253
+ <li>
254
+ <a href="#" data-type="dates">
255
+ <?php esc_html_e( 'Date Range', 'restaurant-reservations' ); ?>
256
+ </a>
257
+ </li>
258
+ </ul>
259
+ <div class="today">
260
+ <?php esc_html_e( "Today's bookings", 'restaurant-reservations' ); ?>
261
+ </div>
262
+ <div class="upcoming">
263
+ <?php esc_html_e( 'All upcoming bookings', 'restaurant-reservations' ); ?>
264
+ </div>
265
+ <div class="dates">
266
+ <div class="date-start">
267
+ <label for="ebfrtb-start-date">
268
+ <?php esc_html_e( 'Start', 'restaurant-reservations' ); ?>
269
+ </label>
270
+ <input type="text" name="start_date" id="ebfrtb-start-date" value="<?php echo ! empty( $_GET['start_date'] ) ? esc_attr( $_GET['start_date'] ) : ''; ?>">
271
+ </div>
272
+ <div class="date-end">
273
+ <label for="ebfrtb-end-date">
274
+ <?php esc_html_e( 'End', 'restaurant-reservations' ); ?>
275
+ </label>
276
+ <input type="text" name="end_date" id="ebfrtb-end-date" value="<?php echo ! empty( $_GET['end_date'] ) ? esc_attr( $_GET['end_date'] ) : ''; ?>">
277
+ </div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+
282
+ <div class="status">
283
+ <?php foreach( $rtb_controller->cpts->booking_statuses as $key => $status ) : ?>
284
+ <label>
285
+ <input type="checkbox" name="status" value="<?php echo esc_attr( $key ); ?>" <?php checked( $key, 'confirmed' ); ?>>
286
+ <?php echo esc_html( $status['label'] ); ?>
287
+ </label>
288
+ <?php endforeach; ?>
289
+ </div>
290
+
291
+ </fieldset>
292
+ <button type="submit" class="button button-primary">
293
+ <?php esc_html_e( 'Export', 'restaurant-reservations' ); ?>
294
+ </button>
295
+ <a href="#" class="button" id="ebfrtb-cancel-export-modal">
296
+ <?php esc_html_e( 'Cancel', 'restaurant-reservations' ); ?>
297
+ </a>
298
+ <a href="<?php echo admin_url( 'admin.php?page=rtb-settings&tab=rtb-export-tab' ); ?>" class="settings">
299
+ <?php esc_html_e( 'Settings', 'restaurant-reservations' ); ?>
300
+ </a>
301
+ </form>
302
+ </div>
303
+ </div>
304
+
305
+ <?php
306
+ }
307
+
308
+ }
309
+ } // endif;
includes/Field.class.php CHANGED
@@ -1,611 +1,611 @@
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 ] )
543
- ? ( is_array( $_POST['rtb-' . $this->slug ] )
544
- ? array_map( 'absint', $_POST['rtb-' . $this->slug ] )
545
- : sanitize_text_field( $_POST['rtb-' . $this->slug] ) )
546
- : '';
547
-
548
- // Skip empty fields but do not skip checkboxes.
549
- // required checks are performed by base plugin validation
550
-
551
- $checkbox = 'options' === $this->type && 'checkbox' === $this->subtype;
552
-
553
- if ( ( is_string( $input ) && trim( $input ) == '' ) ||
554
- ( is_array( $input ) && empty( $input ) ) ) {
555
-
556
- // When a checkbox is unselected, it will not override the previously selected
557
- // value because HTML Form will not submit empty checkboxes
558
- if($checkbox) {
559
- $input = [];
560
- }
561
- else {
562
- return;
563
- }
564
- }
565
-
566
- // Option fields
567
- if ( $this->type == 'options' ) {
568
-
569
- if ( !is_array( $input ) ) {
570
- $input = array( $input );
571
- }
572
-
573
- foreach( $input as $input_i ) {
574
- if ( !$this->is_valid_option( $input_i ) ) {
575
- $booking->validation_errors[] = array(
576
- 'field' => $this->slug,
577
- 'post_variable' => $input,
578
- 'message' => __( 'The option you selected is not valid. Please make another choice.', 'custom-fields-for-rtb' )
579
- );
580
-
581
- return;
582
- }
583
- }
584
-
585
- if ( $this->subtype === 'select' || $this->subtype === 'radio' ) {
586
- $val = absint( $input[0] );
587
- if ( isset( $this->options[ $val ] ) ) {
588
- $booking->custom_fields[ $this->slug ] = $val;
589
- }
590
- } elseif ( $this->subtype === 'checkbox' ) {
591
- $val = array();
592
- foreach( $input as $input_i ) {
593
- if ( isset( $this->options[ $input_i ] ) ) {
594
- $val[] = $input_i;
595
- }
596
- }
597
- $booking->custom_fields[ $this->slug ] = $val;
598
- }
599
-
600
- // Confirm fields (always true if we've reached this stage)
601
- } elseif ( $this->type === 'confirm' ) {
602
- $booking->custom_fields[ $this->slug ] = true;
603
-
604
- // Text fields just need to be sanitized
605
- } elseif ( $this->type === 'text' ) {
606
- $booking->custom_fields[ $this->slug ] = sanitize_text_field( $input );
607
- }
608
- }
609
-
610
- }
611
- } // 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 ] )
543
+ ? ( is_array( $_POST['rtb-' . $this->slug ] )
544
+ ? array_map( 'absint', $_POST['rtb-' . $this->slug ] )
545
+ : sanitize_text_field( $_POST['rtb-' . $this->slug] ) )
546
+ : '';
547
+
548
+ // Skip empty fields but do not skip checkboxes.
549
+ // required checks are performed by base plugin validation
550
+
551
+ $checkbox = 'options' === $this->type && 'checkbox' === $this->subtype;
552
+
553
+ if ( ( is_string( $input ) && trim( $input ) == '' ) ||
554
+ ( is_array( $input ) && empty( $input ) ) ) {
555
+
556
+ // When a checkbox is unselected, it will not override the previously selected
557
+ // value because HTML Form will not submit empty checkboxes
558
+ if($checkbox) {
559
+ $input = [];
560
+ }
561
+ else {
562
+ return;
563
+ }
564
+ }
565
+
566
+ // Option fields
567
+ if ( $this->type == 'options' ) {
568
+
569
+ if ( !is_array( $input ) ) {
570
+ $input = array( $input );
571
+ }
572
+
573
+ foreach( $input as $input_i ) {
574
+ if ( !$this->is_valid_option( $input_i ) ) {
575
+ $booking->validation_errors[] = array(
576
+ 'field' => $this->slug,
577
+ 'post_variable' => $input,
578
+ 'message' => __( 'The option you selected is not valid. Please make another choice.', 'custom-fields-for-rtb' )
579
+ );
580
+
581
+ return;
582
+ }
583
+ }
584
+
585
+ if ( $this->subtype === 'select' || $this->subtype === 'radio' ) {
586
+ $val = absint( $input[0] );
587
+ if ( isset( $this->options[ $val ] ) ) {
588
+ $booking->custom_fields[ $this->slug ] = $val;
589
+ }
590
+ } elseif ( $this->subtype === 'checkbox' ) {
591
+ $val = array();
592
+ foreach( $input as $input_i ) {
593
+ if ( isset( $this->options[ $input_i ] ) ) {
594
+ $val[] = $input_i;
595
+ }
596
+ }
597
+ $booking->custom_fields[ $this->slug ] = $val;
598
+ }
599
+
600
+ // Confirm fields (always true if we've reached this stage)
601
+ } elseif ( $this->type === 'confirm' ) {
602
+ $booking->custom_fields[ $this->slug ] = true;
603
+
604
+ // Text fields just need to be sanitized
605
+ } elseif ( $this->type === 'text' ) {
606
+ $booking->custom_fields[ $this->slug ] = sanitize_text_field( $input );
607
+ }
608
+ }
609
+
610
+ }
611
+ } // endif;
includes/Helper.class.php CHANGED
@@ -1,104 +1,104 @@
1
- <?php
2
- if ( !defined( 'ABSPATH' ) ) exit;
3
-
4
- if ( !class_exists( 'rtbHelper' ) ) {
5
- /**
6
- * Class to to provide helper functions
7
- *
8
- * @since 2.4.10
9
- */
10
- class rtbHelper {
11
-
12
- // Hold the class instance.
13
- private static $instance = null;
14
-
15
- /**
16
- * The constructor is private
17
- * to prevent initiation with outer code.
18
- *
19
- **/
20
- private function __construct() {}
21
-
22
- /**
23
- * The object is created from within the class itself
24
- * only if the class has no instance.
25
- */
26
- public static function getInstance() {
27
-
28
- if ( self::$instance == null ) {
29
-
30
- self::$instance = new rtbHelper();
31
- }
32
-
33
- return self::$instance;
34
- }
35
-
36
- /**
37
- * Handle ajax requests from the admin bookings area from logged out users
38
- * @since 2.4.10
39
- */
40
- public static function admin_nopriv_ajax() {
41
-
42
- wp_send_json_error(
43
- array(
44
- 'error' => 'loggedout',
45
- 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( admin_url( 'admin.php?page=rtb-dashboard' ) ) . '">', '</a>' ),
46
- )
47
- );
48
- }
49
-
50
- /**
51
- * Handle ajax requests where an invalid nonce is passed with the request
52
- * @since 2.4.10
53
- */
54
- public static function bad_nonce_ajax() {
55
-
56
- wp_send_json_error(
57
- array(
58
- 'error' => 'badnonce',
59
- 'msg' => __( 'The request has been rejected because it does not appear to have come from this site.', 'restaurant-reservations' ),
60
- )
61
- );
62
- }
63
-
64
- /**
65
- * sanitize_text_field for array's each value, recusivly
66
- * @since 2.4.10
67
- */
68
- public static function sanitize_text_field_recursive( $input ) {
69
-
70
- if ( is_array( $input ) || is_object( $input ) ) {
71
-
72
- foreach ( $input as $key => $value ) {
73
-
74
- $input[ sanitize_key( $key ) ] = self::sanitize_text_field_recursive( $value );
75
- }
76
-
77
- return $input;
78
- }
79
-
80
- return sanitize_text_field( $input );
81
- }
82
-
83
- /**
84
- * sanitize_recursive for array's each value by applying given sanitization
85
- * method, recusivly
86
- * @since 2.4.10
87
- */
88
- public static function sanitize_recursive( $input, $method ) {
89
-
90
- if ( is_array( $input ) || is_object( $input ) ) {
91
-
92
- foreach ( $input as $key => $value ) {
93
-
94
- $input[ sanitize_key( $key ) ] = self::sanitize_recursive( $value, $method);
95
- }
96
-
97
- return $input;
98
- }
99
-
100
- return $method( $input );
101
- }
102
- }
103
-
104
  }
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbHelper' ) ) {
5
+ /**
6
+ * Class to to provide helper functions
7
+ *
8
+ * @since 2.4.10
9
+ */
10
+ class rtbHelper {
11
+
12
+ // Hold the class instance.
13
+ private static $instance = null;
14
+
15
+ /**
16
+ * The constructor is private
17
+ * to prevent initiation with outer code.
18
+ *
19
+ **/
20
+ private function __construct() {}
21
+
22
+ /**
23
+ * The object is created from within the class itself
24
+ * only if the class has no instance.
25
+ */
26
+ public static function getInstance() {
27
+
28
+ if ( self::$instance == null ) {
29
+
30
+ self::$instance = new rtbHelper();
31
+ }
32
+
33
+ return self::$instance;
34
+ }
35
+
36
+ /**
37
+ * Handle ajax requests from the admin bookings area from logged out users
38
+ * @since 2.4.10
39
+ */
40
+ public static function admin_nopriv_ajax() {
41
+
42
+ wp_send_json_error(
43
+ array(
44
+ 'error' => 'loggedout',
45
+ 'msg' => sprintf( __( 'You have been logged out. Please %slogin again%s.', 'restaurant-reservations' ), '<a href="' . wp_login_url( admin_url( 'admin.php?page=rtb-dashboard' ) ) . '">', '</a>' ),
46
+ )
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Handle ajax requests where an invalid nonce is passed with the request
52
+ * @since 2.4.10
53
+ */
54
+ public static function bad_nonce_ajax() {
55
+
56
+ wp_send_json_error(
57
+ array(
58
+ 'error' => 'badnonce',
59
+ 'msg' => __( 'The request has been rejected because it does not appear to have come from this site.', 'restaurant-reservations' ),
60
+ )
61
+ );
62
+ }
63
+
64
+ /**
65
+ * sanitize_text_field for array's each value, recusivly
66
+ * @since 2.4.10
67
+ */
68
+ public static function sanitize_text_field_recursive( $input ) {
69
+
70
+ if ( is_array( $input ) || is_object( $input ) ) {
71
+
72
+ foreach ( $input as $key => $value ) {
73
+
74
+ $input[ sanitize_key( $key ) ] = self::sanitize_text_field_recursive( $value );
75
+ }
76
+
77
+ return $input;
78
+ }
79
+
80
+ return sanitize_text_field( $input );
81
+ }
82
+
83
+ /**
84
+ * sanitize_recursive for array's each value by applying given sanitization
85
+ * method, recusivly
86
+ * @since 2.4.10
87
+ */
88
+ public static function sanitize_recursive( $input, $method ) {
89
+
90
+ if ( is_array( $input ) || is_object( $input ) ) {
91
+
92
+ foreach ( $input as $key => $value ) {
93
+
94
+ $input[ sanitize_key( $key ) ] = self::sanitize_recursive( $value, $method);
95
+ }
96
+
97
+ return $input;
98
+ }
99
+
100
+ return $method( $input );
101
+ }
102
+ }
103
+
104
  }
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,425 +1,425 @@
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_nopriv_rtb-welcome-add-menu-page' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
20
- add_action( 'wp_ajax_rtb-welcome-add-menu-page', array( $this, 'add_reservations_page' ) );
21
- add_action( 'wp_ajax_nopriv_rtb-welcome-set-schedule' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
22
- add_action( 'wp_ajax_rtb-welcome-set-schedule', array( $this, 'set_schedule' ) );
23
- add_action( 'wp_ajax_nopriv_rtb-welcome-set-options' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
24
- add_action( 'wp_ajax_rtb-welcome-set-options', array( $this, 'set_options' ) );
25
- }
26
-
27
- public function redirect() {
28
- if ( ! get_transient( 'rtb-getting-started' ) )
29
- return;
30
-
31
- delete_transient( 'rtb-getting-started' );
32
-
33
- if ( is_network_admin() || isset( $_GET['activate-multi'] ) )
34
- return;
35
-
36
- $bookings = get_posts( array( 'post_type' => 'rtb-booking', 'post_status' => 'any' ) );
37
- if ( ! empty( $bookings ) ) {
38
- set_transient( 'rtb-admin-install-notice', true, 5 );
39
- return;
40
- }
41
-
42
- wp_safe_redirect( admin_url( 'index.php?page=rtb-getting-started' ) );
43
- exit;
44
- }
45
-
46
- public function register_install_screen() {
47
- add_dashboard_page(
48
- esc_html__( 'Five-Star Restaurant Reservations - Welcome!', 'restaurant-reservations' ),
49
- esc_html__( 'Five-Star Restaurant Reservations - Welcome!', 'restaurant-reservations' ),
50
- 'manage_options',
51
- 'rtb-getting-started',
52
- array($this, 'display_install_screen')
53
- );
54
- }
55
-
56
- public function hide_install_screen_menu_item() {
57
- remove_submenu_page( 'index.php', 'rtb-getting-started' );
58
- }
59
-
60
- public function add_reservations_page() {
61
-
62
- // Authenticate request
63
- if ( !check_ajax_referer( 'rtb-getting-started', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
64
- rtbHelper::admin_nopriv_ajax();
65
- }
66
-
67
- $reservations_page = wp_insert_post(array(
68
- 'post_title' => (isset($_POST['reservations_page_title']) ? sanitize_text_field( $_POST['reservations_page_title'] ) : ''),
69
- 'post_content' => '',
70
- 'post_status' => 'publish',
71
- 'post_type' => 'page'
72
- ));
73
-
74
- if ( $reservations_page ) {
75
- $rtb_options = get_option( 'rtb-settings' );
76
- $rtb_options['booking-page'] = $reservations_page;
77
- update_option( 'rtb-settings', $rtb_options );
78
- }
79
-
80
- exit();
81
- }
82
-
83
- public function set_schedule() {
84
-
85
- // Authenticate request
86
- if ( !check_ajax_referer( 'rtb-getting-started', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
87
- rtbHelper::admin_nopriv_ajax();
88
- }
89
-
90
- $rtb_options = get_option( 'rtb-settings' );
91
-
92
- $rtb_options['schedule-open'] = rtbHelper::sanitize_text_field_recursive( $_POST['schedule_open'] );
93
-
94
- update_option( 'rtb-settings', $rtb_options );
95
-
96
- exit();
97
- }
98
-
99
- public function set_options() {
100
-
101
- // Authenticate request
102
- if ( !check_ajax_referer( 'rtb-getting-started', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
103
- rtbHelper::admin_nopriv_ajax();
104
- }
105
-
106
- $rtb_options = get_option( 'rtb-settings' );
107
- $rtb_options['party-size-min'] = sanitize_text_field( $_POST['party_size_min'] );
108
- $rtb_options['party-size'] = sanitize_text_field( $_POST['party_size'] );
109
- $rtb_options['early-bookings'] = sanitize_text_field( $_POST['early_bookings'] );
110
- $rtb_options['late-bookings'] = sanitize_text_field( $_POST['late_bookings'] );
111
- $rtb_options['time-interval'] = sanitize_text_field( $_POST['time_interval'] );
112
- update_option( 'rtb-settings', $rtb_options );
113
-
114
- exit();
115
- }
116
-
117
- function admin_enqueue() {
118
-
119
- if ( ! isset( $_GET['page'] ) or $_GET['page'] != 'rtb-getting-started' ) { return; }
120
-
121
- wp_enqueue_style( 'rtb-admin-css', RTB_PLUGIN_URL . '/lib/simple-admin-pages/css/admin.css', array(), RTB_VERSION );
122
- wp_enqueue_style( 'rtb-welcome-screen', RTB_PLUGIN_URL . '/assets/css/admin-rtb-welcome-screen.css', array(), RTB_VERSION );
123
- wp_enqueue_style( 'pickadate-default', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.css', array(), RTB_VERSION );
124
- wp_enqueue_style( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.date.css', array(), RTB_VERSION );
125
- wp_enqueue_style( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.time.css', array(), RTB_VERSION );
126
-
127
- wp_enqueue_script( 'rtb-getting-started', RTB_PLUGIN_URL . '/assets/js/admin-rtb-welcome-screen.js', array('jquery'), RTB_VERSION );
128
- wp_localize_script(
129
- 'rtb-getting-started',
130
- 'rtb_getting_started',
131
- array(
132
- 'nonce' => wp_create_nonce( 'rtb-getting-started' )
133
- )
134
- );
135
-
136
- wp_enqueue_script( 'pickadate', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.js', array('jquery'), RTB_VERSION, true );
137
- wp_enqueue_script( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.date.js', array('jquery'), RTB_VERSION, true );
138
- wp_enqueue_script( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.time.js', array('jquery'), RTB_VERSION, true );
139
- wp_enqueue_script( 'pickadate-legacy', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/legacy.js', array('jquery'), RTB_VERSION, true );
140
- wp_enqueue_script( 'sap-scheduler', RTB_PLUGIN_URL . '/lib/simple-admin-pages/js/scheduler.js', array('jquery'), RTB_VERSION, true );
141
-
142
- $sap_scheduler_settings[ 'schedule-open' ] = array(
143
- 'time_interval' => 15,
144
- 'time_format' => 'h:i A',
145
- 'date_format' => 'd mmmm, yyyy',
146
- 'template' => $this->get_template(),
147
- 'weekdays' => array(
148
- 'monday' => 'Mo',
149
- 'tuesday' => 'Tu',
150
- 'wednesday' => 'We',
151
- 'thursday' => 'Th',
152
- 'friday' => 'Fr',
153
- 'saturday' => 'Sa',
154
- 'sunday' => 'Su',
155
- ),
156
- 'weeks' => array(
157
- 'first' => '1st',
158
- 'second' => '2nd',
159
- 'third' => '3rd',
160
- 'fourth' => '4th',
161
- 'last' => 'last',
162
- ),
163
- 'disable_weekdays' => false,
164
- 'disable_weeks' => true,
165
- 'disable_date' => true,
166
- 'disable_time' => false,
167
- 'disable_multiple' => false,
168
- 'summaries' => array(
169
- 'never' => __( 'Never', 'restaurant-reservations' ),
170
- 'weekly_always' => __( 'Every day', 'restaurant-reservations' ),
171
- 'monthly_weekdays' => sprintf( __( '%s on the %s week of the month', 'restaurant-reservations' ), '{days}', '{weeks}' ),
172
- 'monthly_weeks' => sprintf( __( '%s week of the month', 'restaurant-reservations' ), '{weeks}' ),
173
- 'all_day' => __( 'All day', 'restaurant-reservations' ),
174
- 'before' => __( 'Ends at', 'restaurant-reservations' ),
175
- 'after' => __( 'Starts at', 'restaurant-reservations' ),
176
- 'separator' => __( '&mdash', 'restaurant-reservations' ),
177
- ),
178
- );
179
-
180
- // This gets called multiple times, but only the last call is actually
181
- // pushed to the script.
182
- wp_localize_script(
183
- 'sap-scheduler',
184
- 'sap_scheduler',
185
- array(
186
- 'settings' => $sap_scheduler_settings
187
- )
188
- );
189
- }
190
-
191
- public function display_install_screen() { ?>
192
- <div class='rtb-welcome-screen'>
193
- <?php if (!isset($_GET['exclude'])) { ?>
194
- <div class='rtb-welcome-screen-header'>
195
- <h1><?php _e('Welcome to the Five-Star Restaurant Reservations Plugin', 'restaurant-reservations'); ?></h1>
196
- <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>
197
- </div>
198
- <?php } ?>
199
- <?php if (!isset($_GET['exclude'])) { ?>
200
- <div class='rtb-welcome-screen-box rtb-welcome-screen-reservations_page rtb-welcome-screen-open' data-screen='reservations_page'>
201
- <h2><?php _e('Add a Reservations Page', 'restaurant-reservations'); ?></h2>
202
- <div class='rtb-welcome-screen-box-content'>
203
- <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>
204
- <div class='rtb-welcome-screen-menu-page'>
205
- <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>
206
- <div class='rtb-welcome-screen-add-reservations-page-button' data-nextaction='schedule_open'><?php _e('Create Page', 'restaurant-reservations'); ?></div>
207
- </div>
208
- <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>
209
- <div class='clear'></div>
210
- </div>
211
- </div>
212
- <?php } ?>
213
- <div class='rtb-welcome-screen-box rtb-welcome-screen-schedule_open' data-screen='schedule_open'>
214
- <h2><?php echo (isset($_GET['exclude']) ? '1.' : '2.') . __(' Create Booking Schedule', 'restaurant-reservations'); ?></h2>
215
- <div class='rtb-welcome-screen-box-content'>
216
- <p><?php _e('Choose what times each week your restaurant is available to book reservations.', 'restaurant-reservations'); ?></p>
217
- <div class='rtb-welcome-screen-created-schedule-open'>
218
- <div class="sap-scheduler" id="schedule-open"></div>
219
- <div class="sap-add-scheduler">
220
- <a href="#" class="button">
221
- <?php _e('Add new scheduling rule', 'restaurant-reservations' ); ?>
222
- </a>
223
- </div>
224
- </div>
225
- <div class='rtb-welcome-screen-save-schedule-open-button'><?php _e('Save Schedule', 'restaurant-reservations'); ?></div>
226
- <div class="rtb-welcome-clear"></div>
227
- <div class='rtb-welcome-screen-next-button' data-nextaction='options'><?php _e('Next Step', 'restaurant-reservations'); ?></div>
228
- <div class='rtb-welcome-screen-previous-button' data-previousaction='reservations_page'><?php _e('Previous Step', 'restaurant-reservations'); ?></div>
229
- <div class='clear'></div>
230
- </div>
231
- </div>
232
-
233
- <div class='rtb-welcome-screen-box rtb-welcome-screen-options' data-screen='options'>
234
- <h2><?php echo (isset($_GET['exclude']) ? '2.' : '3.') . __(' Set Key Options', 'restaurant-reservations'); ?></h2>
235
- <div class='rtb-welcome-screen-box-content'>
236
- <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>
237
- <div class='rtb-welcome-screen-option'>
238
- <label><?php _e('Min Party Size:', 'restaurant-reservations'); ?></label>
239
- <select name='min-party-size'>
240
- <?php for ( $i = 1; $i <= 100; $i++ ) { ?>
241
- <option value='<?php echo $i; ?>'><?php echo $i; ?></option>
242
- <?php } ?>
243
- </select>
244
- </div>
245
- <div class='rtb-welcome-screen-option'>
246
- <label><?php _e('Max Party Size:', 'restaurant-reservations'); ?></label>
247
- <select name='party-size'>
248
- <option value='0'><?php _e('Any Size', 'restaurant-reservations' ); ?></option>
249
- <?php for ( $i = 1; $i <= 100; $i++ ) { ?>
250
- <option value='<?php echo $i; ?>'><?php echo $i; ?></option>
251
- <?php } ?>
252
- </select>
253
- </div>
254
- <div class='rtb-welcome-screen-option'>
255
- <label><?php _e('Early Bookings:', 'restaurant-reservations'); ?></label>
256
- <select name='early-bookings'>
257
- <option><?php _e('Any Time', 'restaurant-reservations' ); ?></option>
258
- <option value='1'><?php _e('From 1 day in advance', 'restaurant-reservations' ); ?></option>
259
- <option value='7'><?php _e('From 1 week in advance', 'restaurant-reservations' ); ?></option>
260
- <option value='14'><?php _e('From 2 weeks in advance', 'restaurant-reservations' ); ?></option>
261
- <option value='30'><?php _e('From 30 days in advance', 'restaurant-reservations' ); ?></option>
262
- <option value='90'><?php _e('From 90 days in advance', 'restaurant-reservations' ); ?></option>
263
- </select>
264
- </div>
265
- <div class='rtb-welcome-screen-option'>
266
- <label><?php _e('Late Bookings:', 'restaurant-reservations'); ?></label>
267
- <select name='late-bookings'>
268
- <option><?php _e('Up to the last minute', 'restaurant-reservations' ); ?></option>
269
- <option value='15'><?php _e('At least 15 minutes in advance', 'restaurant-reservations' ); ?></option>
270
- <option value='30'><?php _e('At least 30 minutes in advance', 'restaurant-reservations' ); ?></option>
271
- <option value='45'><?php _e('At least 45 minutes in advance', 'restaurant-reservations' ); ?></option>
272
- <option value='60'><?php _e('At least 1 hour in advance', 'restaurant-reservations' ); ?></option>
273
- <option value='240'><?php _e('At least 4 hours in advance', 'restaurant-reservations' ); ?></option>
274
- <option value='1440'><?php _e('At least 24 hours in advance', 'restaurant-reservations' ); ?></option>
275
- <option value='same_day'><?php _e('Block same-day-bookings', 'restaurant-reservations' ); ?></option>
276
- </select>
277
- </div>
278
- <div class='rtb-welcome-screen-option'>
279
- <label><?php _e('Time Interval:', 'restaurant-reservations'); ?></label>
280
- <select name='time-interval'>
281
- <option value='30'><?php _e('Every 30 minutes', 'restaurant-reservations' ); ?></option>
282
- <option value='15'><?php _e('Every 15 minutes', 'restaurant-reservations' ); ?></option>
283
- <option value='10'><?php _e('Every 10 minutes', 'restaurant-reservations' ); ?></option>
284
- <option value='5'><?php _e('Every 5 minutes', 'restaurant-reservations' ); ?></option>
285
- </select>
286
- </div>
287
- <div class='rtb-welcome-screen-save-options-button'><?php _e('Save Options', 'restaurant-reservations'); ?></div>
288
- <div class="rtb-welcome-clear"></div>
289
- <div class='rtb-welcome-screen-previous-button' data-previousaction='schedule_open'><?php _e('Previous Step', 'restaurant-reservations'); ?></div>
290
- <div class='rtb-welcome-screen-finish-button'><a href='admin.php?page=rtb-dashboard'><?php _e('Finish', 'restaurant-reservations'); ?></a></div>
291
- <div class='clear'></div>
292
- </div>
293
- </div>
294
-
295
- <div class='rtb-welcome-screen-skip-container'>
296
- <a href='admin.php?page=rtb-dashboard'><div class='rtb-welcome-screen-skip-button'><?php _e('Skip Setup', 'restaurant-reservations'); ?></div></a>
297
- </div>
298
- </div>
299
-
300
- <?php }
301
-
302
- /**
303
- * Retrieve the template for a scheduling rule
304
- * @since 2.0
305
- */
306
- public function get_template( $id = 0, $values = array(), $list = false ) {
307
-
308
- $date_format = 'weekly';
309
- $time_format = 'all-day';
310
-
311
- $weekdays = array(
312
- 'monday' => 'Mo',
313
- 'tuesday' => 'Tu',
314
- 'wednesday' => 'We',
315
- 'thursday' => 'Th',
316
- 'friday' => 'Fr',
317
- 'saturday' => 'Sa',
318
- 'sunday' => 'Su',
319
- );
320
-
321
- ob_start();
322
- ?>
323
-
324
- <div class="sap-scheduler-rule clearfix<?php echo $list ? ' list' : ''; ?>">
325
- <div class="sap-scheduler-date weekly">
326
- <ul class="sap-selector">
327
-
328
- <li>
329
- <div class="dashicons dashicons-calendar"></div>
330
- <?php _e( 'Weekly', 'restaurant-reservations' ); ?>
331
- </li>
332
-
333
- </ul>
334
-
335
- <ul class="sap-scheduler-weekdays">
336
- <li class="label">
337
- <?php _e( 'Days of the week', 'restaurant-reservations' ); ?>
338
- </li>
339
- <?php
340
- foreach ( $weekdays as $slug => $label ) :
341
- $input_name = 'rtb-setting[schedule_open][' . $id . '][weekdays][' . esc_attr( $slug ) . ']';
342
- ?>
343
- <li>
344
- &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>
345
- </li>
346
- <?php endforeach; ?>
347
- </ul>
348
-
349
- </div>
350
-
351
- <div class="sap-scheduler-time all-day">
352
-
353
- <ul class="sap-selector">
354
- <li>
355
- <div class="dashicons dashicons-clock"></div>
356
- <a href="#" data-format="time-slot">
357
- <?php _e( 'Time', 'restaurant-reservations' ); ?>
358
- </a>
359
- </li>
360
- <li>
361
- <a href="#" data-format="all-day" class="selected">
362
- <?php _e( 'All day', 'restaurant-reservations' ); ?>
363
- </a>
364
- </li>
365
- </ul>
366
-
367
- <div class="sap-scheduler-time-input clearfix">
368
-
369
- <div class="start">
370
- <label for="rtb-setting[schedule_open][<?php echo $id; ?>][time][start]">
371
- <?php _e( 'Start', 'restaurant-reservations' ); ?>
372
- </label>
373
- <input type="text" name="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][start]'; ?>" id="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][start]'; ?>">
374
- </div>
375
-
376
- <div class="end">
377
- <label for="rtb-setting[schedule_open][<?php echo $id; ?>][time][end]">
378
- <?php _e( 'End', 'restaurant-reservations' ); ?>
379
- </label>
380
- <input type="text" name="<?php echo'rtb-setting[schedule_open][' . $id . '][time][end]'; ?>" id="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][end]'; ?>">
381
- </div>
382
-
383
- </div>
384
-
385
- <div class="sap-scheduler-all-day">
386
- <?php printf( __( 'All day long. Want to %sset a time slot%s?', 'restaurant-reservations' ), '<a href="#" data-format="time-slot">', '</a>' ); ?>
387
- </div>
388
-
389
- </div>
390
-
391
- <div class="sap-scheduler-brief">
392
- <div class="date">
393
- <div class="dashicons dashicons-calendar"></div>
394
- <span class="value"></span>
395
- </div>
396
- <div class="time">
397
- <div class="dashicons dashicons-clock"></div>
398
- <span class="value"></span>
399
- </div>
400
- </div>
401
- <div class="sap-scheduler-control">
402
- <a href="#" class="toggle" title="<?php _e( 'Open and close this rule', 'restaurant-reservations' ); ?>">
403
- <div class="dashicons dashicons-<?php echo $list ? 'edit' : 'arrow-up-alt2'; ?>"></div>
404
- <span class="screen-reader-text">
405
- <?php _e( 'Open and close this rule', 'restaurant-reservations' ); ?>
406
- </span>
407
- </a>
408
- <a href="#" class="delete" title="<?php _e( 'Delete rule', 'restaurant-reservations' ); ?>">
409
- <div class="dashicons dashicons-dismiss"></div>
410
- <span class="screen-reader-text">
411
- <?php _e( 'Delete scheduling rule', 'restaurant-reservations' ); ?>
412
- </span>
413
- </a>
414
- </div>
415
- </div>
416
-
417
- <?php
418
- $output = ob_get_clean();
419
-
420
- return $output;
421
- }
422
- }
423
-
424
-
425
  ?>
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_nopriv_rtb-welcome-add-menu-page' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
20
+ add_action( 'wp_ajax_rtb-welcome-add-menu-page', array( $this, 'add_reservations_page' ) );
21
+ add_action( 'wp_ajax_nopriv_rtb-welcome-set-schedule' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
22
+ add_action( 'wp_ajax_rtb-welcome-set-schedule', array( $this, 'set_schedule' ) );
23
+ add_action( 'wp_ajax_nopriv_rtb-welcome-set-options' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
24
+ add_action( 'wp_ajax_rtb-welcome-set-options', array( $this, 'set_options' ) );
25
+ }
26
+
27
+ public function redirect() {
28
+ if ( ! get_transient( 'rtb-getting-started' ) )
29
+ return;
30
+
31
+ delete_transient( 'rtb-getting-started' );
32
+
33
+ if ( is_network_admin() || isset( $_GET['activate-multi'] ) )
34
+ return;
35
+
36
+ $bookings = get_posts( array( 'post_type' => 'rtb-booking', 'post_status' => 'any' ) );
37
+ if ( ! empty( $bookings ) ) {
38
+ set_transient( 'rtb-admin-install-notice', true, 5 );
39
+ return;
40
+ }
41
+
42
+ wp_safe_redirect( admin_url( 'index.php?page=rtb-getting-started' ) );
43
+ exit;
44
+ }
45
+
46
+ public function register_install_screen() {
47
+ add_dashboard_page(
48
+ esc_html__( 'Five-Star Restaurant Reservations - Welcome!', 'restaurant-reservations' ),
49
+ esc_html__( 'Five-Star Restaurant Reservations - Welcome!', 'restaurant-reservations' ),
50
+ 'manage_options',
51
+ 'rtb-getting-started',
52
+ array($this, 'display_install_screen')
53
+ );
54
+ }
55
+
56
+ public function hide_install_screen_menu_item() {
57
+ remove_submenu_page( 'index.php', 'rtb-getting-started' );
58
+ }
59
+
60
+ public function add_reservations_page() {
61
+
62
+ // Authenticate request
63
+ if ( !check_ajax_referer( 'rtb-getting-started', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
64
+ rtbHelper::admin_nopriv_ajax();
65
+ }
66
+
67
+ $reservations_page = wp_insert_post(array(
68
+ 'post_title' => (isset($_POST['reservations_page_title']) ? sanitize_text_field( $_POST['reservations_page_title'] ) : ''),
69
+ 'post_content' => '',
70
+ 'post_status' => 'publish',
71
+ 'post_type' => 'page'
72
+ ));
73
+
74
+ if ( $reservations_page ) {
75
+ $rtb_options = get_option( 'rtb-settings' );
76
+ $rtb_options['booking-page'] = $reservations_page;
77
+ update_option( 'rtb-settings', $rtb_options );
78
+ }
79
+
80
+ exit();
81
+ }
82
+
83
+ public function set_schedule() {
84
+
85
+ // Authenticate request
86
+ if ( !check_ajax_referer( 'rtb-getting-started', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
87
+ rtbHelper::admin_nopriv_ajax();
88
+ }
89
+
90
+ $rtb_options = get_option( 'rtb-settings' );
91
+
92
+ $rtb_options['schedule-open'] = rtbHelper::sanitize_text_field_recursive( $_POST['schedule_open'] );
93
+
94
+ update_option( 'rtb-settings', $rtb_options );
95
+
96
+ exit();
97
+ }
98
+
99
+ public function set_options() {
100
+
101
+ // Authenticate request
102
+ if ( !check_ajax_referer( 'rtb-getting-started', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
103
+ rtbHelper::admin_nopriv_ajax();
104
+ }
105
+
106
+ $rtb_options = get_option( 'rtb-settings' );
107
+ $rtb_options['party-size-min'] = sanitize_text_field( $_POST['party_size_min'] );
108
+ $rtb_options['party-size'] = sanitize_text_field( $_POST['party_size'] );
109
+ $rtb_options['early-bookings'] = sanitize_text_field( $_POST['early_bookings'] );
110
+ $rtb_options['late-bookings'] = sanitize_text_field( $_POST['late_bookings'] );
111
+ $rtb_options['time-interval'] = sanitize_text_field( $_POST['time_interval'] );
112
+ update_option( 'rtb-settings', $rtb_options );
113
+
114
+ exit();
115
+ }
116
+
117
+ function admin_enqueue() {
118
+
119
+ if ( ! isset( $_GET['page'] ) or $_GET['page'] != 'rtb-getting-started' ) { return; }
120
+
121
+ wp_enqueue_style( 'rtb-admin-css', RTB_PLUGIN_URL . '/lib/simple-admin-pages/css/admin.css', array(), RTB_VERSION );
122
+ wp_enqueue_style( 'rtb-welcome-screen', RTB_PLUGIN_URL . '/assets/css/admin-rtb-welcome-screen.css', array(), RTB_VERSION );
123
+ wp_enqueue_style( 'pickadate-default', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.css', array(), RTB_VERSION );
124
+ wp_enqueue_style( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.date.css', array(), RTB_VERSION );
125
+ wp_enqueue_style( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.time.css', array(), RTB_VERSION );
126
+
127
+ wp_enqueue_script( 'rtb-getting-started', RTB_PLUGIN_URL . '/assets/js/admin-rtb-welcome-screen.js', array('jquery'), RTB_VERSION );
128
+ wp_localize_script(
129
+ 'rtb-getting-started',
130
+ 'rtb_getting_started',
131
+ array(
132
+ 'nonce' => wp_create_nonce( 'rtb-getting-started' )
133
+ )
134
+ );
135
+
136
+ wp_enqueue_script( 'pickadate', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.js', array('jquery'), RTB_VERSION, true );
137
+ wp_enqueue_script( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.date.js', array('jquery'), RTB_VERSION, true );
138
+ wp_enqueue_script( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.time.js', array('jquery'), RTB_VERSION, true );
139
+ wp_enqueue_script( 'pickadate-legacy', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/legacy.js', array('jquery'), RTB_VERSION, true );
140
+ wp_enqueue_script( 'sap-scheduler', RTB_PLUGIN_URL . '/lib/simple-admin-pages/js/scheduler.js', array('jquery'), RTB_VERSION, true );
141
+
142
+ $sap_scheduler_settings[ 'schedule-open' ] = array(
143
+ 'time_interval' => 15,
144
+ 'time_format' => 'h:i A',
145
+ 'date_format' => 'd mmmm, yyyy',
146
+ 'template' => $this->get_template(),
147
+ 'weekdays' => array(
148
+ 'monday' => 'Mo',
149
+ 'tuesday' => 'Tu',
150
+ 'wednesday' => 'We',
151
+ 'thursday' => 'Th',
152
+ 'friday' => 'Fr',
153
+ 'saturday' => 'Sa',
154
+ 'sunday' => 'Su',
155
+ ),
156
+ 'weeks' => array(
157
+ 'first' => '1st',
158
+ 'second' => '2nd',
159
+ 'third' => '3rd',
160
+ 'fourth' => '4th',
161
+ 'last' => 'last',
162
+ ),
163
+ 'disable_weekdays' => false,
164
+ 'disable_weeks' => true,
165
+ 'disable_date' => true,
166
+ 'disable_time' => false,
167
+ 'disable_multiple' => false,
168
+ 'summaries' => array(
169
+ 'never' => __( 'Never', 'restaurant-reservations' ),
170
+ 'weekly_always' => __( 'Every day', 'restaurant-reservations' ),
171
+ 'monthly_weekdays' => sprintf( __( '%s on the %s week of the month', 'restaurant-reservations' ), '{days}', '{weeks}' ),
172
+ 'monthly_weeks' => sprintf( __( '%s week of the month', 'restaurant-reservations' ), '{weeks}' ),
173
+ 'all_day' => __( 'All day', 'restaurant-reservations' ),
174
+ 'before' => __( 'Ends at', 'restaurant-reservations' ),
175
+ 'after' => __( 'Starts at', 'restaurant-reservations' ),
176
+ 'separator' => __( '&mdash', 'restaurant-reservations' ),
177
+ ),
178
+ );
179
+
180
+ // This gets called multiple times, but only the last call is actually
181
+ // pushed to the script.
182
+ wp_localize_script(
183
+ 'sap-scheduler',
184
+ 'sap_scheduler',
185
+ array(
186
+ 'settings' => $sap_scheduler_settings
187
+ )
188
+ );
189
+ }
190
+
191
+ public function display_install_screen() { ?>
192
+ <div class='rtb-welcome-screen'>
193
+ <?php if (!isset($_GET['exclude'])) { ?>
194
+ <div class='rtb-welcome-screen-header'>
195
+ <h1><?php _e('Welcome to the Five-Star Restaurant Reservations Plugin', 'restaurant-reservations'); ?></h1>
196
+ <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>
197
+ </div>
198
+ <?php } ?>
199
+ <?php if (!isset($_GET['exclude'])) { ?>
200
+ <div class='rtb-welcome-screen-box rtb-welcome-screen-reservations_page rtb-welcome-screen-open' data-screen='reservations_page'>
201
+ <h2><?php _e('Add a Reservations Page', 'restaurant-reservations'); ?></h2>
202
+ <div class='rtb-welcome-screen-box-content'>
203
+ <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>
204
+ <div class='rtb-welcome-screen-menu-page'>
205
+ <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>
206
+ <div class='rtb-welcome-screen-add-reservations-page-button' data-nextaction='schedule_open'><?php _e('Create Page', 'restaurant-reservations'); ?></div>
207
+ </div>
208
+ <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>
209
+ <div class='clear'></div>
210
+ </div>
211
+ </div>
212
+ <?php } ?>
213
+ <div class='rtb-welcome-screen-box rtb-welcome-screen-schedule_open' data-screen='schedule_open'>
214
+ <h2><?php echo (isset($_GET['exclude']) ? '1.' : '2.') . __(' Create Booking Schedule', 'restaurant-reservations'); ?></h2>
215
+ <div class='rtb-welcome-screen-box-content'>
216
+ <p><?php _e('Choose what times each week your restaurant is available to book reservations.', 'restaurant-reservations'); ?></p>
217
+ <div class='rtb-welcome-screen-created-schedule-open'>
218
+ <div class="sap-scheduler" id="schedule-open"></div>
219
+ <div class="sap-add-scheduler">
220
+ <a href="#" class="button">
221
+ <?php _e('Add new scheduling rule', 'restaurant-reservations' ); ?>
222
+ </a>
223
+ </div>
224
+ </div>
225
+ <div class='rtb-welcome-screen-save-schedule-open-button'><?php _e('Save Schedule', 'restaurant-reservations'); ?></div>
226
+ <div class="rtb-welcome-clear"></div>
227
+ <div class='rtb-welcome-screen-next-button' data-nextaction='options'><?php _e('Next Step', 'restaurant-reservations'); ?></div>
228
+ <div class='rtb-welcome-screen-previous-button' data-previousaction='reservations_page'><?php _e('Previous Step', 'restaurant-reservations'); ?></div>
229
+ <div class='clear'></div>
230
+ </div>
231
+ </div>
232
+
233
+ <div class='rtb-welcome-screen-box rtb-welcome-screen-options' data-screen='options'>
234
+ <h2><?php echo (isset($_GET['exclude']) ? '2.' : '3.') . __(' Set Key Options', 'restaurant-reservations'); ?></h2>
235
+ <div class='rtb-welcome-screen-box-content'>
236
+ <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>
237
+ <div class='rtb-welcome-screen-option'>
238
+ <label><?php _e('Min Party Size:', 'restaurant-reservations'); ?></label>
239
+ <select name='min-party-size'>
240
+ <?php for ( $i = 1; $i <= 100; $i++ ) { ?>
241
+ <option value='<?php echo $i; ?>'><?php echo $i; ?></option>
242
+ <?php } ?>
243
+ </select>
244
+ </div>
245
+ <div class='rtb-welcome-screen-option'>
246
+ <label><?php _e('Max Party Size:', 'restaurant-reservations'); ?></label>
247
+ <select name='party-size'>
248
+ <option value='0'><?php _e('Any Size', 'restaurant-reservations' ); ?></option>
249
+ <?php for ( $i = 1; $i <= 100; $i++ ) { ?>
250
+ <option value='<?php echo $i; ?>'><?php echo $i; ?></option>
251
+ <?php } ?>
252
+ </select>
253
+ </div>
254
+ <div class='rtb-welcome-screen-option'>
255
+ <label><?php _e('Early Bookings:', 'restaurant-reservations'); ?></label>
256
+ <select name='early-bookings'>
257
+ <option><?php _e('Any Time', 'restaurant-reservations' ); ?></option>
258
+ <option value='1'><?php _e('From 1 day in advance', 'restaurant-reservations' ); ?></option>
259
+ <option value='7'><?php _e('From 1 week in advance', 'restaurant-reservations' ); ?></option>
260
+ <option value='14'><?php _e('From 2 weeks in advance', 'restaurant-reservations' ); ?></option>
261
+ <option value='30'><?php _e('From 30 days in advance', 'restaurant-reservations' ); ?></option>
262
+ <option value='90'><?php _e('From 90 days in advance', 'restaurant-reservations' ); ?></option>
263
+ </select>
264
+ </div>
265
+ <div class='rtb-welcome-screen-option'>
266
+ <label><?php _e('Late Bookings:', 'restaurant-reservations'); ?></label>
267
+ <select name='late-bookings'>
268
+ <option><?php _e('Up to the last minute', 'restaurant-reservations' ); ?></option>
269
+ <option value='15'><?php _e('At least 15 minutes in advance', 'restaurant-reservations' ); ?></option>
270
+ <option value='30'><?php _e('At least 30 minutes in advance', 'restaurant-reservations' ); ?></option>
271
+ <option value='45'><?php _e('At least 45 minutes in advance', 'restaurant-reservations' ); ?></option>
272
+ <option value='60'><?php _e('At least 1 hour in advance', 'restaurant-reservations' ); ?></option>
273
+ <option value='240'><?php _e('At least 4 hours in advance', 'restaurant-reservations' ); ?></option>
274
+ <option value='1440'><?php _e('At least 24 hours in advance', 'restaurant-reservations' ); ?></option>
275
+ <option value='same_day'><?php _e('Block same-day-bookings', 'restaurant-reservations' ); ?></option>
276
+ </select>
277
+ </div>
278
+ <div class='rtb-welcome-screen-option'>
279
+ <label><?php _e('Time Interval:', 'restaurant-reservations'); ?></label>
280
+ <select name='time-interval'>
281
+ <option value='30'><?php _e('Every 30 minutes', 'restaurant-reservations' ); ?></option>
282
+ <option value='15'><?php _e('Every 15 minutes', 'restaurant-reservations' ); ?></option>
283
+ <option value='10'><?php _e('Every 10 minutes', 'restaurant-reservations' ); ?></option>
284
+ <option value='5'><?php _e('Every 5 minutes', 'restaurant-reservations' ); ?></option>
285
+ </select>
286
+ </div>
287
+ <div class='rtb-welcome-screen-save-options-button'><?php _e('Save Options', 'restaurant-reservations'); ?></div>
288
+ <div class="rtb-welcome-clear"></div>
289
+ <div class='rtb-welcome-screen-previous-button' data-previousaction='schedule_open'><?php _e('Previous Step', 'restaurant-reservations'); ?></div>
290
+ <div class='rtb-welcome-screen-finish-button'><a href='admin.php?page=rtb-dashboard'><?php _e('Finish', 'restaurant-reservations'); ?></a></div>
291
+ <div class='clear'></div>
292
+ </div>
293
+ </div>
294
+
295
+ <div class='rtb-welcome-screen-skip-container'>
296
+ <a href='admin.php?page=rtb-dashboard'><div class='rtb-welcome-screen-skip-button'><?php _e('Skip Setup', 'restaurant-reservations'); ?></div></a>
297
+ </div>
298
+ </div>
299
+
300
+ <?php }
301
+
302
+ /**
303
+ * Retrieve the template for a scheduling rule
304
+ * @since 2.0
305
+ */
306
+ public function get_template( $id = 0, $values = array(), $list = false ) {
307
+
308
+ $date_format = 'weekly';
309
+ $time_format = 'all-day';
310
+
311
+ $weekdays = array(
312
+ 'monday' => 'Mo',
313
+ 'tuesday' => 'Tu',
314
+ 'wednesday' => 'We',
315
+ 'thursday' => 'Th',
316
+ 'friday' => 'Fr',
317
+ 'saturday' => 'Sa',
318
+ 'sunday' => 'Su',
319
+ );
320
+
321
+ ob_start();
322
+ ?>
323
+
324
+ <div class="sap-scheduler-rule clearfix<?php echo $list ? ' list' : ''; ?>">
325
+ <div class="sap-scheduler-date weekly">
326
+ <ul class="sap-selector">
327
+
328
+ <li>
329
+ <div class="dashicons dashicons-calendar"></div>
330
+ <?php _e( 'Weekly', 'restaurant-reservations' ); ?>
331
+ </li>
332
+
333
+ </ul>
334
+
335
+ <ul class="sap-scheduler-weekdays">
336
+ <li class="label">
337
+ <?php _e( 'Days of the week', 'restaurant-reservations' ); ?>
338
+ </li>
339
+ <?php
340
+ foreach ( $weekdays as $slug => $label ) :
341
+ $input_name = 'rtb-setting[schedule_open][' . $id . '][weekdays][' . esc_attr( $slug ) . ']';
342
+ ?>
343
+ <li>
344
+ &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>
345
+ </li>
346
+ <?php endforeach; ?>
347
+ </ul>
348
+
349
+ </div>
350
+
351
+ <div class="sap-scheduler-time all-day">
352
+
353
+ <ul class="sap-selector">
354
+ <li>
355
+ <div class="dashicons dashicons-clock"></div>
356
+ <a href="#" data-format="time-slot">
357
+ <?php _e( 'Time', 'restaurant-reservations' ); ?>
358
+ </a>
359
+ </li>
360
+ <li>
361
+ <a href="#" data-format="all-day" class="selected">
362
+ <?php _e( 'All day', 'restaurant-reservations' ); ?>
363
+ </a>
364
+ </li>
365
+ </ul>
366
+
367
+ <div class="sap-scheduler-time-input clearfix">
368
+
369
+ <div class="start">
370
+ <label for="rtb-setting[schedule_open][<?php echo $id; ?>][time][start]">
371
+ <?php _e( 'Start', 'restaurant-reservations' ); ?>
372
+ </label>
373
+ <input type="text" name="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][start]'; ?>" id="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][start]'; ?>">
374
+ </div>
375
+
376
+ <div class="end">
377
+ <label for="rtb-setting[schedule_open][<?php echo $id; ?>][time][end]">
378
+ <?php _e( 'End', 'restaurant-reservations' ); ?>
379
+ </label>
380
+ <input type="text" name="<?php echo'rtb-setting[schedule_open][' . $id . '][time][end]'; ?>" id="<?php echo 'rtb-setting[schedule_open][' . $id . '][time][end]'; ?>">
381
+ </div>
382
+
383
+ </div>
384
+
385
+ <div class="sap-scheduler-all-day">
386
+ <?php printf( __( 'All day long. Want to %sset a time slot%s?', 'restaurant-reservations' ), '<a href="#" data-format="time-slot">', '</a>' ); ?>
387
+ </div>
388
+
389
+ </div>
390
+
391
+ <div class="sap-scheduler-brief">
392
+ <div class="date">
393
+ <div class="dashicons dashicons-calendar"></div>
394
+ <span class="value"></span>
395
+ </div>
396
+ <div class="time">
397
+ <div class="dashicons dashicons-clock"></div>
398
+ <span class="value"></span>
399
+ </div>
400
+ </div>
401
+ <div class="sap-scheduler-control">
402
+ <a href="#" class="toggle" title="<?php _e( 'Open and close this rule', 'restaurant-reservations' ); ?>">
403
+ <div class="dashicons dashicons-<?php echo $list ? 'edit' : 'arrow-up-alt2'; ?>"></div>
404
+ <span class="screen-reader-text">
405
+ <?php _e( 'Open and close this rule', 'restaurant-reservations' ); ?>
406
+ </span>
407
+ </a>
408
+ <a href="#" class="delete" title="<?php _e( 'Delete rule', 'restaurant-reservations' ); ?>">
409
+ <div class="dashicons dashicons-dismiss"></div>
410
+ <span class="screen-reader-text">
411
+ <?php _e( 'Delete scheduling rule', 'restaurant-reservations' ); ?>
412
+ </span>
413
+ </a>
414
+ </div>
415
+ </div>
416
+
417
+ <?php
418
+ $output = ob_get_clean();
419
+
420
+ return $output;
421
+ }
422
+ }
423
+
424
+
425
  ?>
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 esc_html( $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 esc_html( $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,615 +1,615 @@
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 $transit_cookie_name = 'rtb-mc-sbscrb';
18
-
19
- public function __construct() {
20
- global $rtb_controller;
21
-
22
- add_action( 'init', array( $this, 'init' ) );
23
-
24
- if ( !is_admin() and $rtb_controller->permissions->check_permission( 'mailchimp' ) ) {
25
-
26
- // Add optin checkbox to booking form
27
- add_filter( 'rtb_booking_form_fields', array( $this, 'add_optin_field' ), 10, 2 );
28
-
29
- // Validate the optin request data
30
- add_action( 'rtb_validate_booking_submission', array( $this, 'validate_optin_request' ) );
31
-
32
- // Enqueue assets to send subscription request
33
- add_filter( 'rtb_insert_booking', array( $this, 'enqueue_subscription_call' ) );
34
- add_filter( 'init', array( $this, 'enqueue_subscription_call' ) );
35
-
36
- // Save mc-optin user input value
37
- add_filter( 'rtb_insert_booking_metadata', array( $this, 'add_booking_meta' ), 10, 2 );
38
- // Reload meta information
39
- add_action( 'rtb_booking_load_post_data', array( $this, 'reload_booking_meta' ), 20, 2 );
40
- }
41
-
42
- }
43
-
44
- /**
45
- * Initialize the plugin and register hooks
46
- */
47
- public function init() {
48
-
49
- // Initialize the plugin
50
- $this->load_config();
51
-
52
- add_action( 'mcfrtb_list_merge_fields', array( $this, 'maybe_add_location_merge_field' ) );
53
- add_action( 'mcfrtb_list_merge_fields', array( $this, 'maybe_add_merge_options' ) );
54
-
55
- // Load assets
56
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
57
-
58
- // Receive ajax calls for mailchimp lists
59
- add_action( 'wp_ajax_nopriv_mcfrtb-get-lists' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
60
- add_action( 'wp_ajax_mcfrtb-get-lists', array( $this, 'ajax_get_lists' ) );
61
-
62
- // Receive ajax calls for merge fields
63
- add_action( 'wp_ajax_nopriv_mcfrtb-load-merge-fields' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
64
- add_action( 'wp_ajax_mcfrtb-load-merge-fields', array( $this, 'ajax_load_merge_fields' ) );
65
-
66
-
67
- // Process subscription calls
68
- add_action( 'wp_ajax_nopriv_mcfrtb-subscribe' , array( $this , 'subscribe' ) );
69
- add_action( 'wp_ajax_mcfrtb-subscribe', array( $this, 'subscribe' ) );
70
- add_filter( 'mcfrtb_merge_fields_data', array( $this, 'maybe_send_location_merge_field' ), 10, 3 );
71
- add_filter( 'mcfrtb_merge_fields_data', array( $this, 'maybe_send_merge_fields' ), 10, 3 );
72
- }
73
-
74
-
75
- /**
76
- * Load the configuration parameters
77
- */
78
- public function load_config() {
79
-
80
- global $rtb_controller;
81
- $api_key = $rtb_controller->settings->get_setting( 'mc-apikey' );
82
-
83
- if( ! is_array( $api_key ) ) {
84
- $api_key = [];
85
- }
86
-
87
- $this->api_key = array_key_exists('api_key', $api_key) ? $api_key['api_key'] : '';
88
- $this->status = array_key_exists('status', $api_key) ? $api_key['status'] : '';
89
-
90
- $this->merge_fields = apply_filters(
91
- 'mcfrtb_list_merge_fields',
92
- array(
93
- 'datetime' => __( 'Date/Time of Booking', 'restaurant-reservations' ),
94
- 'name' => __( 'Name', 'restaurant-reservations' ),
95
- 'party' => __( 'Party Size', 'restaurant-reservations' ),
96
- 'phone' => __( 'Phone Number', 'restaurant-reservations' ),
97
- 'message' => __( 'Message', 'restaurant-reservations' ),
98
- )
99
- );
100
-
101
- }
102
-
103
- /**
104
- * Add merge field for location if multi-location support is active
105
- *
106
- * @param array $fields Key/value list of booking data available for merge
107
- * @since 1.2
108
- */
109
- public function maybe_add_location_merge_field( $fields ) {
110
-
111
- global $rtb_controller;
112
-
113
- if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) {
114
- $fields['location'] = __( 'Location', 'restaurant-reservations' );
115
- }
116
-
117
- return $fields;
118
- }
119
-
120
- /**
121
- * Add merge field options for custom fields
122
- *
123
- * @param array $fields Key/value list of booking data available for merge
124
- * @since 1.3
125
- */
126
- public function maybe_add_merge_options( $fields ) {
127
-
128
- $custom_fields = rtb_get_custom_fields();
129
-
130
- $custom_merge_fields = array();
131
- foreach( $custom_fields as $custom_field ) {
132
- $custom_merge_fields['cf-' . $custom_field->slug] = $custom_field->title;
133
- }
134
-
135
- return array_merge( $fields, $custom_merge_fields );
136
- }
137
-
138
- /**
139
- * Enqueue the admin-only CSS and Javascript
140
- * @since 0.0.1
141
- */
142
- public function enqueue_admin_assets() {
143
-
144
- global $rtb_controller;
145
-
146
- // Use the page reference in $admin_page_hooks because
147
- // it changes in SOME hooks when it is translated.
148
- // https://core.trac.wordpress.org/ticket/18857
149
- global $admin_page_hooks;
150
-
151
- $screen = get_current_screen();
152
- if ( empty( $screen ) || empty( $admin_page_hooks['rtb-bookings'] ) ) {
153
- return;
154
- }
155
-
156
- if ( $screen->base == 'toplevel_page_rtb-bookings' || $screen->base == $admin_page_hooks['rtb-bookings'] . '_page_rtb-settings' ) {
157
-
158
- wp_enqueue_script( 'rtb-admin-mc', RTB_PLUGIN_URL . '/assets/js/mailchimp-admin.js', array( 'jquery' ), '', true );
159
- wp_localize_script(
160
- 'rtb-admin-mc',
161
- 'rtb_admin_mc',
162
- array(
163
- 'ajax_nonce' => wp_create_nonce( 'rtb-admin-mc' ),
164
- 'merge_fields' => $this->merge_fields,
165
- 'lists' => $rtb_controller->settings->get_setting( 'mc-lists' ),
166
- 'strings' => array(
167
- 'merge_booking_data' => __( 'Booking Form Data', 'restaurant-reservations' ),
168
- 'merge_list_field' => __( 'MailChimp List Field', 'restaurant-reservations' ),
169
- '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' ),
170
- 'api_unknown_error' => __( 'There was an unexpected error when trying to retrieve the list\'s merge fields.', 'restaurant-reservations' ),
171
- 'merge_email_label' => __( 'Email', 'restaurant-reservations' ),
172
- 'merge_email_description' => __( 'The email field is automatically merged.', 'restaurant-reservations' ),
173
- )
174
- )
175
- );
176
- }
177
- }
178
-
179
- /**
180
- * Handle ajax request for lists
181
- */
182
- public function ajax_get_lists() {
183
-
184
- // Authenticate request
185
- if ( !check_ajax_referer( 'rtb-admin-mc', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
186
- rtbHelper::admin_nopriv_ajax();
187
- }
188
-
189
- $this->load_api( $this->api_key );
190
-
191
- $this->api_call( '/lists' )->send_json_response();
192
- }
193
-
194
- /**
195
- * Handle ajax request for list merge fields
196
- */
197
- public function ajax_load_merge_fields() {
198
-
199
- // Authenticate request
200
- if ( !check_ajax_referer( 'rtb-admin-mc', 'nonce' ) || !current_user_can( 'manage_bookings' ) || empty( $_POST['list'] ) ) {
201
- rtbHelper::admin_nopriv_ajax();
202
- }
203
-
204
- $this->load_api( $this->api_key );
205
-
206
- $this->api_call( '/lists/' . sanitize_key( $_POST['list'] ) . '/merge-fields' )->send_json_response();
207
- }
208
-
209
- /**
210
- * Load the api request class
211
- *
212
- * @param string $api_key MailChimp API key
213
- */
214
- public function load_api( $api_key = '' ) {
215
-
216
- // Don't load it twice
217
- if ( !empty( $this->mc ) ) {
218
- return;
219
- }
220
-
221
- require_once( RTB_PLUGIN_DIR . '/includes/MailChimpRequest.class.php' );
222
-
223
- // Update the api key
224
- if ( $api_key ) {
225
- $this->api_key = $api_key;
226
- }
227
-
228
- // Load the API wrapper library
229
- $this->mc = new mcrftbMailChimpRequest( $this->api_key );
230
- }
231
-
232
- /**
233
- * Make a call to the API or pull results from cache
234
- *
235
- * @param string $method HTTP method. Only GET and POST supported for now
236
- * @param string $endpoint API endpoint to query, eg: /lists
237
- * @param array $params Parameters to pass with the API request
238
- */
239
- public function api_call( $endpoint = '', $method = 'GET', $params = array() ) {
240
- return $this->mc->call( $endpoint, $method, $params );
241
- }
242
-
243
- /**
244
- * Check if the API key is valid
245
- */
246
- public function is_valid_api_key() {
247
-
248
- if ( empty( $this->api_key ) || empty( $this->mc ) ) {
249
- return false;
250
- }
251
-
252
- // Bad API key if no data center available
253
- if ( strpos( $this->api_key, '-' ) === false ) {
254
- return false;
255
- }
256
-
257
- // Make a test call to the API
258
- $result = $this->api_call( '/lists' )->get_response();
259
- if ( empty( $result ) || ( is_object( $result ) && get_class( $result ) == 'WP_Error' ) ) {
260
- return false;
261
- } else {
262
- return true;
263
- }
264
-
265
- return false;
266
- }
267
-
268
- /**
269
- * Add the optin checkbox field to the booking form
270
- */
271
- public function add_optin_field( $fields, $request ) {
272
-
273
- global $rtb_controller;
274
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
275
- $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
276
-
277
- if ( $optout !== 'no' && !empty( $lists['list'] ) ) {
278
- $optprompt = $rtb_controller->settings->get_setting( 'mc-optprompt' );
279
-
280
- $fields['optin'] = array(
281
- 'fields' => array(
282
- 'mc-optin' => array(
283
- 'title' => $optprompt,
284
- 'request_input' => empty( $request->mc_optin ) ? '' : $request->mc_optin,
285
- 'callback' => array( $this, 'print_optin_field' ),
286
- )
287
- ),
288
- 'order' => 1000,
289
- );
290
- }
291
-
292
- return $fields;
293
- }
294
-
295
- /**
296
- * Print the optin checkbox field on the booking form
297
- */
298
- public function print_optin_field( $slug, $title, $value ) {
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
- // Check the box if it's been selected or if the setting is
305
- // auto-checked and the form hasn't been submitted with it
306
- // un-checked
307
- $checked = $value ? true : false;
308
- if ( !$checked && $optout == 'checked' && ( empty( $_POST['action'] ) || $_POST['action'] !== 'booking_request' ) ) {
309
- $checked = true;
310
- }
311
-
312
- if ( $optout !== 'no' && !empty( $lists['list'] ) ) {
313
- $label = $rtb_controller->settings->get_setting( 'mc-optprompt' );
314
- ?>
315
-
316
- <div class="mc-optin">
317
- <label>
318
- <input type="checkbox" name="<?php echo esc_attr( $slug ); ?>" value="1"<?php checked( $checked ); ?>>
319
- <?php echo $label; ?>
320
- </label>
321
- </div>
322
-
323
- <?php
324
- }
325
- }
326
-
327
- /**
328
- * Validate the optin request data and save on booking object
329
- * @param rtbBooking $booking Booking object
330
- * @return void
331
- */
332
- public function validate_optin_request( $booking ) {
333
- global $rtb_controller;
334
-
335
- $booking->mc_optin = false;
336
-
337
- if ( $rtb_controller->settings->get_setting( 'mc-optout' ) !== 'no' && isset( $_POST['mc-optin'] ) && $_POST['mc-optin'] == '1' ) {
338
- $booking->mc_optin = true;
339
- }
340
- }
341
-
342
- /**
343
- * Save mc-optin user selection to booking for future reference
344
- * @param array $meta Booking meta info
345
- * @param rtbBooking $booking Booking object
346
- *
347
- * @return array $meta array of meta info
348
- */
349
- public function add_booking_meta( $meta, $booking )
350
- {
351
- global $rtb_controller;
352
-
353
- // Did they opt out?
354
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
355
-
356
- if ( $optout != 'no' ) {
357
- $meta['mc_optin'] = $booking->mc_optin;
358
- }
359
-
360
- return $meta;
361
- }
362
-
363
- /**
364
- * Reload mailchimp meta data for booking object
365
- * @param rtbBooking $booking Booking object
366
- * @param wp_post $post Booking post object
367
- */
368
- public function reload_booking_meta( $booking, $post )
369
- {
370
- global $rtb_controller;
371
-
372
- // Did they opt out?
373
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
374
-
375
- if ( $optout != 'no' ) {
376
- $booking->mc_optin = false;
377
- $meta = get_post_meta( $booking->ID, 'rtb', true );
378
-
379
- if ( is_array( $meta ) && isset( $meta['mc_optin'] ) ) {
380
- $booking->mc_optin = $meta['mc_optin'];
381
- }
382
- }
383
- }
384
-
385
- /**
386
- * Enqueue some JavaScript to subscribe the user after they've
387
- * booked.
388
- */
389
- public function enqueue_subscription_call( $arg = null ) {
390
- global $rtb_controller;
391
-
392
- $transit = null;
393
- if( null == $arg ) {
394
- $rtb_mc_sbscrb = isset( $_COOKIE['rtb-mc-sbscrb'] ) ? $_COOKIE['rtb-mc-sbscrb'] : null;
395
- if( null != $rtb_mc_sbscrb ) {
396
- $transit = get_transient( $rtb_mc_sbscrb );
397
- }
398
-
399
- if( false == $transit ) {
400
- return $arg;
401
- }
402
-
403
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
404
- $booking = new rtbBooking();
405
- //Invalid booking
406
- if( false == $booking->load_post( $transit ) ) {
407
- return $arg;
408
- }
409
- }
410
- else {
411
- $booking = $arg;
412
- }
413
-
414
- // Did they opt out?
415
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
416
- if ( $optout != 'no' && empty( $booking->mc_optin ) ) {
417
- return $arg;
418
- }
419
-
420
- // Do we have a list and email address to make the subscription
421
- $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
422
- if ( empty( $lists['list'] ) || empty( $booking->email ) ) {
423
- return $arg;
424
- }
425
-
426
- // To later subscribe via JS
427
- if( null == $transit ) {
428
- $hash = $this->get_booking_hash( $booking );
429
- setcookie( $this->transit_cookie_name, $hash, time() + DAY_IN_SECONDS, '/' );
430
- set_transient( $hash, $booking->ID, DAY_IN_SECONDS );
431
- }
432
-
433
- wp_enqueue_script( 'rtb-mc-subscribe', RTB_PLUGIN_URL . '/assets/js/mailchimp-subscribe.js', array( 'jquery' ), '', true );
434
- wp_localize_script(
435
- 'rtb-mc-subscribe',
436
- 'rtb_subscribe_mc',
437
- array(
438
- 'ajax_nonce' => wp_create_nonce( 'rtb-mc-subscribe' ),
439
- 'ajax_url' => admin_url( 'admin-ajax.php' ),
440
- 'booking' => $booking->ID,
441
- )
442
- );
443
-
444
- return $arg;
445
- }
446
-
447
- /**
448
- * Process a subscription request
449
- */
450
- public function subscribe() {
451
- global $rtb_controller;
452
-
453
- if ( !check_ajax_referer( 'rtb-mc-subscribe', 'nonce' ) || empty( $_POST['booking'] ) ) {
454
- wp_send_json_error(
455
- array(
456
- 'error' => 'badnonce',
457
- 'msg' => __( 'The subscription request has been rejected because it does not appear to have come from this site.', 'restaurant-reservations' ),
458
- )
459
- );
460
- return;
461
- }
462
-
463
- $booking_id = sanitize_text_field( $_POST['booking'] );
464
-
465
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
466
- $booking = new rtbBooking();
467
- $this->load_api( $this->api_key );
468
-
469
- // invalid booking
470
- if( false == $booking->load_post( $booking_id ) ) {
471
- $this->mc->send_json_response();
472
- return;
473
- }
474
-
475
- $booking = (array) $booking;
476
-
477
- // Did they opt out?
478
- $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
479
- if ( $optout != 'no' && empty( $booking['mc_optin'] ) ) {
480
- $this->mc->send_json_response();
481
- return;
482
- }
483
-
484
- // Do we have a list and email address to make the subscription
485
- $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
486
- if ( empty( $lists['list'] ) || empty( $booking['email'] ) ) {
487
- $this->mc->send_json_response();
488
- return;
489
- }
490
-
491
- // Prepare post parameters to send
492
- $params = array(
493
- 'email_address' => $booking['email'],
494
- 'status' => 'pending',
495
- 'merge_fields' => (object) $this->get_merge_fields_data( $lists['fields'], $booking ),
496
- );
497
-
498
- // Pass in the user's IP for geolocation if available
499
- if ( !empty( $_SERVER['REMOTE_ADDR'] ) ) {
500
- $params['ip_signup'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
501
- $params['ip_opt'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
502
- }
503
-
504
- $params = apply_filters( 'mcfrtb_mailchimp_subscribe_args', $params, $booking );
505
-
506
- $mc = $this->api_call( '/lists/' . $lists['list'] . '/members', 'POST', $params );
507
-
508
- // Prevent further attempts to subscribe
509
- $hash = $this->get_booking_hash( $booking );
510
- if( ( $transit = get_transient( $hash ) ) !== false ) {
511
- unset( $_COOKIE[ $this->transit_cookie_name ] );
512
- setcookie( $this->transit_cookie_name, null, -1, '/' );
513
- delete_transient( $hash );
514
- }
515
-
516
- $mc->send_json_response();
517
- }
518
-
519
- /**
520
- * Get merge fields array to send to the MailChimp API
521
- *
522
- * @merge_fields array Merge fields data pulled locally from settings
523
- */
524
- public function get_merge_fields_data( $merge_fields, $booking ) {
525
-
526
- $output = array();
527
-
528
- foreach( $this->merge_fields as $field => $title ) {
529
- if ( !empty( $merge_fields[$field] ) ) {
530
-
531
- if ( $field == 'datetime' ) {
532
- $output[$merge_fields[$field]] = $booking['date'];
533
- }
534
-
535
- if ( $field == 'name' ) {
536
- $output[$merge_fields[$field]] = $booking['name'];
537
- }
538
-
539
- if ( $field == 'party' ) {
540
- $output[$merge_fields[$field]] = $booking['party'];
541
- }
542
-
543
- if ( $field == 'phone' ) {
544
- $output[$merge_fields[$field]] = $booking['phone'];
545
- }
546
-
547
- if ( $field == 'message' ) {
548
- $output[$merge_fields[$field]] = $booking['message'];
549
- }
550
- }
551
- }
552
-
553
- return apply_filters( 'mcfrtb_merge_fields_data', $output, $merge_fields, $booking );
554
- }
555
-
556
- /**
557
- * Add location to the data merge field when appropriate
558
- *
559
- * @param array $send Key/value array of merge data to be sent
560
- * @param array $merge_fields Key/value array of configured merge fields
561
- * @param rtbBooking $booking Booking object
562
- * @since 1.2
563
- */
564
- public function maybe_send_location_merge_field( $send, $merge_fields, $booking ) {
565
-
566
- global $rtb_controller;
567
-
568
- if ( empty( $rtb_controller->locations ) || empty( $rtb_controller->locations->post_type ) ) {
569
- return $send;
570
- }
571
-
572
- if ( !empty( $booking['location'] ) && !empty( $merge_fields['location'] ) ) {
573
- $term = get_term( $booking['location'] );
574
- if ( !empty( $term ) && is_a( $term, 'WP_Term' ) ) {
575
- $send[$merge_fields['location']] = $term->name;
576
- }
577
- }
578
-
579
- return $send;
580
- }
581
-
582
- /**
583
- * Send merge field data for custom fields
584
- *
585
- * @param array $send Key/value array of merge data to be sent
586
- * @param array $merge_fields Key/value array of configured merge fields
587
- * @param rtbBooking $booking Booking object
588
- * @since 1.3
589
- */
590
- public function maybe_send_merge_fields( $send, $merge_fields, $booking ) {
591
- global $rtb_controller;
592
-
593
- $custom_fields = rtb_get_custom_fields();
594
-
595
- foreach( $custom_fields as $custom_field ) {
596
- if ( !empty( $merge_fields['cf-' . $custom_field->slug] ) && isset( $booking['custom_fields'] ) && isset( $booking['custom_fields'][$custom_field->slug] ) ) {
597
- if ( $custom_field->type == 'confirm' ) {
598
- $send[$merge_fields['cf-' . $custom_field->slug]] = 'Checked';
599
- } else {
600
- $send[$merge_fields['cf-' . $custom_field->slug]] = $rtb_controller->fields->get_display_value( $booking['custom_fields'][$custom_field->slug], $custom_field, '', false );
601
- }
602
- }
603
- }
604
-
605
- return $send;
606
- }
607
-
608
- // Helper for cookie and transient to re-attempt subsribe
609
- public function get_booking_hash( $booking )
610
- {
611
- $booking = (array) $booking;
612
- return md5( $booking['ID'].'-'.$booking['email'] );
613
- }
614
- }
615
- } // 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 $transit_cookie_name = 'rtb-mc-sbscrb';
18
+
19
+ public function __construct() {
20
+ global $rtb_controller;
21
+
22
+ add_action( 'init', array( $this, 'init' ) );
23
+
24
+ if ( !is_admin() and $rtb_controller->permissions->check_permission( 'mailchimp' ) ) {
25
+
26
+ // Add optin checkbox to booking form
27
+ add_filter( 'rtb_booking_form_fields', array( $this, 'add_optin_field' ), 10, 2 );
28
+
29
+ // Validate the optin request data
30
+ add_action( 'rtb_validate_booking_submission', array( $this, 'validate_optin_request' ) );
31
+
32
+ // Enqueue assets to send subscription request
33
+ add_filter( 'rtb_insert_booking', array( $this, 'enqueue_subscription_call' ) );
34
+ add_filter( 'init', array( $this, 'enqueue_subscription_call' ) );
35
+
36
+ // Save mc-optin user input value
37
+ add_filter( 'rtb_insert_booking_metadata', array( $this, 'add_booking_meta' ), 10, 2 );
38
+ // Reload meta information
39
+ add_action( 'rtb_booking_load_post_data', array( $this, 'reload_booking_meta' ), 20, 2 );
40
+ }
41
+
42
+ }
43
+
44
+ /**
45
+ * Initialize the plugin and register hooks
46
+ */
47
+ public function init() {
48
+
49
+ // Initialize the plugin
50
+ $this->load_config();
51
+
52
+ add_action( 'mcfrtb_list_merge_fields', array( $this, 'maybe_add_location_merge_field' ) );
53
+ add_action( 'mcfrtb_list_merge_fields', array( $this, 'maybe_add_merge_options' ) );
54
+
55
+ // Load assets
56
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
57
+
58
+ // Receive ajax calls for mailchimp lists
59
+ add_action( 'wp_ajax_nopriv_mcfrtb-get-lists' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
60
+ add_action( 'wp_ajax_mcfrtb-get-lists', array( $this, 'ajax_get_lists' ) );
61
+
62
+ // Receive ajax calls for merge fields
63
+ add_action( 'wp_ajax_nopriv_mcfrtb-load-merge-fields' , array( 'rtbHelper' , 'admin_nopriv_ajax' ) );
64
+ add_action( 'wp_ajax_mcfrtb-load-merge-fields', array( $this, 'ajax_load_merge_fields' ) );
65
+
66
+
67
+ // Process subscription calls
68
+ add_action( 'wp_ajax_nopriv_mcfrtb-subscribe' , array( $this , 'subscribe' ) );
69
+ add_action( 'wp_ajax_mcfrtb-subscribe', array( $this, 'subscribe' ) );
70
+ add_filter( 'mcfrtb_merge_fields_data', array( $this, 'maybe_send_location_merge_field' ), 10, 3 );
71
+ add_filter( 'mcfrtb_merge_fields_data', array( $this, 'maybe_send_merge_fields' ), 10, 3 );
72
+ }
73
+
74
+
75
+ /**
76
+ * Load the configuration parameters
77
+ */
78
+ public function load_config() {
79
+
80
+ global $rtb_controller;
81
+ $api_key = $rtb_controller->settings->get_setting( 'mc-apikey' );
82
+
83
+ if( ! is_array( $api_key ) ) {
84
+ $api_key = [];
85
+ }
86
+
87
+ $this->api_key = array_key_exists('api_key', $api_key) ? $api_key['api_key'] : '';
88
+ $this->status = array_key_exists('status', $api_key) ? $api_key['status'] : '';
89
+
90
+ $this->merge_fields = apply_filters(
91
+ 'mcfrtb_list_merge_fields',
92
+ array(
93
+ 'datetime' => __( 'Date/Time of Booking', 'restaurant-reservations' ),
94
+ 'name' => __( 'Name', 'restaurant-reservations' ),
95
+ 'party' => __( 'Party Size', 'restaurant-reservations' ),
96
+ 'phone' => __( 'Phone Number', 'restaurant-reservations' ),
97
+ 'message' => __( 'Message', 'restaurant-reservations' ),
98
+ )
99
+ );
100
+
101
+ }
102
+
103
+ /**
104
+ * Add merge field for location if multi-location support is active
105
+ *
106
+ * @param array $fields Key/value list of booking data available for merge
107
+ * @since 1.2
108
+ */
109
+ public function maybe_add_location_merge_field( $fields ) {
110
+
111
+ global $rtb_controller;
112
+
113
+ if ( !empty( $rtb_controller->locations ) && !empty( $rtb_controller->locations->post_type ) ) {
114
+ $fields['location'] = __( 'Location', 'restaurant-reservations' );
115
+ }
116
+
117
+ return $fields;
118
+ }
119
+
120
+ /**
121
+ * Add merge field options for custom fields
122
+ *
123
+ * @param array $fields Key/value list of booking data available for merge
124
+ * @since 1.3
125
+ */
126
+ public function maybe_add_merge_options( $fields ) {
127
+
128
+ $custom_fields = rtb_get_custom_fields();
129
+
130
+ $custom_merge_fields = array();
131
+ foreach( $custom_fields as $custom_field ) {
132
+ $custom_merge_fields['cf-' . $custom_field->slug] = $custom_field->title;
133
+ }
134
+
135
+ return array_merge( $fields, $custom_merge_fields );
136
+ }
137
+
138
+ /**
139
+ * Enqueue the admin-only CSS and Javascript
140
+ * @since 0.0.1
141
+ */
142
+ public function enqueue_admin_assets() {
143
+
144
+ global $rtb_controller;
145
+
146
+ // Use the page reference in $admin_page_hooks because
147
+ // it changes in SOME hooks when it is translated.
148
+ // https://core.trac.wordpress.org/ticket/18857
149
+ global $admin_page_hooks;
150
+
151
+ $screen = get_current_screen();
152
+ if ( empty( $screen ) || empty( $admin_page_hooks['rtb-bookings'] ) ) {
153
+ return;
154
+ }
155
+
156
+ if ( $screen->base == 'toplevel_page_rtb-bookings' || $screen->base == $admin_page_hooks['rtb-bookings'] . '_page_rtb-settings' ) {
157
+
158
+ wp_enqueue_script( 'rtb-admin-mc', RTB_PLUGIN_URL . '/assets/js/mailchimp-admin.js', array( 'jquery' ), '', true );
159
+ wp_localize_script(
160
+ 'rtb-admin-mc',
161
+ 'rtb_admin_mc',
162
+ array(
163
+ 'ajax_nonce' => wp_create_nonce( 'rtb-admin-mc' ),
164
+ 'merge_fields' => $this->merge_fields,
165
+ 'lists' => $rtb_controller->settings->get_setting( 'mc-lists' ),
166
+ 'strings' => array(
167
+ 'merge_booking_data' => __( 'Booking Form Data', 'restaurant-reservations' ),
168
+ 'merge_list_field' => __( 'MailChimp List Field', 'restaurant-reservations' ),
169
+ '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' ),
170
+ 'api_unknown_error' => __( 'There was an unexpected error when trying to retrieve the list\'s merge fields.', 'restaurant-reservations' ),
171
+ 'merge_email_label' => __( 'Email', 'restaurant-reservations' ),
172
+ 'merge_email_description' => __( 'The email field is automatically merged.', 'restaurant-reservations' ),
173
+ )
174
+ )
175
+ );
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Handle ajax request for lists
181
+ */
182
+ public function ajax_get_lists() {
183
+
184
+ // Authenticate request
185
+ if ( !check_ajax_referer( 'rtb-admin-mc', 'nonce' ) || !current_user_can( 'manage_bookings' ) ) {
186
+ rtbHelper::admin_nopriv_ajax();
187
+ }
188
+
189
+ $this->load_api( $this->api_key );
190
+
191
+ $this->api_call( '/lists' )->send_json_response();
192
+ }
193
+
194
+ /**
195
+ * Handle ajax request for list merge fields
196
+ */
197
+ public function ajax_load_merge_fields() {
198
+
199
+ // Authenticate request
200
+ if ( !check_ajax_referer( 'rtb-admin-mc', 'nonce' ) || !current_user_can( 'manage_bookings' ) || empty( $_POST['list'] ) ) {
201
+ rtbHelper::admin_nopriv_ajax();
202
+ }
203
+
204
+ $this->load_api( $this->api_key );
205
+
206
+ $this->api_call( '/lists/' . sanitize_key( $_POST['list'] ) . '/merge-fields' )->send_json_response();
207
+ }
208
+
209
+ /**
210
+ * Load the api request class
211
+ *
212
+ * @param string $api_key MailChimp API key
213
+ */
214
+ public function load_api( $api_key = '' ) {
215
+
216
+ // Don't load it twice
217
+ if ( !empty( $this->mc ) ) {
218
+ return;
219
+ }
220
+
221
+ require_once( RTB_PLUGIN_DIR . '/includes/MailChimpRequest.class.php' );
222
+
223
+ // Update the api key
224
+ if ( $api_key ) {
225
+ $this->api_key = $api_key;
226
+ }
227
+
228
+ // Load the API wrapper library
229
+ $this->mc = new mcrftbMailChimpRequest( $this->api_key );
230
+ }
231
+
232
+ /**
233
+ * Make a call to the API or pull results from cache
234
+ *
235
+ * @param string $method HTTP method. Only GET and POST supported for now
236
+ * @param string $endpoint API endpoint to query, eg: /lists
237
+ * @param array $params Parameters to pass with the API request
238
+ */
239
+ public function api_call( $endpoint = '', $method = 'GET', $params = array() ) {
240
+ return $this->mc->call( $endpoint, $method, $params );
241
+ }
242
+
243
+ /**
244
+ * Check if the API key is valid
245
+ */
246
+ public function is_valid_api_key() {
247
+
248
+ if ( empty( $this->api_key ) || empty( $this->mc ) ) {
249
+ return false;
250
+ }
251
+
252
+ // Bad API key if no data center available
253
+ if ( strpos( $this->api_key, '-' ) === false ) {
254
+ return false;
255
+ }
256
+
257
+ // Make a test call to the API
258
+ $result = $this->api_call( '/lists' )->get_response();
259
+ if ( empty( $result ) || ( is_object( $result ) && get_class( $result ) == 'WP_Error' ) ) {
260
+ return false;
261
+ } else {
262
+ return true;
263
+ }
264
+
265
+ return false;
266
+ }
267
+
268
+ /**
269
+ * Add the optin checkbox field to the booking form
270
+ */
271
+ public function add_optin_field( $fields, $request ) {
272
+
273
+ global $rtb_controller;
274
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
275
+ $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
276
+
277
+ if ( $optout !== 'no' && !empty( $lists['list'] ) ) {
278
+ $optprompt = $rtb_controller->settings->get_setting( 'mc-optprompt' );
279
+
280
+ $fields['optin'] = array(
281
+ 'fields' => array(
282
+ 'mc-optin' => array(
283
+ 'title' => $optprompt,
284
+ 'request_input' => empty( $request->mc_optin ) ? '' : $request->mc_optin,
285
+ 'callback' => array( $this, 'print_optin_field' ),
286
+ )
287
+ ),
288
+ 'order' => 1000,
289
+ );
290
+ }
291
+
292
+ return $fields;
293
+ }
294
+
295
+ /**
296
+ * Print the optin checkbox field on the booking form
297
+ */
298
+ public function print_optin_field( $slug, $title, $value ) {
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
+ // Check the box if it's been selected or if the setting is
305
+ // auto-checked and the form hasn't been submitted with it
306
+ // un-checked
307
+ $checked = $value ? true : false;
308
+ if ( !$checked && $optout == 'checked' && ( empty( $_POST['action'] ) || $_POST['action'] !== 'booking_request' ) ) {
309
+ $checked = true;
310
+ }
311
+
312
+ if ( $optout !== 'no' && !empty( $lists['list'] ) ) {
313
+ $label = $rtb_controller->settings->get_setting( 'mc-optprompt' );
314
+ ?>
315
+
316
+ <div class="mc-optin">
317
+ <label>
318
+ <input type="checkbox" name="<?php echo esc_attr( $slug ); ?>" value="1"<?php checked( $checked ); ?>>
319
+ <?php echo $label; ?>
320
+ </label>
321
+ </div>
322
+
323
+ <?php
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Validate the optin request data and save on booking object
329
+ * @param rtbBooking $booking Booking object
330
+ * @return void
331
+ */
332
+ public function validate_optin_request( $booking ) {
333
+ global $rtb_controller;
334
+
335
+ $booking->mc_optin = false;
336
+
337
+ if ( $rtb_controller->settings->get_setting( 'mc-optout' ) !== 'no' && isset( $_POST['mc-optin'] ) && $_POST['mc-optin'] == '1' ) {
338
+ $booking->mc_optin = true;
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Save mc-optin user selection to booking for future reference
344
+ * @param array $meta Booking meta info
345
+ * @param rtbBooking $booking Booking object
346
+ *
347
+ * @return array $meta array of meta info
348
+ */
349
+ public function add_booking_meta( $meta, $booking )
350
+ {
351
+ global $rtb_controller;
352
+
353
+ // Did they opt out?
354
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
355
+
356
+ if ( $optout != 'no' ) {
357
+ $meta['mc_optin'] = $booking->mc_optin;
358
+ }
359
+
360
+ return $meta;
361
+ }
362
+
363
+ /**
364
+ * Reload mailchimp meta data for booking object
365
+ * @param rtbBooking $booking Booking object
366
+ * @param wp_post $post Booking post object
367
+ */
368
+ public function reload_booking_meta( $booking, $post )
369
+ {
370
+ global $rtb_controller;
371
+
372
+ // Did they opt out?
373
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
374
+
375
+ if ( $optout != 'no' ) {
376
+ $booking->mc_optin = false;
377
+ $meta = get_post_meta( $booking->ID, 'rtb', true );
378
+
379
+ if ( is_array( $meta ) && isset( $meta['mc_optin'] ) ) {
380
+ $booking->mc_optin = $meta['mc_optin'];
381
+ }
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Enqueue some JavaScript to subscribe the user after they've
387
+ * booked.
388
+ */
389
+ public function enqueue_subscription_call( $arg = null ) {
390
+ global $rtb_controller;
391
+
392
+ $transit = null;
393
+ if( null == $arg ) {
394
+ $rtb_mc_sbscrb = isset( $_COOKIE['rtb-mc-sbscrb'] ) ? $_COOKIE['rtb-mc-sbscrb'] : null;
395
+ if( null != $rtb_mc_sbscrb ) {
396
+ $transit = get_transient( $rtb_mc_sbscrb );
397
+ }
398
+
399
+ if( false == $transit ) {
400
+ return $arg;
401
+ }
402
+
403
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
404
+ $booking = new rtbBooking();
405
+ //Invalid booking
406
+ if( false == $booking->load_post( $transit ) ) {
407
+ return $arg;
408
+ }
409
+ }
410
+ else {
411
+ $booking = $arg;
412
+ }
413
+
414
+ // Did they opt out?
415
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
416
+ if ( $optout != 'no' && empty( $booking->mc_optin ) ) {
417
+ return $arg;
418
+ }
419
+
420
+ // Do we have a list and email address to make the subscription
421
+ $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
422
+ if ( empty( $lists['list'] ) || empty( $booking->email ) ) {
423
+ return $arg;
424
+ }
425
+
426
+ // To later subscribe via JS
427
+ if( null == $transit ) {
428
+ $hash = $this->get_booking_hash( $booking );
429
+ setcookie( $this->transit_cookie_name, $hash, time() + DAY_IN_SECONDS, '/' );
430
+ set_transient( $hash, $booking->ID, DAY_IN_SECONDS );
431
+ }
432
+
433
+ wp_enqueue_script( 'rtb-mc-subscribe', RTB_PLUGIN_URL . '/assets/js/mailchimp-subscribe.js', array( 'jquery' ), '', true );
434
+ wp_localize_script(
435
+ 'rtb-mc-subscribe',
436
+ 'rtb_subscribe_mc',
437
+ array(
438
+ 'ajax_nonce' => wp_create_nonce( 'rtb-mc-subscribe' ),
439
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
440
+ 'booking' => $booking->ID,
441
+ )
442
+ );
443
+
444
+ return $arg;
445
+ }
446
+
447
+ /**
448
+ * Process a subscription request
449
+ */
450
+ public function subscribe() {
451
+ global $rtb_controller;
452
+
453
+ if ( !check_ajax_referer( 'rtb-mc-subscribe', 'nonce' ) || empty( $_POST['booking'] ) ) {
454
+ wp_send_json_error(
455
+ array(
456
+ 'error' => 'badnonce',
457
+ 'msg' => __( 'The subscription request has been rejected because it does not appear to have come from this site.', 'restaurant-reservations' ),
458
+ )
459
+ );
460
+ return;
461
+ }
462
+
463
+ $booking_id = sanitize_text_field( $_POST['booking'] );
464
+
465
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
466
+ $booking = new rtbBooking();
467
+ $this->load_api( $this->api_key );
468
+
469
+ // invalid booking
470
+ if( false == $booking->load_post( $booking_id ) ) {
471
+ $this->mc->send_json_response();
472
+ return;
473
+ }
474
+
475
+ $booking = (array) $booking;
476
+
477
+ // Did they opt out?
478
+ $optout = $rtb_controller->settings->get_setting( 'mc-optout' );
479
+ if ( $optout != 'no' && empty( $booking['mc_optin'] ) ) {
480
+ $this->mc->send_json_response();
481
+ return;
482
+ }
483
+
484
+ // Do we have a list and email address to make the subscription
485
+ $lists = $rtb_controller->settings->get_setting( 'mc-lists' );
486
+ if ( empty( $lists['list'] ) || empty( $booking['email'] ) ) {
487
+ $this->mc->send_json_response();
488
+ return;
489
+ }
490
+
491
+ // Prepare post parameters to send
492
+ $params = array(
493
+ 'email_address' => $booking['email'],
494
+ 'status' => 'pending',
495
+ 'merge_fields' => (object) $this->get_merge_fields_data( $lists['fields'], $booking ),
496
+ );
497
+
498
+ // Pass in the user's IP for geolocation if available
499
+ if ( !empty( $_SERVER['REMOTE_ADDR'] ) ) {
500
+ $params['ip_signup'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
501
+ $params['ip_opt'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
502
+ }
503
+
504
+ $params = apply_filters( 'mcfrtb_mailchimp_subscribe_args', $params, $booking );
505
+
506
+ $mc = $this->api_call( '/lists/' . $lists['list'] . '/members', 'POST', $params );
507
+
508
+ // Prevent further attempts to subscribe
509
+ $hash = $this->get_booking_hash( $booking );
510
+ if( ( $transit = get_transient( $hash ) ) !== false ) {
511
+ unset( $_COOKIE[ $this->transit_cookie_name ] );
512
+ setcookie( $this->transit_cookie_name, null, -1, '/' );
513
+ delete_transient( $hash );
514
+ }
515
+
516
+ $mc->send_json_response();
517
+ }
518
+
519
+ /**
520
+ * Get merge fields array to send to the MailChimp API
521
+ *
522
+ * @merge_fields array Merge fields data pulled locally from settings
523
+ */
524
+ public function get_merge_fields_data( $merge_fields, $booking ) {
525
+
526
+ $output = array();
527
+
528
+ foreach( $this->merge_fields as $field => $title ) {
529
+ if ( !empty( $merge_fields[$field] ) ) {
530
+
531
+ if ( $field == 'datetime' ) {
532
+ $output[$merge_fields[$field]] = $booking['date'];
533
+ }
534
+
535
+ if ( $field == 'name' ) {
536
+ $output[$merge_fields[$field]] = $booking['name'];
537
+ }
538
+
539
+ if ( $field == 'party' ) {
540
+ $output[$merge_fields[$field]] = $booking['party'];
541
+ }
542
+
543
+ if ( $field == 'phone' ) {
544
+ $output[$merge_fields[$field]] = $booking['phone'];
545
+ }
546
+
547
+ if ( $field == 'message' ) {
548
+ $output[$merge_fields[$field]] = $booking['message'];
549
+ }
550
+ }
551
+ }
552
+
553
+ return apply_filters( 'mcfrtb_merge_fields_data', $output, $merge_fields, $booking );
554
+ }
555
+
556
+ /**
557
+ * Add location to the data merge field when appropriate
558
+ *
559
+ * @param array $send Key/value array of merge data to be sent
560
+ * @param array $merge_fields Key/value array of configured merge fields
561
+ * @param rtbBooking $booking Booking object
562
+ * @since 1.2
563
+ */
564
+ public function maybe_send_location_merge_field( $send, $merge_fields, $booking ) {
565
+
566
+ global $rtb_controller;
567
+
568
+ if ( empty( $rtb_controller->locations ) || empty( $rtb_controller->locations->post_type ) ) {
569
+ return $send;
570
+ }
571
+
572
+ if ( !empty( $booking['location'] ) && !empty( $merge_fields['location'] ) ) {
573
+ $term = get_term( $booking['location'] );
574
+ if ( !empty( $term ) && is_a( $term, 'WP_Term' ) ) {
575
+ $send[$merge_fields['location']] = $term->name;
576
+ }
577
+ }
578
+
579
+ return $send;
580
+ }
581
+
582
+ /**
583
+ * Send merge field data for custom fields
584
+ *
585
+ * @param array $send Key/value array of merge data to be sent
586
+ * @param array $merge_fields Key/value array of configured merge fields
587
+ * @param rtbBooking $booking Booking object
588
+ * @since 1.3
589
+ */
590
+ public function maybe_send_merge_fields( $send, $merge_fields, $booking ) {
591
+ global $rtb_controller;
592
+
593
+ $custom_fields = rtb_get_custom_fields();
594
+
595
+ foreach( $custom_fields as $custom_field ) {
596
+ if ( !empty( $merge_fields['cf-' . $custom_field->slug] ) && isset( $booking['custom_fields'] ) && isset( $booking['custom_fields'][$custom_field->slug] ) ) {
597
+ if ( $custom_field->type == 'confirm' ) {
598
+ $send[$merge_fields['cf-' . $custom_field->slug]] = 'Checked';
599
+ } else {
600
+ $send[$merge_fields['cf-' . $custom_field->slug]] = $rtb_controller->fields->get_display_value( $booking['custom_fields'][$custom_field->slug], $custom_field, '', false );
601
+ }
602
+ }
603
+ }
604
+
605
+ return $send;
606
+ }
607
+
608
+ // Helper for cookie and transient to re-attempt subsribe
609
+ public function get_booking_hash( $booking )
610
+ {
611
+ $booking = (array) $booking;
612
+ return md5( $booking['ID'].'-'.$booking['email'] );
613
+ }
614
+ }
615
+ } // 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,1098 +1,1098 @@
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
- add_filter( 'rtb_booking_form_init', array( $this, 'pass_location_data_to_js' ) );
96
- add_filter( 'rtb-payment-summary-data', array( $this, 'add_payment_summary' ), 10, 2);
97
- }
98
-
99
- /**
100
- * Register the location taxonomy
101
- *
102
- * @since 1.6
103
- */
104
- public function register_taxonomy() {
105
-
106
- $args = array(
107
- 'label' => _x( 'Location', 'Name for grouping bookings', 'restaurant-reservations' ),
108
- 'hierarchical' => false,
109
- 'public' => true,
110
- 'rewrite' => false,
111
- );
112
-
113
- /**
114
- * Allow third-party plugins to modify the location taxonomy
115
- * arguments.
116
- *
117
- * @since 1.6
118
- */
119
- $args = apply_filters( 'rtb_locations_args', $args );
120
-
121
- register_taxonomy( $this->location_taxonomy, RTB_BOOKING_POST_TYPE, $args );
122
- }
123
-
124
- /**
125
- * Generate taxonomy terms linked to locations and keep them sync'd
126
- * with any changes
127
- *
128
- * @since 1.6
129
- */
130
- public function save_location( $post_id, $post, $update ) {
131
-
132
- if (
133
- $post->post_status === 'auto-draft' ||
134
- ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ||
135
- !current_user_can( 'edit_post', $post_id ) ||
136
- !isset( $_POST['rtb_location_meta_nonce'] ) ||
137
- !wp_verify_nonce( $_POST['rtb_location_meta_nonce'], 'rtb_location_meta' )
138
- ) {
139
- return $post_id;
140
- }
141
-
142
- $term_id = get_post_meta( $post_id, $this->location_taxonomy, true );
143
-
144
- // Create a new term for this location
145
- if ( !$term_id ) {
146
-
147
- $term = wp_insert_term(
148
- sanitize_text_field( $post->post_title ),
149
- $this->location_taxonomy
150
- );
151
-
152
- if ( !is_a( $term, 'WP_Error' ) ) {
153
- update_post_meta( $post_id, $this->location_taxonomy, $term['term_id'] );
154
- $term_id = $term['term_id'];
155
- }
156
-
157
- // Update the term for this location
158
- } else {
159
- wp_update_term(
160
- $term_id,
161
- $this->location_taxonomy,
162
- array(
163
- 'name' => sanitize_text_field( $post->post_title ),
164
- 'slug' => sanitize_text_field( $post->post_name ),
165
- )
166
- );
167
- }
168
-
169
- if ( !empty( $_POST['rtb_append_booking_form'] ) ) {
170
- update_post_meta( $post_id, 'rtb_append_booking_form', true );
171
- } else {
172
- delete_post_meta( $post_id, 'rtb_append_booking_form' );
173
- }
174
-
175
- if ( $term_id ) {
176
-
177
- if ( !empty( $_POST['rtb_reply_to_name'] ) ) {
178
- $reply_to_name = sanitize_text_field( $_POST['rtb_reply_to_name'] );
179
- update_term_meta( $term_id, 'rtb_reply_to_name', $reply_to_name );
180
- } else {
181
- delete_term_meta( $term_id, 'rtb_reply_to_name' );
182
- }
183
-
184
- if ( !empty( $_POST['rtb_reply_to_address'] ) ) {
185
- $reply_to_address = sanitize_email( $_POST['rtb_reply_to_address'] );
186
- update_term_meta( $term_id, 'rtb_reply_to_address', $reply_to_address );
187
- } else {
188
- delete_term_meta( $term_id, 'rtb_reply_to_address' );
189
- }
190
-
191
- if ( !empty( $_POST['rtb_admin_email_address'] ) ) {
192
- $email = sanitize_text_field( $_POST['rtb_admin_email_address'] );
193
- update_term_meta( $term_id, 'rtb_admin_email_address', $email );
194
- } else {
195
- delete_term_meta( $term_id, 'rtb_admin_email_address' );
196
- }
197
- }
198
-
199
- return $post_id;
200
- }
201
-
202
- /**
203
- * Delete taxonomy terms linked to locations when a location is deleted
204
- *
205
- * Only does this when no bookings are associated with that term.
206
- * Otherwise it may be important to keep the bookings grouped for
207
- * historical data.
208
- *
209
- * @since 1.6
210
- */
211
- public function delete_location( $post_id ) {
212
-
213
- if ( !current_user_can( 'delete_posts' ) ) {
214
- return $post_id;
215
- }
216
-
217
- $term_id = get_post_meta( $post_id, $this->location_taxonomy, true );
218
-
219
- $term = get_term( $term_id, $this->location_taxonomy );
220
-
221
- if ( !$term || is_a( $term, 'WP_Error' ) ) {
222
- return;
223
- }
224
-
225
- $query = new rtbQuery( array( 'location' => $term_id ), 'delete-location-term-check' );
226
- $query->prepare_args();
227
- $query->get_bookings();
228
-
229
- // Don't delete taxonomy terms if there are bookings assigned to
230
- // this location, so the booking associations can remain as
231
- // historical data.
232
- if ( count( $query->bookings ) ) {
233
- add_term_meta( $term_id, 'rtb_location_removed', true );
234
- } else {
235
- wp_delete_term( $term_id, $this->location_taxonomy );
236
- }
237
-
238
- }
239
-
240
- /**
241
- * Get location term id from location post id
242
- *
243
- * Transforms a location post id into its associated term id. If the
244
- * id doesn't match a location post, it will check if the received id
245
- * matches a term id and return it if so. Between versions 1.6 and
246
- * and 1.6.1, only term ids were accepted as shortcodes, and this
247
- * provides a backwards-compatible fallback.
248
- *
249
- * @param $location_id int The location id (post or term)
250
- * @return int The location term id. Default: 0
251
- */
252
- public function get_location_term_id( $location_id ) {
253
-
254
- $location_id = absint( $location_id );
255
- $term_id = 0;
256
-
257
- if ( get_post_type( $location_id ) === $this->post_type ) {
258
- $term_id = get_post_meta( $location_id, $this->location_taxonomy, true );
259
- } elseif ( term_exists( $location_id, $this->location_taxonomy ) ) {
260
- $term_id = $location_id;
261
- }
262
-
263
- return $term_id;
264
- }
265
-
266
-
267
- /**
268
- * Add the location selection field to the booking form
269
- *
270
- * @since 1.6
271
- */
272
- public function add_location_field( $fields, $request = null, $args = array() ) {
273
- global $rtb_controller;
274
-
275
- // If the location is specified, don't add a field.
276
- // A hidden field is added automatically in rtb_print_booking_form()
277
- if ( !empty( $args['location'] ) ) {
278
- $args['location'] = $this->get_location_term_id( $args['location'] );
279
- if ( !empty( $args['location'] ) ) {
280
- return $fields;
281
- }
282
- }
283
-
284
- if ( $request === null ) {
285
- global $rtb_controller;
286
- $request = $rtb_controller->request;
287
- }
288
-
289
- // Select a fieldset in which to place the field
290
- $placement = false;
291
- if ( isset( $fields['reservation'] ) && isset( $fields['reservation']['fields'] ) ) {
292
- $placement = &$fields['reservation']['fields'];
293
- } else {
294
- $key = key( reset( $fields ) );
295
- if ( isset( $fields[$key]['fields'] ) ) {
296
- $placement = &$fields[$key]['fields'];
297
- }
298
- }
299
-
300
- // If we couldn't find any working fieldset, then something odd is
301
- // going on. Just pretend we were never here.
302
- if ( $placement === false ) {
303
- return $fields;
304
- }
305
-
306
- $placement = array_merge(
307
- array(
308
- 'location' => array(
309
- 'title' => esc_html( $rtb_controller->settings->get_setting( 'label-location' ) ),
310
- 'request_input' => empty( $request->location ) ? '' : $request->location,
311
- 'callback' => 'rtb_print_form_select_field',
312
- 'callback_args' => array(
313
- 'options' => $this->get_location_options(),
314
- ),
315
- 'empty_option' => true,
316
- 'required' => true,
317
- )
318
- ),
319
- $placement
320
- );
321
-
322
- return $fields;
323
- }
324
-
325
- /**
326
- * Retrieve a key/value array of location terms and names
327
- *
328
- * @param bool $active_only Whether or not to retrieve only currently
329
- * active locations. Default: true - don't retrieve locations that
330
- * have been removed
331
- * @since 1.6
332
- */
333
- public function get_location_options( $active_only = true ) {
334
-
335
- $terms = get_terms(
336
- array(
337
- 'taxonomy' => $this->location_taxonomy,
338
- 'hide_empty' => false,
339
- )
340
- );
341
-
342
- $options = array();
343
- foreach( $terms as $term ) {
344
- $archived = get_term_meta( $term->term_id, 'rtb_location_removed', true );
345
- if ( !$active_only || !$archived ) {
346
- $options[$term->term_id] = $term->name;
347
- }
348
- }
349
-
350
- return $options;
351
- }
352
-
353
- /**
354
- * Validate location in post data
355
- *
356
- * @since 1.6
357
- */
358
- public function validate_location( $booking ) {
359
-
360
- $booking->location = empty( $_POST['rtb-location'] ) ? '' : absint( $_POST['rtb-location'] );
361
- if ( empty( $booking->location ) ) {
362
- $booking->validation_errors[] = array(
363
- 'field' => 'location',
364
- 'post_variable' => $booking->location,
365
- 'message' => __( 'Please select a location for your booking.', 'restaurant-reservations' ),
366
- );
367
-
368
- } elseif ( !term_exists( $booking->location, $this->location_taxonomy ) ) {
369
- $booking->validation_errors[] = array(
370
- 'field' => 'location',
371
- 'post_variable' => $booking->location,
372
- 'message' => __( 'The location you selected is not valid. Please select another location.', 'restaurant-reservations' ),
373
- );
374
- }
375
- }
376
-
377
- /**
378
- * Save the booking location when the booking is created or updated.
379
- *
380
- * @since 1.6
381
- */
382
- public function save_booking_location( $booking ) {
383
-
384
- if ( !empty( $booking->location ) ) {
385
- wp_set_object_terms( $booking->ID, $booking->location, $this->location_taxonomy );
386
- }
387
- }
388
-
389
- /**
390
- * Load the booking location when teh booking is loaded
391
- *
392
- * @since 1.6
393
- */
394
- public function load_booking_location( $booking, $post ) {
395
-
396
- $terms = wp_get_object_terms( $booking->ID, $this->location_taxonomy, array( 'fields' => 'ids' ) );
397
-
398
- if ( is_a( $terms, 'WP_Error' ) ) {
399
- return;
400
- }
401
-
402
- $booking->location = current( $terms );
403
- }
404
-
405
- /**
406
- * Add location column to the list table
407
- *
408
- * @since 1.6
409
- */
410
- public function add_location_column( $columns ) {
411
-
412
- $first = array_splice( $columns, 0, 2 );
413
- $first['location'] = __( 'Location', 'restaurant-reservations' );
414
-
415
- return array_merge( $first, $columns );
416
- }
417
-
418
- /**
419
- * Print the value in the location column for the list table
420
- *
421
- * @since 1.6
422
- */
423
- public function print_location_column( $value, $booking, $column_name ) {
424
-
425
- if ( $column_name !== 'location' ) {
426
- return $value;
427
- }
428
-
429
- $terms = wp_get_object_terms( $booking->ID, $this->location_taxonomy );
430
-
431
- if ( empty( $terms ) || is_a( $terms, 'WP_Error' ) ) {
432
- return '';
433
- }
434
-
435
- $location = current( $terms );
436
-
437
- return $location->name;
438
- }
439
-
440
- /**
441
- * Modify queries to add location taxonomy parameters
442
- *
443
- * @param array $args Array of arguments passed to rtbQuery
444
- * @since 1.6
445
- */
446
- public function modify_query( $args, $context = '' ) {
447
-
448
- global $rtb_controller;
449
-
450
- if ( !empty( $args['location'] ) && !empty( $rtb_controller->locations->post_type ) ) {
451
-
452
- if ( !is_array( $args['location'] ) ) {
453
- $args['location'] = array( $args['location'] );
454
- }
455
-
456
- $args['tax_query'] = array(
457
- array(
458
- 'taxonomy' => $rtb_controller->locations->location_taxonomy,
459
- 'field' => 'term_id',
460
- 'terms' => $args['location'],
461
-
462
- )
463
- );
464
- }
465
-
466
- return $args;
467
- }
468
-
469
- /**
470
- * Add meta box to the location post editing screen
471
- *
472
- * @since 1.6
473
- */
474
- public function add_meta_boxes() {
475
-
476
- $meta_boxes = array(
477
-
478
- // Metabox to enter schema type
479
- array(
480
- 'id' => 'rtb_location',
481
- 'title' => __( 'Reservations', 'restaurant-reservations' ),
482
- 'callback' => array( $this, 'print_location_metabox' ),
483
- 'post_type' => $this->post_type,
484
- 'context' => 'side',
485
- 'priority' => 'default',
486
- ),
487
- );
488
-
489
- // Create filter so addons can modify the metaboxes
490
- $meta_boxes = apply_filters( 'rtb_location_metaboxes', $meta_boxes );
491
-
492
- // Create the metaboxes
493
- foreach ( $meta_boxes as $meta_box ) {
494
- add_meta_box(
495
- $meta_box['id'],
496
- $meta_box['title'],
497
- $meta_box['callback'],
498
- $meta_box['post_type'],
499
- $meta_box['context'],
500
- $meta_box['priority']
501
- );
502
- }
503
- }
504
-
505
- /**
506
- * Output a hidden nonce field to secure the saving of term meta
507
- *
508
- * @since 1.6
509
- */
510
- public function add_meta_nonce() {
511
- global $post;
512
- if ( $post->post_type == $this->post_type ) {
513
- wp_nonce_field( 'rtb_location_meta', 'rtb_location_meta_nonce' );
514
- }
515
- }
516
-
517
- /**
518
- * Print metabox on location post editing screen
519
- *
520
- * @since 1.6
521
- */
522
- public function print_location_metabox( $post ) {
523
-
524
- global $rtb_controller;
525
-
526
- $notification_email = '';
527
- $reply_to_name = '';
528
- $reply_to_address = '';
529
- $term_id = get_post_meta( $post->ID, $this->location_taxonomy, true );
530
- $admin_email_option = $rtb_controller->settings->get_setting( 'admin-email-option' );
531
- if ( $term_id ) {
532
- $reply_to_name = get_term_meta( $term_id, 'rtb_reply_to_name', true );
533
- $reply_to_address = get_term_meta( $term_id, 'rtb_reply_to_address', true );
534
- if ( $admin_email_option ) {
535
- $notification_email = get_term_meta( $term_id, 'rtb_admin_email_address', true );
536
- }
537
- }
538
-
539
- $append_booking_form = get_post_meta( $post->ID, 'rtb_append_booking_form', true );
540
-
541
- ?>
542
-
543
- <style type="text/css">.rtb-location-meta-input + .rtb-location-meta-input { margin-top: 2em; }</style>
544
-
545
- <div class="rtb-location-meta-input rtb-location-meta-append-form">
546
- <label>
547
- <input type="checkbox" name="rtb_append_booking_form" value="1"<?php if ( $append_booking_form ) : ?> checked="checked"<?php endif; ?>>
548
- <?php esc_html_e( "Automatically add the booking form to this page.", 'restaurant-reservations' ); ?>
549
- </label>
550
- </div>
551
-
552
- <div class="rtb-location-meta-input rtb-location-meta-reply-to-name">
553
- <label for="rtb_reply_to_name">
554
- <?php esc_html_e( 'Reply-To Name', 'restaurant-reservations' ); ?>
555
- </label>
556
- <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' ) ); ?>">
557
- <p class="description">
558
- <?php esc_html_e( 'The name which should appear in the Reply-To field of a user notification email.', 'restaurant-reservations' ); ?>
559
- </p>
560
- </div>
561
-
562
- <div class="rtb-location-meta-input rtb-location-meta-reply-to-address">
563
- <label for="rtb_reply_to_address">
564
- <?php esc_html_e( 'Reply-To Email Address', 'restaurant-reservations' ); ?>
565
- </label>
566
- <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' ) ); ?>">
567
- <p class="description">
568
- <?php esc_html_e( 'The email address which should appear in the Reply-To field of a user notification email.', 'restaurant-reservations' ); ?>
569
- </p>
570
- </div>
571
-
572
- <?php if ( $admin_email_option ) : ?>
573
- <div class="rtb-location-meta-input rtb-location-meta-admin-email">
574
- <label for="rtb_admin_email_address">
575
- <?php esc_html_e( 'Admin Notification Email Address', 'restaurant-reservations' ); ?>
576
- </label>
577
- <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' ) ); ?>">
578
- <p class="description">
579
- <?php esc_html_e( 'The email address where admin notifications for bookings at this location should be sent.', 'restaurant-reservations' ); ?>
580
- </p>
581
- </div>
582
- <?php endif; ?>
583
-
584
- <?php
585
- }
586
-
587
- /**
588
- * Append booking form to a location's `post_content`
589
- * @since 0.0.1
590
- */
591
- public function append_to_content( $content ) {
592
-
593
- if ( !is_main_query() || !in_the_loop() || post_password_required() ) {
594
- return $content;
595
- }
596
-
597
- global $post;
598
-
599
- $append_booking_form = get_post_meta( $post->ID, 'rtb_append_booking_form', true );
600
-
601
- if ( !$append_booking_form ) {
602
- return $content;
603
- }
604
-
605
- $term_id = get_post_meta( $post->ID, $this->location_taxonomy, true );
606
-
607
- if ( empty( $term_id ) ) {
608
- return $content;
609
- }
610
-
611
- return $content . do_shortcode( '[booking-form location=' . absint( $term_id ) .']' );
612
- }
613
-
614
- /**
615
- * Modify the notification email recipient for each location
616
- *
617
- * @since 1.6
618
- */
619
- public function notification_to_email( $email, $notification ) {
620
-
621
- if ( $notification->target == 'user' || empty( $notification->booking->location ) ) {
622
- return $email;
623
- }
624
-
625
- $val = get_term_meta( $notification->booking->location, 'rtb_admin_email_address', true );
626
- $email = empty( $val ) ? $email : $val;
627
-
628
- return $email;
629
- }
630
-
631
- /**
632
- * Modify the notification email sender address for each location
633
- *
634
- * @since 1.6
635
- */
636
- public function notification_from_email( $email, $notification ) {
637
-
638
- if ( $notification->target != 'user' || empty( $notification->booking->location ) ) {
639
- return $email;
640
- }
641
-
642
- $val = get_term_meta( $notification->booking->location, 'rtb_reply_to_address', true );
643
- $email = empty( $val ) ? $email : $val;
644
-
645
- return $email;
646
- }
647
-
648
- /**
649
- * Modify the notification email sender name for each location
650
- *
651
- * @since 1.6
652
- */
653
- public function notification_from_name( $name, $notification ) {
654
-
655
- if ( $notification->target != 'user' || empty( $notification->booking->location ) ) {
656
- return $name;
657
- }
658
-
659
- $val = get_term_meta( $notification->booking->location, 'rtb_reply_to_name', true );
660
- $name = empty( $val ) ? $name : $val;
661
-
662
- return $name;
663
- }
664
-
665
- /**
666
- * Add a location template tag for notifications
667
- *
668
- * @since 1.6.1
669
- */
670
- public function notification_template_tags( $template_tags, $notification ) {
671
-
672
- $term = empty( $notification->booking->location ) ? null : get_term( $notification->booking->location, $this->location_taxonomy );
673
- $location_name = is_null( $term ) || is_wp_error( $term ) ? '' : $term->name;
674
-
675
- $table_number = empty( $notification->booking->table ) ? '' : $notification->booking->table;
676
- $table_number = is_array($table_number) ? implode(',', $table_number ) : $table_number;
677
-
678
- return array_merge(
679
- array(
680
- '{location}' => $location_name,
681
- '{table}' => $table_number,
682
- ),
683
- $template_tags
684
- );
685
- }
686
-
687
- /**
688
- * Add a description for the location template tag
689
- *
690
- * @since 1.6.1
691
- */
692
- public function notification_template_tag_descriptions( $descriptions ) {
693
- return array_merge(
694
- array( '{location}' => __( 'Location for which this booking was made.', 'restaurant-reservations' ) ),
695
- $descriptions
696
- );
697
- }
698
-
699
- /**
700
- * Removes Auto-Draft locations that were added due to a bug in v1.7
701
- *
702
- * Version 1.7 introduced a bug which caused a location term to be
703
- * created if the location Add New page was loaded. This term
704
- * corresponded to an auto-draft post object and will be removed when
705
- * that object is removed. This provides a one-time fix in v1.7.1
706
- *
707
- * @see https://github.com/NateWr/restaurant-reservations/issues/91
708
- * @see https://developer.wordpress.org/reference/functions/wp_delete_auto_drafts/
709
- * @since 1.7.1
710
- */
711
- public function fix_autodraft_term_error() {
712
-
713
- if ( get_option( 'rtb_autodraft_terms_fixed', false ) ) {
714
- return;
715
- }
716
-
717
- global $wpdb;
718
-
719
- if ( !$wpdb ) {
720
- return;
721
- }
722
-
723
- $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND post_type = '$this->post_type';" );
724
- foreach ( (array) $old_posts as $delete ) {
725
- // Force delete.
726
- wp_delete_post( $delete, true );
727
- }
728
-
729
- // Set the `rtb_location_removed` term meta on any terms that are
730
- // no longer attached to posts
731
- global $wp_version;
732
- if ( version_compare( $wp_version, '3.9', '>=' ) ) {
733
- $live_terms = $wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key='$this->location_taxonomy';" );
734
- $all_terms = get_terms( array(
735
- 'taxonomy' => $this->location_taxonomy,
736
- 'hide_empty' => false,
737
- 'meta_query' => array(
738
- array(
739
- 'compare' => 'NOT EXISTS',
740
- 'key' => 'rtb_location_removed',
741
- )
742
- )
743
- ) );
744
- if ( is_array( $all_terms ) ) {
745
- foreach( $all_terms as $term ) {
746
- if ( !in_array( $term->term_id, $live_terms ) ) {
747
- $query = new rtbQuery( array( 'location' => $term->term_id ), 'delete-location-term-check' );
748
- $query->prepare_args();
749
- $query->get_bookings();
750
-
751
- // Don't delete taxonomy terms if there are bookings assigned to
752
- // this location, so the booking associations can remain as
753
- // historical data.
754
- if ( count( $query->bookings ) ) {
755
- add_term_meta( $term->term_id, 'rtb_location_removed', true );
756
- } else {
757
- wp_delete_term( $term->term_id, $this->location_taxonomy );
758
- }
759
- }
760
- }
761
- }
762
- }
763
-
764
- update_option( 'rtb_autodraft_terms_fixed', true );
765
- }
766
-
767
- /**
768
- * If multiple locations exist, adds a select box to the settings page which
769
- * allows a user to select whether certain settings are global or location-specific.
770
- * Also adds in location-specific settings for a number of different settings, and
771
- * makes all settings conditional on the value of the select box.
772
- *
773
- * @since 2.3.6
774
- */
775
- public function maybe_add_location_settings( $sap ) {
776
- global $rtb_controller;
777
-
778
- $args = array(
779
- 'taxonomy' => $this->location_taxonomy,
780
- 'hide_empty' => false,
781
- );
782
-
783
- $terms = get_terms( $args );
784
-
785
- if ( ! $this->do_locations_exist() ) { return $sap; }
786
-
787
- foreach ( $sap->pages['rtb-settings']->sections as $key => $section ) {
788
-
789
- foreach ( $section->settings as $setting_key => $setting ) {
790
-
791
- $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->conditional_on = 'location-select';
792
- $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->conditional_on_value = false;
793
-
794
- $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->set_conditional_display();
795
- }
796
- }
797
-
798
-
799
- $location_options = array(
800
- '' => __( 'Global', 'restaurant-reservations' ),
801
- );
802
-
803
- foreach ( $terms as $term ) {
804
-
805
- $location_options[ $term->slug ] = $term->term_id . ' - '.$term->name;
806
- }
807
-
808
- // Schedule location-specific options
809
- $sap->add_section(
810
- 'rtb-settings',
811
- array(
812
- 'id' => 'rtb-schedule-location-select',
813
- 'title' => __( 'Select Schedule Location', 'restaurant-reservations' ),
814
- 'tab' => 'rtb-schedule-tab',
815
- 'rank' => 2,
816
- )
817
- );
818
-
819
- $sap->add_setting(
820
- 'rtb-settings',
821
- 'rtb-schedule-location-select',
822
- 'select',
823
- array(
824
- 'id' => 'location-select',
825
- 'title' => __( 'Schedule Location', 'restaurant-reservations' ),
826
- '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' ),
827
- 'blank_option' => false,
828
- 'options' => $location_options,
829
- )
830
- );
831
-
832
- // Translateable strings for scheduler components
833
- $scheduler_strings = array(
834
- 'add_rule' => __( 'Add new scheduling rule', 'restaurant-reservations' ),
835
- 'weekly' => _x( 'Weekly', 'Format of a scheduling rule', 'restaurant-reservations' ),
836
- 'monthly' => _x( 'Monthly', 'Format of a scheduling rule', 'restaurant-reservations' ),
837
- 'date' => _x( 'Date', 'Format of a scheduling rule', 'restaurant-reservations' ),
838
- 'weekdays' => _x( 'Days of the week', 'Label for selecting days of the week in a scheduling rule', 'restaurant-reservations' ),
839
- 'month_weeks' => _x( 'Weeks of the month', 'Label for selecting weeks of the month in a scheduling rule', 'restaurant-reservations' ),
840
- 'date_label' => _x( 'Date', 'Label to select a date for a scheduling rule', 'restaurant-reservations' ),
841
- 'time_label' => _x( 'Time', 'Label to select a time slot for a scheduling rule', 'restaurant-reservations' ),
842
- 'allday' => _x( 'All day', 'Label to set a scheduling rule to last all day', 'restaurant-reservations' ),
843
- 'start' => _x( 'Start', 'Label for the starting time of a scheduling rule', 'restaurant-reservations' ),
844
- 'end' => _x( 'End', 'Label for the ending time of a scheduling rule', 'restaurant-reservations' ),
845
- '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' ),
846
- 'toggle' => _x( 'Open and close this rule', 'Toggle a scheduling rule open and closed', 'restaurant-reservations' ),
847
- 'delete' => _x( 'Delete rule', 'Delete a scheduling rule', 'restaurant-reservations' ),
848
- 'delete_schedule' => __( 'Delete scheduling rule', 'restaurant-reservations' ),
849
- 'never' => _x( 'Never', 'Brief default description of a scheduling rule when no weekdays or weeks are included in the rule', 'restaurant-reservations' ),
850
- 'weekly_always' => _x( 'Every day', 'Brief default description of a scheduling rule when all the weekdays/weeks are included in the rule', 'restaurant-reservations' ),
851
- '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' ),
852
- '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' ),
853
- 'all_day' => _x( 'All day', 'Brief default description of a scheduling rule when no times are set', 'restaurant-reservations' ),
854
- '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' ),
855
- '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' ),
856
- 'separator' => _x( '&mdash;', 'Separator between times of a scheduling rule', 'restaurant-reservations' ),
857
- );
858
-
859
- foreach ( $terms as $term ) {
860
-
861
- $sap->add_setting(
862
- 'rtb-settings',
863
- 'rtb-schedule',
864
- 'scheduler',
865
- array(
866
- 'id' => $term->slug . '-schedule-open',
867
- 'title' => __( 'Schedule', 'restaurant-reservations' ),
868
- 'description' => __( 'Define the weekly schedule during which you accept bookings.', 'restaurant-reservations' ),
869
- 'weekdays' => array(
870
- 'monday' => _x( 'Mo', 'Monday abbreviation', 'restaurant-reservations' ),
871
- 'tuesday' => _x( 'Tu', 'Tuesday abbreviation', 'restaurant-reservations' ),
872
- 'wednesday' => _x( 'We', 'Wednesday abbreviation', 'restaurant-reservations' ),
873
- 'thursday' => _x( 'Th', 'Thursday abbreviation', 'restaurant-reservations' ),
874
- 'friday' => _x( 'Fr', 'Friday abbreviation', 'restaurant-reservations' ),
875
- 'saturday' => _x( 'Sa', 'Saturday abbreviation', 'restaurant-reservations' ),
876
- 'sunday' => _x( 'Su', 'Sunday abbreviation', 'restaurant-reservations' )
877
- ),
878
- 'time_format' => $rtb_controller->settings->get_setting( 'time-format' ),
879
- 'date_format' => $rtb_controller->settings->get_setting( 'date-format' ),
880
- 'disable_weeks' => true,
881
- 'disable_date' => true,
882
- 'strings' => $scheduler_strings,
883
- 'conditional_on' => 'location-select',
884
- 'conditional_on_value' => $term->slug,
885
- )
886
- );
887
-
888
- $sap->add_setting(
889
- 'rtb-settings',
890
- 'rtb-schedule',
891
- 'scheduler',
892
- array(
893
- 'id' => $term->slug . '-schedule-closed',
894
- 'title' => __( 'Exceptions', 'restaurant-reservations' ),
895
- 'description' => __( "Define special opening hours for holidays, events or other needs. Leave the time empty if you're closed all day.", 'restaurant-reservations' ),
896
- 'time_format' => esc_attr( $rtb_controller->settings->get_setting( 'time-format' ) ),
897
- 'date_format' => esc_attr( $rtb_controller->settings->get_setting( 'date-format' ) ),
898
- 'disable_weekdays' => true,
899
- 'disable_weeks' => true,
900
- 'strings' => $scheduler_strings,
901
- 'conditional_on' => 'location-select',
902
- 'conditional_on_value' => $term->slug,
903
- )
904
- );
905
- }
906
-
907
- // Restriction location-specific options
908
- $sap->add_section(
909
- 'rtb-settings',
910
- array(
911
- 'id' => 'rtb-restrictions-location-select',
912
- 'title' => __( 'Select Seat Restrictions Location', 'restaurant-reservations' ),
913
- 'tab' => 'rtb-advanced-tab',
914
- 'rank' => 11,
915
- )
916
- );
917
-
918
- $sap->add_setting(
919
- 'rtb-settings',
920
- 'rtb-restrictions-location-select',
921
- 'select',
922
- array(
923
- 'id' => 'location-select',
924
- 'title' => __( 'Seat Restrictions Location', 'restaurant-reservations' ),
925
- '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' ),
926
- 'blank_option' => false,
927
- 'options' => $location_options,
928
- )
929
- );
930
-
931
- $max_reservation_options = array();
932
- $max_reservations_upper_limit = apply_filters( 'rtb-max-reservations-upper-limit', 100 );
933
-
934
- for ( $i = 1; $i <= $max_reservations_upper_limit; $i++ ) {
935
-
936
- $max_reservation_options[$i] = $i;
937
- }
938
-
939
- $max_people_options = array();
940
- $max_people_upper_limit = apply_filters( 'rtb-max-people-upper-limit', 400 );
941
-
942
- for ( $i = 1; $i <= $max_people_upper_limit; $i++ ) {
943
-
944
- $max_people_options[$i] = $i;
945
- }
946
-
947
- foreach ( $terms as $term ) {
948
-
949
- $sap->add_setting(
950
- 'rtb-settings',
951
- 'rtb-seat-assignments',
952
- 'select',
953
- array(
954
- 'id' => $term->slug . '-rtb-max-tables-count',
955
- 'title' => __( 'Max Reservations', 'restaurant-reservations' ),
956
- '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' ),
957
- 'options' => $max_reservation_options,
958
- 'conditional_on' => 'location-select',
959
- 'conditional_on_value' => $term->slug,
960
- )
961
- );
962
-
963
- $sap->add_setting(
964
- 'rtb-settings',
965
- 'rtb-seat-assignments',
966
- 'select',
967
- array(
968
- 'id' => $term->slug . '-rtb-max-people-count',
969
- 'title' => __( 'Max People', 'restaurant-reservations' ),
970
- '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' ),
971
- 'options' => $max_people_options,
972
- 'conditional_on' => 'location-select',
973
- 'conditional_on_value' => $term->slug,
974
- )
975
- );
976
- }
977
-
978
- return $sap;
979
- }
980
-
981
- /**
982
- * Blank out the location setting, so that it's always set to 'Global'
983
- * on page load, except for immediately after saving a setting.
984
- *
985
- * @since 2.3.6
986
- */
987
- public function remove_location_select_setting() {
988
- global $rtb_controller;
989
-
990
- $rtb_controller->settings->set_setting( 'location-select', null );
991
-
992
- $rtb_controller->settings->save_settings();
993
- }
994
-
995
- /**
996
- * Returns true if locations have been created, false otherwise
997
- *
998
- * @since 2.3.6
999
- */
1000
- public function do_locations_exist() {
1001
-
1002
- $args = array(
1003
- 'taxonomy' => $this->location_taxonomy,
1004
- 'hide_empty' => false,
1005
- );
1006
-
1007
- $terms = get_terms( $args );
1008
-
1009
- return ( ! empty( $terms ) and ! is_wp_error( $terms ) );
1010
- }
1011
-
1012
- function pass_location_data_to_js( $shortcode_arg ) {
1013
- global $rtb_controller;
1014
-
1015
- $is_location_specific_shortcode = false;
1016
-
1017
- $locations = [
1018
- // false means no slug
1019
- 'global' => false
1020
- ];
1021
-
1022
- // Specific location
1023
- $location_term = ! empty( $shortcode_arg['location'] ) ? get_term( $shortcode_arg['location'] ) : false;
1024
- if( $location_term and ! is_wp_error( $location_term ) ) {
1025
- $locations[$location_term->term_id] = $location_term->slug;
1026
-
1027
- $is_location_specific_shortcode = true;
1028
- }
1029
- // All locations
1030
- else {
1031
- $terms = get_terms(
1032
- array(
1033
- 'taxonomy' => $this->location_taxonomy,
1034
- 'hide_empty' => false,
1035
- )
1036
- );
1037
-
1038
- $options = array();
1039
- foreach( $terms as $term ) {
1040
- $archived = get_term_meta( $term->term_id, 'rtb_location_removed', true );
1041
- if ( ! $archived ) {
1042
- $locations[$term->term_id] = $term->slug;
1043
- }
1044
- }
1045
- }
1046
-
1047
- $data = [];
1048
- foreach ($locations as $idx => $slug) {
1049
- $data[$idx]['disable_dates'] = rtb_get_datepicker_rules( $slug );
1050
- $data[$idx]['schedule_open'] = $rtb_controller->settings->get_setting( 'schedule-open', $slug );
1051
- $data[$idx]['schedule_closed'] = $rtb_controller->settings->get_setting( 'schedule-closed', $slug );
1052
- $data[$idx]['enable_max_reservations'] = is_admin() && current_user_can( 'manage_bookings' ) ? false : $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $slug );
1053
- $data[$idx]['max_people'] = is_admin() && current_user_can( 'manage_bookings' ) ? 100 : $rtb_controller->settings->get_setting( 'rtb-max-people-count', $slug );
1054
-
1055
- // Also override initial instance of rtb_pickadate to apply location data
1056
- if( $is_location_specific_shortcode ) {
1057
- add_filter( 'rtb_pickadate_args', function( $variable_list ) use ( $data, $idx ) {
1058
-
1059
- return array_merge( $variable_list, $data[$idx] );
1060
- });
1061
- }
1062
- }
1063
-
1064
- wp_localize_script(
1065
- 'rtb-booking-form',
1066
- 'rtb_location_data',
1067
- apply_filters(
1068
- 'rtb_location_data',
1069
- $data
1070
- )
1071
- );
1072
-
1073
- return $shortcode_arg;
1074
- }
1075
-
1076
- /**
1077
- * Add location information in Payment Summary list on checkout
1078
- * @param array $summary_data [label -> data] list
1079
- * @param rtbBooking $booking current booking
1080
- */
1081
- public function add_payment_summary( $summary_data, $booking )
1082
- {
1083
- global $rtb_controller;
1084
-
1085
- if( property_exists( $booking, 'location' ) && ! empty( $booking->location ) ) {
1086
- $loc_term = get_term( $booking->location, $rtb_controller->locations->location_taxonomy, OBJECT );
1087
- if ( !$loc_term || is_a( $loc_term, 'WP_Error' ) ) {
1088
- unset( $summary_data['location'] );
1089
- }
1090
- else {
1091
- $summary_data['location'] = $loc_term->name;
1092
- }
1093
- }
1094
-
1095
- return $summary_data;
1096
- }
1097
- }
1098
- }
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
+ add_filter( 'rtb_booking_form_init', array( $this, 'pass_location_data_to_js' ) );
96
+ add_filter( 'rtb-payment-summary-data', array( $this, 'add_payment_summary' ), 10, 2);
97
+ }
98
+
99
+ /**
100
+ * Register the location taxonomy
101
+ *
102
+ * @since 1.6
103
+ */
104
+ public function register_taxonomy() {
105
+
106
+ $args = array(
107
+ 'label' => _x( 'Location', 'Name for grouping bookings', 'restaurant-reservations' ),
108
+ 'hierarchical' => false,
109
+ 'public' => true,
110
+ 'rewrite' => false,
111
+ );
112
+
113
+ /**
114
+ * Allow third-party plugins to modify the location taxonomy
115
+ * arguments.
116
+ *
117
+ * @since 1.6
118
+ */
119
+ $args = apply_filters( 'rtb_locations_args', $args );
120
+
121
+ register_taxonomy( $this->location_taxonomy, RTB_BOOKING_POST_TYPE, $args );
122
+ }
123
+
124
+ /**
125
+ * Generate taxonomy terms linked to locations and keep them sync'd
126
+ * with any changes
127
+ *
128
+ * @since 1.6
129
+ */
130
+ public function save_location( $post_id, $post, $update ) {
131
+
132
+ if (
133
+ $post->post_status === 'auto-draft' ||
134
+ ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ||
135
+ !current_user_can( 'edit_post', $post_id ) ||
136
+ !isset( $_POST['rtb_location_meta_nonce'] ) ||
137
+ !wp_verify_nonce( $_POST['rtb_location_meta_nonce'], 'rtb_location_meta' )
138
+ ) {
139
+ return $post_id;
140
+ }
141
+
142
+ $term_id = get_post_meta( $post_id, $this->location_taxonomy, true );
143
+
144
+ // Create a new term for this location
145
+ if ( !$term_id ) {
146
+
147
+ $term = wp_insert_term(
148
+ sanitize_text_field( $post->post_title ),
149
+ $this->location_taxonomy
150
+ );
151
+
152
+ if ( !is_a( $term, 'WP_Error' ) ) {
153
+ update_post_meta( $post_id, $this->location_taxonomy, $term['term_id'] );
154
+ $term_id = $term['term_id'];
155
+ }
156
+
157
+ // Update the term for this location
158
+ } else {
159
+ wp_update_term(
160
+ $term_id,
161
+ $this->location_taxonomy,
162
+ array(
163
+ 'name' => sanitize_text_field( $post->post_title ),
164
+ 'slug' => sanitize_text_field( $post->post_name ),
165
+ )
166
+ );
167
+ }
168
+
169
+ if ( !empty( $_POST['rtb_append_booking_form'] ) ) {
170
+ update_post_meta( $post_id, 'rtb_append_booking_form', true );
171
+ } else {
172
+ delete_post_meta( $post_id, 'rtb_append_booking_form' );
173
+ }
174
+
175
+ if ( $term_id ) {
176
+
177
+ if ( !empty( $_POST['rtb_reply_to_name'] ) ) {
178
+ $reply_to_name = sanitize_text_field( $_POST['rtb_reply_to_name'] );
179
+ update_term_meta( $term_id, 'rtb_reply_to_name', $reply_to_name );
180
+ } else {
181
+ delete_term_meta( $term_id, 'rtb_reply_to_name' );
182
+ }
183
+
184
+ if ( !empty( $_POST['rtb_reply_to_address'] ) ) {
185
+ $reply_to_address = sanitize_email( $_POST['rtb_reply_to_address'] );
186
+ update_term_meta( $term_id, 'rtb_reply_to_address', $reply_to_address );
187
+ } else {
188
+ delete_term_meta( $term_id, 'rtb_reply_to_address' );
189
+ }
190
+
191
+ if ( !empty( $_POST['rtb_admin_email_address'] ) ) {
192
+ $email = sanitize_text_field( $_POST['rtb_admin_email_address'] );
193
+ update_term_meta( $term_id, 'rtb_admin_email_address', $email );
194
+ } else {
195
+ delete_term_meta( $term_id, 'rtb_admin_email_address' );
196
+ }
197
+ }
198
+
199
+ return $post_id;
200
+ }
201
+
202
+ /**
203
+ * Delete taxonomy terms linked to locations when a location is deleted
204
+ *
205
+ * Only does this when no bookings are associated with that term.
206
+ * Otherwise it may be important to keep the bookings grouped for
207
+ * historical data.
208
+ *
209
+ * @since 1.6
210
+ */
211
+ public function delete_location( $post_id ) {
212
+
213
+ if ( !current_user_can( 'delete_posts' ) ) {
214
+ return $post_id;
215
+ }
216
+
217
+ $term_id = get_post_meta( $post_id, $this->location_taxonomy, true );
218
+
219
+ $term = get_term( $term_id, $this->location_taxonomy );
220
+
221
+ if ( !$term || is_a( $term, 'WP_Error' ) ) {
222
+ return;
223
+ }
224
+
225
+ $query = new rtbQuery( array( 'location' => $term_id ), 'delete-location-term-check' );
226
+ $query->prepare_args();
227
+ $query->get_bookings();
228
+
229
+ // Don't delete taxonomy terms if there are bookings assigned to
230
+ // this location, so the booking associations can remain as
231
+ // historical data.
232
+ if ( count( $query->bookings ) ) {
233
+ add_term_meta( $term_id, 'rtb_location_removed', true );
234
+ } else {
235
+ wp_delete_term( $term_id, $this->location_taxonomy );
236
+ }
237
+
238
+ }
239
+
240
+ /**
241
+ * Get location term id from location post id
242
+ *
243
+ * Transforms a location post id into its associated term id. If the
244
+ * id doesn't match a location post, it will check if the received id
245
+ * matches a term id and return it if so. Between versions 1.6 and
246
+ * and 1.6.1, only term ids were accepted as shortcodes, and this
247
+ * provides a backwards-compatible fallback.
248
+ *
249
+ * @param $location_id int The location id (post or term)
250
+ * @return int The location term id. Default: 0
251
+ */
252
+ public function get_location_term_id( $location_id ) {
253
+
254
+ $location_id = absint( $location_id );
255
+ $term_id = 0;
256
+
257
+ if ( get_post_type( $location_id ) === $this->post_type ) {
258
+ $term_id = get_post_meta( $location_id, $this->location_taxonomy, true );
259
+ } elseif ( term_exists( $location_id, $this->location_taxonomy ) ) {
260
+ $term_id = $location_id;
261
+ }
262
+
263
+ return $term_id;
264
+ }
265
+
266
+
267
+ /**
268
+ * Add the location selection field to the booking form
269
+ *
270
+ * @since 1.6
271
+ */
272
+ public function add_location_field( $fields, $request = null, $args = array() ) {
273
+ global $rtb_controller;
274
+
275
+ // If the location is specified, don't add a field.
276
+ // A hidden field is added automatically in rtb_print_booking_form()
277
+ if ( !empty( $args['location'] ) ) {
278
+ $args['location'] = $this->get_location_term_id( $args['location'] );
279
+ if ( !empty( $args['location'] ) ) {
280
+ return $fields;
281
+ }
282
+ }
283
+
284
+ if ( $request === null ) {
285
+ global $rtb_controller;
286
+ $request = $rtb_controller->request;
287
+ }
288
+
289
+ // Select a fieldset in which to place the field
290
+ $placement = false;
291
+ if ( isset( $fields['reservation'] ) && isset( $fields['reservation']['fields'] ) ) {
292
+ $placement = &$fields['reservation']['fields'];
293
+ } else {
294
+ $key = key( reset( $fields ) );
295
+ if ( isset( $fields[$key]['fields'] ) ) {
296
+ $placement = &$fields[$key]['fields'];
297
+ }
298
+ }
299
+
300
+ // If we couldn't find any working fieldset, then something odd is
301
+ // going on. Just pretend we were never here.
302
+ if ( $placement === false ) {
303
+ return $fields;
304
+ }
305
+
306
+ $placement = array_merge(
307
+ array(
308
+ 'location' => array(
309
+ 'title' => esc_html( $rtb_controller->settings->get_setting( 'label-location' ) ),
310
+ 'request_input' => empty( $request->location ) ? '' : $request->location,
311
+ 'callback' => 'rtb_print_form_select_field',
312
+ 'callback_args' => array(
313
+ 'options' => $this->get_location_options(),
314
+ ),
315
+ 'empty_option' => true,
316
+ 'required' => true,
317
+ )
318
+ ),
319
+ $placement
320
+ );
321
+
322
+ return $fields;
323
+ }
324
+
325
+ /**
326
+ * Retrieve a key/value array of location terms and names
327
+ *
328
+ * @param bool $active_only Whether or not to retrieve only currently
329
+ * active locations. Default: true - don't retrieve locations that
330
+ * have been removed
331
+ * @since 1.6
332
+ */
333
+ public function get_location_options( $active_only = true ) {
334
+
335
+ $terms = get_terms(
336
+ array(
337
+ 'taxonomy' => $this->location_taxonomy,
338
+ 'hide_empty' => false,
339
+ )
340
+ );
341
+
342
+ $options = array();
343
+ foreach( $terms as $term ) {
344
+ $archived = get_term_meta( $term->term_id, 'rtb_location_removed', true );
345
+ if ( !$active_only || !$archived ) {
346
+ $options[$term->term_id] = $term->name;
347
+ }
348
+ }
349
+
350
+ return $options;
351
+ }
352
+
353
+ /**
354
+ * Validate location in post data
355
+ *
356
+ * @since 1.6
357
+ */
358
+ public function validate_location( $booking ) {
359
+
360
+ $booking->location = empty( $_POST['rtb-location'] ) ? '' : absint( $_POST['rtb-location'] );
361
+ if ( empty( $booking->location ) ) {
362
+ $booking->validation_errors[] = array(
363
+ 'field' => 'location',
364
+ 'post_variable' => $booking->location,
365
+ 'message' => __( 'Please select a location for your booking.', 'restaurant-reservations' ),
366
+ );
367
+
368
+ } elseif ( !term_exists( $booking->location, $this->location_taxonomy ) ) {
369
+ $booking->validation_errors[] = array(
370
+ 'field' => 'location',
371
+ 'post_variable' => $booking->location,
372
+ 'message' => __( 'The location you selected is not valid. Please select another location.', 'restaurant-reservations' ),
373
+ );
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Save the booking location when the booking is created or updated.
379
+ *
380
+ * @since 1.6
381
+ */
382
+ public function save_booking_location( $booking ) {
383
+
384
+ if ( !empty( $booking->location ) ) {
385
+ wp_set_object_terms( $booking->ID, $booking->location, $this->location_taxonomy );
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Load the booking location when teh booking is loaded
391
+ *
392
+ * @since 1.6
393
+ */
394
+ public function load_booking_location( $booking, $post ) {
395
+
396
+ $terms = wp_get_object_terms( $booking->ID, $this->location_taxonomy, array( 'fields' => 'ids' ) );
397
+
398
+ if ( is_a( $terms, 'WP_Error' ) ) {
399
+ return;
400
+ }
401
+
402
+ $booking->location = current( $terms );
403
+ }
404
+
405
+ /**
406
+ * Add location column to the list table
407
+ *
408
+ * @since 1.6
409
+ */
410
+ public function add_location_column( $columns ) {
411
+
412
+ $first = array_splice( $columns, 0, 2 );
413
+ $first['location'] = __( 'Location', 'restaurant-reservations' );
414
+
415
+ return array_merge( $first, $columns );
416
+ }
417
+
418
+ /**
419
+ * Print the value in the location column for the list table
420
+ *
421
+ * @since 1.6
422
+ */
423
+ public function print_location_column( $value, $booking, $column_name ) {
424
+
425
+ if ( $column_name !== 'location' ) {
426
+ return $value;
427
+ }
428
+
429
+ $terms = wp_get_object_terms( $booking->ID, $this->location_taxonomy );
430
+
431
+ if ( empty( $terms ) || is_a( $terms, 'WP_Error' ) ) {
432
+ return '';
433
+ }
434
+
435
+ $location = current( $terms );
436
+
437
+ return $location->name;
438
+ }
439
+
440
+ /**
441
+ * Modify queries to add location taxonomy parameters
442
+ *
443
+ * @param array $args Array of arguments passed to rtbQuery
444
+ * @since 1.6
445
+ */
446
+ public function modify_query( $args, $context = '' ) {
447
+
448
+ global $rtb_controller;
449
+
450
+ if ( !empty( $args['location'] ) && !empty( $rtb_controller->locations->post_type ) ) {
451
+
452
+ if ( !is_array( $args['location'] ) ) {
453
+ $args['location'] = array( $args['location'] );
454
+ }
455
+
456
+ $args['tax_query'] = array(
457
+ array(
458
+ 'taxonomy' => $rtb_controller->locations->location_taxonomy,
459
+ 'field' => 'term_id',
460
+ 'terms' => $args['location'],
461
+
462
+ )
463
+ );
464
+ }
465
+
466
+ return $args;
467
+ }
468
+
469
+ /**
470
+ * Add meta box to the location post editing screen
471
+ *
472
+ * @since 1.6
473
+ */
474
+ public function add_meta_boxes() {
475
+
476
+ $meta_boxes = array(
477
+
478
+ // Metabox to enter schema type
479
+ array(
480
+ 'id' => 'rtb_location',
481
+ 'title' => __( 'Reservations', 'restaurant-reservations' ),
482
+ 'callback' => array( $this, 'print_location_metabox' ),
483
+ 'post_type' => $this->post_type,
484
+ 'context' => 'side',
485
+ 'priority' => 'default',
486
+ ),
487
+ );
488
+
489
+ // Create filter so addons can modify the metaboxes
490
+ $meta_boxes = apply_filters( 'rtb_location_metaboxes', $meta_boxes );
491
+
492
+ // Create the metaboxes
493
+ foreach ( $meta_boxes as $meta_box ) {
494
+ add_meta_box(
495
+ $meta_box['id'],
496
+ $meta_box['title'],
497
+ $meta_box['callback'],
498
+ $meta_box['post_type'],
499
+ $meta_box['context'],
500
+ $meta_box['priority']
501
+ );
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Output a hidden nonce field to secure the saving of term meta
507
+ *
508
+ * @since 1.6
509
+ */
510
+ public function add_meta_nonce() {
511
+ global $post;
512
+ if ( $post->post_type == $this->post_type ) {
513
+ wp_nonce_field( 'rtb_location_meta', 'rtb_location_meta_nonce' );
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Print metabox on location post editing screen
519
+ *
520
+ * @since 1.6
521
+ */
522
+ public function print_location_metabox( $post ) {
523
+
524
+ global $rtb_controller;
525
+
526
+ $notification_email = '';
527
+ $reply_to_name = '';
528
+ $reply_to_address = '';
529
+ $term_id = get_post_meta( $post->ID, $this->location_taxonomy, true );
530
+ $admin_email_option = $rtb_controller->settings->get_setting( 'admin-email-option' );
531
+ if ( $term_id ) {
532
+ $reply_to_name = get_term_meta( $term_id, 'rtb_reply_to_name', true );
533
+ $reply_to_address = get_term_meta( $term_id, 'rtb_reply_to_address', true );
534
+ if ( $admin_email_option ) {
535
+ $notification_email = get_term_meta( $term_id, 'rtb_admin_email_address', true );
536
+ }
537
+ }
538
+
539
+ $append_booking_form = get_post_meta( $post->ID, 'rtb_append_booking_form', true );
540
+
541
+ ?>
542
+
543
+ <style type="text/css">.rtb-location-meta-input + .rtb-location-meta-input { margin-top: 2em; }</style>
544
+
545
+ <div class="rtb-location-meta-input rtb-location-meta-append-form">
546
+ <label>
547
+ <input type="checkbox" name="rtb_append_booking_form" value="1"<?php if ( $append_booking_form ) : ?> checked="checked"<?php endif; ?>>
548
+ <?php esc_html_e( "Automatically add the booking form to this page.", 'restaurant-reservations' ); ?>
549
+ </label>
550
+ </div>
551
+
552
+ <div class="rtb-location-meta-input rtb-location-meta-reply-to-name">
553
+ <label for="rtb_reply_to_name">
554
+ <?php esc_html_e( 'Reply-To Name', 'restaurant-reservations' ); ?>
555
+ </label>
556
+ <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' ) ); ?>">
557
+ <p class="description">
558
+ <?php esc_html_e( 'The name which should appear in the Reply-To field of a user notification email.', 'restaurant-reservations' ); ?>
559
+ </p>
560
+ </div>
561
+
562
+ <div class="rtb-location-meta-input rtb-location-meta-reply-to-address">
563
+ <label for="rtb_reply_to_address">
564
+ <?php esc_html_e( 'Reply-To Email Address', 'restaurant-reservations' ); ?>
565
+ </label>
566
+ <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' ) ); ?>">
567
+ <p class="description">
568
+ <?php esc_html_e( 'The email address which should appear in the Reply-To field of a user notification email.', 'restaurant-reservations' ); ?>
569
+ </p>
570
+ </div>
571
+
572
+ <?php if ( $admin_email_option ) : ?>
573
+ <div class="rtb-location-meta-input rtb-location-meta-admin-email">
574
+ <label for="rtb_admin_email_address">
575
+ <?php esc_html_e( 'Admin Notification Email Address', 'restaurant-reservations' ); ?>
576
+ </label>
577
+ <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' ) ); ?>">
578
+ <p class="description">
579
+ <?php esc_html_e( 'The email address where admin notifications for bookings at this location should be sent.', 'restaurant-reservations' ); ?>
580
+ </p>
581
+ </div>
582
+ <?php endif; ?>
583
+
584
+ <?php
585
+ }
586
+
587
+ /**
588
+ * Append booking form to a location's `post_content`
589
+ * @since 0.0.1
590
+ */
591
+ public function append_to_content( $content ) {
592
+
593
+ if ( !is_main_query() || !in_the_loop() || post_password_required() ) {
594
+ return $content;
595
+ }
596
+
597
+ global $post;
598
+
599
+ $append_booking_form = get_post_meta( $post->ID, 'rtb_append_booking_form', true );
600
+
601
+ if ( !$append_booking_form ) {
602
+ return $content;
603
+ }
604
+
605
+ $term_id = get_post_meta( $post->ID, $this->location_taxonomy, true );
606
+
607
+ if ( empty( $term_id ) ) {
608
+ return $content;
609
+ }
610
+
611
+ return $content . do_shortcode( '[booking-form location=' . absint( $term_id ) .']' );
612
+ }
613
+
614
+ /**
615
+ * Modify the notification email recipient for each location
616
+ *
617
+ * @since 1.6
618
+ */
619
+ public function notification_to_email( $email, $notification ) {
620
+
621
+ if ( $notification->target == 'user' || empty( $notification->booking->location ) ) {
622
+ return $email;
623
+ }
624
+
625
+ $val = get_term_meta( $notification->booking->location, 'rtb_admin_email_address', true );
626
+ $email = empty( $val ) ? $email : $val;
627
+
628
+ return $email;
629
+ }
630
+
631
+ /**
632
+ * Modify the notification email sender address for each location
633
+ *
634
+ * @since 1.6
635
+ */
636
+ public function notification_from_email( $email, $notification ) {
637
+
638
+ if ( $notification->target != 'user' || empty( $notification->booking->location ) ) {
639
+ return $email;
640
+ }
641
+
642
+ $val = get_term_meta( $notification->booking->location, 'rtb_reply_to_address', true );
643
+ $email = empty( $val ) ? $email : $val;
644
+
645
+ return $email;
646
+ }
647
+
648
+ /**
649
+ * Modify the notification email sender name for each location
650
+ *
651
+ * @since 1.6
652
+ */
653
+ public function notification_from_name( $name, $notification ) {
654
+
655
+ if ( $notification->target != 'user' || empty( $notification->booking->location ) ) {
656
+ return $name;
657
+ }
658
+
659
+ $val = get_term_meta( $notification->booking->location, 'rtb_reply_to_name', true );
660
+ $name = empty( $val ) ? $name : $val;
661
+
662
+ return $name;
663
+ }
664
+
665
+ /**
666
+ * Add a location template tag for notifications
667
+ *
668
+ * @since 1.6.1
669
+ */
670
+ public function notification_template_tags( $template_tags, $notification ) {
671
+
672
+ $term = empty( $notification->booking->location ) ? null : get_term( $notification->booking->location, $this->location_taxonomy );
673
+ $location_name = is_null( $term ) || is_wp_error( $term ) ? '' : $term->name;
674
+
675
+ $table_number = empty( $notification->booking->table ) ? '' : $notification->booking->table;
676
+ $table_number = is_array($table_number) ? implode(',', $table_number ) : $table_number;
677
+
678
+ return array_merge(
679
+ array(
680
+ '{location}' => $location_name,
681
+ '{table}' => $table_number,
682
+ ),
683
+ $template_tags
684
+ );
685
+ }
686
+
687
+ /**
688
+ * Add a description for the location template tag
689
+ *
690
+ * @since 1.6.1
691
+ */
692
+ public function notification_template_tag_descriptions( $descriptions ) {
693
+ return array_merge(
694
+ array( '{location}' => __( 'Location for which this booking was made.', 'restaurant-reservations' ) ),
695
+ $descriptions
696
+ );
697
+ }
698
+
699
+ /**
700
+ * Removes Auto-Draft locations that were added due to a bug in v1.7
701
+ *
702
+ * Version 1.7 introduced a bug which caused a location term to be
703
+ * created if the location Add New page was loaded. This term
704
+ * corresponded to an auto-draft post object and will be removed when
705
+ * that object is removed. This provides a one-time fix in v1.7.1
706
+ *
707
+ * @see https://github.com/NateWr/restaurant-reservations/issues/91
708
+ * @see https://developer.wordpress.org/reference/functions/wp_delete_auto_drafts/
709
+ * @since 1.7.1
710
+ */
711
+ public function fix_autodraft_term_error() {
712
+
713
+ if ( get_option( 'rtb_autodraft_terms_fixed', false ) ) {
714
+ return;
715
+ }
716
+
717
+ global $wpdb;
718
+
719
+ if ( !$wpdb ) {
720
+ return;
721
+ }
722
+
723
+ $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND post_type = '$this->post_type';" );
724
+ foreach ( (array) $old_posts as $delete ) {
725
+ // Force delete.
726
+ wp_delete_post( $delete, true );
727
+ }
728
+
729
+ // Set the `rtb_location_removed` term meta on any terms that are
730
+ // no longer attached to posts
731
+ global $wp_version;
732
+ if ( version_compare( $wp_version, '3.9', '>=' ) ) {
733
+ $live_terms = $wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key='$this->location_taxonomy';" );
734
+ $all_terms = get_terms( array(
735
+ 'taxonomy' => $this->location_taxonomy,
736
+ 'hide_empty' => false,
737
+ 'meta_query' => array(
738
+ array(
739
+ 'compare' => 'NOT EXISTS',
740
+ 'key' => 'rtb_location_removed',
741
+ )
742
+ )
743
+ ) );
744
+ if ( is_array( $all_terms ) ) {
745
+ foreach( $all_terms as $term ) {
746
+ if ( !in_array( $term->term_id, $live_terms ) ) {
747
+ $query = new rtbQuery( array( 'location' => $term->term_id ), 'delete-location-term-check' );
748
+ $query->prepare_args();
749
+ $query->get_bookings();
750
+
751
+ // Don't delete taxonomy terms if there are bookings assigned to
752
+ // this location, so the booking associations can remain as
753
+ // historical data.
754
+ if ( count( $query->bookings ) ) {
755
+ add_term_meta( $term->term_id, 'rtb_location_removed', true );
756
+ } else {
757
+ wp_delete_term( $term->term_id, $this->location_taxonomy );
758
+ }
759
+ }
760
+ }
761
+ }
762
+ }
763
+
764
+ update_option( 'rtb_autodraft_terms_fixed', true );
765
+ }
766
+
767
+ /**
768
+ * If multiple locations exist, adds a select box to the settings page which
769
+ * allows a user to select whether certain settings are global or location-specific.
770
+ * Also adds in location-specific settings for a number of different settings, and
771
+ * makes all settings conditional on the value of the select box.
772
+ *
773
+ * @since 2.3.6
774
+ */
775
+ public function maybe_add_location_settings( $sap ) {
776
+ global $rtb_controller;
777
+
778
+ $args = array(
779
+ 'taxonomy' => $this->location_taxonomy,
780
+ 'hide_empty' => false,
781
+ );
782
+
783
+ $terms = get_terms( $args );
784
+
785
+ if ( ! $this->do_locations_exist() ) { return $sap; }
786
+
787
+ foreach ( $sap->pages['rtb-settings']->sections as $key => $section ) {
788
+
789
+ foreach ( $section->settings as $setting_key => $setting ) {
790
+
791
+ $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->conditional_on = 'location-select';
792
+ $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->conditional_on_value = false;
793
+
794
+ $sap->pages['rtb-settings']->sections[ $key ]->settings[ $setting_key ]->set_conditional_display();
795
+ }
796
+ }
797
+
798
+
799
+ $location_options = array(
800
+ '' => __( 'Global', 'restaurant-reservations' ),
801
+ );
802
+
803
+ foreach ( $terms as $term ) {
804
+
805
+ $location_options[ $term->slug ] = $term->term_id . ' - '.$term->name;
806
+ }
807
+
808
+ // Schedule location-specific options
809
+ $sap->add_section(
810
+ 'rtb-settings',
811
+ array(
812
+ 'id' => 'rtb-schedule-location-select',
813
+ 'title' => __( 'Select Schedule Location', 'restaurant-reservations' ),
814
+ 'tab' => 'rtb-schedule-tab',
815
+ 'rank' => 2,
816
+ )
817
+ );
818
+
819
+ $sap->add_setting(
820
+ 'rtb-settings',
821
+ 'rtb-schedule-location-select',
822
+ 'select',
823
+ array(
824
+ 'id' => 'location-select',
825
+ 'title' => __( 'Schedule Location', 'restaurant-reservations' ),
826
+ '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' ),
827
+ 'blank_option' => false,
828
+ 'options' => $location_options,
829
+ )
830
+ );
831
+
832
+ // Translateable strings for scheduler components
833
+ $scheduler_strings = array(
834
+ 'add_rule' => __( 'Add new scheduling rule', 'restaurant-reservations' ),
835
+ 'weekly' => _x( 'Weekly', 'Format of a scheduling rule', 'restaurant-reservations' ),
836
+ 'monthly' => _x( 'Monthly', 'Format of a scheduling rule', 'restaurant-reservations' ),
837
+ 'date' => _x( 'Date', 'Format of a scheduling rule', 'restaurant-reservations' ),
838
+ 'weekdays' => _x( 'Days of the week', 'Label for selecting days of the week in a scheduling rule', 'restaurant-reservations' ),
839
+ 'month_weeks' => _x( 'Weeks of the month', 'Label for selecting weeks of the month in a scheduling rule', 'restaurant-reservations' ),
840
+ 'date_label' => _x( 'Date', 'Label to select a date for a scheduling rule', 'restaurant-reservations' ),
841
+ 'time_label' => _x( 'Time', 'Label to select a time slot for a scheduling rule', 'restaurant-reservations' ),
842
+ 'allday' => _x( 'All day', 'Label to set a scheduling rule to last all day', 'restaurant-reservations' ),
843
+ 'start' => _x( 'Start', 'Label for the starting time of a scheduling rule', 'restaurant-reservations' ),
844
+ 'end' => _x( 'End', 'Label for the ending time of a scheduling rule', 'restaurant-reservations' ),
845
+ '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' ),
846
+ 'toggle' => _x( 'Open and close this rule', 'Toggle a scheduling rule open and closed', 'restaurant-reservations' ),
847
+ 'delete' => _x( 'Delete rule', 'Delete a scheduling rule', 'restaurant-reservations' ),
848
+ 'delete_schedule' => __( 'Delete scheduling rule', 'restaurant-reservations' ),
849
+ 'never' => _x( 'Never', 'Brief default description of a scheduling rule when no weekdays or weeks are included in the rule', 'restaurant-reservations' ),
850
+ 'weekly_always' => _x( 'Every day', 'Brief default description of a scheduling rule when all the weekdays/weeks are included in the rule', 'restaurant-reservations' ),
851
+ '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' ),
852
+ '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' ),
853
+ 'all_day' => _x( 'All day', 'Brief default description of a scheduling rule when no times are set', 'restaurant-reservations' ),
854
+ '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' ),
855
+ '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' ),
856
+ 'separator' => _x( '&mdash;', 'Separator between times of a scheduling rule', 'restaurant-reservations' ),
857
+ );
858
+
859
+ foreach ( $terms as $term ) {
860
+
861
+ $sap->add_setting(
862
+ 'rtb-settings',
863
+ 'rtb-schedule',
864
+ 'scheduler',
865
+ array(
866
+ 'id' => $term->slug . '-schedule-open',
867
+ 'title' => __( 'Schedule', 'restaurant-reservations' ),
868
+ 'description' => __( 'Define the weekly schedule during which you accept bookings.', 'restaurant-reservations' ),
869
+ 'weekdays' => array(
870
+ 'monday' => _x( 'Mo', 'Monday abbreviation', 'restaurant-reservations' ),
871
+ 'tuesday' => _x( 'Tu', 'Tuesday abbreviation', 'restaurant-reservations' ),
872
+ 'wednesday' => _x( 'We', 'Wednesday abbreviation', 'restaurant-reservations' ),
873
+ 'thursday' => _x( 'Th', 'Thursday abbreviation', 'restaurant-reservations' ),
874
+ 'friday' => _x( 'Fr', 'Friday abbreviation', 'restaurant-reservations' ),
875
+ 'saturday' => _x( 'Sa', 'Saturday abbreviation', 'restaurant-reservations' ),
876
+ 'sunday' => _x( 'Su', 'Sunday abbreviation', 'restaurant-reservations' )
877
+ ),
878
+ 'time_format' => $rtb_controller->settings->get_setting( 'time-format' ),
879
+ 'date_format' => $rtb_controller->settings->get_setting( 'date-format' ),
880
+ 'disable_weeks' => true,
881
+ 'disable_date' => true,
882
+ 'strings' => $scheduler_strings,
883
+ 'conditional_on' => 'location-select',
884
+ 'conditional_on_value' => $term->slug,
885
+ )
886
+ );
887
+
888
+ $sap->add_setting(
889
+ 'rtb-settings',
890
+ 'rtb-schedule',
891
+ 'scheduler',
892
+ array(
893
+ 'id' => $term->slug . '-schedule-closed',
894
+ 'title' => __( 'Exceptions', 'restaurant-reservations' ),
895
+ 'description' => __( "Define special opening hours for holidays, events or other needs. Leave the time empty if you're closed all day.", 'restaurant-reservations' ),
896
+ 'time_format' => esc_attr( $rtb_controller->settings->get_setting( 'time-format' ) ),
897
+ 'date_format' => esc_attr( $rtb_controller->settings->get_setting( 'date-format' ) ),
898
+ 'disable_weekdays' => true,
899
+ 'disable_weeks' => true,
900
+ 'strings' => $scheduler_strings,
901
+ 'conditional_on' => 'location-select',
902
+ 'conditional_on_value' => $term->slug,
903
+ )
904
+ );
905
+ }
906
+
907
+ // Restriction location-specific options
908
+ $sap->add_section(
909
+ 'rtb-settings',
910
+ array(
911
+ 'id' => 'rtb-restrictions-location-select',
912
+ 'title' => __( 'Select Seat Restrictions Location', 'restaurant-reservations' ),
913
+ 'tab' => 'rtb-advanced-tab',
914
+ 'rank' => 11,
915
+ )
916
+ );
917
+
918
+ $sap->add_setting(
919
+ 'rtb-settings',
920
+ 'rtb-restrictions-location-select',
921
+ 'select',
922
+ array(
923
+ 'id' => 'location-select',
924
+ 'title' => __( 'Seat Restrictions Location', 'restaurant-reservations' ),
925
+ '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' ),
926
+ 'blank_option' => false,
927
+ 'options' => $location_options,
928
+ )
929
+ );
930
+
931
+ $max_reservation_options = array();
932
+ $max_reservations_upper_limit = apply_filters( 'rtb-max-reservations-upper-limit', 100 );
933
+
934
+ for ( $i = 1; $i <= $max_reservations_upper_limit; $i++ ) {
935
+
936
+ $max_reservation_options[$i] = $i;
937
+ }
938
+
939
+ $max_people_options = array();
940
+ $max_people_upper_limit = apply_filters( 'rtb-max-people-upper-limit', 400 );
941
+
942
+ for ( $i = 1; $i <= $max_people_upper_limit; $i++ ) {
943
+
944
+ $max_people_options[$i] = $i;
945
+ }
946
+
947
+ foreach ( $terms as $term ) {
948
+
949
+ $sap->add_setting(
950
+ 'rtb-settings',
951
+ 'rtb-seat-assignments',
952
+ 'select',
953
+ array(
954
+ 'id' => $term->slug . '-rtb-max-tables-count',
955
+ 'title' => __( 'Max Reservations', 'restaurant-reservations' ),
956
+ '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' ),
957
+ 'options' => $max_reservation_options,
958
+ 'conditional_on' => 'location-select',
959
+ 'conditional_on_value' => $term->slug,
960
+ )
961
+ );
962
+
963
+ $sap->add_setting(
964
+ 'rtb-settings',
965
+ 'rtb-seat-assignments',
966
+ 'select',
967
+ array(
968
+ 'id' => $term->slug . '-rtb-max-people-count',
969
+ 'title' => __( 'Max People', 'restaurant-reservations' ),
970
+ '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' ),
971
+ 'options' => $max_people_options,
972
+ 'conditional_on' => 'location-select',
973
+ 'conditional_on_value' => $term->slug,
974
+ )
975
+ );
976
+ }
977
+
978
+ return $sap;
979
+ }
980
+
981
+ /**
982
+ * Blank out the location setting, so that it's always set to 'Global'
983
+ * on page load, except for immediately after saving a setting.
984
+ *
985
+ * @since 2.3.6
986
+ */
987
+ public function remove_location_select_setting() {
988
+ global $rtb_controller;
989
+
990
+ $rtb_controller->settings->set_setting( 'location-select', null );
991
+
992
+ $rtb_controller->settings->save_settings();
993
+ }
994
+
995
+ /**
996
+ * Returns true if locations have been created, false otherwise
997
+ *
998
+ * @since 2.3.6
999
+ */
1000
+ public function do_locations_exist() {
1001
+
1002
+ $args = array(
1003
+ 'taxonomy' => $this->location_taxonomy,
1004
+ 'hide_empty' => false,
1005
+ );
1006
+
1007
+ $terms = get_terms( $args );
1008
+
1009
+ return ( ! empty( $terms ) and ! is_wp_error( $terms ) );
1010
+ }
1011
+
1012
+ function pass_location_data_to_js( $shortcode_arg ) {
1013
+ global $rtb_controller;
1014
+
1015
+ $is_location_specific_shortcode = false;
1016
+
1017
+ $locations = [
1018
+ // false means no slug
1019
+ 'global' => false
1020
+ ];
1021
+
1022
+ // Specific location
1023
+ $location_term = ! empty( $shortcode_arg['location'] ) ? get_term( $shortcode_arg['location'] ) : false;
1024
+ if( $location_term and ! is_wp_error( $location_term ) ) {
1025
+ $locations[$location_term->term_id] = $location_term->slug;
1026
+
1027
+ $is_location_specific_shortcode = true;
1028
+ }
1029
+ // All locations
1030
+ else {
1031
+ $terms = get_terms(
1032
+ array(
1033
+ 'taxonomy' => $this->location_taxonomy,
1034
+ 'hide_empty' => false,
1035
+ )
1036
+ );
1037
+
1038
+ $options = array();
1039
+ foreach( $terms as $term ) {
1040
+ $archived = get_term_meta( $term->term_id, 'rtb_location_removed', true );
1041
+ if ( ! $archived ) {
1042
+ $locations[$term->term_id] = $term->slug;
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ $data = [];
1048
+ foreach ($locations as $idx => $slug) {
1049
+ $data[$idx]['disable_dates'] = rtb_get_datepicker_rules( $slug );
1050
+ $data[$idx]['schedule_open'] = $rtb_controller->settings->get_setting( 'schedule-open', $slug );
1051
+ $data[$idx]['schedule_closed'] = $rtb_controller->settings->get_setting( 'schedule-closed', $slug );
1052
+ $data[$idx]['enable_max_reservations'] = is_admin() && current_user_can( 'manage_bookings' ) ? false : $rtb_controller->settings->get_setting( 'rtb-enable-max-tables', $slug );
1053
+ $data[$idx]['max_people'] = is_admin() && current_user_can( 'manage_bookings' ) ? 100 : $rtb_controller->settings->get_setting( 'rtb-max-people-count', $slug );
1054
+
1055
+ // Also override initial instance of rtb_pickadate to apply location data
1056
+ if( $is_location_specific_shortcode ) {
1057
+ add_filter( 'rtb_pickadate_args', function( $variable_list ) use ( $data, $idx ) {
1058
+
1059
+ return array_merge( $variable_list, $data[$idx] );
1060
+ });
1061
+ }
1062
+ }
1063
+
1064
+ wp_localize_script(
1065
+ 'rtb-booking-form',
1066
+ 'rtb_location_data',
1067
+ apply_filters(
1068
+ 'rtb_location_data',
1069
+ $data
1070
+ )
1071
+ );
1072
+
1073
+ return $shortcode_arg;
1074
+ }
1075
+
1076
+ /**
1077
+ * Add location information in Payment Summary list on checkout
1078
+ * @param array $summary_data [label -> data] list
1079
+ * @param rtbBooking $booking current booking
1080
+ */
1081
+ public function add_payment_summary( $summary_data, $booking )
1082
+ {
1083
+ global $rtb_controller;
1084
+
1085
+ if( property_exists( $booking, 'location' ) && ! empty( $booking->location ) ) {
1086
+ $loc_term = get_term( $booking->location, $rtb_controller->locations->location_taxonomy, OBJECT );
1087
+ if ( !$loc_term || is_a( $loc_term, 'WP_Error' ) ) {
1088
+ unset( $summary_data['location'] );
1089
+ }
1090
+ else {
1091
+ $summary_data['location'] = $loc_term->name;
1092
+ }
1093
+ }
1094
+
1095
+ return $summary_data;
1096
+ }
1097
+ }
1098
+ }
includes/Notification.Email.class.php CHANGED
@@ -1,308 +1,308 @@
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 ( ! empty( $this->to_email ) ) {
93
-
94
- $to_email = $this->to_email;
95
- }
96
- elseif ( $this->target == 'user' ) {
97
-
98
- $to_email = empty( $this->booking->email ) ? null : $this->booking->email;
99
- }
100
- else {
101
-
102
- $to_email = $rtb_controller->settings->get_setting( 'admin-email-address' );
103
- }
104
-
105
- $this->to_email = apply_filters( 'rtb_notification_email_to_email', $to_email, $this );
106
-
107
- }
108
-
109
- /**
110
- * Set from email
111
- * @since 0.0.1
112
- */
113
- public function set_from_email() {
114
- global $rtb_controller;
115
-
116
- if ( ! empty( $this->from_email ) and ! empty( $this->from_name ) ) {
117
-
118
- $from_email = $this->from_email;
119
- $from_name = $this->from_name;
120
- }
121
- elseif ( $this->target == 'user' ) {
122
-
123
- $from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
124
- $from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
125
- }
126
- else {
127
-
128
- $from_email = $this->booking->email;
129
- $from_name = $this->booking->name;
130
- }
131
-
132
- $this->from_email = apply_filters( 'rtb_notification_email_from_email', $from_email, $this );
133
- $this->from_name = apply_filters( 'rtb_notification_email_from_name', $from_name, $this );
134
-
135
- }
136
-
137
- /**
138
- * Set email subject
139
- * @since 0.0.1
140
- */
141
- public function set_subject() {
142
-
143
- global $rtb_controller;
144
-
145
- if( $this->event == 'new_submission' ) {
146
- if ( $this->target == 'admin' ) {
147
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-admin' );
148
- } elseif ( $this->target == 'user' ) {
149
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-user' );
150
- }
151
-
152
- } elseif ( $this->event == 'rtb_confirmed_booking' ) {
153
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-confirmed-admin' );
154
-
155
- }elseif ( $this->event == 'pending_to_confirmed' ) {
156
- $subject = $rtb_controller->settings->get_setting( 'subject-confirmed-user' );
157
-
158
- } elseif ( $this->event == 'pending_to_closed' ) {
159
- $subject = $rtb_controller->settings->get_setting( 'subject-rejected-user' );
160
-
161
- } elseif ( $this->event == 'booking_cancelled' ) {
162
- if ( $this->target == 'admin' ) {
163
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-cancelled-admin' );
164
- } elseif ( $this->target == 'user' ) {
165
- $subject = $rtb_controller->settings->get_setting( 'subject-booking-cancelled-user' );
166
- }
167
-
168
- } elseif ( $this->event == 'late_user' ) {
169
- $subject = $rtb_controller->settings->get_setting( 'subject-late-user' );
170
-
171
- } elseif ( $this->event == 'reminder' ) {
172
- $subject = $rtb_controller->settings->get_setting( 'subject-reminder-user' );
173
-
174
- // Use a subject that's been appended manually if available
175
- } else {
176
- $subject = empty( $this->subject ) ? '' : $this->subject;
177
- }
178
-
179
- $this->subject = $this->process_subject_template( apply_filters( 'rtb_notification_email_subject', $subject, $this ) );
180
-
181
- }
182
-
183
- /**
184
- * Set email headers
185
- * @since 0.0.1
186
- */
187
- public function set_headers( $headers = null ) {
188
-
189
- global $rtb_controller;
190
-
191
- $from_email = apply_filters( 'rtb_notification_email_header_from_email', $rtb_controller->settings->get_setting( 'from-email-address' ) );
192
-
193
- $headers = "From: =?UTF-8?Q?" .
194
- quoted_printable_encode(
195
- html_entity_decode(
196
- $rtb_controller->settings->get_setting( 'reply-to-name' ),
197
- ENT_QUOTES,
198
- 'UTF-8'
199
- )
200
- ) .
201
- "?= <" . $from_email . ">\r\n";
202
-
203
- $headers .= "Reply-To: =?UTF-8?Q?" .
204
- quoted_printable_encode(
205
- html_entity_decode(
206
- $this->from_name,
207
- ENT_QUOTES,
208
- 'UTF-8'
209
- )
210
- ) .
211
- "?= <" . $this->from_email . ">\r\n";
212
-
213
- $headers .= "Content-Type: text/html; charset=utf-8\r\n";
214
-
215
- $this->headers = apply_filters( 'rtb_notification_email_headers', $headers, $this );
216
-
217
- }
218
-
219
- /**
220
- * Set email message body
221
- * @since 0.0.1
222
- */
223
- public function set_message() {
224
-
225
- if ( $this->event == 'new_submission' ) {
226
- if ( $this->target == 'user' ) {
227
- $template = $this->get_template( 'template-booking-user' );
228
- } elseif ( $this->target == 'admin' ) {
229
- $template = $this->get_template( 'template-booking-admin' );
230
- }
231
-
232
- } elseif ( $this->event == 'rtb_confirmed_booking' ) {
233
- if ( $this->target == 'admin' ) {
234
- $template = $this->get_template( 'template-booking-confirmed-admin' );
235
- }
236
-
237
- } elseif ( $this->event == 'pending_to_confirmed' ) {
238
- if ( $this->target == 'user' ) {
239
- $template = $this->get_template( 'template-confirmed-user' );
240
- }
241
-
242
- } elseif ( $this->event == 'pending_to_closed' ) {
243
- if ( $this->target == 'user' ) {
244
- $template = $this->get_template( 'template-rejected-user' );
245
- }
246
-
247
- } elseif ( $this->event == 'booking_cancelled' ) {
248
- if ( $this->target == 'user' ) {
249
- $template = $this->get_template( 'template-booking-cancelled-user' );
250
- } elseif ( $this->target == 'admin' ) {
251
- $template = $this->get_template( 'template-booking-cancelled-admin' );
252
- }
253
-
254
- } elseif ( $this->event == 'late_user' ) {
255
- if ( $this->target == 'user' ) {
256
- $template = $this->get_template( 'template-late-user' );
257
- }
258
-
259
- } elseif ( $this->event == 'reminder' ) {
260
- if ( $this->target == 'user' ) {
261
- $template = $this->get_template( 'template-reminder-user' );
262
- }
263
-
264
- // Use a message that's been appended manually if available
265
- } else {
266
- $template = empty( $this->message ) ? '' : $this->message;
267
- }
268
-
269
- $template = apply_filters( 'rtb_notification_email_template', $template, $this );
270
-
271
- if ( ! empty( $this->manual_message ) ) {
272
- $this->message = $this->manual_message;
273
- }
274
- elseif ( empty( $template ) ) {
275
- $this->message = '';
276
- } else {
277
- $this->message = wpautop( $this->process_template( $template ) );
278
- }
279
-
280
- }
281
-
282
- /**
283
- * Process template tags for email subjects
284
- * @since 0.0.1
285
- */
286
- public function process_subject_template( $subject ) {
287
-
288
- $template_tags = array(
289
- '{user_name}' => ! empty( $this->booking->name ) ? $this->booking->name : '',
290
- '{party}' => ! empty( $this->booking->party ) ? $this->booking->party : '',
291
- '{date}' => ! empty( $this->booking->date ) ? $this->booking->format_date( $this->booking->date ) : '',
292
- );
293
-
294
- $template_tags = apply_filters( 'rtb_notification_email_subject_template_tags', $template_tags, $this );
295
-
296
- return str_replace( array_keys( $template_tags ), array_values( $template_tags ), $subject );
297
-
298
- }
299
-
300
- /**
301
- * Send notification
302
- * @since 0.0.1
303
- */
304
- public function send_notification() {
305
- return wp_mail( $this->to_email, $this->subject, $this->message, $this->headers, apply_filters( 'rtb_notification_email_attachments', array(), $this ) );
306
- }
307
- }
308
- } // 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 ( ! empty( $this->to_email ) ) {
93
+
94
+ $to_email = $this->to_email;
95
+ }
96
+ elseif ( $this->target == 'user' ) {
97
+
98
+ $to_email = empty( $this->booking->email ) ? null : $this->booking->email;
99
+ }
100
+ else {
101
+
102
+ $to_email = $rtb_controller->settings->get_setting( 'admin-email-address' );
103
+ }
104
+
105
+ $this->to_email = apply_filters( 'rtb_notification_email_to_email', $to_email, $this );
106
+
107
+ }
108
+
109
+ /**
110
+ * Set from email
111
+ * @since 0.0.1
112
+ */
113
+ public function set_from_email() {
114
+ global $rtb_controller;
115
+
116
+ if ( ! empty( $this->from_email ) and ! empty( $this->from_name ) ) {
117
+
118
+ $from_email = $this->from_email;
119
+ $from_name = $this->from_name;
120
+ }
121
+ elseif ( $this->target == 'user' ) {
122
+
123
+ $from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
124
+ $from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
125
+ }
126
+ else {
127
+
128
+ $from_email = $this->booking->email;
129
+ $from_name = $this->booking->name;
130
+ }
131
+
132
+ $this->from_email = apply_filters( 'rtb_notification_email_from_email', $from_email, $this );
133
+ $this->from_name = apply_filters( 'rtb_notification_email_from_name', $from_name, $this );
134
+
135
+ }
136
+
137
+ /**
138
+ * Set email subject
139
+ * @since 0.0.1
140
+ */
141
+ public function set_subject() {
142
+
143
+ global $rtb_controller;
144
+
145
+ if( $this->event == 'new_submission' ) {
146
+ if ( $this->target == 'admin' ) {
147
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-admin' );
148
+ } elseif ( $this->target == 'user' ) {
149
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-user' );
150
+ }
151
+
152
+ } elseif ( $this->event == 'rtb_confirmed_booking' ) {
153
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-confirmed-admin' );
154
+
155
+ }elseif ( $this->event == 'pending_to_confirmed' ) {
156
+ $subject = $rtb_controller->settings->get_setting( 'subject-confirmed-user' );
157
+
158
+ } elseif ( $this->event == 'pending_to_closed' ) {
159
+ $subject = $rtb_controller->settings->get_setting( 'subject-rejected-user' );
160
+
161
+ } elseif ( $this->event == 'booking_cancelled' ) {
162
+ if ( $this->target == 'admin' ) {
163
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-cancelled-admin' );
164
+ } elseif ( $this->target == 'user' ) {
165
+ $subject = $rtb_controller->settings->get_setting( 'subject-booking-cancelled-user' );
166
+ }
167
+
168
+ } elseif ( $this->event == 'late_user' ) {
169
+ $subject = $rtb_controller->settings->get_setting( 'subject-late-user' );
170
+
171
+ } elseif ( $this->event == 'reminder' ) {
172
+ $subject = $rtb_controller->settings->get_setting( 'subject-reminder-user' );
173
+
174
+ // Use a subject that's been appended manually if available
175
+ } else {
176
+ $subject = empty( $this->subject ) ? '' : $this->subject;
177
+ }
178
+
179
+ $this->subject = $this->process_subject_template( apply_filters( 'rtb_notification_email_subject', $subject, $this ) );
180
+
181
+ }
182
+
183
+ /**
184
+ * Set email headers
185
+ * @since 0.0.1
186
+ */
187
+ public function set_headers( $headers = null ) {
188
+
189
+ global $rtb_controller;
190
+
191
+ $from_email = apply_filters( 'rtb_notification_email_header_from_email', $rtb_controller->settings->get_setting( 'from-email-address' ) );
192
+
193
+ $headers = "From: =?UTF-8?Q?" .
194
+ quoted_printable_encode(
195
+ html_entity_decode(
196
+ $rtb_controller->settings->get_setting( 'reply-to-name' ),
197
+ ENT_QUOTES,
198
+ 'UTF-8'
199
+ )
200
+ ) .
201
+ "?= <" . $from_email . ">\r\n";
202
+
203
+ $headers .= "Reply-To: =?UTF-8?Q?" .
204
+ quoted_printable_encode(
205
+ html_entity_decode(
206
+ $this->from_name,
207
+ ENT_QUOTES,
208
+ 'UTF-8'
209
+ )
210
+ ) .
211
+ "?= <" . $this->from_email . ">\r\n";
212
+
213
+ $headers .= "Content-Type: text/html; charset=utf-8\r\n";
214
+
215
+ $this->headers = apply_filters( 'rtb_notification_email_headers', $headers, $this );
216
+
217
+ }
218
+
219
+ /**
220
+ * Set email message body
221
+ * @since 0.0.1
222
+ */
223
+ public function set_message() {
224
+
225
+ if ( $this->event == 'new_submission' ) {
226
+ if ( $this->target == 'user' ) {
227
+ $template = $this->get_template( 'template-booking-user' );
228
+ } elseif ( $this->target == 'admin' ) {
229
+ $template = $this->get_template( 'template-booking-admin' );
230
+ }
231
+
232
+ } elseif ( $this->event == 'rtb_confirmed_booking' ) {
233
+ if ( $this->target == 'admin' ) {
234
+ $template = $this->get_template( 'template-booking-confirmed-admin' );
235
+ }
236
+
237
+ } elseif ( $this->event == 'pending_to_confirmed' ) {
238
+ if ( $this->target == 'user' ) {
239
+ $template = $this->get_template( 'template-confirmed-user' );
240
+ }
241
+
242
+ } elseif ( $this->event == 'pending_to_closed' ) {
243
+ if ( $this->target == 'user' ) {
244
+ $template = $this->get_template( 'template-rejected-user' );
245
+ }
246
+
247
+ } elseif ( $this->event == 'booking_cancelled' ) {
248
+ if ( $this->target == 'user' ) {
249
+ $template = $this->get_template( 'template-booking-cancelled-user' );
250
+ } elseif ( $this->target == 'admin' ) {
251
+ $template = $this->get_template( 'template-booking-cancelled-admin' );
252
+ }
253
+
254
+ } elseif ( $this->event == 'late_user' ) {
255
+ if ( $this->target == 'user' ) {
256
+ $template = $this->get_template( 'template-late-user' );
257
+ }
258
+
259
+ } elseif ( $this->event == 'reminder' ) {
260
+ if ( $this->target == 'user' ) {
261
+ $template = $this->get_template( 'template-reminder-user' );
262
+ }
263
+
264
+ // Use a message that's been appended manually if available
265
+ } else {
266
+ $template = empty( $this->message ) ? '' : $this->message;
267
+ }
268
+
269
+ $template = apply_filters( 'rtb_notification_email_template', $template, $this );
270
+
271
+ if ( ! empty( $this->manual_message ) ) {
272
+ $this->message = $this->manual_message;
273
+ }
274
+ elseif ( empty( $template ) ) {
275
+ $this->message = '';
276
+ } else {
277
+ $this->message = wpautop( $this->process_template( $template ) );
278
+ }
279
+
280
+ }
281
+
282
+ /**
283
+ * Process template tags for email subjects
284
+ * @since 0.0.1
285
+ */
286
+ public function process_subject_template( $subject ) {
287
+
288
+ $template_tags = array(
289
+ '{user_name}' => ! empty( $this->booking->name ) ? $this->booking->name : '',
290
+ '{party}' => ! empty( $this->booking->party ) ? $this->booking->party : '',
291
+ '{date}' => ! empty( $this->booking->date ) ? $this->booking->format_date( $this->booking->date ) : '',
292
+ );
293
+
294
+ $template_tags = apply_filters( 'rtb_notification_email_subject_template_tags', $template_tags, $this );
295
+
296
+ return str_replace( array_keys( $template_tags ), array_values( $template_tags ), $subject );
297
+
298
+ }
299
+
300
+ /**
301
+ * Send notification
302
+ * @since 0.0.1
303
+ */
304
+ public function send_notification() {
305
+ return wp_mail( $this->to_email, $this->subject, $this->message, $this->headers, apply_filters( 'rtb_notification_email_attachments', array(), $this ) );
306
+ }
307
+ }
308
+ } // 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' ) . '">' . esc_html( $rtb_controller->settings->get_setting( 'label-bookings-link-tag' ) ) . '</a>',
105
- '{cancel_link}' => '<a href="' . esc_attr( $cancellation_url ) . '">' . esc_html( $rtb_controller->settings->get_setting( 'label-cancel-link-tag' ) ) . '</a>',
106
- '{confirm_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=confirm&booking=' . esc_attr( $this->booking->ID ) ) . '">' . esc_html( $rtb_controller->settings->get_setting( 'label-confirm-link-tag' ) ) . '</a>',
107
- '{close_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=close&booking=' . esc_attr( $this->booking->ID ) ) . '">' . esc_html( $rtb_controller->settings->get_setting( 'label-close-link-tag' ) ) . '</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' ) . '">' . esc_html( $rtb_controller->settings->get_setting( 'label-bookings-link-tag' ) ) . '</a>',
105
+ '{cancel_link}' => '<a href="' . esc_attr( $cancellation_url ) . '">' . esc_html( $rtb_controller->settings->get_setting( 'label-cancel-link-tag' ) ) . '</a>',
106
+ '{confirm_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=confirm&booking=' . esc_attr( $this->booking->ID ) ) . '">' . esc_html( $rtb_controller->settings->get_setting( 'label-confirm-link-tag' ) ) . '</a>',
107
+ '{close_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=close&booking=' . esc_attr( $this->booking->ID ) ) . '">' . esc_html( $rtb_controller->settings->get_setting( 'label-close-link-tag' ) ) . '</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,360 +1,360 @@
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
- $last_send_datetime = new DateTime( get_option( 'rtb-daily-summary-send-date', date( 'Y-m-d', strtotime( '-1 day' ) ) ), wp_timezone() );
103
-
104
- $last_send_datetime->add( new DateInterval( 'P1D' ) );
105
-
106
- $send_time_hours = substr( $rtb_controller->settings->get_setting( 'daily-summary-address-send-time' ), 0, strpos( $rtb_controller->settings->get_setting( 'daily-summary-address-send-time' ), ':' ) );
107
- $send_time_minutes = substr( $rtb_controller->settings->get_setting( 'daily-summary-address-send-time' ), strpos( $rtb_controller->settings->get_setting( 'daily-summary-address-send-time' ), ':' ) + 1 );
108
-
109
- $next_send_time = $last_send_datetime->format('U') + $send_time_hours * 60*60 + $send_time_minutes * 60;
110
-
111
- if ( $next_send_time > time() ) { return; }
112
-
113
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
114
-
115
- $display_table = $rtb_controller->permissions->check_permission( 'premium_table_restrictions' ) && $rtb_controller->settings->get_setting( 'enable-tables' );
116
-
117
- $args = array(
118
- 'post_type' => 'rtb-booking',
119
- 'posts_per_page' => -1,
120
- 'date_query' => array(
121
- 'year' => date( 'Y' ),
122
- 'month' => date( 'm' ),
123
- 'day' => date( 'd' )
124
- ),
125
- 'post_status' => array_keys( $rtb_controller->cpts->booking_statuses ),
126
- 'orderby' => 'date',
127
- 'order' => 'ASC'
128
- );
129
-
130
- $bookings = get_posts( $args );
131
-
132
- ob_start();
133
-
134
- ?>
135
-
136
- <?php if ( empty( $bookings ) ) { ?>
137
-
138
- <p><?php _e( 'There are currently no bookings today.', 'restaurant-reservations' ); ?></p>
139
-
140
- <?php } else { ?>
141
-
142
- <p><?php _e( 'Please find a summary of today\'s reservations in the table below.', 'restaurant-reservations' ); ?></p>
143
-
144
- <table class='rtb-view-bookings-table'>
145
- <thead>
146
- <tr>
147
- <th><?php _e('Time', 'restaurant-reservations'); ?></th>
148
- <th><?php _e('Party', 'restaurant-reservations'); ?></th>
149
- <th><?php _e('Name', 'restaurant-reservations'); ?></th>
150
- <th><?php _e('Email', 'restaurant-reservations'); ?></th>
151
- <th><?php _e('Phone', 'restaurant-reservations'); ?></th>
152
- <?php if ( $display_table ) {?> <th><?php _e('Table', 'restaurant-reservations'); ?></th><?php } ?>
153
- <th><?php _e('Status', 'restaurant-reservations'); ?></th>
154
- <th><?php _e('Details', 'restaurant-reservations'); ?></th>
155
- </tr>
156
- </thead>
157
- <tbody>
158
- <?php foreach ( $bookings as $booking ) { ?>
159
- <?php $booking_object = new rtbBooking(); ?>
160
- <?php $booking_object->load_post( $booking ); ?>
161
- <tr>
162
- <td><?php echo ( new DateTime( $booking_object->date ) )->format( 'H:i:s' ); ?></td>
163
- <td><?php echo $booking_object->party; ?></td>
164
- <td><?php echo $booking_object->name; ?></td>
165
- <td><?php echo $booking_object->email; ?></td>
166
- <td><?php echo $booking_object->phone; ?></td>
167
- <?php if ( $display_table ) { $table = implode(', ', $booking_object->table ); echo "<td>{$table}</td>"; } ?>
168
- <td><?php echo $rtb_controller->cpts->booking_statuses[$booking_object->post_status]['label'] ?></td>
169
- <td><?php echo apply_filters( 'rtb_bookings_table_column_details', $booking_object->message, $booking_object ); ?></td>
170
- </tr>
171
- <?php } ?>
172
- </tbody>
173
- </table>
174
-
175
- <?php } ?>
176
-
177
- <?php
178
-
179
- $email_content = ob_get_clean();
180
-
181
- $notification = new rtbNotificationEmail( 'daily_summary', 'admin' );
182
-
183
- $notification->to_email = $rtb_controller->settings->get_setting( 'daily-summary-address' );
184
- $notification->from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
185
- $notification->from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
186
- $notification->subject = __( 'Daily Email Summary', 'restaurant-reservations' );
187
- $notification->manual_message = $email_content;
188
-
189
- if ( $notification->prepare_notification() ) {
190
-
191
- if ( $notification->send_notification() ) {
192
-
193
- $now = new DateTime( 'now', wp_timezone() );
194
-
195
- update_option( 'rtb-daily-summary-send-date', $now->format( 'Y-m-d' ) );
196
- }
197
- }
198
- }
199
-
200
- /**
201
- * Set booking data
202
- * @since 0.0.1
203
- */
204
- public function set_booking( $booking_post ) {
205
- require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
206
- $this->booking = new rtbBooking();
207
- $this->booking->load_wp_post( $booking_post );
208
- }
209
-
210
- /**
211
- * New booking submissions
212
- *
213
- * @var object $booking
214
- * @since 0.0.1
215
- */
216
- public function new_submission( $booking ) {
217
-
218
- // Bail early if $booking is not a rtbBooking object
219
- if ( get_class( $booking ) != 'rtbBooking' ) {
220
- return;
221
- }
222
-
223
- // trigger an event so that admin notifications for a new confirmed booking can be sent
224
- if ( $booking->post_status == 'confirmed' ) {
225
- do_action( 'rtb_confirmed_booking', get_post( $booking->ID ) );
226
- }
227
-
228
- // If the post status is not pending, trigger a post status
229
- // transition as though it's gone from pending_to_{status}
230
- if ( $booking->post_status != 'pending' and $booking->post_status != 'draft' ) {
231
- do_action( 'pending_to_' . $booking->post_status, get_post( $booking->ID ) );
232
-
233
- // Otherwise proceed with the new_submission event
234
- } else {
235
- $this->booking = $booking;
236
- $this->event( 'new_submission' );
237
- }
238
- }
239
-
240
- /**
241
- * New confirmed booking
242
- * @since 2.1.0
243
- */
244
- public function new_confirmed_submission( $booking_post ) {
245
-
246
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
247
- return;
248
- }
249
-
250
- $this->set_booking( $booking_post );
251
-
252
- $this->event( 'rtb_confirmed_booking' );
253
-
254
- }
255
-
256
- /**
257
- * Booking confirmed
258
- * @since 0.0.1
259
- */
260
- public function pending_to_confirmed( $booking_post ) {
261
-
262
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
263
- return;
264
- }
265
-
266
- $this->clear_to_email( 'pending_to_confirmed' );
267
-
268
- $this->set_booking( $booking_post );
269
-
270
- $this->event( 'pending_to_confirmed' );
271
-
272
- }
273
-
274
- /**
275
- * Booking can not be made
276
- * @since 0.0.1
277
- */
278
- public function pending_to_closed( $booking_post ) {
279
-
280
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
281
- return;
282
- }
283
-
284
- $this->set_booking( $booking_post );
285
-
286
- $this->event( 'pending_to_closed' );
287
-
288
- }
289
-
290
- /**
291
- * Booking has been cancelled by the guest
292
- */
293
- public function booking_cancelled( $booking_post ) {
294
-
295
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
296
- return;
297
- }
298
-
299
- $this->set_booking( $booking_post );
300
-
301
- $this->event( 'booking_cancelled' );
302
-
303
- }
304
-
305
- /**
306
- * Booking was confirmed and is now completed. Send out an optional
307
- * follow-up email.
308
- *
309
- * @since 0.0.1
310
- */
311
- public function confirmed_to_closed( $booking_post ) {
312
-
313
- if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
314
- return;
315
- }
316
-
317
- $this->set_booking( $booking_post );
318
-
319
- $this->event( 'confirmed_to_closed' );
320
-
321
- }
322
-
323
- /**
324
- * Clear the 'to_email' property of the selected event notification
325
- *
326
- * @since 2.5.2
327
- */
328
- public function clear_to_email( $event ) {
329
-
330
- foreach ( $this->notifications as $notification ) {
331
-
332
- if ( $event == $notification->event ) {
333
-
334
- $notification->to_email = '';
335
- }
336
- }
337
- }
338
-
339
- /**
340
- * Process notifications for an event
341
- * @since 0.0.1
342
- */
343
- public function event( $event ) {
344
-
345
- foreach( $this->notifications as $notification ) {
346
-
347
- if ( $event == $notification->event ) {
348
- $notification->set_booking( $this->booking );
349
- if ( $notification->prepare_notification() ) {
350
- do_action( 'rtb_send_notification_before', $notification );
351
- $notification->send_notification();
352
- do_action( 'rtb_send_notification_after', $notification );
353
- }
354
- }
355
- }
356
-
357
- }
358
-
359
- }
360
- } // 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
+ $last_send_datetime = new DateTime( get_option( 'rtb-daily-summary-send-date', date( 'Y-m-d', strtotime( '-1 day' ) ) ), wp_timezone() );
103
+
104
+ $last_send_datetime->add( new DateInterval( 'P1D' ) );
105
+
106
+ $send_time_hours = substr( $rtb_controller->settings->get_setting( 'daily-summary-address-send-time' ), 0, strpos( $rtb_controller->settings->get_setting( 'daily-summary-address-send-time' ), ':' ) );
107
+ $send_time_minutes = substr( $rtb_controller->settings->get_setting( 'daily-summary-address-send-time' ), strpos( $rtb_controller->settings->get_setting( 'daily-summary-address-send-time' ), ':' ) + 1 );
108
+
109
+ $next_send_time = $last_send_datetime->format('U') + $send_time_hours * 60*60 + $send_time_minutes * 60;
110
+
111
+ if ( $next_send_time > time() ) { return; }
112
+
113
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
114
+
115
+ $display_table = $rtb_controller->permissions->check_permission( 'premium_table_restrictions' ) && $rtb_controller->settings->get_setting( 'enable-tables' );
116
+
117
+ $args = array(
118
+ 'post_type' => 'rtb-booking',
119
+ 'posts_per_page' => -1,
120
+ 'date_query' => array(
121
+ 'year' => date( 'Y' ),
122
+ 'month' => date( 'm' ),
123
+ 'day' => date( 'd' )
124
+ ),
125
+ 'post_status' => array_keys( $rtb_controller->cpts->booking_statuses ),
126
+ 'orderby' => 'date',
127
+ 'order' => 'ASC'
128
+ );
129
+
130
+ $bookings = get_posts( $args );
131
+
132
+ ob_start();
133
+
134
+ ?>
135
+
136
+ <?php if ( empty( $bookings ) ) { ?>
137
+
138
+ <p><?php _e( 'There are currently no bookings today.', 'restaurant-reservations' ); ?></p>
139
+
140
+ <?php } else { ?>
141
+
142
+ <p><?php _e( 'Please find a summary of today\'s reservations in the table below.', 'restaurant-reservations' ); ?></p>
143
+
144
+ <table class='rtb-view-bookings-table'>
145
+ <thead>
146
+ <tr>
147
+ <th><?php _e('Time', 'restaurant-reservations'); ?></th>
148
+ <th><?php _e('Party', 'restaurant-reservations'); ?></th>
149
+ <th><?php _e('Name', 'restaurant-reservations'); ?></th>
150
+ <th><?php _e('Email', 'restaurant-reservations'); ?></th>
151
+ <th><?php _e('Phone', 'restaurant-reservations'); ?></th>
152
+ <?php if ( $display_table ) {?> <th><?php _e('Table', 'restaurant-reservations'); ?></th><?php } ?>
153
+ <th><?php _e('Status', 'restaurant-reservations'); ?></th>
154
+ <th><?php _e('Details', 'restaurant-reservations'); ?></th>
155
+ </tr>
156
+ </thead>
157
+ <tbody>
158
+ <?php foreach ( $bookings as $booking ) { ?>
159
+ <?php $booking_object = new rtbBooking(); ?>
160
+ <?php $booking_object->load_post( $booking ); ?>
161
+ <tr>
162
+ <td><?php echo ( new DateTime( $booking_object->date ) )->format( 'H:i:s' ); ?></td>
163
+ <td><?php echo $booking_object->party; ?></td>
164
+ <td><?php echo $booking_object->name; ?></td>
165
+ <td><?php echo $booking_object->email; ?></td>
166
+ <td><?php echo $booking_object->phone; ?></td>
167
+ <?php if ( $display_table ) { $table = implode(', ', $booking_object->table ); echo "<td>{$table}</td>"; } ?>
168
+ <td><?php echo $rtb_controller->cpts->booking_statuses[$booking_object->post_status]['label'] ?></td>
169
+ <td><?php echo apply_filters( 'rtb_bookings_table_column_details', $booking_object->message, $booking_object ); ?></td>
170
+ </tr>
171
+ <?php } ?>
172
+ </tbody>
173
+ </table>
174
+
175
+ <?php } ?>
176
+
177
+ <?php
178
+
179
+ $email_content = ob_get_clean();
180
+
181
+ $notification = new rtbNotificationEmail( 'daily_summary', 'admin' );
182
+
183
+ $notification->to_email = $rtb_controller->settings->get_setting( 'daily-summary-address' );
184
+ $notification->from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
185
+ $notification->from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
186
+ $notification->subject = __( 'Daily Email Summary', 'restaurant-reservations' );
187
+ $notification->manual_message = $email_content;
188
+
189
+ if ( $notification->prepare_notification() ) {
190
+
191
+ if ( $notification->send_notification() ) {
192
+
193
+ $now = new DateTime( 'now', wp_timezone() );
194
+
195
+ update_option( 'rtb-daily-summary-send-date', $now->format( 'Y-m-d' ) );
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Set booking data
202
+ * @since 0.0.1
203
+ */
204
+ public function set_booking( $booking_post ) {
205
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
206
+ $this->booking = new rtbBooking();
207
+ $this->booking->load_wp_post( $booking_post );
208
+ }
209
+
210
+ /**
211
+ * New booking submissions