Restaurant Reservations - Version 1.0.1

Version Description

(2014-05-08) = * Replace dashicons caret with CSS-only caret in booking form

Download this release

Release Info

Developer NateWr
Plugin Icon 128x128 Restaurant Reservations
Version 1.0.1
Comparing to
See all releases

Version 1.0.1

Files changed (92) hide show
  1. GPL.txt +280 -0
  2. assets/css/admin.css +91 -0
  3. assets/css/booking-form.css +73 -0
  4. assets/js/admin.js +22 -0
  5. assets/js/booking-form.js +201 -0
  6. docs/index.html +169 -0
  7. docs/style.css +31 -0
  8. includes/Booking.class.php +421 -0
  9. includes/CustomPostTypes.class.php +231 -0
  10. includes/Notification.Email.class.php +187 -0
  11. includes/Notification.class.php +106 -0
  12. includes/Notifications.class.php +187 -0
  13. includes/Settings.class.php +540 -0
  14. includes/WP_List_Table.BookingsTable.class.php +650 -0
  15. includes/WP_Widget.BookingFormWidget.class.php +87 -0
  16. includes/template-functions.php +283 -0
  17. lib/simple-admin-pages/README.md +379 -0
  18. lib/simple-admin-pages/classes/AdminPage.Submenu.class.php +38 -0
  19. lib/simple-admin-pages/classes/AdminPage.Themes.class.php +15 -0
  20. lib/simple-admin-pages/classes/AdminPage.class.php +215 -0
  21. lib/simple-admin-pages/classes/AdminPageSection.class.php +132 -0
  22. lib/simple-admin-pages/classes/AdminPageSetting.Editor.class.php +44 -0
  23. lib/simple-admin-pages/classes/AdminPageSetting.HTML.class.php +33 -0
  24. lib/simple-admin-pages/classes/AdminPageSetting.OpeningHours.class.php +167 -0
  25. lib/simple-admin-pages/classes/AdminPageSetting.Scheduler.class.php +596 -0
  26. lib/simple-admin-pages/classes/AdminPageSetting.Select.class.php +60 -0
  27. lib/simple-admin-pages/classes/AdminPageSetting.SelectPost.class.php +61 -0
  28. lib/simple-admin-pages/classes/AdminPageSetting.SelectTaxonomy.class.php +66 -0
  29. lib/simple-admin-pages/classes/AdminPageSetting.Text.class.php +35 -0
  30. lib/simple-admin-pages/classes/AdminPageSetting.Textarea.class.php +56 -0
  31. lib/simple-admin-pages/classes/AdminPageSetting.Toggle.class.php +43 -0
  32. lib/simple-admin-pages/classes/AdminPageSetting.class.php +225 -0
  33. lib/simple-admin-pages/classes/Library.class.php +406 -0
  34. lib/simple-admin-pages/css/admin.css +225 -0
  35. lib/simple-admin-pages/js/admin.js +340 -0
  36. lib/simple-admin-pages/lib/pickadate/legacy.js +10 -0
  37. lib/simple-admin-pages/lib/pickadate/picker.date.js +5 -0
  38. lib/simple-admin-pages/lib/pickadate/picker.js +7 -0
  39. lib/simple-admin-pages/lib/pickadate/picker.time.js +5 -0
  40. lib/simple-admin-pages/lib/pickadate/themes/default.css +4 -0
  41. lib/simple-admin-pages/lib/pickadate/themes/default.date.css +1 -0
  42. lib/simple-admin-pages/lib/pickadate/themes/default.time.css +1 -0
  43. lib/simple-admin-pages/lib/pickadate/themes/rtl.css +3 -0
  44. lib/simple-admin-pages/lib/pickadate/translations/ar.js +1 -0
  45. lib/simple-admin-pages/lib/pickadate/translations/bg_BG.js +1 -0
  46. lib/simple-admin-pages/lib/pickadate/translations/bs_BA.js +1 -0
  47. lib/simple-admin-pages/lib/pickadate/translations/ca_ES.js +1 -0
  48. lib/simple-admin-pages/lib/pickadate/translations/cs_CZ.js +1 -0
  49. lib/simple-admin-pages/lib/pickadate/translations/da_DK.js +1 -0
  50. lib/simple-admin-pages/lib/pickadate/translations/de_DE.js +1 -0
  51. lib/simple-admin-pages/lib/pickadate/translations/el_GR.js +1 -0
  52. lib/simple-admin-pages/lib/pickadate/translations/es_ES.js +1 -0
  53. lib/simple-admin-pages/lib/pickadate/translations/et_EE.js +1 -0
  54. lib/simple-admin-pages/lib/pickadate/translations/eu_ES.js +1 -0
  55. lib/simple-admin-pages/lib/pickadate/translations/fi_FI.js +1 -0
  56. lib/simple-admin-pages/lib/pickadate/translations/fr_FR.js +1 -0
  57. lib/simple-admin-pages/lib/pickadate/translations/gl_ES.js +1 -0
  58. lib/simple-admin-pages/lib/pickadate/translations/he_IL.js +1 -0
  59. lib/simple-admin-pages/lib/pickadate/translations/hr_HR.js +1 -0
  60. lib/simple-admin-pages/lib/pickadate/translations/hu_HU.js +1 -0
  61. lib/simple-admin-pages/lib/pickadate/translations/id_ID.js +1 -0
  62. lib/simple-admin-pages/lib/pickadate/translations/is_IS.js +1 -0
  63. lib/simple-admin-pages/lib/pickadate/translations/it_IT.js +1 -0
  64. lib/simple-admin-pages/lib/pickadate/translations/ja_JP.js +1 -0
  65. lib/simple-admin-pages/lib/pickadate/translations/ko_KR.js +1 -0
  66. lib/simple-admin-pages/lib/pickadate/translations/nl_NL.js +1 -0
  67. lib/simple-admin-pages/lib/pickadate/translations/no_NO.js +1 -0
  68. lib/simple-admin-pages/lib/pickadate/translations/pl_PL.js +1 -0
  69. lib/simple-admin-pages/lib/pickadate/translations/pt_BR.js +1 -0
  70. lib/simple-admin-pages/lib/pickadate/translations/pt_PT.js +1 -0
  71. lib/simple-admin-pages/lib/pickadate/translations/ro_RO.js +1 -0
  72. lib/simple-admin-pages/lib/pickadate/translations/ru_RU.js +1 -0
  73. lib/simple-admin-pages/lib/pickadate/translations/sk_SK.js +1 -0
  74. lib/simple-admin-pages/lib/pickadate/translations/sl_SI.js +1 -0
  75. lib/simple-admin-pages/lib/pickadate/translations/sv_SE.js +1 -0
  76. lib/simple-admin-pages/lib/pickadate/translations/th_TH.js +1 -0
  77. lib/simple-admin-pages/lib/pickadate/translations/tr_TR.js +1 -0
  78. lib/simple-admin-pages/lib/pickadate/translations/uk_UA.js +1 -0
  79. lib/simple-admin-pages/lib/pickadate/translations/zh_CN.js +1 -0
  80. lib/simple-admin-pages/lib/pickadate/translations/zh_TW.js +1 -0
  81. lib/simple-admin-pages/simple-admin-pages.php +57 -0
  82. readme.txt +61 -0
  83. restaurant-reservations.php +334 -0
  84. screenshot-1.PNG +0 -0
  85. screenshot-2.PNG +0 -0
  86. screenshot-3.PNG +0 -0
  87. screenshot-4.PNG +0 -0
  88. screenshot-5.PNG +0 -0
  89. screenshot-6.PNG +0 -0
  90. screenshot-7.PNG +0 -0
  91. screenshot-8.PNG +0 -0
  92. screenshot-9.PNG +0 -0
GPL.txt ADDED
@@ -0,0 +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
assets/css/admin.css ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* CSS Stylesheet for the admin interface for Restaurant Reservations */
2
+
3
+ .clearfix:before,
4
+ .clearfix:after { /* thanks bootstrap */
5
+ content: " ";
6
+ display: table;
7
+ }
8
+
9
+ .clearfix:after {
10
+ clear: both;
11
+ }
12
+
13
+ /* Bookings Table */
14
+ #rtb-bookings-table .subsubsub {
15
+ float: right;
16
+ }
17
+
18
+ #rtb-bookings-table .subsubsub .trash a {
19
+ color: #a00;
20
+ }
21
+
22
+ #rtb-bookings-table .subsubsub .trash a:hover {
23
+ color: red;
24
+ }
25
+
26
+ #rtb-filters {
27
+ float: left;
28
+ }
29
+
30
+ #rtb-filters .subsubsub {
31
+ margin-bottom: 0.5em;
32
+ float: none;
33
+ }
34
+
35
+ #rtb-filters .date-filters {
36
+ clear: both;
37
+ padding: 1em;
38
+ margin: 1em 0 2em;
39
+ background: #fff;
40
+ -webkit-box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
41
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
42
+ }
43
+
44
+ #rtb-filters .date-filters .datepicker {
45
+ max-width: 10em;
46
+ }
47
+
48
+ #rtb-filters .current {
49
+ font-weight: 600;
50
+ color: #000;
51
+ }
52
+
53
+ #rtb-bookings-table .column-message .rtb-message-data,
54
+ #rtb-bookings-table .message-row {
55
+ display: none;
56
+ }
57
+
58
+ #rtb-bookings-table tr.closed {
59
+ opacity: 0.7;
60
+ filter: opacity(alpha=70);
61
+ }
62
+ #rtb-bookings-table tr.closed:hover {
63
+ opacity: 1;
64
+ filter: opacity(alpha=100);
65
+ }
66
+
67
+ #rtb-bookings-table tr.pending .check-column,
68
+ #rtb-bookings-table tr.pending.message-row td {
69
+ border-left: 4px solid #dd3d36;
70
+ }
71
+
72
+ @media screen and (max-width: 782px) {
73
+
74
+ #rtb-bookings-table .fixed .column-date {
75
+ display: table-cell;
76
+ }
77
+ }
78
+
79
+ /* Settings Pages (most of this is handled by the Simple Admin Pages library) */
80
+ .rtb-template-tags-box {
81
+ margin-top: 0.5em;
82
+ padding: 1em;
83
+ border-left: 4px solid #2ea2cc;
84
+ background: #fff;
85
+ -webkit-box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
86
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
87
+ }
88
+ .rtb-template-tags-box strong {
89
+ min-width: 8em;
90
+ display: inline-block;
91
+ }
assets/css/booking-form.css ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Frontend CSS Stylesheet for Restaurant Reservations */
2
+
3
+ .rtb-booking-form fieldset {
4
+ padding-bottom: 1em;
5
+ margin-bottom: 1em;
6
+ }
7
+ .rtb-booking-form legend {
8
+ padding: 0 0.5em;
9
+ }
10
+ .rtb-booking-form fieldset>div {
11
+ margin-top: 1em;
12
+ }
13
+ .rtb-booking-form label {
14
+ display: block;
15
+ }
16
+ .rtb-booking-form input {
17
+ width: 100%;
18
+ max-width: 15em;
19
+ }
20
+ .rtb-booking-form textarea {
21
+ width: 100%;
22
+ max-width: 30em;
23
+ }
24
+ .rtb-booking-form .add-message {
25
+ margin-top: 1em;
26
+ }
27
+ .rtb-booking-form .message {
28
+ position: absolute;
29
+ top: -9999px;
30
+ left: -9999px;
31
+ }
32
+ .rtb-booking-form .message-open {
33
+ position: relative;
34
+ top: auto;
35
+ left: auto;
36
+ }
37
+ .rtb-booking-form .message textarea {
38
+ min-height: 6em;
39
+ }
40
+ .rtb-booking-form .rtb-error {
41
+ font-size: 0.85em;
42
+ padding: 0.25em;
43
+ background: #f50;
44
+ color: #fff;
45
+ }
46
+ .rtb-booking-form .rtb-error:before {
47
+ content: ' ';
48
+ border-top: 4px solid;
49
+ border-right: 4px solid transparent;
50
+ border-left: 4px solid transparent;
51
+ width: 0;
52
+ height: 0;
53
+ display: inline-block;
54
+ margin: 0.25em 0.5em;
55
+ }
56
+
57
+ /* Compatibility styles for pickadate on common themes */
58
+ #rtb-date_root .picker__button--clear,
59
+ #rtb-date_root .picker__button--today {
60
+ /* don't adopt the theme's button text color */
61
+ color: #000;
62
+ }
63
+ #rtb-date_root .picker__nav--next,
64
+ #rtb-date_root .picker__nav--prev {
65
+ /* next/prev calendar arrows button areas sometimes don't cover the arrows */
66
+ min-height: 2em;
67
+ }
68
+ #rtb-time_root .picker__list,
69
+ #rtb-time_root .picker__list li {
70
+ /* override some theme's list styles */
71
+ list-style: none;
72
+ margin: 0 0 0 1px;
73
+ }
assets/js/admin.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Javascript for Restaurant Reservations admin */
2
+ jQuery(document).ready(function ($) {
3
+
4
+ // Show or hide a booking message in the bookings table
5
+ $( '#rtb-bookings-table .column-message a' ).click( function () {
6
+ var message_id = $(this).data( 'id' );
7
+ if ( $(this).parent().parent().siblings( '#' + message_id ).length ) {
8
+ $( '#' + $(this).data( 'id' ) ).fadeOut( function() {
9
+ $(this).remove();
10
+ });
11
+ $(this).children( '.dashicons' ).removeClass( 'dashicons-welcome-comments' ).addClass( 'dashicons-testimonial' );
12
+ } else {
13
+ var row = $(this).closest( 'tr' );
14
+ row.after( '<tr class="' + row.attr( 'class' ) + ' message-row" id="' + message_id + '"><td colspan="' + row.children( 'th, td' ).length + '">' + $(this).siblings( '.rtb-message-data' ).html() + '</td></tr>' );
15
+ $( '#' + message_id ).fadeIn();
16
+ $(this).children( '.dashicons' ).removeClass( 'dashicons-testimonial' ).addClass( 'dashicons-welcome-comments' );
17
+ }
18
+
19
+ return false;
20
+ });
21
+
22
+ });
assets/js/booking-form.js ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Javascript for Restaurant Reservations booking form */
2
+ jQuery(document).ready(function ($) {
3
+
4
+ /**
5
+ * Scroll to the first error message on the booking form
6
+ */
7
+ if ( $( '.rtb-booking-form .rtb-error' ).length ) {
8
+ $('html, body').animate({
9
+ scrollTop: $( '.rtb-booking-form .rtb-error' ).first().offset().top + -40
10
+ }, 500);
11
+ }
12
+
13
+ /**
14
+ * Show the message field on the booking form
15
+ */
16
+ $( '.rtb-booking-form .add-message a' ).click( function() {
17
+ $(this).hide();
18
+ $(this).parent().siblings( '.message' ).addClass( 'message-open' );
19
+
20
+ return false;
21
+ });
22
+
23
+ /**
24
+ * Enable datepickers on load
25
+ */
26
+ if ( typeof rtb_pickadate !== 'undefined' ) {
27
+
28
+ // Declare datepicker
29
+ var $date_input = $( '#rtb-date' ).pickadate({
30
+ format: rtb_pickadate.date_format,
31
+ min: true,
32
+ container: 'body',
33
+ });
34
+
35
+ // Declare timepicker
36
+ var $time_input = $( '#rtb-time' ).pickatime({
37
+ format: rtb_pickadate.time_format,
38
+ });
39
+
40
+ var datepicker = $date_input.pickadate( 'picker' );
41
+ var timepicker = $time_input.pickatime( 'picker' );
42
+
43
+ if ( typeof datepicker == 'undefined' ) {
44
+ return;
45
+ }
46
+
47
+ // Pass conditional configuration parameters
48
+ if ( rtb_pickadate.disable_dates.length ) {
49
+ datepicker.set( 'disable', rtb_pickadate.disable_dates );
50
+ }
51
+
52
+ // Update timepicker on pageload and whenever the datepicker is closed
53
+ rtb_update_timepicker_range();
54
+ datepicker.on( {
55
+ close: function() {
56
+ rtb_update_timepicker_range();
57
+ }
58
+ });
59
+ }
60
+
61
+ // Update the timepicker's range based on the currently selected date
62
+ function rtb_update_timepicker_range() {
63
+
64
+ // Reset enabled/disabled rules on this timepicker
65
+ timepicker.set( 'enable', false );
66
+ timepicker.set( 'disable', false );
67
+
68
+ var selected_date = new Date( datepicker.get() );
69
+ var selected_date_year = selected_date.getFullYear();
70
+ var selected_date_month = selected_date.getMonth();
71
+ var selected_date_date = selected_date.getDate();
72
+
73
+ // Declaring the first element true inverts the timepicker settings. All
74
+ // times subsequently declared are valid. Any time that doesn't fall
75
+ // within those declarations is invalid.
76
+ // See: http://amsul.ca/pickadate.js/time.htm#disable-times-all
77
+ var valid_times = [ { from: [0, 0], to: [24, 0] } ];
78
+
79
+ // Check if this date is an exception to the rules
80
+ if ( typeof rtb_pickadate.schedule_closed != 'undefined' ) {
81
+
82
+ var excp_date = [];
83
+ var excp_start_date = [];
84
+ var excp_start_time = [];
85
+ var excp_end_date = [];
86
+ var excp_end_time = [];
87
+ for ( var closed_key in rtb_pickadate.schedule_closed ) {
88
+
89
+ excp_date = new Date( rtb_pickadate.schedule_closed[closed_key].date );
90
+ if ( excp_date.getFullYear() == selected_date_year &&
91
+ excp_date.getMonth() == selected_date_month &&
92
+ excp_date.getDate() == selected_date_date
93
+ ) {
94
+
95
+ // Closed all day
96
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time == 'undefined' ) {
97
+ timepicker.set( 'disable', [ true ] );
98
+
99
+ return;
100
+ }
101
+
102
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time.start !== 'undefined' ) {
103
+ excp_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.start );
104
+ excp_start_time = [ excp_start_date.getHours(), excp_start_date.getMinutes() ];
105
+ } else {
106
+ excp_start_time = [ 0, 0 ]; // Start of the day
107
+ }
108
+
109
+ if ( typeof rtb_pickadate.schedule_closed[closed_key].time.end !== 'undefined' ) {
110
+ excp_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_closed[closed_key].time.end );
111
+ excp_end_time = [ excp_end_date.getHours(), excp_end_date.getMinutes() ];
112
+ } else {
113
+ excp_end_time = [ 24, 0 ]; // End of the day
114
+ }
115
+
116
+ valid_times.push( { from: excp_start_time, to: excp_end_time, inverted: true } );
117
+ }
118
+ }
119
+
120
+ excp_date = excp_start_date = excp_start_time = excp_end_date = excp_end_time = null;
121
+
122
+ // Exit early if this date is an exception
123
+ if ( valid_times.length > 1 ) {
124
+ timepicker.set( 'disable', valid_times );
125
+
126
+ return;
127
+ }
128
+ }
129
+
130
+ // Get any rules which apply to this weekday
131
+ if ( typeof rtb_pickadate.schedule_open != 'undefined' ) {
132
+
133
+ var selected_date_weekday = selected_date.getDay();
134
+
135
+ var weekdays = {
136
+ sunday: 0,
137
+ monday: 1,
138
+ tuesday: 2,
139
+ wednesday: 3,
140
+ thursday: 4,
141
+ friday: 5,
142
+ saturday: 6,
143
+ };
144
+
145
+ var rule_start_date = [];
146
+ var rule_start_time = [];
147
+ var rule_end_date = [];
148
+ var rule_end_time = [];
149
+ for ( var open_key in rtb_pickadate.schedule_open ) {
150
+
151
+ if ( typeof rtb_pickadate.schedule_open[open_key].weekdays !== 'undefined' ) {
152
+ for ( var weekdays_key in rtb_pickadate.schedule_open[open_key].weekdays ) {
153
+ if ( weekdays[weekdays_key] == selected_date_weekday ) {
154
+
155
+ // Closed all day
156
+ if ( typeof rtb_pickadate.schedule_open[open_key].time == 'undefined' ) {
157
+ timepicker.set( 'disable', [ true ] );
158
+
159
+ return;
160
+ }
161
+
162
+ if ( typeof rtb_pickadate.schedule_open[open_key].time.start !== 'undefined' ) {
163
+ rule_start_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.start );
164
+ rule_start_time = [ rule_start_date.getHours(), rule_start_date.getMinutes() ];
165
+ } else {
166
+ rule_start_time = [ 0, 0 ]; // Start of the day
167
+ }
168
+
169
+ if ( typeof rtb_pickadate.schedule_open[open_key].time.end !== 'undefined' ) {
170
+ rule_end_date = new Date( '1 January 2000 ' + rtb_pickadate.schedule_open[open_key].time.end );
171
+ rule_end_time = [ rule_end_date.getHours(), rule_end_date.getMinutes() ];
172
+ } else {
173
+ rule_end_time = [ 24, 0 ]; // End of the day
174
+ }
175
+
176
+ valid_times.push( { from: rule_start_time, to: rule_end_time, inverted: true } );
177
+
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ rule_start_date = rule_start_time = rule_end_date = rule_end_time = null;
184
+
185
+ // Pass any valid times located
186
+ if ( valid_times.length > 1 ) {
187
+ timepicker.set( 'disable', valid_times );
188
+
189
+ return;
190
+ }
191
+
192
+ }
193
+
194
+ // Set it to always open if no rules have been defined
195
+ timepicker.set( 'enable', true );
196
+ timepicker.set( 'disable', false );
197
+
198
+ return;
199
+ }
200
+
201
+ });
docs/index.html ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <head>
4
+
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Restaurant Reservations | How to install and use this plugin</title>
8
+ <link rel="profile" href="http://gmpg.org/xfn/11">
9
+ <meta name="robots" content="noindex,nofollow">
10
+ <link rel="stylesheet" href="style.css" type="text/css" media="all" />
11
+
12
+ </head>
13
+
14
+ <body>
15
+
16
+ <div id="toc">
17
+ <ul>
18
+ <li>
19
+ <a href="#install">Installation</a>
20
+ </li>
21
+ <li>
22
+ <a href="#setup">Quick Setup</a>
23
+ </li>
24
+ <li>
25
+ <a href="#manage_bookings">Manage bookings</a>
26
+ </li>
27
+ <li>
28
+ <a href="#schedule">Set the booking schedule</a>
29
+ </li>
30
+ <li>
31
+ <a href="#settings">The settings page</a>
32
+ </li>
33
+ <li>
34
+ <a href="#extend">Extend this plugin</a>
35
+ </li>
36
+ </ul>
37
+ </div>
38
+
39
+ <div id="content">
40
+
41
+ <h1>Guide to the Restaurant Reservations plugin for WordPress</h1>
42
+ <p>This guide will help you install and configure the plugin for your site.</p>
43
+ <p>Further support for this plugin can be found at <a href="http://themeofthecrop.com/?utm_source=Plugin&utm_medium=Plugin%20Help%20Documentation&utm_campaign=Restaurant%20Reservations">themeofthecrop.com</a>.</p>
44
+
45
+ <a name="install"></a>
46
+ <h2>Installation</h2>
47
+ <p>The following steps describe how to upload the Restaurant Reservations plugin to an
48
+ existing WordPress installation. If you have not yet installed WordPress, consult the
49
+ <a href="http://codex.wordpress.org/Installing_WordPress">WordPress
50
+ documentation</a> for more information.</p>
51
+ <ol>
52
+ <li>
53
+ Unpack the restaurant-reservations.zip file. It should create a folder
54
+ named <code>/restaurant-reservations/</code>.
55
+ </li>
56
+ <li>
57
+ Upload the <code>/restaurant-reservations/</code> folder to your
58
+ WordPress plugin directory at <code>/wp-content/plugins/</code>.
59
+ </li>
60
+ <li>
61
+ Go to your WordPress admin dashboard at yoursite.com/wp-admin and
62
+ click the <strong>Plugins</strong> item in the menu on the left.
63
+ </li>
64
+ <li>
65
+ Find <strong>Restaurant Reservations</strong> in the list of Plugins
66
+ and click the <strong>Activate</strong> link below the plugin name.
67
+ </li>
68
+ </ol>
69
+ <p>The plugin is now active. Next you'll need to get it setup.</p>
70
+
71
+ <a name="setup"></a>
72
+ <h2>Quick Setup</h2>
73
+ <p>To get this plugin working as quickly as possible, all you need to do is
74
+ display the booking form. There are three ways to do this:</p>
75
+ <ol>
76
+ <li>
77
+ Automatically add the booking form to any page on your website. Go
78
+ to the <strong>Booking > Settings</strong> page in your admin
79
+ dashboard. Select one of your pages in the dropdown list for
80
+ <strong>Booking Page</strong>. The booking form will automatically
81
+ be added to that page.
82
+ </li>
83
+ <li>
84
+ Add the Booking Form widget. Go to <strong>Appearance > Widgets</strong>
85
+ and find the Booking Form widget. Add that to any sidebar to display
86
+ your booking form.
87
+ </li>
88
+ <li>
89
+ Use the <code>[booking-form]</code> shortcode to add your booking
90
+ form to any page or post on your website. Simply paste
91
+ <code>[booking-form]</code> into the content of any page or post.
92
+ </li>
93
+ </ol>
94
+ <p>Once you've done that, your users can submit their bookings. The next
95
+ section will describe how to view, confirm and reject them in your
96
+ admin panel.</p>
97
+
98
+ <a name="manage_bookings"></a>
99
+ <h2>Manage Bookings</h2>
100
+ <p>To view, confirm and reject bookings, go to the <strong>Bookings</strong>
101
+ page in your admin panel. This will show you all of your bookings with
102
+ the current status on the right.</p>
103
+ <p>On this page you can filter bookings by date, filter bookings by status
104
+ and change a booking's status. <em>Tip: you can combine filters, so that you
105
+ are viewing just Pending bookings for Today</em>.</p>
106
+ <p>When a new booking is made,
107
+ it is set to Pending. You can then either set the status to Confirmed
108
+ or Closed. The user will then receive an email letting them know that
109
+ their booking was confirmed or rejected.</p>
110
+ <p>To change the status of any booking, click the checkbox to the left of
111
+ the booking. From the <strong>Bulk Actions</strong> dropdown list,
112
+ select the appropriate action (ie - Set to Confirmed) and click
113
+ <strong>Apply</strong>.</p>
114
+ <p><em>Remember, a user will receive an email notification, so only change
115
+ the status when you're ready.</em> At the moment, a notification is sent
116
+ to the user when they make a booking request and when that request is
117
+ changed from pending to confirmed or pending to closed.</p>
118
+
119
+ <a name="schedule"></a>
120
+ <h2>Set the booking schedule</h2>
121
+ <p>You probably only want to accept bookings when you're open. You may only
122
+ accept bookings during certain opening hours. You can define scheduling
123
+ rules to limit when people can submit booking requests.</p>
124
+ <p>To do this, go to the <strong>Bookings > Settings</strong> page in the
125
+ admin dashboard. On this page, click the <strong>Booking Schedule</strong>
126
+ tab.</p>
127
+ <p>Under <strong>Schedule</strong>, click the <strong>Add new scheduling
128
+ rule</strong> button. Set the weekdays and opening hours.</p>
129
+ <p>These rules are <em>complimentary</em>. That means if you have two rules
130
+ which occur at different times on the same day, bookings will be
131
+ accepted during both times. This allows you to set morning and evening
132
+ hours, for example.</p>
133
+ <p>There are three other settings on this page.</p>
134
+ <p><strong>Exceptions</strong> allows you to specify custom scheduling rules
135
+ on certain dates. This will help you indicate if you're closed on
136
+ a holiday or have special opening hours one day.</p>
137
+ <p><strong>Early Bookings</strong> and <strong>Late Bookings</strong> allow
138
+ you to set limits on how early or late people can make bookings. If you
139
+ don't want to recieve a booking for 4:45pm at 4:40pm, set the
140
+ <strong>Late Bookings</strong> setting to something more appropriate.</p>
141
+
142
+ <a name="settings"></a>
143
+ <h2>The settings page</h2>
144
+ <p>In addition to the <a href="#schedule">Booking Schedule</a>, there are
145
+ two other settings tabs. These settings allow you to customize
146
+ messages when people submit a booking, notification emails sent out
147
+ when bookings are confirmed or rejected, and more.</p>
148
+ <p>If you want to receive an admin notification email when a new booking
149
+ request is made, make sure to check the box next to <strong>Admin
150
+ Notification</strong> under the Notificaitons tab.</p>
151
+
152
+ <a name="extend"></a>
153
+ <h2>Extend this plugin</h2>
154
+ <p>This plugin has been designed from the ground up to be extensible and
155
+ customizable. There are dozens of hooks to allow you to customize the
156
+ functionality and output. These include hooks to completely replace the
157
+ booking form's HTML output, add new settings, add new booking form
158
+ information, and add your own notifications.</p>
159
+ <p>If you have any questions, please
160
+ <a href="http://themeofthecrop.com/about/support/?utm_source=Plugin&utm_medium=Plugin%20Help%20Documentation&utm_campaign=Restaurant%20Reservations">contact me</a>.</p>
161
+ <p>This plugin is available on
162
+ <a href="https://github.com/NateWr/restaurant-reservations">GitHub</a>
163
+ if you would like to contribute pull requests.</p>
164
+
165
+ </div>
166
+
167
+ </body>
168
+
169
+ </html>
docs/style.css ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Documentation Markup
3
+ **********************/
4
+ body {
5
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
6
+ background-color: #eee;
7
+ margin: 0;
8
+ }
9
+ li {
10
+ padding-bottom: 5px;
11
+ }
12
+ #toc {
13
+ position: absolute;
14
+ width: 250px;
15
+ }
16
+ #content {
17
+ padding: 20px;
18
+ margin-left: 300px;
19
+ background-color: #fff;
20
+ box-shadow: 0 0 5px #000;
21
+ }
22
+ #toc>ul>li {
23
+ padding-bottom: 10px;
24
+ padding-top: 10px;
25
+ }
26
+ code {
27
+ font-family: Courier, monospace;
28
+ padding: 2px 5px;
29
+ background-color: #eee;
30
+ border-radius: 2px;
31
+ }
includes/Booking.class.php ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ public function __construct() {}
27
+
28
+ /**
29
+ * Load the booking information from a WP_Post object or an ID
30
+ *
31
+ * @uses load_wp_post()
32
+ * @since 0.0.1
33
+ */
34
+ public function load_post( $post ) {
35
+
36
+ if ( is_int( $post ) || is_string( $post ) ) {
37
+ $post = get_post( $post );
38
+ }
39
+
40
+ if ( get_class( $post ) == 'WP_Post' && $post->post_type == RTB_BOOKING_POST_TYPE ) {
41
+ $this->load_wp_post( $post );
42
+ return true;
43
+ } else {
44
+ return false;
45
+ }
46
+
47
+ }
48
+
49
+ /**
50
+ * Load data from WP post object and retrieve metadata
51
+ *
52
+ * @uses load_post_metadata()
53
+ * @since 0.0.1
54
+ */
55
+ public function load_wp_post( $post ) {
56
+
57
+ // Store post for access to other data if needed by extensions
58
+ $this->post = $post;
59
+
60
+ $this->ID = $post->ID;
61
+ $this->name = $post->post_title;
62
+ $this->date = $this->format_date( $post->post_date );
63
+ $this->message = apply_filters( 'the_content', $post->post_content );
64
+ $this->post_status = $post->post_status;
65
+
66
+ $this->load_post_metadata();
67
+
68
+ do_action( 'rtb_booking_load_post_data', $this, $post );
69
+ }
70
+
71
+ /**
72
+ * Store metadata for post
73
+ * @since 0.0.1
74
+ */
75
+ public function load_post_metadata() {
76
+
77
+ $meta_defaults = array(
78
+ 'party' => '',
79
+ 'email' => '',
80
+ 'phone' => '',
81
+ 'date_submission' => '',
82
+ );
83
+
84
+ $meta_defaults = apply_filters( 'rtb_booking_metadata_defaults', $meta_defaults );
85
+
86
+ if ( is_array( $meta = get_post_meta( $this->ID, 'rtb', true ) ) ) {
87
+ $meta = array_merge( $meta_defaults, get_post_meta( $this->ID, 'rtb', true ) );
88
+ } else {
89
+ $meta = $meta_defaults;
90
+ }
91
+
92
+ $this->party = $meta['party'];
93
+ $this->email = $meta['email'];
94
+ $this->phone = $meta['phone'];
95
+ $this->date_submission = $meta['date_submission'];
96
+ }
97
+
98
+ /**
99
+ * Format date
100
+ * @since 0.0.1
101
+ */
102
+ public function format_date( $date ) {
103
+ $date = mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $date);
104
+ return apply_filters( 'get_the_date', $date );
105
+ }
106
+
107
+ /**
108
+ * Insert a new booking submission into the database
109
+ *
110
+ * Validates the data, adds it to the database and executes notifications
111
+ * @since 0.0.1
112
+ */
113
+ public function insert_booking() {
114
+
115
+ // Check if this request has already been processed. If multiple forms
116
+ // exist on the same page, this prevents a single submission from
117
+ // being added twice.
118
+ if ( $this->request_processed === true ) {
119
+ return true;
120
+ }
121
+
122
+ $this->request_processed = true;
123
+
124
+ $this->validate_submission();
125
+ if ( $this->is_valid_submission() === false ) {
126
+ return false;
127
+ }
128
+
129
+ if ( $this->insert_post_data() === false ) {
130
+ return false;
131
+ } else {
132
+ $this->request_inserted = true;
133
+ }
134
+
135
+ do_action( 'rtb_insert_booking', $this );
136
+
137
+ return true;
138
+ }
139
+
140
+ /**
141
+ * Validate submission data. Expects to find data in $_POST.
142
+ * @since 0.0.1
143
+ */
144
+ public function validate_submission() {
145
+
146
+ $this->validation_errors = array();
147
+
148
+ // Date
149
+ $date = empty( $_POST['rtb-date'] ) ? false : $_POST['rtb-date'];
150
+ if ( $date === false ) {
151
+ $this->validation_errors[] = array(
152
+ 'field' => 'date',
153
+ 'error_msg' => 'Booking request missing date',
154
+ 'message' => __( 'Please enter the date you would like to book.', RTB_TEXTDOMAIN ),
155
+ );
156
+
157
+ } else {
158
+ try {
159
+ $date = new DateTime( $_POST['rtb-date'] );
160
+ } catch ( Exception $e ) {
161
+ $this->validation_errors[] = array(
162
+ 'field' => 'date',
163
+ 'error_msg' => $e->getMessage(),
164
+ 'message' => __( 'The date you entered is not valid. Please select from one of the dates in the calendar.', RTB_TEXTDOMAIN ),
165
+ );
166
+ }
167
+ }
168
+
169
+ // Time
170
+ $time = empty( $_POST['rtb-time'] ) ? false : $_POST['rtb-time'];
171
+ if ( $time === false ) {
172
+ $this->validation_errors[] = array(
173
+ 'field' => 'time',
174
+ 'error_msg' => 'Booking request missing time',
175
+ 'message' => __( 'Please enter the time you would like to book.', RTB_TEXTDOMAIN ),
176
+ );
177
+
178
+ } else {
179
+ try {
180
+ $time = new DateTime( $_POST['rtb-time'] );
181
+ } catch ( Exception $e ) {
182
+ $this->validation_errors[] = array(
183
+ 'field' => 'time',
184
+ 'error_msg' => $e->getMessage(),
185
+ 'message' => __( 'The time you entered is not valid. Please select from one of the times provided.', RTB_TEXTDOMAIN ),
186
+ );
187
+ }
188
+ }
189
+
190
+ // Check against valid open dates/times
191
+ if ( is_object( $time ) && is_object( $date ) ) {
192
+
193
+ global $rtb_controller;
194
+
195
+ $request = new DateTime( $date->format( 'Y-m-d' ) . ' ' . $time->format( 'H:i:s' ) );
196
+
197
+ $early_bookings = $rtb_controller->settings->get_setting( 'early-bookings' );
198
+ if ( !empty( $early_bookings ) ) {
199
+ $early_bookings_seconds = $early_bookings * 24 * 60 * 60; // Advanced bookings allowance in seconds
200
+ if ( $request->format( 'U' ) > ( current_time( 'timestamp' ) + $early_bookings_seconds ) ) {
201
+ $this->validation_errors[] = array(
202
+ 'field' => 'time',
203
+ 'error_msg' => 'Booking request too far in the future',
204
+ 'message' => sprintf( __( 'Sorry, bookings can not be made more than %s days in advance.', RTB_TEXTDOMAIN ), $early_bookings ),
205
+ );
206
+ }
207
+ }
208
+
209
+ $late_bookings = $rtb_controller->settings->get_setting( 'late-bookings' );
210
+ if ( empty( $late_bookings ) ) {
211
+ if ( $request->format( 'U' ) < current_time( 'timestamp' ) ) {
212
+ $this->validation_errors[] = array(
213
+ 'field' => 'time',
214
+ 'error_msg' => 'Booking request in the past',
215
+ 'message' => __( 'Sorry, bookings can not be made in the past.', RTB_TEXTDOMAIN ),
216
+ );
217
+ }
218
+
219
+ } else {
220
+ $late_bookings_seconds = $late_bookings * 60; // Late bookings allowance in seconds
221
+ if ( $request->format( 'U' ) < ( current_time( 'timestamp' ) + $late_bookings_seconds ) ) {
222
+ $this->validation_errors[] = array(
223
+ 'field' => 'time',
224
+ 'error_msg' => 'Booking request made too close to the reserved time',
225
+ 'message' => sprintf( __( 'Sorry, bookings must be made more than %s minutes in advance.', RTB_TEXTDOMAIN ), $late_bookings ),
226
+ );
227
+ }
228
+ }
229
+
230
+ // Check against scheduling exception rules
231
+ $exceptions = $rtb_controller->settings->get_setting( 'schedule-closed' );
232
+ if ( empty( $this->validation_errors ) && !empty( $exceptions ) ) {
233
+ $exception_is_active = false;
234
+ $datetime_is_valid = false;
235
+ foreach( $exceptions as $exception ) {
236
+ $excp_date = new DateTime( $exception['date'] );
237
+ if ( $excp_date->format( 'Y-m-d' ) == $request->format( 'Y-m-d' ) ) {
238
+ $exception_is_active = true;
239
+
240
+ // Closed all day
241
+ if ( empty( $exception['time'] ) ) {
242
+ continue;
243
+ }
244
+
245
+ $excp_start_time = empty( $exception['time']['start'] ) ? $request : new DateTime( $exception['date'] . ' ' . $exception['time']['start'] );
246
+ $excp_end_time = empty( $exception['time']['end'] ) ? $request : new DateTime( $exception['date'] . ' ' . $exception['time']['end'] );
247
+
248
+ if ( $request->format( 'U' ) >= $excp_start_time->format( 'U' ) && $request->format( 'U' ) <= $excp_end_time->format( 'U' ) ) {
249
+ $datetime_is_valid = true;
250
+ break;
251
+ }
252
+ }
253
+ }
254
+
255
+ if ( $exception_is_active && !$datetime_is_valid ) {
256
+ $this->validation_errors[] = array(
257
+ 'field' => 'date',
258
+ 'error_msg' => 'Booking request made on invalid date or time in an exception rule',
259
+ 'message' => __( 'Sorry, no bookings are being accepted then.', RTB_TEXTDOMAIN ),
260
+ );
261
+ }
262
+ }
263
+
264
+ // Check against weekly scheduling rules
265
+ $rules = $rtb_controller->settings->get_setting( 'schedule-open' );
266
+ if ( empty( $exception_is_active ) && empty( $this->validation_errors ) && !empty( $rules ) ) {
267
+ $request_weekday = strtolower( $request->format( 'l' ) );
268
+ $time_is_valid = null;
269
+ $day_is_valid = null;
270
+ foreach( $rules as $rule ) {
271
+
272
+ if ( !empty( $rule['weekdays'][ $request_weekday ] ) ) {
273
+ $day_is_valid = true;
274
+
275
+ if ( empty( $rule['time'] ) ) {
276
+ $time_is_valid = true; // Days with no time values are open all day
277
+ break;
278
+ }
279
+
280
+ $too_early = true;
281
+ $too_late = true;
282
+
283
+ // Too early
284
+ if ( !empty( $rule['time']['start'] ) ) {
285
+ $rule_start_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['start'] );
286
+ if ( $rule_start_time->format( 'U' ) <= $request->format( 'U' ) ) {
287
+ $too_early = false;
288
+ }
289
+ }
290
+
291
+ // Too late
292
+ if ( !empty( $rule['time']['end'] ) ) {
293
+ $rule_end_time = new DateTime( $request->format( 'Y-m-d' ) . ' ' . $rule['time']['end'] );
294
+ if ( $rule_end_time->format( 'U' ) >= $request->format( 'U' ) ) {
295
+ $too_late = false;
296
+ }
297
+ }
298
+
299
+ // Valid time found
300
+ if ( $too_early === false && $too_late === false) {
301
+ $time_is_valid = true;
302
+ break;
303
+ }
304
+ }
305
+ }
306
+
307
+ if ( !$day_is_valid ) {
308
+ $this->validation_errors[] = array(
309
+ 'field' => 'date',
310
+ 'error_msg' => 'Booking request made on an invalid date',
311
+ 'message' => __( 'Sorry, no bookings are being accepted on that date.', RTB_TEXTDOMAIN ),
312
+ );
313
+ } elseif ( !$time_is_valid ) {
314
+ $this->validation_errors[] = array(
315
+ 'field' => 'time',
316
+ 'error_msg' => 'Booking request made at an invalid time',
317
+ 'message' => __( 'Sorry, no bookings are being accepted at that time.', RTB_TEXTDOMAIN ),
318
+ );
319
+ }
320
+ }
321
+
322
+ // Accept the date if it has passed validation
323
+ if ( empty( $this->validation_errors ) ) {
324
+ $this->date = $request->format( 'Y-m-d H:i:s' );
325
+ }
326
+ }
327
+
328
+ // Save requested date/time values in case they need to be
329
+ // printed in the form again
330
+ $this->request_date = empty( $_POST['rtb-date'] ) ? '' : $_POST['rtb-date'];
331
+ $this->request_time = empty( $_POST['rtb-time'] ) ? '' : $_POST['rtb-time'];
332
+
333
+ // Name
334
+ $this->name = empty( $_POST['rtb-name'] ) ? '' : wp_strip_all_tags( sanitize_text_field( $_POST['rtb-name'] ), true ); // @todo should I limit length?
335
+ if ( empty( $this->name ) ) {
336
+ $this->validation_errors[] = array(
337
+ 'field' => 'name',
338
+ 'post_variable' => $this->name,
339
+ 'message' => __( 'Please enter a name for this booking.', RTB_TEXTDOMAIN ),
340
+ );
341
+ }
342
+
343
+ // Party
344
+ $this->party = empty( $_POST['rtb-party'] ) ? '' : sanitize_text_field( $_POST['rtb-party'] );
345
+ if ( empty( $this->party ) ) {
346
+ $this->validation_errors[] = array(
347
+ 'field' => 'party',
348
+ 'post_variable' => $this->party,
349
+ 'message' => __( 'Please let us know how many people will be in your party.', RTB_TEXTDOMAIN ),
350
+ );
351
+ }
352
+
353
+ // Email/Phone
354
+ $this->email = empty( $_POST['rtb-email'] ) ? '' : sanitize_text_field( $_POST['rtb-email'] ); // @todo email validation? send notification back to form on bad email address.
355
+ if ( empty( $this->email ) ) {
356
+ $this->validation_errors[] = array(
357
+ 'field' => 'email',
358
+ 'post_variable' => $this->email,
359
+ 'message' => __( 'Please enter an email address so we can confirm your booking.', RTB_TEXTDOMAIN ),
360
+ );
361
+ }
362
+
363
+ // Phone/Message
364
+ $this->phone = empty( $_POST['rtb-phone'] ) ? '' : sanitize_text_field( $_POST['rtb-phone'] );
365
+ $this->message = empty( $_POST['rtb-message'] ) ? '' : sanitize_text_field( $_POST['rtb-message'] );
366
+
367
+ do_action( 'rtb_validate_booking_submission', $this );
368
+
369
+ }
370
+
371
+ /**
372
+ * Check if submission is valid
373
+ * @since 0.0.1
374
+ */
375
+ public function is_valid_submission() {
376
+ if ( !count( $this->validation_errors ) ) {
377
+ return true;
378
+ }
379
+ return false;
380
+ }
381
+
382
+ /**
383
+ * Insert post data for a new booking.
384
+ * @since 0.0.1
385
+ */
386
+ public function insert_post_data() {
387
+
388
+ $args = array(
389
+ 'post_type' => RTB_BOOKING_POST_TYPE,
390
+ 'post_title' => $this->name,
391
+ 'post_content' => $this->message,
392
+ 'post_date' => $this->date,
393
+ 'post_status' => 'pending',
394
+ );
395
+
396
+ $args = apply_filters( 'rtb_insert_booking_data', $args, $this );
397
+
398
+ $id = wp_insert_post( $args );
399
+
400
+ if ( is_wp_error( $id ) || $id === false ) {
401
+ $this->insert_post_error = $id;
402
+ return false;
403
+ } else {
404
+ $this->ID = $id;
405
+ }
406
+
407
+ $meta = array(
408
+ 'party' => $this->party,
409
+ 'email' => $this->email,
410
+ 'phone' => $this->phone,
411
+ 'date_submission' => current_time( 'timestamp' ),
412
+ );
413
+
414
+ $meta = apply_filters( 'rtb_insert_booking_metadata', $meta, $this );
415
+
416
+ return update_post_meta( $this->ID, 'rtb', $meta );
417
+
418
+ }
419
+
420
+ }
421
+ } // endif;
includes/CustomPostTypes.class.php ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 stasuses
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
+
27
+ }
28
+
29
+ /**
30
+ * Initialize custom post types
31
+ * @since 0.1
32
+ */
33
+ public function load_cpts() {
34
+
35
+ // Define the booking custom post type
36
+ $args = array(
37
+ 'labels' => array(
38
+ 'name' => __( 'Bookings', RTB_TEXTDOMAIN ),
39
+ 'singular_name' => __( 'Booking', RTB_TEXTDOMAIN ),
40
+ 'menu_name' => __( 'Bookings', RTB_TEXTDOMAIN ),
41
+ 'name_admin_bar' => __( 'Bookings', RTB_TEXTDOMAIN ),
42
+ 'add_new' => __( 'Add New', RTB_TEXTDOMAIN ),
43
+ 'add_new_item' => __( 'Add New Booking', RTB_TEXTDOMAIN ),
44
+ 'edit_item' => __( 'Edit Booking', RTB_TEXTDOMAIN ),
45
+ 'new_item' => __( 'New Booking', RTB_TEXTDOMAIN ),
46
+ 'view_item' => __( 'View Booking', RTB_TEXTDOMAIN ),
47
+ 'search_items' => __( 'Search Bookings', RTB_TEXTDOMAIN ),
48
+ 'not_found' => __( 'No bookings found', RTB_TEXTDOMAIN ),
49
+ 'not_found_in_trash' => __( 'No bookings found in trash', RTB_TEXTDOMAIN ),
50
+ 'all_items' => __( 'All Bookings', RTB_TEXTDOMAIN ),
51
+ ),
52
+ 'menu_icon' => 'dashicons-calendar',
53
+ 'public' => false,
54
+ 'supports' => array(
55
+ 'title',
56
+ 'revisions'
57
+ )
58
+ );
59
+
60
+ // Create filter so addons can modify the arguments
61
+ $args = apply_filters( 'rtb_booking_args', $args );
62
+
63
+ // Add an action so addons can hook in before the post type is registered
64
+ do_action( 'rtb_booking_pre_register' );
65
+
66
+ // Register the post type
67
+ register_post_type( RTB_BOOKING_POST_TYPE, $args );
68
+
69
+ // Add an action so addons can hook in after the post type is registered
70
+ do_action( 'rtb_booking_post_register' );
71
+ }
72
+
73
+ /**
74
+ * Set an array of valid booking statuses and register any custom statuses
75
+ * @since 0.0.1
76
+ */
77
+ public function set_booking_statuses() {
78
+
79
+ $this->booking_statuses['pending'] = array(
80
+ 'label' => _x( 'Pending', 'Booking status when it is pending review', RTB_TEXTDOMAIN ),
81
+ 'default' => true, // Whether or not this status is part of WP Core
82
+ 'user_selectable' => true, // Whether or not a user can set a booking to this status
83
+ );
84
+
85
+ $this->booking_statuses['confirmed'] = array (
86
+ 'label' => _x( 'Confirmed', 'Booking status for a confirmed booking', RTB_TEXTDOMAIN ),
87
+ 'default' => false, // Whether or not this status is part of WP Core
88
+ 'user_selectable' => true, // Whether or not a user can set a booking to this status
89
+ 'public' => false,
90
+ 'exclude_from_search' => true,
91
+ 'show_in_admin_all_list' => true,
92
+ 'show_in_admin_status_list' => true,
93
+ 'label_count' => _n_noop( 'Confirmed <span class="count">(%s)</span>', 'Confirmed <span class="count">(%s)</span>', RTB_TEXTDOMAIN ),
94
+ );
95
+
96
+ $this->booking_statuses['closed'] = array(
97
+ 'label' => _x( 'Closed', 'Booking status for a closed booking', RTB_TEXTDOMAIN ),
98
+ 'default' => false, // Whether or not this status is part of WP Core
99
+ 'user_selectable' => true, // Whether or not a user can set a booking to this status
100
+ 'public' => false,
101
+ 'exclude_from_search' => true,
102
+ 'show_in_admin_all_list' => true,
103
+ 'show_in_admin_status_list' => true,
104
+ 'label_count' => _n_noop( 'Closed <span class="count">(%s)</span>', 'Closed <span class="count">(%s)</span>', RTB_TEXTDOMAIN )
105
+ );
106
+
107
+ // Let addons hook in to add/edit/remove post statuses
108
+ $this->booking_statuses = apply_filters( 'rtb_post_statuses_args', $this->booking_statuses );
109
+
110
+ // Register the custom post statuses
111
+ foreach ( $this->booking_statuses as $status => $args ) {
112
+ if ( $args['default'] === false ) {
113
+ register_post_status( $status, $args );
114
+ }
115
+ }
116
+
117
+ }
118
+
119
+ /**
120
+ * Print an HTML element to select a booking status
121
+ * @since 0.0.1
122
+ * @note This is no longer used in the bookings table, but it could be
123
+ * useful in the future, so leave it in for now (0.0.1) until the plugin is
124
+ * more fleshed out.
125
+ */
126
+ public function print_booking_status_select( $current = false ) {
127
+
128
+ if ( $current === false ) {
129
+ $current = 'none';
130
+ }
131
+
132
+ // Output stored select field if available
133
+ if ( !empty( $this->status_select_html[$current] ) ) {
134
+ return $this->status_select_html[$current];
135
+ }
136
+
137
+ ob_start();
138
+ ?>
139
+
140
+ <select name="rtb-select-status">
141
+ <?php foreach ( $this->booking_statuses as $status => $args ) : ?>
142
+ <?php if ( $args['user_selectable'] === true ) : ?>
143
+ <option value="<?php echo esc_attr( $status ); ?>"<?php echo $status == $current ? ' selected="selected"' : ''; ?>><?php echo esc_attr( $args['label'] ); ?></option>
144
+ <?php endif; ?>
145
+ <?php endforeach; ?>
146
+ </select>
147
+
148
+ <?php
149
+ $output = ob_get_clean();
150
+
151
+ // Store output so we don't need to loop for every row
152
+ $this->status_select_html[$current] = $output;
153
+
154
+ return $output;
155
+
156
+ }
157
+
158
+ /**
159
+ * Delete a booking request (or send to trash)
160
+ *
161
+ * @since 0.0.1
162
+ */
163
+ public function delete_booking( $id ) {
164
+
165
+ if ( !current_user_can( 'manage_bookings' ) ) {
166
+ return;
167
+ }
168
+
169
+ // If we're already looking at trashed posts, delete it for good.
170
+ // Otherwise, just send it to trash.
171
+ if ( !empty( $_GET['status'] ) && $_GET['status'] == 'trash' ) {
172
+ $screen = get_current_screen();
173
+ if ( $screen->base == 'toplevel_page_rtb-bookings' ) {
174
+ $result = wp_delete_post( $id, true );
175
+ }
176
+ } else {
177
+ $result = wp_trash_post( $id );
178
+ }
179
+
180
+ if ( $result === false ) {
181
+ return false;
182
+ } else {
183
+ return true;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Update a booking status.
189
+ * @since 0.0.1
190
+ */
191
+ function update_booking_status( $id, $status ) {
192
+
193
+ if ( !current_user_can( 'manage_bookings' ) ) {
194
+ return;
195
+ }
196
+
197
+ if ( !$this->is_valid_booking_status( $status ) ) {
198
+ return false;
199
+ }
200
+
201
+ $booking = get_post( $id );
202
+
203
+ if ( is_wp_error( $booking ) || !is_object( $booking ) ) {
204
+ return false;
205
+ }
206
+
207
+ if ( $booking->post_status === $status ) {
208
+ return null;
209
+ }
210
+
211
+ $result = wp_update_post(
212
+ array(
213
+ 'ID' => $id,
214
+ 'post_status' => $status,
215
+ 'edit_date' => current_time( 'mysql' ),
216
+ )
217
+ );
218
+
219
+ return $result ? true : false;
220
+ }
221
+
222
+ /**
223
+ * Check if status is valid for bookings
224
+ * @since 0.0.1
225
+ */
226
+ public function is_valid_booking_status( $status ) {
227
+ return isset( $this->booking_statuses[$status] ) ? true : false;
228
+ }
229
+
230
+ }
231
+ } // endif;
includes/Notification.Email.class.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * Prepare and validate notification data
54
+ *
55
+ * @return boolean if the data is valid and ready for transport
56
+ * @since 0.0.1
57
+ */
58
+ public function prepare_notification() {
59
+
60
+ $this->set_to_email();
61
+ $this->set_from_email();
62
+ $this->set_subject();
63
+ $this->set_headers();
64
+ $this->set_message();
65
+
66
+ // Return false if we're missing any of the required information
67
+ if ( empty( $this->to_email) ||
68
+ empty( $this->from_email) ||
69
+ empty( $this->from_name) ||
70
+ empty( $this->subject) ||
71
+ empty( $this->headers) ||
72
+ empty( $this->message) ) {
73
+ return false;
74
+ }
75
+
76
+ return true;
77
+ }
78
+
79
+ /**
80
+ * Set to email
81
+ * @since 0.0.1
82
+ */
83
+ public function set_to_email() {
84
+
85
+ if ( $this->target == 'user' ) {
86
+ $this->to_email = empty( $this->booking->email ) ? null : $this->booking->email;
87
+
88
+ } else {
89
+ global $rtb_controller;
90
+ $this->to_email = $rtb_controller->settings->get_setting( 'admin-email-address' );
91
+ }
92
+
93
+ }
94
+
95
+ /**
96
+ * Set from email
97
+ * @since 0.0.1
98
+ */
99
+ public function set_from_email() {
100
+
101
+ global $rtb_controller;
102
+
103
+ $this->from_email = $rtb_controller->settings->get_setting( 'reply-to-address' );
104
+ $this->from_name = $rtb_controller->settings->get_setting( 'reply-to-name' );
105
+
106
+ }
107
+
108
+ /**
109
+ * Set email subject
110
+ * @since 0.0.1
111
+ */
112
+ public function set_subject() {
113
+
114
+ global $rtb_controller;
115
+
116
+ if( $this->event == 'new_submission' ) {
117
+ if ( $this->target == 'admin' ) {
118
+ $this->subject = $rtb_controller->settings->get_setting( 'subject-booking-admin' );
119
+ } elseif ( $this->target == 'user' ) {
120
+ $this->subject = $rtb_controller->settings->get_setting( 'subject-booking-user' );
121
+ }
122
+
123
+ } elseif ( $this->event == 'pending_to_confirmed' ) {
124
+ $this->subject = $rtb_controller->settings->get_setting( 'subject-confirmed-user' );
125
+
126
+ } elseif ( $this->event == 'pending_to_closed' ) {
127
+ $this->subject = $rtb_controller->settings->get_setting( 'subject-rejected-user' );
128
+ }
129
+
130
+ }
131
+
132
+ /**
133
+ * Set email headers
134
+ * @since 0.0.1
135
+ */
136
+ public function set_headers( $headers = null ) {
137
+
138
+ $headers = "From: " . stripslashes_deep( html_entity_decode( $this->from_name, ENT_COMPAT, 'UTF-8' ) ) . " <" . $this->from_email . ">\r\n";
139
+ $headers .= "Reply-To: ". $this->from_email . "\r\n";
140
+ $headers .= "Content-Type: text/html; charset=utf-8\r\n";
141
+ $this->headers = apply_filters( 'rtb_notification_email_headers', $headers, $this );
142
+
143
+ }
144
+
145
+ /**
146
+ * Set email message body
147
+ * @since 0.0.1
148
+ */
149
+ public function set_message() {
150
+
151
+ $settings = get_option( 'rtb-settings' );
152
+
153
+ if ( $this->event == 'new_submission' ) {
154
+ if ( $this->target == 'user' ) {
155
+ $template = 'template-booking-user';
156
+ } elseif ( $this->target == 'admin' ) {
157
+ $template = 'template-booking-admin';
158
+ }
159
+
160
+ } elseif ( $this->event == 'pending_to_confirmed' ) {
161
+ if ( $this->target == 'user' ) {
162
+ $template = 'template-confirmed-user';
163
+ }
164
+
165
+ } elseif ( $this->event == 'pending_to_closed' ) {
166
+ if ( $this->target == 'user' ) {
167
+ $template = 'template-rejected-user';
168
+ }
169
+ }
170
+
171
+ if ( !isset( $template ) ) {
172
+ $this->message = '';
173
+ } else {
174
+ $this->message = wpautop( $this->process_template( $this->get_template( $template ) ) );
175
+ }
176
+
177
+ }
178
+
179
+ /**
180
+ * Send notification
181
+ * @since 0.0.1
182
+ */
183
+ public function send_notification() {
184
+ wp_mail( $this->to_email, $this->subject, $this->message, $this->headers );
185
+ }
186
+ }
187
+ } // endif;
includes/Notification.class.php ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
81
+ $template_tags = array(
82
+ '{user_name}' => $this->booking->name,
83
+ '{party}' => $this->booking->party,
84
+ '{date}' => $this->booking->date,
85
+ '{bookings_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&status=pending' ) . '">' . __( 'View pending bookings', RTB_TEXTDOMAIN ) . '</a>',
86
+ '{confirm_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=confirm&booking=' . esc_attr( $this->booking->ID ) ) . '">' . __( 'Confirm this booking', RTB_TEXTDOMAIN ) . '</a>',
87
+ '{close_link}' => '<a href="' . admin_url( 'admin.php?page=rtb-bookings&rtb-quicklink=close&booking=' . esc_attr( $this->booking->ID ) ) . '">' . __( 'Reject this booking', RTB_TEXTDOMAIN ) . '</a>',
88
+ '{site_name}' => get_bloginfo( 'name' ),
89
+ '{site_link}' => '<a href="' . home_url( '/' ) . '">' . get_bloginfo( 'name' ) . '</a>',
90
+ '{current_time}' => date( get_option( 'date_format' ), current_time( 'timestamp' ) ) . ' ' . date( get_option( 'time_format' ), current_time( 'timestamp' ) ),
91
+ );
92
+
93
+ $template_tags = apply_filters( 'rtb_notification_template_tags', $template_tags, $this );
94
+
95
+ return str_replace( array_keys( $template_tags ), array_values( $template_tags ), $message );
96
+
97
+ }
98
+
99
+ /**
100
+ * Send notification
101
+ * @since 0.0.1
102
+ */
103
+ abstract public function send_notification();
104
+
105
+ }
106
+ } // endif;
includes/Notifications.class.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * Hooks to execute notifications on
33
+ *
34
+ * Post Status Transitions and other hooks.
35
+ * See: http://codex.wordpress.org/Post_Status_Transitions
36
+ *
37
+ * @var array
38
+ * @since 0.0.1
39
+ */
40
+ public $hooks;
41
+
42
+ /**
43
+ * Set up notifications
44
+ * @since 0.0.1
45
+ */
46
+ public function __construct() {
47
+
48
+ // Hook into all status changes that require notifications
49
+ $hooks = array(
50
+ 'rtb_insert_booking' => array( $this, 'new_submission' ), // Booking submitted
51
+ 'pending_to_confirmed' => array( $this, 'pending_to_confirmed' ), // Booking confirmed
52
+ 'pending_to_closed' => array( $this, 'pending_to_closed' ), // Booking can not be made
53
+ );
54
+
55
+ $hooks = apply_filters( 'rtb_notification_transition_callbacks', $hooks );
56
+
57
+ foreach ( $hooks as $hook => $callback ) {
58
+ add_action( $hook, $callback );
59
+ }
60
+
61
+ // Register notifications
62
+ add_action( 'init', array( $this, 'register_notifications' ) );
63
+ }
64
+
65
+ /**
66
+ * Register notifications
67
+ * @since 0.0.1
68
+ */
69
+ public function register_notifications() {
70
+
71
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.class.php' );
72
+ require_once( RTB_PLUGIN_DIR . '/includes/Notification.Email.class.php' );
73
+
74
+ $this->notifications = array(
75
+ new rtbNotificationEmail( 'new_submission', 'user' ),
76
+ new rtbNotificationEmail( 'pending_to_confirmed', 'user' ),
77
+ new rtbNotificationEmail( 'pending_to_closed', 'user' ),
78
+ );
79
+
80
+ global $rtb_controller;
81
+ $admin_email_option = $rtb_controller->settings->get_setting( 'admin-email-option' );
82
+ if ( $admin_email_option ) {
83
+ $this->notifications[] = new rtbNotificationEmail( 'new_submission', 'admin' );
84
+ }
85
+
86
+ $this->notifications = apply_filters( 'rtb_notifications', $this->notifications );
87
+ }
88
+
89
+ /**
90
+ * Set booking data
91
+ * @since 0.0.1
92
+ */
93
+ public function set_booking( $booking_post ) {
94
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
95
+ $this->booking = new rtbBooking();
96
+ $this->booking->load_wp_post( $booking_post );
97
+ }
98
+
99
+ /**
100
+ * New booking submissions
101
+ *
102
+ * @var object $booking
103
+ * @since 0.0.1
104
+ */
105
+ public function new_submission( $booking ) {
106
+
107
+ // Bail early if $booking is not a rtbBooking object
108
+ if ( get_class( $booking ) != 'rtbBooking' ) {
109
+ return;
110
+ }
111
+
112
+ $this->booking = $booking;
113
+
114
+ $this->event( 'new_submission' );
115
+
116
+ }
117
+
118
+ /**
119
+ * Booking confirmed
120
+ * @since 0.0.1
121
+ */
122
+ public function pending_to_confirmed( $booking_post ) {
123
+
124
+ if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
125
+ return;
126
+ }
127
+
128
+ $this->set_booking( $booking_post );
129
+
130
+ $this->event( 'pending_to_confirmed' );
131
+
132
+ }
133
+
134
+ /**
135
+ * Booking can not be made
136
+ * @since 0.0.1
137
+ */
138
+ public function pending_to_closed( $booking_post ) {
139
+
140
+ if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
141
+ return;
142
+ }
143
+
144
+ $this->set_booking( $booking_post );
145
+
146
+ $this->event( 'pending_to_closed' );
147
+
148
+ }
149
+
150
+ /**
151
+ * Booking was confirmed and is now completed. Send out an optional
152
+ * follow-up email.
153
+ *
154
+ * @since 0.0.1
155
+ */
156
+ public function confirmed_to_closed( $booking_post ) {
157
+
158
+ if ( $booking_post->post_type != RTB_BOOKING_POST_TYPE ) {
159
+ return;
160
+ }
161
+
162
+ $this->set_booking( $booking_post );
163
+
164
+ $this->event( 'confirmed_to_closed' );
165
+
166
+ }
167
+
168
+ /**
169
+ * Process notifications for an event
170
+ * @since 0.0.1
171
+ */
172
+ public function event( $event ) {
173
+
174
+ foreach( $this->notifications as $notification ) {
175
+
176
+ if ( $event == $notification->event ) {
177
+ $notification->set_booking( $this->booking );
178
+ if ( $notification->prepare_notification() ) {
179
+ $notification->send_notification();
180
+ }
181
+ }
182
+ }
183
+
184
+ }
185
+
186
+ }
187
+ } // endif;
includes/Settings.class.php ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( !defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( !class_exists( 'rtbBooking' ) ) {
5
+ /**
6
+ * Class to handle configurable settings for Restaurant Reservations
7
+ *
8
+ * @since 0.0.1
9
+ */
10
+ class rtbSettings {
11
+
12
+ /**
13
+ * Default values for settings
14
+ * @since 0.0.1
15
+ */
16
+ public $defaults = array();
17
+
18
+ /**
19
+ * Stored values for settings
20
+ * @since 0.0.1
21
+ */
22
+ public $settings = array();
23
+
24
+ public function __construct() {
25
+
26
+ add_action( 'init', array( $this, 'set_defaults' ) );
27
+
28
+ add_action( 'init', array( $this, 'load_settings_panel' ) );
29
+
30
+ }
31
+
32
+ /**
33
+ * Load the plugin's default settings
34
+ * @since 0.0.1
35
+ */
36
+ public function set_defaults() {
37
+
38
+ $this->defaults = array(
39
+
40
+ 'success-message' => _x( 'Thanks, your booking request is waiting to be confirmed. Updates will be sent to the email address you provided.', RTB_TEXTDOMAIN ),
41
+ 'date-format' => _x( 'mmmm d, yyyy', 'Default date format for display. Must match formatting rules at http://amsul.ca/pickadate.js/date.htm#formatting-rules', RTB_TEXTDOMAIN ),
42
+ 'time-format' => _x( 'h:i A', 'Default time format for display. Must match formatting rules at http://amsul.ca/pickadate.js/time.htm#formats', RTB_TEXTDOMAIN ),
43
+
44
+ // Email address where admin notifications should be sent
45
+ 'admin-email-address' => get_option( 'admin_email' ),
46
+
47
+ // Name and email address which should appear in the Reply-To section of notification emails
48
+ 'reply-to-name' => get_bloginfo( 'name' ),
49
+ 'reply-to-address' => get_option( 'admin_email' ),
50
+
51
+ // Email template sent to an admin when a new booking request is made
52
+ 'subject-booking-admin' => _x( 'New Booking Request', 'Default email subject for admin notifications of new bookings', RTB_TEXTDOMAIN ),
53
+ 'template-booking-admin' => _x( 'A new booking request has been made at {site_name}:
54
+
55
+ {user_name}
56
+ {party} people
57
+ {date}
58
+
59
+ {bookings_link}
60
+ {confirm_link}
61
+ {close_link}
62
+
63
+ &nbsp;
64
+
65
+ <em>This message was sent by {site_link} on {current_time}.</em>',
66
+ 'Default email sent to the admin when a new booking request is made. The tags in {brackets} will be replaced by the appropriate content and should be left in place. HTML is allowed, but be aware that many email clients do not handle HTML very well.',
67
+ RTB_TEXTDOMAIN
68
+ ),
69
+
70
+ // Email template sent to a user when a new booking request is made
71
+ 'subject-booking-user' => sprintf( _x( 'Your booking at %s is pending', 'Default email subject sent to user when they request a booking. %s will be replaced by the website name', RTB_TEXTDOMAIN ), get_bloginfo( 'name' ) ),
72
+ 'template-booking-user' => _x( 'Thanks {user_name},
73
+
74
+ Your booking request is <strong>waiting to be confirmed</strong>.
75
+
76
+ Give us a few moments to make sure that we\'ve got space for you. You will receive another email from us soon. If this request was made outside of our normal working hours, we may not be able to confirm it until we\'re open again.
77
+
78
+ <strong>Your request details:</strong>
79
+ {user_name}
80
+ {party} people
81
+ {date}
82
+
83
+ &nbsp;
84
+
85
+ <em>This message was sent by {site_link} on {current_time}.</em>',
86
+ 'Default email sent to users when they make a new booking request. The tags in {brackets} will be replaced by the appropriate content and should be left in place. HTML is allowed, but be aware that many email clients do not handle HTML very well.',
87
+ RTB_TEXTDOMAIN
88
+ ),
89
+
90
+ // Email template sent to a user when a booking request is confirmed
91
+ 'subject-confirmed-user' => sprintf( _x( 'Your booking at %s is confirmed', 'Default email subject sent to user when their booking is confirmed. %s will be replaced by the website name', RTB_TEXTDOMAIN ), get_bloginfo( 'name' ) ),
92
+ 'template-confirmed-user' => _x( 'Hi {user_name},
93
+
94
+ Your booking request has been <strong>confirmed</strong>. We look forward to seeing you soon.
95
+
96
+ <strong>Your booking:</strong>
97
+ {user_name}
98
+ {party} people
99
+ {date}
100
+
101
+ &nbsp;
102
+
103
+ <em>This message was sent by {site_link} on {current_time}.</em>',
104
+ 'Default email sent to users when they make a new booking request. The tags in {brackets} will be replaced by the appropriate content and should be left in place. HTML is allowed, but be aware that many email clients do not handle HTML very well.',
105
+ RTB_TEXTDOMAIN
106
+ ),
107
+
108
+ // Email template sent to a user when a booking request is rejected
109
+ 'subject-rejected-user' => sprintf( _x( 'Your booking at %s was not accepted', 'Default email subject sent to user when their booking is rejected. %s will be replaced by the website name', RTB_TEXTDOMAIN ), get_bloginfo( 'name' ) ),
110
+ 'template-rejected-user' => _x( 'Hi {user_name},
111
+
112
+ Sorry, we could not accomodate your booking request. We\'re full or not open at the time you requested:
113
+
114
+ {user_name}
115
+ {party} people
116
+ {date}
117
+
118
+ &nbsp;
119
+
120
+ <em>This message was sent by {site_link} on {current_time}.</em>',
121
+ 'Default email sent to users when they make a new booking request. The tags in {brackets} will be replaced by the appropriate content and should be left in place. HTML is allowed, but be aware that many email clients do not handle HTML very well.',
122
+ RTB_TEXTDOMAIN
123
+ ),
124
+ );
125
+
126
+ $this->defaults = apply_filters( 'rtb_defaults', $this->defaults );
127
+ }
128
+
129
+ /**
130
+ * Get a setting's value or fallback to a default if one exists
131
+ * @since 0.0.1
132
+ */
133
+ public function get_setting( $setting ) {
134
+
135
+ if ( empty( $this->settings ) ) {
136
+ $this->settings = get_option( 'rtb-settings' );
137
+ }
138
+
139
+ if ( !empty( $this->settings[ $setting ] ) ) {
140
+ return $this->settings[ $setting ];
141
+ }
142
+
143
+ if ( !empty( $this->defaults[ $setting ] ) ) {
144
+ return $this->defaults[ $setting ];
145
+ }
146
+
147
+ return null;
148
+ }
149
+
150
+ /**
151
+ * Load the admin settings page
152
+ * @since 0.0.1
153
+ * @sa https://github.com/NateWr/simple-admin-pages
154
+ */
155
+ public function load_settings_panel() {
156
+
157
+ require_once( RTB_PLUGIN_DIR . '/lib/simple-admin-pages/simple-admin-pages.php' );
158
+ $sap = sap_initialize_library(
159
+ $args = array(
160
+ 'version' => '2.0.a.1',
161
+ 'lib_url' => RTB_PLUGIN_URL . '/lib/simple-admin-pages/',
162
+ )
163
+ );
164
+
165
+ $sap->add_page(
166
+ 'submenu',
167
+ array(
168
+ 'id' => 'rtb-settings',
169
+ 'title' => __( 'Settings', RTB_TEXTDOMAIN ),
170
+ 'menu_title' => __( 'Settings', RTB_TEXTDOMAIN ),
171
+ 'parent_menu' => 'rtb-bookings',
172
+ 'description' => '',
173
+ 'capability' => 'manage_options',
174
+ 'default_tab' => 'general',
175
+ )
176
+ );
177
+
178
+ $sap->add_section(
179
+ 'rtb-settings',
180
+ array(
181
+ 'id' => 'general',
182
+ 'title' => __( 'General', RTB_TEXTDOMAIN ),
183
+ 'is_tab' => true,
184
+ )
185
+ );
186
+
187
+ $sap->add_setting(
188
+ 'rtb-settings',
189
+ 'general',
190
+ 'post',
191
+ array(
192
+ 'id' => 'booking-page',
193
+ 'title' => __( 'Booking Page', RTB_TEXTDOMAIN ),
194
+ 'description' => __( 'Select a page on your site to automatically display the booking form and confirmation message.', RTB_TEXTDOMAIN ),
195
+ 'blank_option' => true,
196
+ 'args' => array(
197
+ 'post_type' => 'page',
198
+ 'posts_per_page' => -1,
199
+ 'post_status' => 'publish',
200
+ ),
201
+ )
202
+ );
203
+
204
+ $sap->add_setting(
205
+ 'rtb-settings',
206
+ 'general',
207
+ 'textarea',
208
+ array(
209
+ 'id' => 'success-message',
210
+ 'title' => __( 'Success Message', RTB_TEXTDOMAIN ),
211
+ 'description' => __( 'Enter the message to display when a booking request is made.', RTB_TEXTDOMAIN ),
212
+ 'placeholder' => $this->defaults['success-message'],
213
+ )
214
+ );
215
+
216
+ $sap->add_setting(
217
+ 'rtb-settings',
218
+ 'general',
219
+ 'text',
220
+ array(
221
+ 'id' => 'date-format',
222
+ 'title' => __( 'Date Format', RTB_TEXTDOMAIN ),
223
+ 'description' => __( 'Define how the date should appear after it has been selected. <a href="http://amsul.ca/pickadate.js/date.htm#formatting-rules">Formatting rules</a>', RTB_TEXTDOMAIN ),
224
+ 'placeholder' => $this->defaults['date-format'],
225
+ )
226
+ );
227
+
228
+ $sap->add_setting(
229
+ 'rtb-settings',
230
+ 'general',
231
+ 'text',
232
+ array(
233
+ 'id' => 'time-format',
234
+ 'title' => __( 'Time Format', RTB_TEXTDOMAIN ),
235
+ 'description' => __( 'Define how the time should appear after it has been selected. <a href="http://amsul.ca/pickadate.js/time.htm#formatting-rules">Formatting rules</a>', RTB_TEXTDOMAIN ),
236
+ 'placeholder' => $this->defaults['time-format'],
237
+ )
238
+ );
239
+
240
+ $sap->add_section(
241
+ 'rtb-settings',
242
+ array(
243
+ 'id' => 'schedule',
244
+ 'title' => __( 'Booking Schedule', RTB_TEXTDOMAIN ),
245
+ 'is_tab' => true,
246
+ )
247
+ );
248
+
249
+ $sap->add_setting(
250
+ 'rtb-settings',
251
+ 'schedule',
252
+ 'scheduler',
253
+ array(
254
+ 'id' => 'schedule-open',
255
+ 'title' => __( 'Schedule', RTB_TEXTDOMAIN ),
256
+ 'description' => __( 'Define the weekly schedule during which you accept bookings.', RTB_TEXTDOMAIN ),
257
+ 'weekdays' => array(
258
+ 'monday' => _x( 'Mo', 'Monday abbreviation', RTB_TEXTDOMAIN ),
259
+ 'tuesday' => _x( 'Tu', 'Tuesday abbreviation', RTB_TEXTDOMAIN ),
260
+ 'wednesday' => _x( 'We', 'Wednesday abbreviation', RTB_TEXTDOMAIN ),
261
+ 'thursday' => _x( 'Th', 'Thursday abbreviation', RTB_TEXTDOMAIN ),
262
+ 'friday' => _x( 'Fr', 'Friday abbreviation', RTB_TEXTDOMAIN ),
263
+ 'saturday' => _x( 'Sa', 'Saturday abbreviation', RTB_TEXTDOMAIN ),
264
+ 'sunday' => _x( 'Su', 'Sunday abbreviation', RTB_TEXTDOMAIN )
265
+ ),
266
+ 'time_format' => $this->get_setting( 'time-format' ),
267
+ 'date_format' => $this->get_setting( 'date-format' ),
268
+ 'disable_weeks' => true,
269
+ 'disable_date' => true,
270
+ )
271
+ );
272
+
273
+ $sap->add_setting(
274
+ 'rtb-settings',
275
+ 'schedule',
276
+ 'scheduler',
277
+ array(
278
+ 'id' => 'schedule-closed',
279
+ 'title' => __( 'Exceptions', RTB_TEXTDOMAIN ),
280
+ 'description' => __( "Define special opening hours for holidays, events or other needs. Leave the time empty if you're closed all day.", RTB_TEXTDOMAIN ),
281
+ 'time_format' => $this->get_setting( 'time-format' ),
282
+ 'date_format' => $this->get_setting( 'date-format' ),
283
+ 'disable_weekdays' => true,
284
+ 'disable_weeks' => true,
285
+ 'instance_schedule_summaries' => array(
286
+ 'all_day' => _x( 'Closed all day', 'Brief description of a scheduling exception when no times are set', SAP_TEXTDOMAIN ),
287
+ ),
288
+ )
289
+ );
290
+
291
+ $sap->add_setting(
292
+ 'rtb-settings',
293
+ 'schedule',
294
+ 'select',
295
+ array(
296
+ 'id' => 'early-bookings',
297
+ 'title' => __( 'Early Bookings', RTB_TEXTDOMAIN ),
298
+ 'description' => __( 'Select how early customers can make their booking.', RTB_TEXTDOMAIN ),
299
+ 'blank_option' => false,
300
+ 'options' => array(
301
+ '' => __( 'Any time', RTB_TEXTDOMAIN ),
302
+ '1' => __( 'Up to 1 day in advance', RTB_TEXTDOMAIN ),
303
+ '7' => __( 'Up to 1 week in advance', RTB_TEXTDOMAIN ),
304
+ '14' => __( 'Up to 2 weeks in advance', RTB_TEXTDOMAIN ),
305
+ '30' => __( 'Up to 30 days in advance', RTB_TEXTDOMAIN ),
306
+ '90' => __( 'Up to 90 days in advance', RTB_TEXTDOMAIN ),
307
+ )
308
+ )
309
+ );
310
+
311
+ $sap->add_setting(
312
+ 'rtb-settings',
313
+ 'schedule',
314
+ 'select',
315
+ array(
316
+ 'id' => 'late-bookings',
317
+ 'title' => __( 'Late Bookings', RTB_TEXTDOMAIN ),
318
+ 'description' => __( 'Select how late customers can make their booking.', RTB_TEXTDOMAIN ),
319
+ 'blank_option' => false,
320
+ 'options' => array(
321
+ '' => __( 'Up to the last minute', RTB_TEXTDOMAIN ),
322
+ '15' => __( 'Up to 15 minutes in advance', RTB_TEXTDOMAIN ),
323
+ '30' => __( 'Up to 30 minutes in advance', RTB_TEXTDOMAIN ),
324
+ '45' => __( 'Up to 45 minutes in advance', RTB_TEXTDOMAIN ),
325
+ '60' => __( 'Up to 1 hour in advance', RTB_TEXTDOMAIN ),
326
+ )
327
+ )
328
+ );
329
+
330
+ $sap->add_section(
331
+ 'rtb-settings',
332
+ array(
333
+ 'id' => 'notifications',
334
+ 'title' => __( 'Notifications', RTB_TEXTDOMAIN ),
335
+ 'is_tab' => true,
336
+ )
337
+ );
338
+
339
+ $sap->add_setting(
340
+ 'rtb-settings',
341
+ 'notifications',
342
+ 'text',
343
+ array(
344
+ 'id' => 'reply-to-name',
345
+ 'title' => __( 'Reply-To Name', RTB_TEXTDOMAIN ),
346
+ 'description' => __( 'The name which should appear in the Reply-To field of a notification email', RTB_TEXTDOMAIN ),
347
+ 'placeholder' => $this->defaults['reply-to-name'],
348
+ )
349
+ );
350
+
351
+ $sap->add_setting(
352
+ 'rtb-settings',
353
+ 'notifications',
354
+ 'text',
355
+ array(
356
+ 'id' => 'reply-to-address',
357
+ 'title' => __( 'Reply-To Email Address', RTB_TEXTDOMAIN ),
358
+ 'description' => __( 'The email address which should appear in the Reply-To field of a notification email.', RTB_TEXTDOMAIN ),
359
+ 'placeholder' => $this->defaults['reply-to-address'],
360
+ )
361
+ );
362
+
363
+ $sap->add_setting(
364
+ 'rtb-settings',
365
+ 'notifications',
366
+ 'toggle',
367
+ array(
368
+ 'id' => 'admin-email-option',
369
+ 'title' => __( 'Admin Notification', RTB_TEXTDOMAIN ),
370
+ 'label' => __( 'Send an email notification to an administrator when a new booking is requested.', RTB_TEXTDOMAIN )
371
+ )
372
+ );
373
+
374
+ $sap->add_setting(
375
+ 'rtb-settings',
376
+ 'notifications',
377
+ 'text',
378
+ array(
379
+ 'id' => 'admin-email-address',
380
+ 'title' => __( 'Admin Email Address', RTB_TEXTDOMAIN ),
381
+ 'description' => __( 'The email address where admin notifications should be sent.', RTB_TEXTDOMAIN ),
382
+ 'placeholder' => $this->defaults['admin-email-address'],
383
+ )
384
+ );
385
+
386
+ $sap->add_section(
387
+ 'rtb-settings',
388
+ array(
389
+ 'id' => 'notifications-templates',
390
+ 'title' => __( 'Email Templates', RTB_TEXTDOMAIN ),
391
+ 'tab' => 'notifications',
392
+ 'description' => 'Adjust the messages that are emailed to users and admins during the booking process.',
393
+ )
394
+ );
395
+
396
+ // @todo this should be generated automatically from an array of tags/descriptions somewhere, so that addons
397
+ // can easily add/edit without conflicting with each other.
398
+ $sap->add_setting(
399
+ 'rtb-settings',
400
+ 'notifications-templates',
401
+ 'html',
402
+ array(
403
+ 'id' => 'template-tags-description',
404
+ 'title' => __( 'Template Tags', RTB_TEXTDOMAIN ),
405
+ 'html' => '
406
+ <p class="description">' . __( 'Use the following tags to automatically add booking information to the emails.', RTB_TEXTDOMAIN ) . '</p>
407
+ <div class="rtb-template-tags-box">
408
+ <strong>{user_name}</strong> ' . __( 'Name of the user who made the booking', RTB_TEXTDOMAIN ) . '
409
+ </div>
410
+ <div class="rtb-template-tags-box">
411
+ <strong>{party}</strong> ' . __( 'Number of people booked', RTB_TEXTDOMAIN ) . '
412
+ </div>
413
+ <div class="rtb-template-tags-box">
414
+ <strong>{date}</strong> ' . __( 'Date and time of the booking', RTB_TEXTDOMAIN ) . '
415
+ </div>
416
+ <div class="rtb-template-tags-box">
417
+ <strong>{bookings_link}</strong> ' . __( 'A link to the admin panel showing pending bookings', RTB_TEXTDOMAIN ) . '
418
+ </div>
419
+ <div class="rtb-template-tags-box">
420
+ <strong>{confirm_link}</strong> ' . __( 'A link to confirm this booking. Only include this in admin notifications', RTB_TEXTDOMAIN ) . '
421
+ </div>
422
+ <div class="rtb-template-tags-box">
423
+ <strong>{close_link}</strong> ' . __( 'A link to reject this booking. Only include this in admin notifications', RTB_TEXTDOMAIN ) . '
424
+ </div>
425
+ <div class="rtb-template-tags-box">
426
+ <strong>{site_name}</strong> ' . __( 'The name of this website', RTB_TEXTDOMAIN ) . '
427
+ </div>
428
+ <div class="rtb-template-tags-box">
429
+ <strong>{site_link}</strong> ' . __( 'A link to this website', RTB_TEXTDOMAIN ) . '
430
+ </div>
431
+ <div class="rtb-template-tags-box">
432
+ <strong>{current_time}</strong> ' . __( 'Current date and time', RTB_TEXTDOMAIN ) . '
433
+ </div>',
434
+ )
435
+ );
436
+
437
+ $sap->add_setting(
438
+ 'rtb-settings',
439
+ 'notifications-templates',
440
+ 'text',
441
+ array(
442
+ 'id' => 'subject-booking-admin',
443
+ 'title' => __( 'Admin Notification Subject', RTB_TEXTDOMAIN ),
444
+ 'description' => __( 'The email subject for admin notifications.', RTB_TEXTDOMAIN ),
445
+ 'placeholder' => $this->defaults['subject-booking-admin'],
446
+ )
447
+ );
448
+
449
+ $sap->add_setting(
450
+ 'rtb-settings',
451
+ 'notifications-templates',
452
+ 'editor',
453
+ array(
454
+ 'id' => 'template-booking-admin',
455
+ 'title' => __( 'Admin Notification Email', RTB_TEXTDOMAIN ),
456
+ 'description' => __( 'Enter the email an admin should receive when an initial booking request is made.', RTB_TEXTDOMAIN ),
457
+ 'default' => $this->defaults['template-booking-admin'],
458
+ )
459
+ );
460
+
461
+ $sap->add_setting(
462
+ 'rtb-settings',
463
+ 'notifications-templates',
464
+ 'text',
465
+ array(
466
+ 'id' => 'subject-booking-user',
467
+ 'title' => __( 'New Request Email Subject', RTB_TEXTDOMAIN ),
468
+ 'description' => __( 'The email subject a user should receive when they make an initial booking request.', RTB_TEXTDOMAIN ),
469
+ 'placeholder' => $this->defaults['subject-booking-user'],
470
+ )
471
+ );
472
+
473
+ $sap->add_setting(
474
+ 'rtb-settings',
475
+ 'notifications-templates',
476
+ 'editor',
477
+ array(
478
+ 'id' => 'template-booking-user',
479
+ 'title' => __( 'New Request Email', RTB_TEXTDOMAIN ),
480
+ 'description' => __( 'Enter the email a user should receive when they make an initial booking request.', RTB_TEXTDOMAIN ),
481
+ 'default' => $this->defaults['template-booking-user'],
482
+ )
483
+ );
484
+
485
+ $sap->add_setting(
486
+ 'rtb-settings',
487
+ 'notifications-templates',
488
+ 'text',
489
+ array(
490
+ 'id' => 'subject-confirmed-user',
491
+ 'title' => __( 'Confirmed Email Subject', RTB_TEXTDOMAIN ),
492
+ 'description' => __( 'The email subject a user should receive when their booking has been confirmed.', RTB_TEXTDOMAIN ),
493
+ 'placeholder' => $this->defaults['subject-confirmed-user'],
494
+ )
495
+ );
496
+
497
+ $sap->add_setting(
498
+ 'rtb-settings',
499
+ 'notifications-templates',
500
+ 'editor',
501
+ array(
502
+ 'id' => 'template-confirmed-user',
503
+ 'title' => __( 'Confirmed Email', RTB_TEXTDOMAIN ),
504
+ 'description' => __( 'Enter the email a user should receive when their booking has been confirmed.', RTB_TEXTDOMAIN ),
505
+ 'default' => $this->defaults['template-confirmed-user'],
506
+ )
507
+ );
508
+
509
+ $sap->add_setting(
510
+ 'rtb-settings',
511
+ 'notifications-templates',
512
+ 'text',
513
+ array(
514
+ 'id' => 'subject-rejected-user',
515
+ 'title' => __( 'Rejected Email Subject', RTB_TEXTDOMAIN ),
516
+ 'description' => __( 'The email subject a user should receive when their booking has been rejected.', RTB_TEXTDOMAIN ),
517
+ 'placeholder' => $this->defaults['subject-rejected-user'],
518
+ )
519
+ );
520
+
521
+ $sap->add_setting(
522
+ 'rtb-settings',
523
+ 'notifications-templates',
524
+ 'editor',
525
+ array(
526
+ 'id' => 'template-rejected-user',
527
+ 'title' => __( 'Rejected Email', RTB_TEXTDOMAIN ),
528
+ 'description' => __( 'Enter the email a user should receive when their booking has been rejected.', RTB_TEXTDOMAIN ),
529
+ 'default' => $this->defaults['template-rejected-user'],
530
+ )
531
+ );
532
+
533
+ $sap = apply_filters( 'rtb_settings_page', $sap );
534
+
535
+ $sap->add_admin_menus();
536
+
537
+ }
538
+
539
+ }
540
+ } // endif;
includes/WP_List_Table.BookingsTable.class.php ADDED
@@ -0,0 +1,650 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( ! class_exists( 'WP_List_Table' ) ) {
5
+ require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
6
+ }
7
+
8
+ if ( !class_exists( 'rtbBookingsTable' ) ) {
9
+ /**
10
+ * Bookings Table Class
11
+ *
12
+ * Extends WP_List_Table to display the list of bookings in a format similar to
13
+ * the default WordPress post tables.
14
+ *
15
+ * @h/t Easy Digital Downloads by Pippin: https://easydigitaldownloads.com/
16
+ * @since 0.0.1
17
+ */
18
+ class rtbBookingsTable extends WP_List_Table {
19
+
20
+ /**
21
+ * Number of results to show per page
22
+ *
23
+ * @var string
24
+ * @since 0.0.1
25
+ */
26
+ public $per_page = 30;
27
+
28
+ /**
29
+ * URL of this page
30
+ *
31
+ * @var string
32
+ * @since 0.0.1
33
+ */
34
+ public $base_url;
35
+
36
+ /**
37
+ * Array of booking counts by total and status
38
+ *
39
+ * @var array
40
+ * @since 0.0.1
41
+ */
42
+ public $booking_counts;
43
+
44
+ /**
45
+ * Array of bookings
46
+ *
47
+ * @var array
48
+ * @since 0.0.1
49
+ */
50
+ public $bookings;
51
+
52
+ /**
53
+ * Current date filters
54
+ *
55
+ * @var string
56
+ * @since 0.0.1
57
+ */
58
+ public $filter_start_date = null;
59
+ public $filter_end_date = null;
60
+
61
+ /**
62
+ * Current query string
63
+ *
64
+ * @var string
65
+ * @since 0.0.1
66
+ */
67
+ public $query_string;
68
+
69
+ public function __construct() {
70
+
71
+ global $status, $page;
72
+
73
+ // Set parent defaults
74
+ parent::__construct( array(
75
+ 'singular' => __( 'Booking', RTB_TEXTDOMAIN ),
76
+ 'plural' => __( 'Bookings', RTB_TEXTDOMAIN ),
77
+ 'ajax' => false
78
+ ) );
79
+
80
+ // Set the date filter
81
+ $this->set_date_filter();
82
+
83
+ // Strip unwanted query vars from the query string or ensure the correct
84
+ // vars are used
85
+ $this->query_string_maintenance();
86
+
87
+ // Run any bulk action requests
88
+ $this->process_bulk_action();
89
+
90
+ // Run any quicklink requests
91
+ $this->process_quicklink_action();
92
+
93
+ // Retrieve a count of the number of bookings by status
94
+ $this->get_booking_counts();
95
+
96
+ // Retrieve bookings data for the table
97
+ $this->bookings_data();
98
+
99
+ $this->base_url = admin_url( 'admin.php?page=' . RTB_BOOKING_POST_TYPE );
100
+ }
101
+
102
+ /**
103
+ * Set the correct date filter
104
+ *
105
+ * $_POST values should always overwrite $_GET values
106
+ *
107
+ * @since 0.0.1
108
+ */
109
+ public function set_date_filter( $start_date = null, $end_date = null) {
110
+
111
+ if ( !empty( $_GET['action'] ) && $_GET['action'] == 'clear_date_filters' ) {
112
+ $this->filter_start_date = null;
113
+ $this->filter_end_date = null;
114
+ }
115
+
116
+ $this->filter_start_date = $start_date;
117
+ $this->filter_end_date = $end_date;
118
+
119
+ if ( $start_date === null ) {
120
+ $this->filter_start_date = !empty( $_GET['start-date'] ) ? sanitize_text_field( $_GET['start-date'] ) : null;
121
+ $this->filter_start_date = !empty( $_POST['start-date'] ) ? sanitize_text_field( $_POST['start-date'] ) : $this->filter_start_date;
122
+ }
123
+
124
+ if ( $end_date === null ) {
125
+ $this->filter_end_date = !empty( $_GET['end-date'] ) ? sanitize_text_field( $_GET['end-date'] ) : null;
126
+ $this->filter_end_date = !empty( $_POST['end-date'] ) ? sanitize_text_field( $_POST['end-date'] ) : $this->filter_end_date;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Strip unwanted query vars from the query string or ensure the correct
132
+ * vars are passed around and those we don't want to preserve are discarded.
133
+ *
134
+ * @since 0.0.1
135
+ */
136
+ public function query_string_maintenance() {
137
+
138
+ $this->query_string = remove_query_arg( array( 'action', 'start-date', 'end-date' ) );
139
+
140
+ if ( $this->filter_start_date !== null ) {
141
+ $this->query_string = add_query_arg( array( 'start-date' => $this->filter_start_date ), $this->query_string );
142
+ }
143
+
144
+ if ( $this->filter_end_date !== null ) {
145
+ $this->query_string = add_query_arg( array( 'end-date' => $this->filter_end_date ), $this->query_string );
146
+ }
147
+
148
+ }
149
+
150
+ /**
151
+ * Show the time views, date filters and the search box
152
+ * @since 0.0.1
153
+ */
154
+ public function advanced_filters() {
155
+
156
+ // Show the schedule views (today, upcoming, all)
157
+ if ( !empty( $_GET['schedule'] ) ) {
158
+ $schedule = sanitize_text_field( $_GET['schedule'] );
159
+ } else {
160
+ $schedule = '';
161
+ }
162
+
163
+ // Use a custom schedule if a date range has been entered
164
+ if ( $this->filter_start_date !== null || $this->filter_end_date !== null ) {
165
+ $schedule = 'custom';
166
+ }
167
+
168
+ // Strip out existing date filters from the schedule view urls
169
+ $schedule_query_string = remove_query_arg( array( 'schedule', 'start-date', 'end-date' ), $this->query_string );
170
+
171
+ $views = array(
172
+ 'all' => sprintf( '<a href="%s"%s>%s</a>', $schedule_query_string, $schedule == '' ? ' class="current"' : '', __( 'All', RTB_TEXTDOMAIN ) ),
173
+ 'today' => sprintf( '<a href="%s"%s>%s</a>', add_query_arg( array( 'schedule' => 'today', 'paged' => FALSE ), $schedule_query_string ), $schedule === 'today' ? ' class="current"' : '', __( 'Today', RTB_TEXTDOMAIN ) ),
174
+ 'upcoming' => sprintf( '<a href="%s"%s>%s</a>', add_query_arg( array( 'schedule' => 'upcoming', 'paged' => FALSE ), remove_query_arg( array( 'order' ), $schedule_query_string ) ), $schedule === 'upcoming' ? ' class="current"' : '', __( 'Upcoming', RTB_TEXTDOMAIN ) ),
175
+ );
176
+
177
+ if ( $schedule == 'custom' ) {
178
+ $start_date = !empty( $this->filter_start_date ) ? $this->filter_start_date : '*';
179
+ $end_date = !empty( $this->filter_end_date ) ? $this->filter_end_date : '*';
180
+ $views['custom'] = '<span class="current">' . $start_date . _x( '&mdash;', 'Separator between two dates in a date range', RTB_TEXTDOMAIN ) . $end_date . '</span>';
181
+ }
182
+
183
+ $views = apply_filters( 'rtn_bookings_table_views_schedule', $views );
184
+ ?>
185
+
186
+ <div id="rtb-filters" class="clearfix">
187
+ <ul class="subsubsub rtb-views-schedule">
188
+ <li><?php echo join( ' | </li><li>', $views ); ?></li>
189
+ </ul>
190
+
191
+ <div class="date-filters">
192
+ <label for="start-date" class="screen-reader-text"><?php _e( 'Start Date:', RTB_TEXTDOMAIN ); ?></label>
193
+ <input type="text" id="start-date" name="start-date" class="datepicker" value="<?php echo esc_attr( $this->filter_start_date ); ?>" placeholder="<?php _e( 'Start Date', RTB_TEXTDOMAIN ); ?>" />
194
+ <label for="end-date" class="screen-reader-text"><?php _e( 'End Date:', RTB_TEXTDOMAIN ); ?></label>
195
+ <input type="text" id="end-date" name="end-date" class="datepicker" value="<?php echo esc_attr( $this->filter_end_date ); ?>" placeholder="<?php _e( 'End Date', RTB_TEXTDOMAIN ); ?>" />
196
+ <input type="submit" class="button-secondary" value="<?php _e( 'Apply', RTB_TEXTDOMAIN ); ?>"/>
197
+ <?php if( !empty( $start_date ) || !empty( $end_date ) ) : ?>
198
+ <a href="<?php echo add_query_arg( array( 'action' => 'clear_date_filters' ) ); ?>" class="button-secondary"><?php _e( 'Clear Filter', RTB_TEXTDOMAIN ); ?></a>
199
+ <?php endif; ?>
200
+ </div>
201
+
202
+ <?php if( !empty( $_GET['status'] ) ) : ?>
203
+ <input type="hidden" name="status" value="<?php echo esc_attr( sanitize_text_field( $_GET['status'] ) ); ?>"/>
204
+ <?php endif; ?>
205
+
206
+ <?php
207
+ // @todo Add support for the search box that uses more than just
208
+ // the 's' argument in WP_Query. I need to search at least the
209
+ // email post meta as well or this search box could be
210
+ // misleading for people who expect to search across all
211
+ // visible data
212
+ // $this->search_box( __( 'Search', RTB_TEXTDOMAIN ), 'rtb-bookings' );
213
+ ?>
214
+
215
+ <?php
216
+ // @todo use a datepicker. need to bring in styles for jquery ui or use pickadate
217
+ // wp_enqueue_script('jquery-ui-datepicker');
218
+ ?>
219
+
220
+ </div>
221
+
222
+ <?php
223
+ }
224
+
225
+ /**
226
+ * Retrieve the view types
227
+ * @since 0.0.1
228
+ * @todo it would be nice if the default view showed all upcoming pending + confirmed bookings, but not closed ones.
229
+ */
230
+ public function get_views() {
231
+
232
+ $current = isset( $_GET['status'] ) ? $_GET['status'] : '';
233
+
234
+ $views = array(
235
+ 'all' => sprintf( '<a href="%s"%s>%s</a>', remove_query_arg( array( 'status', 'paged' ), $this->query_string ), $current === 'all' || $current == '' ? ' class="current"' : '', __( 'All', RTB_TEXTDOMAIN ) . ' <span class="count">(' . $this->booking_counts['total'] . ')</span>' ),
236
+ 'pending' => sprintf( '<a href="%s"%s>%s</a>', add_query_arg( array( 'status' => 'pending', 'paged' => FALSE ), $this->query_string ), $current === 'pending' ? ' class="current"' : '', __( 'Pending', RTB_TEXTDOMAIN ) . ' <span class="count">(' . $this->booking_counts['pending'] . ')</span>' ),
237
+ 'confirmed' => sprintf( '<a href="%s"%s>%s</a>', add_query_arg( array( 'status' => 'confirmed', 'paged' => FALSE ), $this->query_string ), $current === 'confirmed' ? ' class="current"' : '', __( 'Confirmed', RTB_TEXTDOMAIN ) . ' <span class="count">(' . $this->booking_counts['confirmed'] . ')</span>' ),
238
+ 'closed' => sprintf( '<a href="%s"%s>%s</a>', add_query_arg( array( 'status' => 'closed', 'paged' => FALSE ), $this->query_string ), $current === 'closed' ? ' class="current"' : '', __( 'Closed', RTB_TEXTDOMAIN ) . ' <span class="count">(' . $this->booking_counts['closed'] . ')</span>' ),
239
+ 'trash' => sprintf( '<a href="%s"%s>%s</a>', add_query_arg( array( 'status' => 'trash', 'paged' => FALSE ), $this->query_string ), $current === 'trash' ? ' class="current"' : '', __( 'Trash', RTB_TEXTDOMAIN ) . ' <span class="count">(' . $this->booking_counts['trash'] . ')</span>' ),
240
+ );
241
+
242
+ return apply_filters( 'rtb_bookings_table_views_status', $views );
243
+ }
244
+
245
+ /**
246
+ * Generates content for a single row of the table
247
+ * @since 0.0.1
248
+ */
249
+ function single_row( $item ) {
250
+ static $row_class = '';
251
+ $row_class = ( $row_class == '' ? 'alternate' : '' );
252
+
253
+ echo '<tr class="' . esc_attr( $item->post_status );
254
+ echo $row_class == '' ? '' : ' ' . $row_class;
255
+ echo '">';
256
+ $this->single_row_columns( $item );
257
+ echo '</tr>';
258
+ }
259
+
260
+ /**
261
+ * Retrieve the table columns
262
+ * @since 0.0.1
263
+ */
264
+ public function get_columns() {
265
+ $columns = array(
266
+ 'cb' => '<input type="checkbox" />', //Render a checkbox instead of text
267
+ 'date' => __( 'Date', RTB_TEXTDOMAIN ),
268
+ 'party' => __( 'Party', RTB_TEXTDOMAIN ),
269
+ 'name' => __( 'Name', RTB_TEXTDOMAIN ),
270
+ 'email' => __( 'Email', RTB_TEXTDOMAIN ),
271
+ 'phone' => __( 'Phone', RTB_TEXTDOMAIN ),
272
+ 'message' => __( 'Message', RTB_TEXTDOMAIN ),
273
+ 'status' => __( 'Status', RTB_TEXTDOMAIN )
274
+ );
275
+
276
+ return apply_filters( 'rtb_bookings_table_columns', $columns );
277
+ }
278
+
279
+ /**
280
+ * Retrieve the table's sortable columns
281
+ * @since 0.0.1
282
+ */
283
+ public function get_sortable_columns() {
284
+ $columns = array(
285
+ 'date' => array( 'date', true ),
286
+ 'name' => array( 'title', true ),
287
+ );
288
+ return apply_filters( 'rtb_bookings_table_sortable_columns', $columns );
289
+ }
290
+
291
+ /**
292
+ * This function renders most of the columns in the list table.
293
+ * @since 0.0.1
294
+ */
295
+ public function column_default( $booking, $column_name ) {
296
+ switch ( $column_name ) {
297
+ case 'date' :
298
+ $value = $booking->date;
299
+ break;
300
+ case 'party' :
301
+ $value = $booking->party;
302
+ break;
303
+ case 'name' :
304
+ $value = $booking->name;
305
+ break;
306
+ case 'email' :
307
+ $value = $booking->email;
308
+ break;
309
+ case 'phone' :
310
+ $value = $booking->phone;
311
+ break;
312
+ case 'message' :
313
+ $value = '';
314
+ if ( trim( $booking->message ) ) {
315
+ $value = '<a href="#" data-id="message-' . esc_attr( $booking->ID ) . '"><span class="dashicons dashicons-testimonial"></span></a>';
316
+ $value .= '<div class="rtb-message-data">' . $booking->message . '</div>';
317
+ }
318
+ break;
319
+ case 'status' :
320
+ global $rtb_controller;
321
+ if ( !empty( $rtb_controller->cpts->booking_statuses[$booking->post_status] ) ) {
322
+ $value = $rtb_controller->cpts->booking_statuses[$booking->post_status]['label'];
323
+ } elseif ( $booking->post_status == 'trash' ) {
324
+ $value = _x( 'Trash', 'Status label for bookings put in the trash', RTB_TEXTDOMAIN );
325
+ } else {
326
+ $value = $booking->post_status;
327
+ }
328
+ break;
329
+ default:
330
+ $value = isset( $booking->$column_name ) ? $booking->$column_name : '';
331
+ break;
332
+
333
+ }
334
+
335
+ return apply_filters( 'rtb_bookings_table_column', $value, $booking, $column_name );
336
+ }
337
+
338
+ /**
339
+ * Render the checkbox column
340
+ * @since 0.0.1
341
+ */
342
+ public function column_cb( $booking ) {
343
+ return sprintf(
344
+ '<input type="checkbox" name="%1$s[]" value="%2$s" />',
345
+ 'bookings',
346
+ $booking->ID
347
+ );
348
+ }
349
+
350
+ /**
351
+ * Retrieve the bulk actions
352
+ * @since 0.0.1
353
+ */
354
+ public function get_bulk_actions() {
355
+ $actions = array(
356
+ 'delete' => __( 'Delete', RTB_TEXTDOMAIN ),
357
+ 'set-status-confirmed' => __( 'Set To Confirmed', RTB_TEXTDOMAIN ),
358
+ 'set-status-pending' => __( 'Set To Pending Review', RTB_TEXTDOMAIN ),
359
+ 'set-status-closed' => __( 'Set To Closed', RTB_TEXTDOMAIN )
360
+ );
361
+
362
+ return apply_filters( 'rtb_bookings_table_bulk_actions', $actions );
363
+ }
364
+
365
+ /**
366
+ * Process the bulk actions
367
+ * @since 0.0.1
368
+ */
369
+ public function process_bulk_action() {
370
+ $ids = isset( $_POST['bookings'] ) ? $_POST['bookings'] : false;
371
+ $action = isset( $_POST['action'] ) ? $_POST['action'] : false;
372
+
373
+ if( empty( $action ) || $action == '-1' ) {
374
+ return;
375
+ }
376
+
377
+ if ( !current_user_can( 'manage_bookings' ) ) {
378
+ return;
379
+ }
380
+
381
+ if ( ! is_array( $ids ) ) {
382
+ $ids = array( $ids );
383
+ }
384
+
385
+ global $rtb_controller;
386
+ $results = array();
387
+ foreach ( $ids as $id ) {
388
+ if ( 'delete' === $action ) {
389
+ $results[$id] = $rtb_controller->cpts->delete_booking( $id );
390
+ }
391
+
392
+ if ( 'set-status-confirmed' === $action ) {
393
+ $results[$id] = $rtb_controller->cpts->update_booking_status( $id, 'confirmed' );
394
+ }
395
+
396
+ if ( 'set-status-pending' === $action ) {
397
+ $results[$id] = $rtb_controller->cpts->update_booking_status( $id, 'pending' );
398
+ }
399
+
400
+ if ( 'set-status-closed' === $action ) {
401
+ $results[$id] = $rtb_controller->cpts->update_booking_status( $id, 'closed' );
402
+ }
403
+
404
+ $results = apply_filters( 'rtb_bookings_table_bulk_action', $results, $id, $action );
405
+ }
406
+
407
+ if( count( $results ) ) {
408
+ $this->results = $results;
409
+ $this->last_action = $action;
410
+ add_action( 'rtb_bookings_table_top', array( $this, 'admin_notice_bulk_actions' ) );
411
+ }
412
+
413
+ }
414
+
415
+ /**
416
+ * Process quicklink actions sent out in notification emails
417
+ * @since 0.0.1
418
+ */
419
+ public function process_quicklink_action() {
420
+
421
+ if ( empty( $_REQUEST['rtb-quicklink'] ) ) {
422
+ return;
423
+ }
424
+
425
+ if ( !current_user_can( 'manage_bookings' ) ) {
426
+ return;
427
+ }
428
+
429
+ global $rtb_controller;
430
+
431
+ $results = array();
432
+
433
+ $id = !empty( $_REQUEST['booking'] ) ? $_REQUEST['booking'] : false;
434
+
435
+ if ( $_REQUEST['rtb-quicklink'] == 'confirm' ) {
436
+ $results[$id] = $rtb_controller->cpts->update_booking_status( $id, 'confirmed' );
437
+ $this->last_action = 'set-status-confirmed';
438
+ } elseif ( $_REQUEST['rtb-quicklink'] == 'close' ) {
439
+ $results[$id] = $rtb_controller->cpts->update_booking_status( $id, 'closed' );
440
+ $this->last_action = 'set-status-closed';
441
+ }
442
+
443
+ if( count( $results ) ) {
444
+ $this->results = $results;
445
+ add_action( 'rtb_bookings_table_top', array( $this, 'admin_notice_bulk_actions' ) );
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Display an admin notice when a bulk action is completed
451
+ * @since 0.0.1
452
+ */
453
+ public function admin_notice_bulk_actions() {
454
+
455
+ $success = 0;
456
+ $failure = 0;
457
+ foreach( $this->results as $id => $result ) {
458
+ if ( $result === true || $result === null ) {
459
+ $success++;
460
+ } else {
461
+ $failure++;
462
+ }
463
+ }
464
+
465
+ if ( $success > 0 ) :
466
+ ?>
467
+
468
+ <div id="rtb-admin-notice-bulk-<?php esc_attr( $this->last_action ); ?>" class="updated">
469
+
470
+ <?php if ( $this->last_action == 'delete' ) : ?>
471
+ <p><?php echo sprintf( _n( '%d booking deleted successfully.', '%d bookings deleted successfully.', $success, RTB_TEXTDOMAIN ), $success ); ?></p>
472
+
473
+ <?php elseif ( $this->last_action == 'set-status-confirmed' ) : ?>
474
+ <p><?php echo sprintf( _n( '%d booking confirmed.', '%d bookings confirmed.', $success, RTB_TEXTDOMAIN ), $success ); ?></p>
475
+
476
+ <?php elseif ( $this->last_action == 'set-status-pending' ) : ?>
477
+ <p><?php echo sprintf( _n( '%d booking set to pending.', '%d bookings set to pending.', $success, RTB_TEXTDOMAIN ), $success ); ?></p>
478
+
479
+ <?php elseif ( $this->last_action == 'set-status-closed' ) : ?>
480
+ <p><?php echo sprintf( _n( '%d booking closed.', '%d bookings closed.', $success, RTB_TEXTDOMAIN ), $success ); ?></p>
481
+
482
+ <?php endif; ?>
483
+ </div>
484
+
485
+ <?php
486
+ endif;
487
+
488
+ if ( $failure > 0 ) :
489
+ ?>
490
+
491
+ <div id="rtb-admin-notice-bulk-<?php esc_attr( $this->last_action ); ?>" class="error">
492
+ <p><?php echo sprintf( _n( '%d booking had errors and could not be processed.', '%d bookings had errors and could not be processed.', $failure, RTB_TEXTDOMAIN ), $failure ); ?></p>
493
+ </div>
494
+
495
+ <?php
496
+ endif;
497
+ }
498
+
499
+ /**
500
+ * Retrieve the counts of bookings
501
+ * @since 0.0.1
502
+ */
503
+ public function get_booking_counts() {
504
+
505
+ global $wpdb;
506
+
507
+ $where = "WHERE p.post_type = '" . RTB_BOOKING_POST_TYPE . "'";
508
+
509
+ if ( $this->filter_start_date !== null || $this->filter_end_date !== null ) {
510
+
511
+ if ( $this->filter_start_date !== null ) {
512
+ $start_date = new DateTime( $this->filter_start_date );
513
+ $where .= " AND p.post_date >= '" . $start_date->format( 'Y-m-d H:i:s' ) . "'";
514
+ }
515
+
516
+ if ( $this->filter_end_date !== null ) {
517
+ $end_date = new DateTime( $this->filter_end_date );
518
+ $where .= " AND p.post_date <= '" . $end_date->format( 'Y-m-d H:i:s' ) . "'";
519
+ }
520
+ } elseif ( !empty( $_GET['schedule'] ) ) {
521
+
522
+ if ( $_GET['schedule'] == 'today' ) {
523
+ $where .= " AND p.post_date >= '" . date( 'Y-m-d' ) . "' AND p.post_date <= '" . date( 'Y-m-d', current_time( 'timestamp' ) + 86400 ) . "'";
524
+
525
+ } elseif ( $_GET['schedule'] == 'upcoming' ) {
526
+ $where .= " AND p.post_date >= '" . date( 'Y-m-d H:i:s', current_time( 'timestamp' ) - 3600 ) . "'";
527
+ }
528
+ }
529
+
530
+
531
+ $query = "SELECT p.post_status,count( * ) AS num_posts
532
+ FROM $wpdb->posts p
533
+ $where
534
+ GROUP BY p.post_status
535
+ ";
536
+
537
+ $count = $wpdb->get_results( $query, ARRAY_A );
538
+
539
+ $this->booking_counts = array();
540
+ foreach ( get_post_stati() as $state ) {
541
+ $this->booking_counts[$state] = 0;
542
+ }
543
+
544
+ $this->booking_counts['total'] = 0;
545
+ foreach ( (array) $count as $row ) {
546
+ $this->booking_counts[$row['post_status']] = $row['num_posts'];
547
+ $this->booking_counts['total'] += $row['num_posts'];
548
+ }
549
+
550
+ }
551
+
552
+ /**
553
+ * Retrieve all the data for all the bookings
554
+ * @since 0.0.1
555
+ */
556
+ public function bookings_data() {
557
+
558
+ $args = array(
559
+ 'post_type' => RTB_BOOKING_POST_TYPE,
560
+ 'posts_per_page' => $this->per_page,
561
+ 'paged' => isset( $_GET['paged'] ) ? $_GET['paged'] : 1,
562
+ 'post_status' => isset( $_GET['status'] ) ? $_GET['status'] : array( 'confirmed', 'pending', 'closed' ),
563
+ );
564
+
565
+ if ( isset( $_GET['orderby'] ) ) {
566
+ $args['orderby'] = $_GET['orderby'];
567
+ }
568
+
569
+ $args['order'] = isset( $_GET['order'] ) ? $_GET['order'] : 'ASC';
570
+
571
+ if ( $this->filter_start_date !== null || $this->filter_end_date !== null ) {
572
+
573
+ $date_query = array();
574
+
575
+ if ( $this->filter_start_date !== null ) {
576
+ $date_query['after'] = $this->filter_start_date;
577
+ }
578
+
579
+ if ( $this->filter_end_date !== null ) {
580
+ $date_query['before'] = $this->filter_end_date;
581
+ }
582
+
583
+ if ( count( $date_query ) ) {
584
+ $args['date_query'] = $date_query;
585
+ }
586
+
587
+ } elseif ( isset( $_GET['schedule'] ) ) {
588
+
589
+ if ( $_GET['schedule'] == 'today' ) {
590
+ $today = getdate();
591
+ $args['year'] = $today['year'];
592
+ $args['monthnum'] = $today['mon'];
593
+ $args['day'] = $today['mday'];
594
+ }
595
+
596
+ if ( $_GET['schedule'] == 'upcoming' ) {
597
+ $args['date_query'] = array(
598
+ array(
599
+ 'after' => '-1 hour', // show bookings that have just passed
600
+ )
601
+ );
602
+ if ( empty( $_GET['order'] ) ) {
603
+ $args['order'] = 'ASC';
604
+ }
605
+ }
606
+ }
607
+
608
+ $args = apply_filters( 'rtb_bookings_table_query_args', $args );
609
+
610
+ // Make query
611
+ $query = new WP_Query( $args );
612
+
613
+ if ( $query->have_posts() ) {
614
+ while ( $query->have_posts() ) {
615
+ $query->the_post();
616
+
617
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
618
+ $booking = new rtbBooking();
619
+ if ( $booking->load_post( $query->post ) ) {
620
+ $this->bookings[] = $booking;
621
+ }
622
+ }
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Setup the final data for the table
628
+ * @since 0.0.1
629
+ */
630
+ public function prepare_items() {
631
+
632
+ $columns = $this->get_columns();
633
+ $hidden = array(); // No hidden columns
634
+ $sortable = $this->get_sortable_columns();
635
+
636
+ $this->_column_headers = array( $columns, $hidden, $sortable );
637
+
638
+ $this->items = $this->bookings;
639
+
640
+ $total_items = empty( $_GET['status'] ) ? $this->booking_counts['total'] : $this->booking_counts[$_GET['status']];
641
+
642
+ $this->set_pagination_args( array(
643
+ 'total_items' => $total_items,
644
+ 'per_page' => $this->per_page,
645
+ 'total_pages' => ceil( $total_items / $this->per_page )
646
+ )
647
+ );
648
+ }
649
+ }
650
+ } // endif;
includes/WP_Widget.BookingFormWidget.class.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ if ( ! class_exists( 'WP_Widget' ) ) {
5
+ require_once ABSPATH . 'wp-admin/includes/widgets.php';
6
+ }
7
+
8
+ if ( !class_exists( 'rtbBookingFormWidget' ) ) {
9
+ /**
10
+ * Booking form widget
11
+ *
12
+ * Extends WP_Widget to display a booking form in a widget.
13
+ * @since 0.0.1
14
+ */
15
+ class rtbBookingFormWidget extends WP_Widget {
16
+
17
+ /**
18
+ * Register widget with WordPress.
19
+ * @since 0.0.1
20
+ */
21
+ function __construct() {
22
+
23
+ parent::__construct(
24
+ 'rtb_booking_form_widget',
25
+ __('Booking Form', RTB_TEXTDOMAIN),
26
+ array( 'description' => __( 'Display a form to accept bookings.', RTB_TEXTDOMAIN ), )
27
+ );
28
+
29
+ }
30
+
31
+ /**
32
+ * Print the widget content
33
+ * @since 0.0.1
34
+ */
35
+ public function widget( $args, $instance ) {
36
+
37
+ global $rtb_controller;
38
+
39
+ // Don't show the widget if the form has already been displayed. The
40
+ // date and time pickers don't yet support multiple forms on a page.
41
+ if ( $rtb_controller->form_rendered === true ) {
42
+ return;
43
+ }
44
+
45
+ // Print the widget's HTML markup
46
+ echo $args['before_widget'];
47
+ if( isset( $instance['title'] ) ) {
48
+ $title = apply_filters( 'widget_title', $instance['title'] );
49
+ echo $args['before_title'] . $title . $args['after_title'];
50
+ }
51
+ echo rtb_print_booking_form();
52
+ echo $args['after_widget'];
53
+
54
+ }
55
+
56
+ /**
57
+ * Print the form to configure this widget in the admin panel
58
+ * @since 1.0
59
+ */
60
+ public function form( $instance ) {
61
+ ?>
62
+
63
+ <p>
64
+ <label for="<?php echo $this->get_field_id( 'title' ); ?>"> <?php _e( 'Title' ); ?></label>
65
+ <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text"<?php if ( isset( $instance['title'] ) ) : ?> value="<?php echo esc_attr( $instance['title'] ); ?>"<?php endif; ?>>
66
+ </p>
67
+
68
+ <?php
69
+ }
70
+
71
+ /**
72
+ * Sanitize and save the widget form values.
73
+ * @since 1.0
74
+ */
75
+ public function update( $new_instance, $old_instance ) {
76
+
77
+ $instance = array();
78
+ if ( !empty( $new_instance['title'] ) ) {
79
+ $instance['title'] = strip_tags( $new_instance['title'] );
80
+ }
81
+
82
+ return $instance;
83
+
84
+ }
85
+
86
+ }
87
+ } // endif
includes/template-functions.php ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template functions for rendering booking forms, etc.
4
+ */
5
+
6
+ /**
7
+ * Create a shortcode to render the booking form
8
+ * @since 0.0.1
9
+ */
10
+ if ( !function_exists( 'rtb_booking_form_shortcode' ) ) {
11
+ function rtb_booking_form_shortcode() {
12
+ return rtb_print_booking_form();
13
+ }
14
+ add_shortcode( 'booking-form', 'rtb_booking_form_shortcode' );
15
+ } // endif;
16
+
17
+ /**
18
+ * Print the booking form's HTML code, including error handling and confirmation
19
+ * notices.
20
+ * @since 0.0.1
21
+ */
22
+ if ( !function_exists( 'rtb_print_booking_form' ) ) {
23
+ function rtb_print_booking_form() {
24
+
25
+ global $rtb_controller;
26
+
27
+ // Only allow the form to be displayed once on a page
28
+ if ( $rtb_controller->form_rendered === true ) {
29
+ return;
30
+ } else {
31
+ $rtb_controller->form_rendered = true;
32
+ }
33
+
34
+ // Enqueue assets for the form
35
+ rtb_enqueue_assets();
36
+
37
+ // Allow themes and plugins to override the booking form's HTML output.
38
+ $output = apply_filters( 'rtb_booking_form_html_pre', '' );
39
+ if ( !empty( $output ) ) {
40
+ return $output;
41
+ }
42
+
43
+ // Process a booking request
44
+ if ( !empty( $_POST['action'] ) && $_POST['action'] == 'booking_request' ) {
45
+
46
+ if ( empty( $rtb_controller->request ) ) {
47
+ require_once( RTB_PLUGIN_DIR . '/includes/Booking.class.php' );
48
+ $rtb_controller->request = new rtbBooking();
49
+ }
50
+
51
+ $rtb_controller->request->insert_booking();
52
+ }
53
+
54
+ // Set up a dummy request object if no request has been made. This just
55
+ // simplifies the display of values in the form below
56
+ if ( empty( $rtb_controller->request ) ) {
57
+ $request = new stdClass();
58
+ $request->request_processed = false;
59
+ $request->request_inserted = false;
60
+ } else {
61
+ $request = $rtb_controller->request;
62
+ }
63
+
64
+ // Define the form's action parameter
65
+ $booking_page = $rtb_controller->settings->get_setting( 'booking-page' );
66
+ if ( !empty( $booking_page ) ) {
67
+ $booking_page = get_permalink( $booking_page );
68
+ }
69
+
70
+ ob_start();
71
+
72
+ ?>
73
+
74
+ <div class="rtb-booking-form">
75
+ <?php if ( $request->request_inserted === true ) : ?>
76
+ <div class="rtb-message">
77
+ <p><?php echo $rtb_controller->settings->get_setting( 'success-message' ); ?></p>
78
+ </div>
79
+ <?php else : ?>
80
+ <form method="POST" action="<?php echo esc_attr( $booking_page ); ?>">
81
+ <input type="hidden" name="action" value="booking_request">
82
+ <fieldset class="reservation">
83
+ <legend>
84
+ <?php _e( 'Book a table', RTB_TEXTDOMAIN ); ?>
85
+ </legend>
86
+ <div class="date">
87
+ <?php echo rtb_print_form_error( 'date' ); ?>
88
+ <label for="rtb-date">
89
+ <?php _e( 'Date', RTB_TEXTDOMAIN ); ?>
90
+ </label>
91
+ <input type="text" name="rtb-date" id="rtb-date" value="<?php echo empty( $request->request_date ) ? '' : esc_attr( $request->request_date ); ?>">
92
+ </div>
93
+ <div class="time">
94
+ <?php echo rtb_print_form_error( 'time' ); ?>
95
+ <label for="rtb-time">
96
+ <?php _e( 'Time', RTB_TEXTDOMAIN ); ?>
97
+ </label>
98
+ <input type="text" name="rtb-time" id="rtb-time" value="<?php echo empty( $request->request_time ) ? '' : esc_attr( $request->request_time ); ?>">
99
+ </div>
100
+ <div class="party">
101
+ <?php echo rtb_print_form_error( 'party' ); ?>
102
+ <label for="rtb-party">
103
+ <?php _e( 'Party', RTB_TEXTDOMAIN ); ?>
104
+ </label>
105
+ <input type="text" name="rtb-party" id="rtb-party" value="<?php echo empty( $request->party ) ? '' : esc_attr( $request->party ); ?>">
106
+ </div>
107
+ </fieldset>
108
+ <fieldset class="contact">
109
+ <legend>
110
+ <?php _e( 'Contact Details', RTB_TEXTDOMAIN ); ?>
111
+ </legend>
112
+ <div class="name">
113
+ <?php echo rtb_print_form_error( 'name' ); ?>
114
+ <label for="rtb-name">
115
+ <?php _e( 'Name', RTB_TEXTDOMAIN ); ?>
116
+ </label>
117
+ <input type="text" name="rtb-name" id="rtb-name" placeholder="Your name" value="<?php echo empty( $request->name ) ? '' : esc_attr( $request->name ); ?>">
118
+ </div>
119
+ <div class="email">
120
+ <?php echo rtb_print_form_error( 'email' ); ?>
121
+ <label for="rtb-email">
122
+ <?php _e( 'Email', RTB_TEXTDOMAIN ); ?>
123
+ </label>
124
+ <input type="text" name="rtb-email" id="rtb-email" placeholder="your@email.com" value="<?php echo empty( $request->email ) ? '' : esc_attr( $request->email ); ?>">
125
+ </div>
126
+ <div class="phone">
127
+ <?php echo rtb_print_form_error( 'phone' ); ?>
128
+ <label for="rtb-phone">
129
+ <?php _e( 'Phone', RTB_TEXTDOMAIN ); ?>
130
+ </label>
131
+ <input type="text" name="rtb-phone" id="rtb-phone" placeholder="Your phone number" value="<?php echo empty( $request->phone ) ? '' : esc_attr( $request->phone ); ?>">
132
+ </div>
133
+ <div class="add-message">
134
+ <a href="#">
135
+ <?php _e( 'Add a Message', RTB_TEXTDOMAIN ); ?>
136
+ </a>
137
+ </div>
138
+ <div class="message">
139
+ <?php echo rtb_print_form_error( 'message' ); ?>
140
+ <label for="rtb-message">
141
+ <?php _e( 'Message', RTB_TEXTDOMAIN ); ?>
142
+ </label>
143
+ <textarea name="rtb-message" id="rtb-message"><?php echo empty( $request->message ) ? '' : esc_attr( $request->message ); ?></textarea>
144
+ </div>
145
+ </fieldset>
146
+ <button type="submit"><?php _e( 'Request Booking', RTB_TEXTDOMAIN ); ?></button>
147
+ </form>
148
+ <?php endif; ?>
149
+ </div>
150
+
151
+ <?php
152
+
153
+ $output = ob_get_clean();
154
+
155
+ $output = apply_filters( 'rtb_booking_form_html_post', $output );
156
+
157
+ return $output;
158
+ }
159
+ } // endif;
160
+
161
+ /**
162
+ * Enqueue the front-end CSS and Javascript for the booking form
163
+ * @since 0.0.1
164
+ */
165
+ if ( !function_exists( 'rtb_enqueue_assets' ) ) {
166
+ function rtb_enqueue_assets() {
167
+
168
+ wp_enqueue_style( 'rtb-booking-form' );
169
+
170
+ wp_enqueue_style( 'pickadate-default', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.css' );
171
+ wp_enqueue_style( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.date.css' );
172
+ wp_enqueue_style( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/default.time.css' );
173
+ wp_enqueue_script( 'pickadate', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.js', array( 'jquery' ), '', true );
174
+ wp_enqueue_script( 'pickadate-date', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.date.js', array( 'jquery' ), '', true );
175
+ wp_enqueue_script( 'pickadate-time', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/picker.time.js', array( 'jquery' ), '', true );
176
+ wp_enqueue_script( 'pickadate-legacy', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/legacy.js', array( 'jquery' ), '', true );
177
+ // @todo is there some way I can enqueue this for RTL languages
178
+ // wp_enqueue_style( 'pickadate-rtl', RTB_PLUGIN_URL . '/lib/simple-admin-pages/lib/pickadate/themes/rtl.css' );
179
+
180
+ wp_enqueue_script( 'rtb-booking-form' );
181
+
182
+ // Pass date and time format settings to the pickadate controls
183
+ global $rtb_controller;
184
+ wp_localize_script(
185
+ 'rtb-booking-form',
186
+ 'rtb_pickadate',
187
+ array(
188
+ 'date_format' => $rtb_controller->settings->get_setting( 'date-format' ),
189
+ 'time_format' => $rtb_controller->settings->get_setting( 'time-format' ),
190
+ 'disable_dates' => rtb_get_datepicker_rules(),
191
+ 'schedule_open' => $rtb_controller->settings->get_setting( 'schedule-open' ),
192
+ 'schedule_closed' => $rtb_controller->settings->get_setting( 'schedule-closed' ),
193
+ 'early_bookings' => $rtb_controller->settings->get_setting( 'early-bookings' ),
194
+ 'late_bookings' => $rtb_controller->settings->get_setting( 'late-bookings' ),
195
+ )
196
+ );
197
+
198
+ }
199
+ } // endif;
200
+
201
+ /**
202
+ * Get rules for datepicker date ranges
203
+ * See: http://amsul.ca/pickadate.js/date.htm#disable-dates
204
+ * @since 0.0.1
205
+ */
206
+ if ( !function_exists( 'rtb_get_datepicker_rules' ) ) {
207
+ function rtb_get_datepicker_rules() {
208
+
209
+ global $rtb_controller;
210
+
211
+ $disable_rules = array();
212
+
213
+ $disabled_weekdays = array(
214
+ 'sunday' => 1,
215
+ 'monday' => 2,
216
+ 'tuesday' => 3,
217
+ 'wednesday' => 4,
218
+ 'thursday' => 5,
219
+ 'friday' => 6,
220
+ 'saturday' => 7,
221
+ );
222
+
223
+ // Determine which weekdays should be disabled
224
+ $enabled_dates = array();
225
+ $schedule_open = $rtb_controller->settings->get_setting( 'schedule-open' );
226
+ if ( is_array( $schedule_open ) ) {
227
+ foreach ( $schedule_open as $rule ) {
228
+ if ( !empty( $rule['weekdays'] ) ) {
229
+ foreach ( $rule['weekdays'] as $weekday => $value ) {
230
+ unset( $disabled_weekdays[ $weekday ] );
231
+ }
232
+ }
233
+ }
234
+
235
+ if ( count( $disabled_weekdays ) < 7 ) {
236
+ foreach ( $disabled_weekdays as $weekday ) {
237
+ $disable_rules[] = $weekday;
238
+ }
239
+ }
240
+ }
241
+
242
+ // Handle exception dates
243
+ $schedule_closed = $rtb_controller->settings->get_setting( 'schedule-closed' );
244
+ if ( is_array( $schedule_closed ) ) {
245
+ foreach ( $schedule_closed as $rule ) {
246
+
247
+ // Disable exception dates that are closed all day
248
+ if ( !empty( $rule['date'] ) && empty( $rule['time'] ) ) {
249
+ $date = new DateTime( $rule['date'] );
250
+ $disable_rules[] = array( $date->format( 'Y' ), ( $date->format( 'n' ) - 1 ), $date->format( 'j' ) );
251
+
252
+ // Enable exception dates that have opening times
253
+ } elseif ( !empty( $rule['date'] ) ) {
254
+ $date = new DateTime( $rule['date'] );
255
+ $disable_rules[] = array( $date->format( 'Y' ), ( $date->format( 'n' ) - 1 ), $date->format( 'j' ), 'inverted' );
256
+ }
257
+
258
+ }
259
+ }
260
+
261
+ return $disable_rules;
262
+
263
+ }
264
+ } // endif;
265
+
266
+ /**
267
+ * Print a form validation error
268
+ * @since 0.0.1
269
+ */
270
+ if ( !function_exists( 'rtb_print_form_error' ) ) {
271
+ function rtb_print_form_error( $field ) {
272
+
273
+ global $rtb_controller;
274
+
275
+ if ( !empty( $rtb_controller->request ) && !empty( $rtb_controller->request->validation_errors ) ) {
276
+ foreach ( $rtb_controller->request->validation_errors as $error ) {
277
+ if ( $error['field'] == $field ) {
278
+ echo '<div class="rtb-error">' . $error['message'] . '</div>';
279
+ }
280
+ }
281
+ }
282
+ }
283
+ } // endif;
lib/simple-admin-pages/README.md ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Simple Admin Pages for WordPress
2
+ ================================
3
+
4
+ Simple Admin Pages is a very small utility library to easily add new admin
5
+ pages to the WordPress admin interface. It simply collects WordPress' useful
6
+ Settings API into reuseable classes and implements a set of simple controls.
7
+
8
+ *Please note that Simple Admin Pages is still undergoing testing and development
9
+ in the wild. I don't recommend you use it in your own plugins and themes just
10
+ yet.*
11
+
12
+ ## Settings Pages Supported
13
+
14
+ - Settings sub-page
15
+ - Themes sub-page
16
+ - Submenu pages for custom menu items
17
+
18
+ ## General Controls Supported
19
+
20
+ - Text field
21
+ - Textarea field
22
+ - Checkbox (single option to enable/disable setting)
23
+ - Select dropdown with custom options
24
+ - Select dropdown of any post type
25
+ - Select dropdown of any taxonomy type
26
+ - HTML Content (for instructions, links or other inert text)
27
+
28
+ ## Controls Supported for Special Use Cases
29
+
30
+ - Business Opening Hours
31
+
32
+ ## Usage
33
+
34
+ Here's a simple example of how you can use this library to create an admin page.
35
+
36
+ ```
37
+ // Instantiate the Simple Admin Library
38
+ require_once( 'path/to/simple-admin-pages/simple-admin-pages.php' );
39
+ $sap = sap_initialize_library(
40
+ array(
41
+ 'version' => '2.0.a.1', // Version of the library
42
+ 'lib_url' => PLUGIN_URL . '/lib/simple-admin-pages/', // URL path to sap library
43
+ )
44
+ );
45
+
46
+ // Create a page for the options under the Settings (options) menu
47
+ $sap->add_page(
48
+ 'options', // Admin menu which this page should be added to
49
+ array( // Array of key/value pairs matching the AdminPage class constructor variables
50
+ 'id' => 'basic-settings',
51
+ 'title' => __( 'Page Title', SAP_TEXTDOMAIN ),
52
+ 'menu_title' => __( 'menu Title', SAP_TEXTDOMAIN ),
53
+ 'description' => '',
54
+ 'capability' => 'manage_options' // User permissions access level
55
+ )
56
+ );
57
+
58
+ // Create a basic details section
59
+ $sap->add_section(
60
+ 'basic-settings', // Page to add this section to
61
+ array( // Array of key/value pairs matching the AdminPageSection class constructor variables
62
+ 'id' => 'basic-details',
63
+ 'title' => __( 'Basic Details', SAP_TEXTDOMAIN ),
64
+ 'description' => __( 'This section includes some basic details for you to configure.', SAP_TEXTDOMAIN )
65
+ )
66
+ );
67
+
68
+ // Create the options fields
69
+ $sap->add_setting(
70
+ 'basic-settings', // Page to add this setting to
71
+ 'basic-details', // Section to add this setting to
72
+ 'select', // Type of setting (see sapLibrary::get_setting_classname()
73
+ array(
74
+ 'id' => 'select-field',
75
+ 'title' => __( 'Select Field', SAP_TEXTDOMAIN ),
76
+ 'description' => __( 'A demonstration of the select field type.', SAP_TEXTDOMAIN ),
77
+ 'options' => array(
78
+ 'one' => __( 'Option 1', SAP_TEXTDOMAIN ),
79
+ 'two' => __( 'Option 2', SAP_TEXTDOMAIN ),
80
+ 'three' => __( 'Option 3', SAP_TEXTDOMAIN )
81
+ )
82
+ )
83
+ );
84
+
85
+ // Allow third-party addons to hook into your settings page
86
+ $sap = apply_filters( 'sap_page_setup', $sap );
87
+
88
+ // Register all admin pages and settings with WordPress
89
+ $sap->add_admin_menus();
90
+ ```
91
+
92
+ Check out the documentation section below for more examples and explanation.
93
+
94
+ ## License
95
+
96
+ Simple Admin Pages is released under the GNU GPL 2 or later.
97
+
98
+ ## Requirements
99
+
100
+ Simple Admin Pages has been tested with WordPress versions 3.5 and above.
101
+
102
+ ## Roadmap
103
+
104
+ - Better documentation
105
+ - Support custom top-level admin pages
106
+ - More custom data types
107
+
108
+ ## Documentation
109
+
110
+ ### sap_initialize_library()
111
+ Instantiate the library by loading the simple-admin-pages.php file and calling sap_initialize_library. You'll do everything with the $sap object that is returned.
112
+
113
+ **args**
114
+ An array of properties to pass to the library.
115
+
116
+ *version*
117
+ (required)
118
+
119
+ This used to ensure that plugins can play well together even if they use different versions of the library. The version will convert . to _ and append that to the class names which are loaded. If version 1.0 is passed, the library will attempt to load a class named sapAdminPage_1_0.
120
+
121
+ *lib_url*
122
+ (required)
123
+
124
+ The lib_url is used to print stylesheets or scripts attached to the library.
125
+
126
+ ```
127
+ require_once( 'path/to/simple-admin-pages/simple-admin-pages.php' );
128
+ $sap = sap_initialize_library(
129
+ $args = array(
130
+ 'version' => '2.0.a.2', // Version of the library
131
+ 'lib_url' => PLUGIN_URL . '/lib/simple-admin-pages/', // URL path to sap library
132
+ )
133
+ );
134
+ ```
135
+
136
+ ### sapLibrary::add_page()
137
+ Create a new page with the library by calling the add_page() method. You can attach the page to the options (Settings) or themes (Appearance) menus or any custom menu.
138
+
139
+ **type**
140
+ (required)
141
+
142
+ What type of admin menu page to create. Accepts:
143
+
144
+ - "options" - A subpage of the Settings menu
145
+ - "themes" - A subpage of the Appearance menu
146
+ - "submenu" - A subpage of any top-level menu item
147
+
148
+ **args**
149
+
150
+ An array of properties to pass to the page.
151
+
152
+ *id*
153
+ (required)
154
+
155
+ All settings attached to this page will be stored with the page ID and can be retrieved with get_options( $page_id ).
156
+
157
+ *title*
158
+ (required)
159
+
160
+ This will be displayed at the top of the page.
161
+
162
+ *menu_title*
163
+ (required)
164
+
165
+ The title to display in the menu.
166
+
167
+ *description*
168
+ (optional)
169
+
170
+ Actually, I think this one isn't used at the moment.
171
+
172
+ *capability*
173
+ (required)
174
+
175
+ The user permissions access level (capability in WP terms) required to access and edit this page.
176
+
177
+ *default_tab*
178
+ (optional)
179
+
180
+ If your page will have multiple tabs, you need to specify a default tab to display when the page is initially loaded. This must match the ID used in the add_section() method. *Leave this parameter out if you don't need any tabs.*
181
+
182
+ ```
183
+ $sap->add_page(
184
+ $type,
185
+ $args = array(
186
+ 'id' => 'my-settings',
187
+ 'title' => __( 'Page Title', SAP_TEXTDOMAIN ),
188
+ 'menu_title' => __( 'menu Title', SAP_TEXTDOMAIN ),
189
+ 'description' => '',
190
+ 'capability' => 'manage_options'
191
+ 'default_tab' => 'tab-one',
192
+ )
193
+ );
194
+ ```
195
+
196
+ ### sapLibrary::add_section()
197
+ Create a new section to attach it to an existing page.
198
+
199
+ Sections can act as Tabs or as internal sections within the normal settings flow of a Tab or Page. In other words, you can define a section as a tab, attach a section to another section which is acting as a tab, or ignore tabs altogether to display all of your sections at once. The example below adds a tab and then adds a sub-section to that tab.
200
+
201
+ **page_id**
202
+
203
+ Page this section should be attached to. Must match the id passed in add_page().
204
+
205
+ **args**
206
+
207
+ An array of properties to pass to the section.
208
+
209
+ *id*
210
+
211
+ (required)
212
+ Unique slug to which settings will be attached.
213
+
214
+ *title*
215
+ (required)
216
+
217
+ This will be displayed at the top of the settings section.
218
+
219
+ *description*
220
+ (optional)
221
+
222
+ An optional description to display below the title.
223
+
224
+ *is_tab*
225
+ (optional)
226
+
227
+ Set this to true if this section should act like a tab.
228
+
229
+ *tab*
230
+ (optional)
231
+
232
+ Use this to attach a section to an existing tab.
233
+
234
+ #### Add a section to a page with no tabs:
235
+ ```
236
+ $sap->add_section(
237
+ $page_id,
238
+ $args = array(
239
+ 'id' => 'basic-details-section',
240
+ 'title' => __( 'Basic Details', SAP_TEXTDOMAIN ),
241
+ 'description' => __( 'This section includes some basic details for you to configure.', SAP_TEXTDOMAIN )
242
+ )
243
+ );
244
+ ```
245
+
246
+ #### Add a tab and a sub-section to a page with tabs:
247
+ ```
248
+ /**
249
+ * Create a section that acts as a tab with the is_tab parameter.
250
+ */
251
+ $sap->add_section(
252
+ $page_id,
253
+ $args = array(
254
+ 'id' => 'tab-one',
255
+ 'title' => __( 'Tab One', SAP_TEXTDOMAIN ),
256
+ 'description' => __( 'This tab includes some settings for you to configure.', SAP_TEXTDOMAIN ),
257
+ 'is_tab' => true,
258
+ )
259
+ );
260
+
261
+ /**
262
+ * Create a sub-section of the tab we just created with the tab parameter.
263
+ */
264
+ $sap->add_section(
265
+ $page_id,
266
+ $args = array(
267
+ 'id' => 'section-one-under-tab-one',
268
+ 'title' => __( 'Section One', SAP_TEXTDOMAIN ),
269
+ 'description' => __( 'This section includes some settings for you to configure.', SAP_TEXTDOMAIN ),
270
+ 'tab' => 'tab-one',
271
+ )
272
+ );
273
+ ```
274
+
275
+ ### sapLibrary::add_setting()
276
+ Create a new setting and attach to an existing section.
277
+
278
+ There are several types of settings, each with their own input arguments. I'll try to document it more in the future. For now, check out the AdminPageSetting.*.class.php files in /classes/.
279
+
280
+ **page_id**
281
+ (required)
282
+
283
+ Page this setting should be attached to. Must match the id passed in add_page().
284
+
285
+ **section_id**
286
+ (required)
287
+
288
+ Section this setting should be attached to. Must match the id passed in add_setting().
289
+
290
+ **type**
291
+ (required)
292
+
293
+ Type of setting to add. There are currently several types supported and you can extend the library with your own. I'll try to document this further. For now, you can see all the types supported by default at sapLibrary::get_setting_classname().
294
+
295
+ **args**
296
+
297
+ An array of properties to pass to the setting.
298
+
299
+ *id*
300
+ (required)
301
+
302
+ Unique slug under which the setting will be saved. You would then retrieve the setting with:
303
+
304
+ ```
305
+ $options = get_option( $page_id );
306
+ $options[$setting_id];
307
+ ```
308
+
309
+ *title*
310
+ (required)
311
+
312
+ Title of the setting. Typically acts as the field label.
313
+
314
+ *description*
315
+ (optional)
316
+
317
+ An optional description to display with the setting. Useful for instructions.
318
+
319
+ *...*
320
+
321
+ Several setting types have additional parameters. I'll try to document them further.
322
+
323
+ ```
324
+ $sap->add_setting(
325
+ $page_id,
326
+ $section_id,
327
+ $type,
328
+ array(
329
+ 'id' => 'my-first-setting',
330
+ 'title' => __( 'My First Setting', SAP_TEXTDOMAIN ),
331
+ 'description' => __( 'A demonstration of my first setting', SAP_TEXTDOMAIN );
332
+ ...
333
+ )
334
+ );
335
+ ```
336
+
337
+ ### sapLibrary::add_admin_menus()
338
+ Once everything is configured, run this method to register the pages with WordPress.
339
+
340
+ ```
341
+ // Before you run add_admin_menus, filter the whole library so that
342
+ // third-party addons can hook into your settings page to add new settings
343
+ // or adjust existing ones.
344
+ $sap = apply_filters( 'sap_page_setup', $sap );
345
+
346
+ $sap->add_admin_menus();
347
+ ```
348
+
349
+ ### Backwards Compatibility
350
+ Version 2.0 introduced changes which break backwards compatibility due to the way that the library now stores data in the database. If you are upgrading the version of this library used in your plugin or theme, you must call ```$sap->port_data(2);``` **after** you have declared all of your settings but **before** you call ```$sap->add_admin_menus();```.
351
+
352
+ *Note: to ensure all of the old options are found and ported, you shouldn't change any of the structure or ids of your settings. Just drop this method into your existing flow.*
353
+
354
+ This changes the way that your settings are stored in the database. Previously, each setting was stored as its own option. Now all the settings on a page are stored in one row.
355
+
356
+ You will need to update your plugin to retrieve the settings from their new location. If you previously accessed a setting this way:
357
+
358
+ ```
359
+ $my_setting = get_option( $my_setting_id );
360
+ ```
361
+
362
+ You should now access the setting this way:
363
+
364
+ ```
365
+ $all_page_settings = get_option( $settings_page_id );
366
+ $all_page_settings[ $my_setting_id ];
367
+ ```
368
+
369
+ ## Changelog
370
+
371
+ - 2.0.a.1 - 2014-04-03
372
+ - Save all data on a page as one row in wp_options
373
+
374
+ - 1.1 - never released
375
+ - Support themes pages
376
+ - Support submenu pages for custom menu items
377
+
378
+ - 1.0 - 2013-11-20
379
+ - Initial release
lib/simple-admin-pages/classes/AdminPage.Submenu.class.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save an settings page as a submenu item in the
5
+ * WordPress admin menu.
6
+ *
7
+ * @since 1.1
8
+ * @package Simple Admin Pages
9
+ */
10
+
11
+ class sapAdminPageSubmenu_2_0_a_1 extends sapAdminPage_2_0_a_1 {
12
+
13
+ public $setup_function = 'add_submenu_page'; // WP function to register the page
14
+
15
+ public $parent_menu = null; // Which menu to attach this submenu page to
16
+
17
+ /**
18
+ * Add the page to the appropriate menu slot.
19
+ * @since 1.0
20
+ */
21
+ public function add_admin_menu() {
22
+
23
+ // Don't register if no parent menu is specified
24
+ if ( !$this->parent_menu ) {
25
+ return;
26
+ }
27
+
28
+ call_user_func(
29
+ $this->setup_function,
30
+ $this->parent_menu,
31
+ $this->title,
32
+ $this->menu_title,
33
+ $this->capability,
34
+ $this->id,
35
+ array( $this, 'display_admin_menu' )
36
+ );
37
+ }
38
+ }
lib/simple-admin-pages/classes/AdminPage.Themes.class.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a settings page in the WordPress admin Appearance
5
+ * menu.
6
+ *
7
+ * @since 1.0
8
+ * @package Simple Admin Pages
9
+ */
10
+
11
+ class sapAdminPageThemes_2_0_a_1 extends sapAdminPage_2_0_a_1 {
12
+
13
+ public $setup_function = 'add_theme_page'; // WP function to register the page
14
+
15
+ }
lib/simple-admin-pages/classes/AdminPage.class.php ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a settings page in the WordPress admin menu.
5
+ *
6
+ * @since 1.0
7
+ * @package Simple Admin Pages
8
+ */
9
+
10
+ class sapAdminPage_2_0_a_1 {
11
+
12
+ public $title;
13
+ public $menu_title;
14
+ public $description; // optional description for this page
15
+ public $capability; // user permissions needed to edit this panel
16
+ public $id; // id of this page
17
+ public $sections = array(); // array of sections to display on this page
18
+ public $show_button = true; // whether or not to show the Save Changes button
19
+
20
+ public $setup_function = 'add_options_page'; // WP function to register the page
21
+
22
+
23
+ /**
24
+ * Initialize the page
25
+ * @since 1.0
26
+ */
27
+ public function __construct( $args ) {
28
+
29
+ // Parse the values passed
30
+ $this->parse_args( $args );
31
+
32
+ }
33
+
34
+ /**
35
+ * Parse the arguments passed in the construction and assign them to
36
+ * internal variables.
37
+ * @since 1.1
38
+ */
39
+ private function parse_args( $args ) {
40
+ foreach ( $args as $key => $val ) {
41
+ switch ( $key ) {
42
+
43
+ case 'id' :
44
+ $this->{$key} = esc_attr( $val );
45
+
46
+ default :
47
+ $this->{$key} = $val;
48
+
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Add the page to the appropriate menu slot.
55
+ * @note The default will be to post to the options page, but other classes
56
+ * should override this function.
57
+ * @since 1.0
58
+ */
59
+ public function add_admin_menu() {
60
+ call_user_func( $this->setup_function, $this->title, $this->menu_title, $this->capability, $this->id, array( $this, 'display_admin_menu' ) );
61
+ }
62
+
63
+ /**
64
+ * Add a section to the page
65
+ * @since 1.0
66
+ */
67
+ public function add_section( $section ) {
68
+
69
+ if ( !$section ) {
70
+ return;
71
+ }
72
+
73
+ $this->sections[ $section->id ] = $section;
74
+
75
+ }
76
+
77
+ /**
78
+ * Register the settings and sanitization callbacks for each setting
79
+ * @since 1.0
80
+ */
81
+ public function register_admin_menu() {
82
+
83
+ foreach ( $this->sections as $section ) {
84
+ $section->add_settings_section();
85
+
86
+ foreach ( $section->settings as $setting ) {
87
+ $setting->add_settings_field( $section->id );
88
+ }
89
+ }
90
+
91
+ register_setting( $this->id, $this->id, array( $this, 'sanitize_callback' ) );
92
+ }
93
+
94
+ /**
95
+ * Loop through the settings and sanitize the data
96
+ * @since 2.0
97
+ */
98
+ public function sanitize_callback( $value ) {
99
+
100
+ if ( empty( $_POST['_wp_http_referer'] ) ) {
101
+ return $value;
102
+ }
103
+
104
+ // Get the current page/tab so we only update those settings
105
+ parse_str( $_POST['_wp_http_referer'], $referrer );
106
+ $current_page = $this->get_current_page( $referrer );
107
+
108
+ // Use a new empty value so only values for settings that were added are
109
+ // passed to the db.
110
+ $new_value = array();
111
+
112
+ foreach ( $this->sections as $section ) {
113
+ foreach ( $section->settings as $setting ) {
114
+ if ( $setting->tab == $current_page ) {
115
+ $setting_value = isset( $value[$setting->id] ) ? $value[$setting->id] : '';
116
+ $new_value[$setting->id] = $setting->sanitize_callback_wrapper( $setting_value );
117
+ }
118
+ }
119
+ }
120
+
121
+ // Pull in the existing values so we never overwrite values that were
122
+ // on a different tab
123
+ $old_value = get_option( $this->id );
124
+
125
+ if ( is_array( $old_value ) ) {
126
+ return array_merge( $old_value, $new_value );
127
+ } else {
128
+ return $new_value;
129
+ }
130
+
131
+ }
132
+
133
+ /**
134
+ * Get the current page/tab being viewed
135
+ * @since 2.0
136
+ */
137
+ public function get_current_page( $request ) {
138
+
139
+ if ( !empty( $request['tab'] ) ) {
140
+ return $request['tab'];
141
+ } elseif ( !empty( $this->default_tab ) ) {
142
+ return $this->default_tab;
143
+ } else {
144
+ return $this->id;
145
+ }
146
+
147
+ }
148
+
149
+ /**
150
+ * Output the settings passed to this page
151
+ * @since 1.0
152
+ */
153
+ public function display_admin_menu() {
154
+
155
+ if ( !$this->title && !count( $this->settings ) ) {
156
+ return;
157
+ }
158
+
159
+ $current_page = $this->get_current_page( $_GET );
160
+
161
+ ?>
162
+
163
+ <div class="wrap">
164
+
165
+ <?php $this->display_page_title(); ?>
166
+
167
+ <?php if ( isset( $this->default_tab ) ) : ?>
168
+ <h2 class="nav-tab-wrapper">
169
+ <?php
170
+ foreach( $this->sections as $section ) {
171
+
172
+ if ( isset( $section->is_tab ) && $section->is_tab === true ) {
173
+
174
+ $tab_url = add_query_arg(
175
+ array(
176
+ 'settings-updated' => false,
177
+ 'tab' => $section->id
178
+ )
179
+ );
180
+
181
+ $active = $current_page == $section->id ? ' nav-tab-active' : '';
182
+ echo '<a href="' . esc_url( $tab_url ) . '" title="' . esc_attr( $section->title ) . '" class="nav-tab' . $active . '">';
183
+ echo esc_html( $section->title );
184
+ echo '</a>';
185
+ }
186
+ }
187
+ ?>
188
+ </h2>
189
+ <?php endif; ?>
190
+
191
+ <form method="post" action="options.php">
192
+ <?php settings_fields( $this->id ); ?>
193
+ <?php do_settings_sections( $current_page ); ?>
194
+ <?php if ( $this->show_button ) { submit_button(); } ?>
195
+ </form>
196
+ </div>
197
+
198
+ <?php
199
+ }
200
+
201
+ /**
202
+ * Output the title of the page
203
+ * @since 1.0
204
+ */
205
+ public function display_page_title() {
206
+
207
+ if ( empty( $this->title ) ) {
208
+ return;
209
+ }
210
+ ?>
211
+ <h2><?php echo $this->title; ?></h2>
212
+ <?php
213
+ }
214
+
215
+ }
lib/simple-admin-pages/classes/AdminPageSection.class.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a section on a custom admin menu
5
+ *
6
+ * @since 1.0
7
+ * @package Simple Admin Pages
8
+ */
9
+
10
+ class sapAdminPageSection_2_0_a_1 {
11
+
12
+ // Page defaults
13
+ public $id; // unique id for this section
14
+ public $title; // optional title to display above this section
15
+ public $description; // optional description of the section
16
+ public $settings = array(); // Array of settings to display in this option set
17
+
18
+ // Array to store errors
19
+ public $errors = array();
20
+
21
+ /**
22
+ * Initialize the section
23
+ * @since 1.0
24
+ */
25
+ public function __construct( $args ) {
26
+
27
+ // Parse the values passed
28
+ $this->parse_args( $args );
29
+
30
+ // Set an error if there is no id for this section
31
+ if ( !isset( $this->id ) ) {
32
+ $this->set_error(
33
+ array(
34
+ 'type' => 'missing_data',
35
+ 'data' => 'id'
36
+ )
37
+ );
38
+ }
39
+
40
+ }
41
+
42
+ /**
43
+ * Parse the arguments passed in the construction and assign them to
44
+ * internal variables.
45
+ * @since 1.0
46
+ */
47
+ private function parse_args( $args ) {
48
+ foreach ( $args as $key => $val ) {
49
+ switch ( $key ) {
50
+
51
+ case 'id' :
52
+ $this->{$key} = esc_attr( $val );
53
+
54
+ default :
55
+ $this->{$key} = $val;
56
+
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Add a setting to this section
63
+ * @since 1.0
64
+ */
65
+ public function add_setting( $setting ) {
66
+ if ( !$setting ) {
67
+ return;
68
+ }
69
+
70
+ $this->settings[ $setting->id ] = $setting;
71
+ }
72
+
73
+ /**
74
+ * Display the description for this section
75
+ * @since 1.0
76
+ */
77
+ public function display_section() {
78
+
79
+ if ( !count( $this->settings ) ) {
80
+ return;
81
+ }
82
+
83
+ if ( !empty( $this->description ) ) :
84
+ ?>
85
+
86
+ <p class="description"><?php echo $this->description; ?></p>
87
+
88
+ <?php
89
+ endif;
90
+ }
91
+
92
+ /**
93
+ * Add the settings section to the page in WordPress
94
+ * @since 1.0
95
+ */
96
+ public function add_settings_section() {
97
+ add_settings_section( $this->id, $this->title, array( $this, 'display_section' ), $this->get_page_slug() );
98
+ }
99
+
100
+ /**
101
+ * Determine the page slug to use when calling add_settings_section.
102
+ *
103
+ * Tabs should use their own ID and settings that are attached to tabs
104
+ * should use that tab's ID.
105
+ * @since 2.0
106
+ */
107
+ public function get_page_slug() {
108
+ if ( isset( $this->is_tab ) && $this->is_tab === true ) {
109
+ return $this->id;
110
+ } elseif ( isset( $this->tab ) ) {
111
+ return $this->tab;
112
+ } else {
113
+ return $this->page;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Set an error
119
+ * @since 1.0
120
+ */
121
+ public function set_error( $error ) {
122
+ $this->errors[] = array_merge(
123
+ $error,
124
+ array(
125
+ 'class' => get_class( $this ),
126
+ 'id' => $this->id,
127
+ 'backtrace' => debug_backtrace()
128
+ )
129
+ );
130
+ }
131
+
132
+ }
lib/simple-admin-pages/classes/AdminPageSetting.Editor.class.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a TinyMC Editor field setting in the admin menu
5
+ *
6
+ * @since 1.0
7
+ * @package Simple Admin Pages
8
+ */
9
+
10
+ class sapAdminPageSettingEditor_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
11
+
12
+ public $sanitize_callback = 'wp_kses_post';
13
+
14
+ /**
15
+ * List of arguments accepted by wp_editor
16
+ * @since 2.0
17
+ */
18
+ public $args = array();
19
+
20
+ /**
21
+ * wp_editor() will handle the escaping
22
+ * @since 2.0
23
+ */
24
+ public function esc_value( $val ) {
25
+ return $val;
26
+ }
27
+
28
+ /**
29
+ * Display this setting
30
+ * @since 2.0
31
+ */
32
+ public function display_setting() {
33
+
34
+ $this->args['textarea_name'] = $this->get_input_name();
35
+
36
+ $value = empty( $this->value ) && !empty( $this->default ) ? $this->default : $this->value;
37
+
38
+ wp_editor( $value, preg_replace( '/[^\da-z]/i', '', $this->id), $this->args );
39
+
40
+ $this->display_description();
41
+
42
+ }
43
+
44
+ }
lib/simple-admin-pages/classes/AdminPageSetting.HTML.class.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register and save an arbitrary HTML chunk in the admin menu
5
+ *
6
+ * This allows you to easily add in a dummy "setting" with any arbitrary HTML
7
+ * code. It's good for displaying a link to documentation, upgrades or anything
8
+ * else you can think of.
9
+ *
10
+ * Data in this field will not be saved or passed. It's purely for presenting
11
+ * information.
12
+ *
13
+ * @since 1.0
14
+ * @package Simple Admin Pages
15
+ */
16
+
17
+ class sapAdminPageSettingHTML_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
18
+
19
+ public $sanitize_callback = 'sanitize_text_field';
20
+
21
+ /**
22
+ * Display this setting
23
+ * @since 1.0
24
+ */
25
+ public function display_setting() {
26
+
27
+ echo $this->html;
28
+
29
+ $this->display_description();
30
+
31
+ }
32
+
33
+ }
lib/simple-admin-pages/classes/AdminPageSetting.OpeningHours.class.php ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a series of fields to specify the opening hours
5
+ * of a business/company.
6
+ *
7
+ * This setting accepts the following arguments in its constructor function.
8
+ *
9
+ * $args = array(
10
+ * 'id' => 'setting_id', // Unique id
11
+ * 'title' => 'My Setting', // Title or label for the setting
12
+ * 'description' => 'Description', // Help text description
13
+ * 'weekday_names' => array( // Optional array of custom
14
+ * 'monday' => 'Monday', // weekday names. These can be
15
+ * 'tuesday' => 'Tuesday', // passed in any order to
16
+ * 'wednesday' => 'Wednesday', // set a new start of the week.
17
+ * 'thursday' => 'Thursday',
18
+ * 'friday' => 'Friday',
19
+ * 'saturday' => 'Saturday',
20
+ * 'sunday' => 'Sunday'
21
+ * );
22
+ * );
23
+ *
24
+ * @since 1.0
25
+ * @package Simple Admin Pages
26
+ */
27
+
28
+ class sapAdminPageSettingOpeningHours_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
29
+
30
+ public $sanitize_callback = 'sanitize_text_field';
31
+
32
+ // Array of days of the week
33
+ public $weekdays = array(
34
+ 'monday' => 'Monday',
35
+ 'tuesday' => 'Tuesday',
36
+ 'wednesday' => 'Wednesday',
37
+ 'thursday' => 'Thursday',
38
+ 'friday' => 'Friday',
39
+ 'saturday' => 'Saturday',
40
+ 'sunday' => 'Sunday'
41
+ );
42
+
43
+ /**
44
+ * Parse the arguments passed in the construction and assign them to
45
+ * internal variables.
46
+ * @since 1.0
47
+ */
48
+ private function parse_args( $args ) {
49
+ foreach ( $args as $key => $val ) {
50
+ switch ( $key ) {
51
+
52
+ case 'id' :
53
+ $this->{$key} = esc_attr( $val );
54
+
55
+ case 'weekdays' :
56
+
57
+ $this->weekdays = $val;
58
+
59
+ default :
60
+ $this->{$key} = $val;
61
+
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Escape the value to display it in text fields and other input fields
68
+ *
69
+ * @since 1.0
70
+ */
71
+ public function esc_value( $val ) {
72
+
73
+ $value = array();
74
+
75
+ // Loop over the values and sanitize them
76
+ for ( $i = 0; $i < 7; $i++ ) {
77
+ $value[$i]['day'] = isset( $val[$i] ) && isset( $val[$i]['day'] ) ? esc_attr( $val[$i]['day'] ) : '';
78
+ $value[$i]['hours'] = isset( $val[$i] ) && isset( $val[$i]['hours'] ) ? esc_attr( $val[$i]['hours'] ) : '';
79
+ }
80
+
81
+ return $value;
82
+ }
83
+
84
+ /**
85
+ * Get a day's display name
86
+ * @since 1.0
87
+ */
88
+ private function get_day_name( $day ) {
89
+ foreach ( $this->weekdays as $id => $name ) {
90
+ if ( $day == $id ) {
91
+ return $name;
92
+ }
93
+ }
94
+
95
+ return '';
96
+ }
97
+
98
+ /**
99
+ * Display this setting
100
+ * @since 1.0
101
+ * @todo integrate time picker
102
+ */
103
+ public function display_setting() {
104
+
105
+ $this->display_description();
106
+
107
+ for ($i = 0; $i < 7; $i++) {
108
+
109
+ ?>
110
+
111
+ <table class="sap-opening-hours">
112
+ <tr>
113
+ <td>
114
+ <input type="hidden" id="sap-opening-hours-day-<?php echo $i; ?>-name" name="<?php echo $this->get_input_name(); ?>[<?php echo $i; ?>][day_name]" value="<?php echo esc_attr( $this->get_day_name( $this->value[$i]['day'] ) ); ?>">
115
+ <select name="<?php echo $this->get_input_name(); ?>[<?php echo $i; ?>][day]" id="<?php echo $this->id . '-' . $i; ?>-day" class="sap-opening-hours-day" data-target="#sap-opening-hours-day-<?php echo $i; ?>-name">
116
+ <option value=""></option>
117
+
118
+ <?php foreach ( $this->weekdays as $id => $name ) : ?>
119
+
120
+ <option value="<?php echo $id; ?>" data-name="<?php echo esc_attr( $name ); ?>"<?php if ( $this->value[$i]['day'] == $id ) : ?> selected<?php endif; ?>>
121
+ <?php echo $name; ?>
122
+ </option>
123
+
124
+ <?php endforeach; ?>
125
+
126
+ </select>
127
+ </td>
128
+ <td>
129
+ <input name="<?php echo $this->get_input_name(); ?>[<?php echo $i; ?>][hours]" type="text" id="<?php echo $this->id . '-' . $i; ?>-hours" value="<?php echo $this->value[$i]['hours']; ?>" class="regular-text sap-opening-hours-hours" />
130
+ </td>
131
+ </tr>
132
+ </table>
133
+
134
+ <?php
135
+
136
+ }
137
+
138
+ }
139
+
140
+ /**
141
+ * Sanitize the array of text inputs for this setting
142
+ * @since 1.0
143
+ */
144
+ public function sanitize_callback_wrapper( $values ) {
145
+
146
+ // If no sanitization callback exists, don't register the setting.
147
+ if ( !isset( $this->sanitize_callback ) || !trim( $this->sanitize_callback ) ) {
148
+ return;
149
+ }
150
+
151
+ // If this isn't an array, just sanitize it as a string
152
+ if (!is_array( $values ) ) {
153
+ return call_user_func( $this->sanitize_callback, $values );
154
+ }
155
+
156
+ // Loop over the values and sanitize them
157
+ for ( $i = 0; $i < 7; $i++ ) {
158
+ if ( isset( $values[ $i ] ) && is_array( $values[ $i ] ) ) {
159
+ $values[$i]['day'] = call_user_func( $this->sanitize_callback, $values[$i]['day'] );
160
+ $values[$i]['hours'] = call_user_func( $this->sanitize_callback, $values[$i]['hours'] );
161
+ }
162
+ }
163
+
164
+ return $values;
165
+ }
166
+
167
+ }
lib/simple-admin-pages/classes/AdminPageSetting.Scheduler.class.php ADDED
@@ -0,0 +1,596 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a schedule of dates and times.
5
+ *
6
+ * This is designed for use for opening hours, a booking schedule or anything
7
+ * that requires recurring dates and times.
8
+ *
9
+ * @since 2.0
10
+ * @package Simple Admin Pages
11
+ */
12
+
13
+ class sapAdminPageSettingScheduler_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
14
+
15
+ public $sanitize_callback = 'sanitize_text_field';
16
+
17
+ public $weekdays = array(
18
+ 'monday' => 'Mo',
19
+ 'tuesday' => 'Tu',
20
+ 'wednesday' => 'We',
21
+ 'thursday' => 'Th',
22
+ 'friday' => 'Fr',
23
+ 'saturday' => 'Sa',
24
+ 'sunday' => 'Su',
25
+ );
26
+
27
+ public $weeks = array(
28
+ 'first' => '1st',
29
+ 'second' => '2nd',
30
+ 'third' => '3rd',
31
+ 'fourth' => '4th',
32
+ 'last' => 'last',
33
+ );
34
+
35
+ /**
36
+ * Number of minutes between time selection intervals
37
+ */
38
+ public $time_interval = 15;
39
+
40
+ /**
41
+ * Display format for time selection
42
+ * See http://amsul.ca/pickadate.js/ for formatting options
43
+ */
44
+ public $time_format = 'h:i A';
45
+
46
+ /**
47
+ * Display format for date selection
48
+ * See http://amsul.ca/pickadate.js/ for formatting options
49
+ */
50
+ public $date_format = 'd mmmm, yyyy';
51
+
52
+ /**
53
+ * Boolean to disable the weekday selection option
54
+ */
55
+ public $disable_weekdays = false;
56
+
57
+ /**
58
+ * Boolean to disable the weeks selection option
59
+ */
60
+ public $disable_weeks = false;
61
+
62
+ /**
63
+ * Boolean to disable the date selection option
64
+ */
65
+ public $disable_date = false;
66
+
67
+ /**
68
+ * Boolean to disable the time selection option
69
+ */
70
+ public $disable_time = false;
71
+
72
+ /**
73
+ * Boolean to disable the end time selection option
74
+ */
75
+ public $disable_end_time = false;
76
+
77
+ /**
78
+ * Array containing strings for schedule summaries. See the default
79
+ * summaries at set_schedule_summaries(). Use this to overwrite the defaults
80
+ * with anything more useful to this instance.
81
+ */
82
+ public $instance_schedule_summaries;
83
+
84
+ /**
85
+ * Escape the value to display it in text fields and other input fields
86
+ * @since 2.0
87
+ */
88
+ public function esc_value( $val ) {
89
+
90
+ $value = array();
91
+
92
+ if ( empty( $val ) ) {
93
+ return $value;
94
+ }
95
+
96
+ foreach ( $val as $i => $rule ) {
97
+
98
+ if ( !empty( $rule['weekdays'] ) ) {
99
+ $value[$i]['weekdays'] = array();
100
+ foreach ( $rule['weekdays'] as $day => $flag ) {
101
+ if ( $flag !== '1' ) {
102
+ continue;
103
+ }
104
+
105
+ $value[$i]['weekdays'][$day] = $flag;
106
+ }
107
+ }
108
+
109
+ if ( !empty( $rule['weeks'] ) ) {
110
+ $value[$i]['weeks'] = array();
111
+ foreach ( $rule['weeks'] as $week => $flag ) {
112
+ if ( $flag !== '1' ) {
113
+ continue;
114
+ }
115
+
116
+ $value[$i]['weeks'][$week] = $flag;
117
+ }
118
+ }
119
+
120
+ if ( !empty( $rule['date'] ) ) {
121
+ $value[$i]['date'] = esc_attr( $rule['date'] );
122
+ }
123
+
124
+ if ( !empty( $rule['time']['start'] ) ) {
125
+ $value[$i]['time']['start'] = esc_attr( $rule['time']['start'] );
126
+ }
127
+ if ( !empty( $rule['time']['end'] ) ) {
128
+ $value[$i]['time']['end'] = esc_attr( $rule['time']['end'] );
129
+ }
130
+ }
131
+
132
+ return $value;
133
+ }
134
+
135
+ /**
136
+ * Compile and pass configurable variables to the javascript file, so they
137
+ * can be used when we initialize the pickadate components
138
+ * @since 2.0
139
+ */
140
+ public function pass_to_scripts() {
141
+
142
+ // Create a global variable containing settings for all schedulers
143
+ // that are being rendered on the page. This allows us to pass different
144
+ // settings for different schedulers on the same page.
145
+ global $sap_scheduler_settings;
146
+
147
+ if ( !isset( $sap_scheduler_settings ) ) {
148
+ $sap_scheduler_settings = array();
149
+ }
150
+
151
+ $sap_scheduler_settings[ $this->id ] = array(
152
+ 'time_interval' => $this->time_interval,
153
+ 'time_format' => $this->time_format,
154
+ 'date_format' => $this->date_format,
155
+ 'template' => $this->get_template(),
156
+ 'weekdays' => $this->weekdays,
157
+ 'weeks' => $this->weeks,
158
+ 'disable_weekdays' => $this->disable_weekdays,
159
+ 'disable_weeks' => $this->disable_weeks,
160
+ 'disable_date' => $this->disable_date,
161
+ 'disable_time' => $this->disable_time,
162
+ 'summaries' => $this->schedule_summaries,
163
+ );
164
+
165
+ // This gets called multiple times, but only the last call is actually
166
+ // pushed to the script.
167
+ wp_localize_script(
168
+ 'sap-admin-script',
169
+ 'sap_scheduler',
170
+ array(
171
+ 'settings' => $sap_scheduler_settings
172
+ )
173
+ );
174
+
175
+ }
176
+
177
+ /**
178
+ * Display this setting
179
+ * @since 2.0
180
+ */
181
+ public function display_setting() {
182
+
183
+ $this->display_description();
184
+
185
+ // Define summary text to use when a rule is displayed in brief
186
+ $this->set_schedule_summaries();
187
+
188
+ // Pass data to the script files to handle js interactions
189
+ $this->pass_to_scripts();
190
+
191
+ ?>
192
+
193
+ <div class="sap-scheduler" id="<?php echo $this->id; ?>">
194
+ <?php
195
+ foreach ( $this->value as $id => $rule ) {
196
+ echo $this->get_template( $id, $rule, true );
197
+ }
198
+ ?>
199
+ </div>
200
+ <div class="sap-add-scheduler">
201
+ <a href="#" class="button">
202
+ <?php _e( 'Add new scheduling rule', SAP_TEXTDOMAIN ); ?>
203
+ </a>
204
+ </div>
205
+
206
+ <?php
207
+ }
208
+
209
+ /**
210
+ * Retrieve the template for a scheduling rule
211
+ * @since 2.0
212
+ */
213
+ public function get_template( $id = 0, $values = array(), $list = false ) {
214
+
215
+ $date_format = $this->get_date_format( $values );
216
+ $time_format = $this->get_time_format( $values );
217
+
218
+ ob_start();
219
+ ?>
220
+
221
+ <div class="sap-scheduler-rule clearfix<?php echo $list ? ' list' : ''; ?>">
222
+ <div class="sap-scheduler-date <?php echo $date_format; echo $this->disable_time === true ? ' full-width' : ''; ?>">
223
+ <ul class="sap-selector">
224
+
225
+ <?php if ( !$this->has_multiple_date_formats() ) : ?>
226
+ <li>
227
+ <div class="dashicons dashicons-calendar"></div>
228
+ <?php if ( $date_format == 'weekly' ) : ?>
229
+ <?php _ex( 'Weekly', 'Format of a scheduling rule', SAP_TEXTDOMAIN ); ?>
230
+ <?php elseif ( $date_format == 'monthly' ) : ?>
231
+ <?php _ex( 'Monthly', 'Format of a scheduling rule', SAP_TEXTDOMAIN ); ?>
232
+ <?php elseif ( $date_format == 'date' ) : ?>
233
+ <?php _ex( 'Date', 'Format of a scheduling rule', SAP_TEXTDOMAIN ); ?>
234
+ <?php endif; ?>
235
+ </li>
236
+ <?php else : ?>
237
+
238
+ <?php if ( $this->disable_weekdays === false ) : ?>
239
+ <li>
240
+ <div class="dashicons dashicons-calendar"></div>
241
+ <a href="#" data-format="weekly"<?php echo $date_format == 'weekly' ? ' class="selected"' : ''; ?>>
242
+ <?php _ex( 'Weekly', 'Format of a scheduling rule', SAP_TEXTDOMAIN ); ?>
243
+ </a>
244
+ </li>
245
+ <?php endif; ?>
246
+
247
+ <?php if ( $this->disable_weeks === false ) : ?>
248
+ <li>
249
+ <a href="#" data-format="monthly"<?php echo $date_format == 'monthly' ? ' class="selected"' : ''; ?>>
250
+ <?php _ex( 'Monthly', 'Format of a scheduling rule', SAP_TEXTDOMAIN ); ?>
251
+ </a>
252
+ </li>
253
+ <?php endif; ?>
254
+
255
+ <?php if ( $this->disable_date === false ) : ?>
256
+ <li>
257
+ <a href="#" data-format="date"<?php echo $date_format == 'date' ? ' class="selected"' : ''; ?>>
258
+ <?php _ex( 'Date', 'Format of a scheduling rule', SAP_TEXTDOMAIN ); ?>
259
+ </a>
260
+ </li>
261
+ <?php endif; ?>
262
+
263
+ <?php endif; ?>
264
+ </ul>
265
+
266
+ <?php if ( $this->disable_weekdays === false ) : ?>
267
+ <ul class="sap-scheduler-weekdays">
268
+ <li class="label">
269
+ <?php _ex( 'Days of the week', 'Label for selecting days of the week in a scheduling rule', SAP_TEXTDOMAIN ); ?>
270
+ </li>
271
+ <?php
272
+ foreach ( $this->weekdays as $slug => $label ) :
273
+ $input_name = $this->get_input_name() . '[' . $id . '][weekdays][' . esc_attr( $slug ) . ']';
274
+ ?>
275
+ <li>
276
+ &nbsp;<input type="checkbox" name="<?php echo $input_name; ?>" id="<?php echo $input_name; ?>" value="1"<?php echo empty( $values['weekdays'][$slug] ) ? '' : ' checked="checked"'; ?> data-day="<?php echo esc_attr( $slug ); ?>"><label for="<?php echo $input_name; ?>"><?php echo ucfirst( $label ); ?></label>
277
+ </li>
278
+ <?php endforeach; ?>
279
+ </ul>
280
+ <?php endif; ?>
281
+
282
+ <?php if ( $this->disable_weeks === false ) : ?>
283
+ <ul class="sap-scheduler-weeks">
284
+ <li class="label">
285
+ <?php _ex( 'Weeks of the month', 'Label for selecting weeks of the month in a scheduling rule', SAP_TEXTDOMAIN ); ?>
286
+ </li>
287
+ <?php
288
+ foreach ( $this->weeks as $slug => $label ) :
289
+ $input_name = $this->get_input_name() . '[' . $id . '][weeks][' . esc_attr( $slug ) . ']';
290
+ ?>
291
+ <li>
292
+ &nbsp;<input type="checkbox" name="<?php echo $input_name; ?>" id="<?php echo $input_name; ?>" value="1"<?php echo empty( $values['weeks'][$slug] ) ? '' : ' checked="checked"'; ?> data-week="<?php echo esc_attr( $slug ); ?>"><label for="<?php echo $input_name; ?>"><?php echo ucfirst( $label ); ?></label>
293
+ </li>
294
+ <?php endforeach; ?>
295
+ </ul>
296
+ <?php endif; ?>
297
+
298
+ <?php if ( $this->disable_date === false ) : ?>
299
+ <div class="sap-scheduler-date-input">
300
+ <label for="<?php echo $this->get_input_name(); ?>[<?php echo $id; ?>][date]">
301
+ <?php _e( 'Date', SAP_TEXTDOMAIN ); ?>
302
+ </label>
303
+ <input type="text" name="<?php echo $this->get_input_name(); ?>[<?php echo $id; ?>][date]" id="<?php echo $this->get_input_name(); ?>[<?php echo $id; ?>][date]" value="<?php echo empty( $values['date'] ) ? '' : $values['date']; ?>">
304
+ </div>
305
+ <?php endif; ?>
306
+
307
+ </div>
308
+
309
+ <?php if ( $this->disable_time === false ) : ?>
310
+ <div class="sap-scheduler-time <?php echo $time_format; ?>">
311
+
312
+ <ul class="sap-selector">
313
+ <li>
314
+ <div class="dashicons dashicons-clock"></div>
315
+ <a href="#" data-format="time-slot"<?php echo $time_format == 'time-slot' ? ' class="selected"' : ''; ?>>
316
+ <?php _ex( 'Time', 'Label to select time slot for a scheduling rule', SAP_TEXTDOMAIN ); ?>
317
+ </a>
318
+ </li>
319
+ <li>
320
+ <a href="#" data-format="all-day"<?php echo $time_format == 'all-day' ? ' class="selected"' : ''; ?>>
321
+ <?php _ex( 'All day', 'Label to set a scheduling rule to last all day', SAP_TEXTDOMAIN ); ?>
322
+ </a>
323
+ </li>
324
+ </ul>
325
+
326
+ <div class="sap-scheduler-time-input clearfix">
327
+
328
+ <div class="start">
329
+ <label for="<?php echo $this->get_input_name(); ?>[<?php echo $id; ?>][time][start]">
330
+ <?php _ex( 'Start', 'Label for the starting time of a scheduling rule', SAP_TEXTDOMAIN ); ?>
331
+ </label>
332
+ <input type="text" name="<?php echo $this->get_input_name() . '[' . $id . '][time][start]'; ?>" id="<?php echo $this->get_input_name() . '[' . $id . '][time][start]'; ?>" value="<?php echo empty( $values['time']['start'] ) ? '' : $values['time']['start']; ?>">
333
+ </div>
334
+
335
+ <?php if ( $this->disable_end_time === false ) : ?>
336
+ <div class="end">
337
+ <label for="<?php echo $this->get_input_name(); ?>[<?php echo $id; ?>][time][end]">
338
+ <?php _ex( 'End', 'Label for the ending time of a scheduling rule', SAP_TEXTDOMAIN ); ?>
339
+ </label>
340
+ <input type="text" name="<?php echo $this->get_input_name() . '[' . $id . '][time][end]'; ?>" id="<?php echo $this->get_input_name() . '[' . $id . '][time][end]'; ?>" value="<?php echo empty( $values['time']['end'] ) ? '' : $values['time']['end']; ?>">
341
+ </div>
342
+ <?php endif; ?>
343
+
344
+ </div>
345
+
346
+ <div class="sap-scheduler-all-day">
347
+ <?php _ex( 'All day long. Want to <a href="#" data-format="time-slot">set a time slot</a>?', 'Prompt displayed when a scheduling rule is set without any time restrictions.', SAP_TEXTDOMAIN ); ?>
348
+ </div>
349
+
350
+ </div>
351
+ <?php endif; ?>
352
+
353
+ <div class="sap-scheduler-brief">
354
+ <div class="date">
355
+ <div class="dashicons dashicons-calendar"></div>
356
+ <span class="value"><?php echo $this->get_date_summary( $values ); ?></span>
357
+ </div>
358
+ <?php if ( $this->disable_time === false ) : ?>
359
+ <div class="time">
360
+ <div class="dashicons dashicons-clock"></div>
361
+ <span class="value"><?php echo $this->get_time_summary( $values ); ?></span>
362
+ </div>
363
+ <?php endif; ?>
364
+ </div>
365
+ <div class="sap-scheduler-control">
366
+ <a href="#" class="toggle" title="<?php _e( 'Open and close this rule', SAP_TEXTDOMAIN ); ?>">
367
+ <div class="dashicons dashicons-<?php echo $list ? 'edit' : 'arrow-up-alt2'; ?>"></div>
368
+ <span class="screen-reader-text">
369
+ <?php _e( 'Open and close this rule', SAP_TEXTDOMAIN ); ?>
370
+ </span>
371
+ </a>
372
+ <a href="#" class="delete" title="<?php _e( 'Delete rule', SAP_TEXTDOMAIN ); ?>">
373
+ <div class="dashicons dashicons-dismiss"></div>
374
+ <span class="screen-reader-text">
375
+ <?php _e( 'Delete scheduling rule', SAP_TEXTDOMAIN ); ?>
376
+ </span>
377
+ </a>
378
+ </div>
379
+ </div>
380
+
381
+ <?php
382
+ $output = ob_get_clean();
383
+
384
+ return $output;
385
+ }
386
+
387
+ /**
388
+ * Determine the date format of a rule (weeky/monthly/date)
389
+ * @since 2.0
390
+ */
391
+ public function get_date_format( $values ) {
392
+
393
+ if ( !empty( $values['date'] ) ) {
394
+ return 'date';
395
+ } elseif ( !empty( $values['weeks'] ) ) {
396
+ return 'monthly';
397
+ } elseif ( !empty( $values['weekdays'] ) ) {
398
+ return 'weekly';
399
+ }
400
+
401
+ if ( $this->disable_weekdays === false ) {
402
+ return 'weekly';
403
+ }
404
+ if ( $this->disable_weeks === false ) {
405
+ return 'monthly';
406
+ }
407
+ if ( $this->disable_date === false ) {
408
+ return 'date';
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Determine the time format of a rule (time-slot/all-day)
414
+ * @since 2.0
415
+ */
416
+ public function get_time_format( $values ) {
417
+ if ( empty( $values['time']['start'] ) && empty( $values['time']['end'] ) ) {
418
+ return 'all-day';
419
+ }
420
+
421
+ return 'time-slot';
422
+ }
423
+
424
+ /**
425
+ * Determine if multiple date formats are enabled
426
+ * @since 2.0
427
+ */
428
+ public function has_multiple_date_formats() {
429
+ $i = 0;
430
+ if ( $this->disable_weekdays === false ) {
431
+ $i++;
432
+ }
433
+ if ( $this->disable_weeks === false ) {
434
+ $i++;
435
+ }
436
+ if ( $this->disable_date === false ) {
437
+ $i++;
438
+ }
439
+
440
+ if ( $i > 1 ) {
441
+ return true;
442
+ } else {
443
+ return false;
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Set some default summary strings that can be used when the scheduler
449
+ * rule is shown in brief
450
+ * @since 2.0
451
+ */
452
+ public function set_schedule_summaries() {
453
+
454
+ if ( !empty( $this->schedule_summaries ) ) {
455
+ return;
456
+ }
457
+
458
+ $this->schedule_summaries = array(
459
+ 'never' => _x( 'Never', 'Brief default description of a scheduling rule when no weekdays or weeks are included in the rule.', SAP_TEXTDOMAIN ),
460
+ 'weekly_always' => _x( 'Every day', 'Brief default description of a scheduling rule when all the weekdays/weeks are included in the rule.', SAP_TEXTDOMAIN ),
461
+ 'monthly_weekdays' => _x( '{days} on the {weeks} week of the month', 'Brief default description of a scheduling rule when some weekdays are included on only some weeks of the month. The {days} and {weeks} bits should be left alone and will be replaced by a comma-separated list of days (the first one) and weeks (the second one) in the following format: M, T, W on the first, second week of the month', SAP_TEXTDOMAIN ),
462
+ 'monthly_weeks' => _x( '{weeks} week of the month', 'Brief description of a scheduling rule when some weeks of the month are included but all or no weekdays are selected. {weeks} should be left alone and will be replaced by a comma-separated list of weeks (the second one) in the following format: First, second week of the month', SAP_TEXTDOMAIN ),
463
+ 'all_day' => _x( 'All day', 'Brief default description of a scheduling rule when no times are set', SAP_TEXTDOMAIN ),
464
+ '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.', SAP_TEXTDOMAIN ),
465
+ '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.', SAP_TEXTDOMAIN ),
466
+ 'separator' => _x( '&mdash;', 'Default separator between times of a scheduling rule.', SAP_TEXTDOMAIN ),
467
+ );
468
+
469
+ if ( !empty( $this->instance_schedule_summaries ) ) {
470
+ $this->schedule_summaries = array_merge( $this->schedule_summaries, $this->instance_schedule_summaries );
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Print the date phrase, a brief description of the date settings
476
+ * @since 2.0
477
+ */
478
+ public function get_date_summary( $values = array() ) {
479
+
480
+ if ( !empty( $values['date'] ) ) {
481
+ return $values['date'];
482
+ }
483
+
484
+ if ( empty( $values['weekdays'] ) && $this->disable_weekdays === false ) {
485
+ return $this->schedule_summaries['never'];
486
+ }
487
+
488
+ if ( empty( $values['weekdays'] ) ) {
489
+ $weekdays = '';
490
+ } elseif ( count( $values['weekdays'] ) == 7 ) {
491
+ $weekdays = $this->schedule_summaries['weekly_always'];
492
+ } else {
493
+ $arr = array();
494
+ foreach ( $values['weekdays'] as $weekday => $state ) {
495
+ $arr[] = $this->weekdays[$weekday];
496
+ }
497
+ $weekdays = join( ', ', $arr );
498
+ }
499
+
500
+ if ( ( empty( $values['weeks'] ) || count( $values['weeks'] ) == 5 ) && $this->disable_weekdays === false ) {
501
+ return $weekdays;
502
+ }
503
+
504
+ if ( empty( $values['weeks'] ) ) {
505
+ return $this->schedule_summaries['never'];
506
+ }
507
+
508
+ $arr = array();
509
+ foreach ( $values['weeks'] as $weeks => $state ) {
510
+ $arr[] = $this->weeks[$weeks];
511
+ }
512
+ $weeks = join( ', ', $arr );
513
+
514
+ if ( !empty( $weekdays ) ) {
515
+ return str_replace( array( '{days}', '{weeks}' ), array( $weekdays, $weeks ), $this->schedule_summaries['monthly_weekdays'] );
516
+ } else {
517
+ return str_replace( '{weeks}', ucfirst( $weeks ), $this->schedule_summaries['monthly_weeks'] );
518
+ }
519
+
520
+ }
521
+
522
+ /**
523
+ * Print the time phrase, a brief description of the time settings
524
+ * @since 2.0
525
+ */
526
+ public function get_time_summary( $values = array() ) {
527
+
528
+ if ( empty( $values['time']['start'] ) && empty( $values['time']['end'] ) ) {
529
+ return $this->schedule_summaries['all_day'];
530
+ }
531
+
532
+ if ( empty( $values['time']['start'] ) ) {
533
+ return $this->schedule_summaries['before'] . ' ' . $values['time']['end'];
534
+ }
535
+
536
+ if ( empty( $values['time']['end'] ) ) {
537
+ return $this->schedule_summaries['after'] . ' ' . $values['time']['start'];
538
+ }
539
+
540
+ return $values['time']['start'] . $this->schedule_summaries['separator'] . $values['time']['end'];
541
+
542
+ }
543
+
544
+ /**
545
+ * Sanitize the array of text inputs for this setting
546
+ * @since 2.0
547
+ */
548
+ public function sanitize_callback_wrapper( $values ) {
549
+
550
+ $output = array();
551
+
552
+ foreach ( $values as $i => $rule ) {
553
+
554
+ if ( !empty( $rule['weekdays'] ) ) {
555
+ $output[$i]['weekdays'] = array();
556
+ foreach ( $rule['weekdays'] as $day => $flag ) {
557
+ if ( $flag !== '1' ||
558
+ ( $day !== 'monday' && $day !== 'tuesday' && $day !== 'wednesday' && $day !== 'thursday' && $day !== 'friday' && $day !== 'saturday' && $day !== 'sunday' ) ) {
559
+ continue;
560
+ }
561
+
562
+ $output[$i]['weekdays'][$day] = $flag;
563
+ }
564
+ }
565
+
566
+ if ( !empty( $rule['weeks'] ) ) {
567
+ $output[$i]['weeks'] = array();
568
+ foreach ( $rule['weeks'] as $week => $flag ) {
569
+ if ( $flag !== '1' ||
570
+ ( $week !== 'first' && $week !== 'second' && $week !== 'third' && $week !== 'fourth' && $week !== 'last' ) ) {
571
+ continue;
572
+ }
573
+
574
+ $output[$i]['weeks'][$week] = $flag;
575
+ }
576
+ }
577
+
578
+ if ( !empty( $rule['date'] ) ) {
579
+ $date = new DateTime( $rule['date'] );
580
+ if ( checkdate( $date->format( 'n' ), $date->format( 'j' ), $date->format( 'Y' ) ) ) {
581
+ $output[$i]['date'] = call_user_func( $this->sanitize_callback, $rule['date'] );
582
+ }
583
+ }
584
+
585
+ if ( !empty( $rule['time']['start'] ) ) {
586
+ $output[$i]['time']['start'] = call_user_func( $this->sanitize_callback, $rule['time']['start'] );
587
+ }
588
+ if ( !empty( $rule['time']['end'] ) ) {
589
+ $output[$i]['time']['end'] = call_user_func( $this->sanitize_callback, $rule['time']['end'] );
590
+ }
591
+ }
592
+
593
+ return $output;
594
+ }
595
+
596
+ }
lib/simple-admin-pages/classes/AdminPageSetting.Select.class.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a selection option with a drop-down menu.
5
+ *
6
+ * This setting accepts the following arguments in its constructor function.
7
+ *
8
+ * $args = array(
9
+ * 'id' => 'setting_id', // Unique id
10
+ * 'title' => 'My Setting', // Title or label for the setting
11
+ * 'description' => 'Description', // Help text description
12
+ * 'blank_option' => true, // Whether or not to show a blank option
13
+ * 'options' => array( // An array of key/value pairs which
14
+ * 'option1' => 'Option 1', // define the options.
15
+ * 'option2' => 'Option 2',
16
+ * ...
17
+ * );
18
+ * );
19
+ *
20
+ * @since 1.0
21
+ * @package Simple Admin Pages
22
+ */
23
+
24
+ class sapAdminPageSettingSelect_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
25
+
26
+ public $sanitize_callback = 'sanitize_text_field';
27
+
28
+ // Whether or not to display a blank option
29
+ public $blank_option = true;
30
+
31
+ // An array of options for this select field, accepted as a key/value pair.
32
+ public $options = array();
33
+
34
+ /**
35
+ * Display this setting
36
+ * @since 1.0
37
+ */
38
+ public function display_setting() {
39
+
40
+ ?>
41
+
42
+ <select name="<?php echo $this->get_input_name(); ?>" id="<?php echo $this->id; ?>">
43
+
44
+ <?php if ( $this->blank_option === true ) : ?>
45
+ <option></option>
46
+ <?php endif; ?>
47
+
48
+ <?php foreach ( $this->options as $id => $title ) : ?>
49
+ <option value="<?php echo esc_attr( $id ); ?>"<?php if( $this->value == $id ) : ?> selected="selected"<?php endif; ?>><?php echo esc_html( $title ); ?></option>
50
+ <?php endforeach; ?>
51
+
52
+ </select>
53
+
54
+ <?php
55
+
56
+ $this->display_description();
57
+
58
+ }
59
+
60
+ }
lib/simple-admin-pages/classes/AdminPageSetting.SelectPost.class.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a selection with a drop-down list of any post type
5
+ *
6
+ * This setting accepts the following arguments in its constructor function.
7
+ *
8
+ * $args = array(
9
+ * 'id' => 'setting_id', // Unique id
10
+ * 'title' => 'My Setting', // Title or label for the setting
11
+ * 'description' => 'Description', // Help text description
12
+ * 'blank_option' => true, // Whether or not to show a blank option
13
+ * 'args' => array(); // Arguments to pass to WordPress's get_post() function
14
+ * );
15
+ *
16
+ * @since 1.0
17
+ * @package Simple Admin Pages
18
+ */
19
+
20
+ class sapAdminPageSettingSelectPost_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
21
+
22
+ public $sanitize_callback = 'intval';
23
+
24
+ // Whether or not to display a blank option
25
+ public $blank_option = true;
26
+
27
+ /**
28
+ * An array of arguments accepted by get_posts().
29
+ * See: http://codex.wordpress.org/Template_Tags/get_posts
30
+ */
31
+ public $args = array();
32
+
33
+ /**
34
+ * Display this setting
35
+ * @since 1.0
36
+ */
37
+ public function display_setting() {
38
+
39
+ $posts = get_posts( $this->args );
40
+
41
+ ?>
42
+
43
+ <select name="<?php echo $this->get_input_name(); ?>" id="<?php echo $this->get_input_name(); ?>">
44
+
45
+ <?php if ( $this->blank_option === true ) : ?>
46
+ <option></option>
47
+ <?php endif; ?>
48
+
49
+ <?php foreach ( $posts as $post ) : ?>
50
+ <option value="<?php echo esc_attr( $post->ID ); ?>"<?php if( $this->value == $post->ID ) : ?> selected="selected"<?php endif; ?>><?php echo esc_html( $post->post_title ); ?></option>
51
+ <?php endforeach; ?>
52
+
53
+ </select>
54
+
55
+ <?php
56
+
57
+ $this->display_description();
58
+
59
+ }
60
+
61
+ }
lib/simple-admin-pages/classes/AdminPageSetting.SelectTaxonomy.class.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a selection with a drop-down list of any taxonomy
5
+ *
6
+ * This setting accepts the following arguments in its constructor function.
7
+ *
8
+ * $args = array(
9
+ * 'id' => 'setting_id', // Unique id
10
+ * 'title' => 'My Setting', // Title or label for the setting
11
+ * 'description' => 'Description', // Help text description
12
+ * 'taxonomies' => array(); // Array of taxonomies to fetch (required)
13
+ * 'blank_option' => true, // Whether or not to show a blank option
14
+ * 'args' => array(); // Arguments to pass to WordPress's get_terms() function
15
+ * );
16
+ * type
17
+ *
18
+ * @since 1.0
19
+ * @package Simple Admin Pages
20
+ */
21
+
22
+ class sapAdminPageSettingSelectTaxonomy_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
23
+
24
+ public $sanitize_callback = 'intval';
25
+
26
+ // Whether or not to display a blank option
27
+ public $blank_option = true;
28
+
29
+ // Arrays of taxonomies to fetch (required)
30
+ public $taxonomies;
31
+
32
+ /**
33
+ * Array of options accepted by get_terms()
34
+ * See: http://codex.wordpress.org/Function_Reference/get_terms
35
+ */
36
+ public $args = array();
37
+
38
+ /**
39
+ * Display this setting
40
+ * @since 1.0
41
+ */
42
+ public function display_setting() {
43
+
44
+ $terms = get_terms( $this->taxonomies, $this->args );
45
+
46
+ ?>
47
+
48
+ <select name="<?php echo $this->get_input_name(); ?>" id="<?php echo $this->get_input_name(); ?>">
49
+
50
+ <?php if ( $this->blank_option === true ) : ?>
51
+ <option></option>
52
+ <?php endif; ?>
53
+
54
+ <?php foreach ( $terms as $term ) : ?>
55
+ <option value="<?php echo esc_attr( $term->term_id ); ?>"<?php if( $this->value == $term->term_id ) : ?> selected="selected"<?php endif; ?>><?php echo esc_html( $term->name ); ?></option>
56
+ <?php endforeach; ?>
57
+
58
+ </select>
59
+
60
+ <?php
61
+
62
+ $this->display_description();
63
+
64
+ }
65
+
66
+ }
lib/simple-admin-pages/classes/AdminPageSetting.Text.class.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a text field setting in the admin menu
5
+ *
6
+ * @since 1.0
7
+ * @package Simple Admin Pages
8
+ */
9
+
10
+ class sapAdminPageSettingText_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
11
+
12
+ public $sanitize_callback = 'sanitize_text_field';
13
+
14
+ /**
15
+ * Placeholder string for the input field
16
+ * @since 2.0
17
+ */
18
+ public $placeholder = '';
19
+
20
+ /**
21
+ * Display this setting
22
+ * @since 1.0
23
+ */
24
+ public function display_setting() {
25
+ ?>
26
+
27
+ <input name="<?php echo $this->get_input_name(); ?>" type="text" id="<?php echo $this->get_input_name(); ?>" value="<?php echo $this->value; ?>"<?php echo !empty( $this->placeholder ) ? ' placeholder="' . esc_attr( $this->placeholder ) . '"' : ''; ?> class="regular-text" />
28
+
29
+ <?php
30
+
31
+ $this->display_description();
32
+
33
+ }
34
+
35
+ }
lib/simple-admin-pages/classes/AdminPageSetting.Textarea.class.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a textarea field setting in the admin menu
5
+ *
6
+ * @since 1.0
7
+ * @package Simple Admin Pages
8
+ *
9
+ * @todo textareas should have an option to swap new lines for <br>s
10
+ */
11
+
12
+ class sapAdminPageSettingTextarea_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
13
+
14
+ /*
15
+ * Size of this textarea
16
+ *
17
+ * This is put directly into a css class [size]-text,
18
+ * and setting this to 'large' will link into WordPress's existing textarea
19
+ * style for full-width textareas.
20
+ */
21
+ public $size = 'small';
22
+
23
+ public $sanitize_callback = 'sanitize_text_field';
24
+
25
+ /**
26
+ * Escape the value to display it safely HTML textarea fields
27
+ * @since 1.0
28
+ */
29
+ public function esc_value( $val ) {
30
+ return esc_textarea( $val );
31
+ }
32
+
33
+ /**
34
+ * Set the size of this textarea field
35
+ * @since 1.0
36
+ */
37
+ public function set_size( $size ) {
38
+ $this->size = esc_attr( $size );
39
+ }
40
+
41
+ /**
42
+ * Display this setting
43
+ * @since 1.0
44
+ */
45
+ public function display_setting() {
46
+ ?>
47
+
48
+ <textarea name="<?php echo $this->get_input_name(); ?>" id="<?php echo $this->get_input_name(); ?>" class="<?php echo $this->size; ?>-text"<?php echo !empty( $this->placeholder ) ? ' placeholder="' . esc_attr( $this->placeholder ) . '"' : ''; ?>><?php echo $this->value; ?></textarea>
49
+
50
+ <?php
51
+
52
+ $this->display_description();
53
+
54
+ }
55
+
56
+ }
lib/simple-admin-pages/classes/AdminPageSetting.Toggle.class.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save an option with a single checkbox.
5
+ *
6
+ * This setting accepts the following arguments in its constructor function.
7
+ *
8
+ * $args = array(
9
+ * 'id' => 'setting_id', // Unique id
10
+ * 'title' => 'My Setting', // Title or label for the setting
11
+ * 'description' => 'Description', // Help text description
12
+ * 'label' => 'Label', // Checkbox label text
13
+ * );
14
+ * );
15
+ *
16
+ * @since 1.0
17
+ * @package Simple Admin Pages
18
+ */
19
+
20
+ class sapAdminPageSettingToggle_2_0_a_1 extends sapAdminPageSetting_2_0_a_1 {
21
+
22
+ public $sanitize_callback = 'sanitize_text_field';
23
+
24
+ /**
25
+ * Display this setting
26
+ * @since 1.0
27
+ */
28
+ public function display_setting() {
29
+
30
+ $input_name = $this->get_input_name();
31
+
32
+ ?>
33
+
34
+ <input type="checkbox" name="<?php echo $input_name; ?>" id="<?php echo $input_name; ?>" value="1"<?php if( $this->value == '1' ) : ?> checked="checked"<?php endif; ?>>
35
+ <label for="<?php echo $input_name; ?>"><?php echo $this->label; ?></label>
36
+
37
+ <?php
38
+
39
+ $this->display_description();
40
+
41
+ }
42
+
43
+ }
lib/simple-admin-pages/classes/AdminPageSetting.class.php ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Register, display and save a setting on a custom admin menu
5
+ *
6
+ * All settings accept the following arguments in their constructor functions.
7
+ *
8
+ * $args = array(
9
+ * 'id' => 'setting_id', // Unique id
10
+ * 'title' => 'My Setting', // Title or label for the setting
11
+ * 'description' => 'Description' // Help text description
12
+ * );
13
+ *
14
+ * @since 1.0
15
+ * @package Simple Admin Pages
16
+ */
17
+
18
+ abstract class sapAdminPageSetting_2_0_a_1 {
19
+
20
+ // Page defaults
21
+ public $id; // used in form fields and database to track and store setting
22
+ public $title; // setting label
23
+ public $description; // optional description of the setting
24
+ public $value; // value of the setting, if a value exists
25
+
26
+ // Array to store errors
27
+ public $errors = array();
28
+
29
+ /*
30
+ * Function to use when sanitizing the data
31
+ *
32
+ * We set this to a strict sanitization function as a default, but a
33
+ * setting should override this in an extended class when needed.
34
+ *
35
+ * @since 1.0
36
+ */
37
+ public $sanitize_callback = 'sanitize_text_field';
38
+
39
+ /**
40
+ * Initialize the setting
41
+ *
42
+ * By default, every setting takes an id, title and description in the $args
43
+ * array.
44
+ *
45
+ * @since 1.0
46
+ */
47
+ public function __construct( $args ) {
48
+
49
+ // Parse the values passed
50
+ $this->parse_args( $args );
51
+
52
+ // Get any existing value
53
+ $this->set_value();
54
+
55
+ // Set an error if the object is missing necessary data
56
+ if ( $this->missing_data() ) {
57
+ $this->set_error();
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Parse the arguments passed in the construction and assign them to
63
+ * internal variables. This function will be overwritten for most subclasses
64
+ * @since 1.0
65
+ */
66
+ private function parse_args( $args ) {
67
+ foreach ( $args as $key => $val ) {
68
+ switch ( $key ) {
69
+
70
+ case 'id' :
71
+ $this->{$key} = esc_attr( $val );
72
+
73
+ default :
74
+ $this->{$key} = $val;
75
+
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Check for missing data when setup.
82
+ * @since 1.0
83
+ */
84
+ private function missing_data( ) {
85
+
86
+ $error_type = 'missing_data';
87
+
88
+ // Required fields
89
+ if ( empty( $this->id ) ) {
90
+ $this->set_error(
91
+ array(
92
+ 'type' => $error_type,
93
+ 'data' => 'id'
94
+ )
95
+ );
96
+ }
97
+ if ( empty( $this->title ) ) {
98
+ $this->set_error(
99
+ array(
100
+ 'type' => $error_type,
101
+ 'data' => 'title'
102
+ )
103
+ );
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Set a value
109
+ * @since 2.0
110
+ */
111
+ public function set_value( $val = null ) {
112
+
113
+ if ( $val === null ) {
114
+ $option_group_value = get_option( $this->page );
115
+ $val = isset( $option_group_value[ $this->id ] ) ? $option_group_value[ $this->id ] : '';
116
+ }
117
+
118
+ $this->value = $this->esc_value( $val );
119
+ }
120
+
121
+ /**
122
+ * Escape the value to display it in text fields and other input fields
123
+ *
124
+ * We use esc_attr() here so that the default is quite strict, but other
125
+ * setting types should override this function with the appropriate escape
126
+ * function. See: http://codex.wordpress.org/Data_Validation
127
+ *
128
+ * @since 1.0
129
+ */
130
+ public function esc_value( $val ) {
131
+ return esc_attr( $val );
132
+ }
133
+
134
+ /**
135
+ * Wrapper for the sanitization callback function.
136
+ *
137
+ * This just reduces code duplication for child classes that need a custom
138
+ * callback function.
139
+ * @since 1.0
140
+ */
141
+ public function sanitize_callback_wrapper( $value ) {
142
+ return call_user_func( $this->sanitize_callback, $value );
143
+ }
144
+
145
+ /**
146
+ * Display this setting
147
+ * @since 1.0
148
+ */
149
+ abstract public function display_setting();
150
+
151
+ /**
152
+ * Display a description for this setting
153
+ * @since 1.0
154
+ */
155
+ public function display_description() {
156
+
157
+ if ( !empty( $this->description ) ) {
158
+
159
+ ?>
160
+
161
+ <p class="description"><?php echo $this->description; ?></p>
162
+
163
+ <?php
164
+
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Generate an option input field name, using the grouped schema:
170
+ * "page[option_name]"
171
+ * @since 1.2
172
+ */
173
+ public function get_input_name() {
174
+ return esc_attr( $this->page ) . '[' . esc_attr( $this->id ) . ']';
175
+ }
176
+
177
+ /**
178
+ * Add and register this setting
179
+ *
180
+ * @since 1.0
181
+ */
182
+ public function add_settings_field( $section_id ) {
183
+
184
+ // If no sanitization callback exists, don't register the setting.
185
+ if ( !$this->has_sanitize_callback() ) {
186
+ return;
187
+ }
188
+
189
+ add_settings_field(
190
+ $this->id,
191
+ $this->title,
192
+ array( $this, 'display_setting' ),
193
+ $this->tab,
194
+ $section_id
195
+ );
196
+
197
+ }
198
+
199
+ /**
200
+ * Check if this field has a sanitization callback set
201
+ * @since 1.2
202
+ */
203
+ public function has_sanitize_callback() {
204
+ if ( isset( $this->sanitize_callback ) && trim( $this->sanitize_callback ) ) {
205
+ return true;
206
+ }
207
+
208
+ return false;
209
+ }
210
+
211
+ /**
212
+ * Set an error
213
+ * @since 1.0
214
+ */
215
+ public function set_error( $error ) {
216
+ $this->errors[] = array_merge(
217
+ $error,
218
+ array(
219
+ 'class' => get_class( $this ),
220
+ 'id' => $this->id,
221
+ 'backtrace' => debug_backtrace()
222
+ )
223
+ );
224
+ }
225
+ }
lib/simple-admin-pages/classes/Library.class.php ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( !class_exists( 'sapLibrary_2_0_a_1' ) ) {
3
+ /**
4
+ * This library class loads and provides access to the correct version of the
5
+ * Simple Admin Pages library.
6
+ *
7
+ * @since 1.0
8
+ * @package Simple Admin Pages
9
+ */
10
+ class sapLibrary_2_0_a_1 {
11
+
12
+ // Version of the library
13
+ private $version = '2.0.a.1';
14
+
15
+ // A full URL to the library which is used to correctly link scripts and
16
+ // stylesheets.
17
+ public $lib_url;
18
+
19
+ // A relative path to any custom library extension classes. When
20
+ // instantiating a custom setting class, the library will search in its own
21
+ // directory of classes adn also the $lib_extension_path. This way,
22
+ // developers can add on their own classes without mixing them with the
23
+ // default classes.
24
+ public $lib_extension_path;
25
+
26
+ // An array of pages to add to the admin menus
27
+ public $pages = array();
28
+
29
+ // Collects errors for debugging
30
+ public $errors = array();
31
+
32
+ // Set debug mode to true to stop and print errors found while processing.
33
+ // @note This is not related to your PHP error reporting setting, but is an
34
+ // internal error tracking mechanism to catch missing or malformed data
35
+ // during development.
36
+ public $debug_mode = false;
37
+
38
+ /**
39
+ * Initialize the library with the appropriate version
40
+ * @since 1.0
41
+ */
42
+ public function __construct( $args ) {
43
+
44
+ // If no URL path to the library is passed, we won't be able to add the
45
+ // CSS and Javascript to the admin panel
46
+ if ( !isset( $args['lib_url'] ) ) {
47
+ $this->set_error(
48
+ array(
49
+ 'id' => 'no-lib-url',
50
+ 'desc' => 'No URL path to the library provided when the libary was created.',
51
+ 'var' => $args,
52
+ 'line' => __LINE__,
53
+ 'function' => __FUNCTION__
54
+ )
55
+ );
56
+ } else {
57
+ $this->lib_url = $args['lib_url'];
58
+ }
59
+
60
+ // Set a library extension path if passed
61
+ if ( isset( $args['lib_extension_path'] ) ) {
62
+ $this->lib_extension_path = $args['lib_extension_path'];
63
+ }
64
+
65
+ // Set the debug mode
66
+ if ( isset( $args['debug_mode'] ) && $args['debug_mode'] === true ) {
67
+ $this->debug_mode = true;
68
+ }
69
+
70
+ // Ensure we have access to WordPress' plugin functions
71
+ require_once(ABSPATH . '/wp-admin/includes/plugin.php');
72
+
73
+ // Load the required classes
74
+ $this->load_class( 'sapAdminPage', 'AdminPage.class.php' );
75
+ $this->load_class( 'sapAdminPageSection', 'AdminPageSection.class.php' );
76
+ $this->load_class( 'sapAdminPageSetting', 'AdminPageSetting.class.php' );
77
+
78
+ // Add the scripts to the admin pages
79
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
80
+
81
+ }
82
+
83
+ /**
84
+ * Load the class if it isn't already loaded
85
+ * @since 1.0
86
+ */
87
+ private function load_class( $class, $file ) {
88
+
89
+ if ( !class_exists( $this->get_versioned_classname( $class ) ) ) {
90
+ require_once( $file );
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Return the version suffix for a class
96
+ * @since 1.0
97
+ */
98
+ private function get_versioned_classname( $class ) {
99
+ return $class . '_' . str_replace( '.', '_', $this->version );
100
+ }
101
+
102
+ /**
103
+ * Check if the correct version of a class exists
104
+ * @since 1.0
105
+ */
106
+ private function versioned_class_exists( $class ) {
107
+ if ( class_exists( $this->get_versioned_classname( $class ) ) ) {
108
+ return true;
109
+ } else {
110
+ return false;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Load the files for a specific setting type and return the class
116
+ * to use when instantiating the setting object.
117
+ *
118
+ * @since 1.0
119
+ */
120
+ private function get_setting_classname( $type ) {
121
+
122
+ switch( $type ) {
123
+
124
+ case 'text' :
125
+ require_once('AdminPageSetting.Text.class.php');
126
+ return $this->get_versioned_classname( 'sapAdminPageSettingText' );
127
+
128
+ case 'textarea' :
129
+ require_once('AdminPageSetting.Textarea.class.php');
130
+ return $this->get_versioned_classname( 'sapAdminPageSettingTextarea' );
131
+
132
+ case 'select' :
133
+ require_once('AdminPageSetting.Select.class.php');
134
+ return $this->get_versioned_classname( 'sapAdminPageSettingSelect' );
135
+
136
+ case 'toggle' :
137
+ require_once('AdminPageSetting.Toggle.class.php');
138
+ return $this->get_versioned_classname( 'sapAdminPageSettingToggle' );
139
+
140
+ case 'post' :
141
+ require_once('AdminPageSetting.SelectPost.class.php');
142
+ return $this->get_versioned_classname( 'sapAdminPageSettingSelectPost' );
143
+
144
+ case 'taxonomy' :
145
+ require_once('AdminPageSetting.SelectTaxonomy.class.php');
146
+ return $this->get_versioned_classname( 'sapAdminPageSettingSelectTaxonomy' );
147
+
148
+ case 'editor' :
149
+ require_once('AdminPageSetting.Editor.class.php');
150
+ return $this->get_versioned_classname( 'sapAdminPageSettingEditor' );
151
+
152
+ case 'html' :
153
+ require_once('AdminPageSetting.HTML.class.php');
154
+ return $this->get_versioned_classname( 'sapAdminPageSettingHTML' );
155
+
156
+ case 'scheduler' :
157
+ require_once('AdminPageSetting.Scheduler.class.php');
158
+ return $this->get_versioned_classname( 'sapAdminPageSettingScheduler' );
159
+
160
+ case 'opening-hours' :
161
+ require_once('AdminPageSetting.OpeningHours.class.php');
162
+ return $this->get_versioned_classname( 'sapAdminPageSettingOpeningHours' );
163
+
164
+ default :
165
+
166
+ // Exit early if a custom type is declared without providing the
167
+ // details to find the type class
168
+ if ( ( !is_array( $type ) || !isset( $type['id'] ) ) ||
169
+ ( !isset( $type['class'] ) || !isset( $type['filename'] ) ) ) {
170
+ return false;
171
+ }
172
+
173
+ // Load the custom type file. Look for the file in the library's
174
+ // folder or check the custom library extension path.
175
+ if ( file_exists( $type['filename'] ) ) {
176
+ require_once( $type['filename'] );
177
+ } elseif ( isset( $this->lib_extension_path ) && file_exists( $this->lib_extension_path . $type['filename'] ) ) {
178
+ require_once( $this->lib_extension_path . '/' . $type['filename'] );
179
+ } else {
180
+ return false;
181
+ }
182
+
183
+
184
+ // Check that we've loaded the appropriate class
185
+ if ( !$this->versioned_class_exists( $type['class'] ) ) {
186
+ return false;
187
+ }
188
+
189
+ return $this->get_versioned_classname( $type['class'] );
190
+
191
+ }
192
+
193
+ }
194
+
195
+ /**
196
+ * Initialize a page
197
+ * @since 1.0
198
+ *
199
+ * @todo perform some checks on args to ensure a valid page can be constructed
200
+ */
201
+ public function add_page( $menu_location, $args = array() ) {
202
+
203
+ // default should be 'options'
204
+ $class = $this->get_versioned_classname( 'sapAdminPage' );
205
+
206
+ if ( $menu_location == 'themes' ) {
207
+ $this->load_class( 'sapAdminPageThemes', 'AdminPage.Themes.class.php' );
208
+ $class = $this->get_versioned_classname( 'sapAdminPageThemes' );
209
+ } elseif ( $menu_location == 'submenu' ) {
210
+ $this->load_class( 'sapAdminPageSubmenu', 'AdminPage.Submenu.class.php' );
211
+ $class = $this->get_versioned_classname( 'sapAdminPageSubmenu' );
212
+ }
213
+
214
+ if ( class_exists( $class ) ) {
215
+ $this->pages[ $args['id'] ] = new $class( $args );
216
+ }
217
+
218
+ }
219
+
220
+ /**
221
+ * Initialize a section
222
+ * @since 1.0
223
+ *
224
+ * @todo perform some checks on args to ensure a valid section can be constructed
225
+ */
226
+ public function add_section( $page, $args = array() ) {
227
+
228
+ if ( !isset( $this->pages[ $page ] ) ) {
229
+ return false;
230
+ } else {
231
+ $args['page'] = $page;
232
+ }
233
+
234
+ $class = $this->get_versioned_classname( 'sapAdminPageSection' );
235
+ if ( class_exists( $class ) ) {
236
+ $this->pages[ $page ]->add_section( new $class( $args ) );
237
+ }
238
+
239
+ }
240
+
241
+ /**
242
+ * Initialize a setting
243
+ *
244
+ * The type variable can be a string pointing to a pre-defined setting type,
245
+ * or an array consisting of an id, classname and filename which references
246
+ * a custom setting type. @sa get_setting_classname()
247
+ *
248
+ * @since 1.0
249
+ */
250
+ public function add_setting( $page, $section, $type, $args = array() ) {
251
+
252
+ if ( !isset( $this->pages[ $page ] ) || !isset( $this->pages[ $page ]->sections[ $section ] ) ) {
253
+ return false;
254
+ } else {
255
+ $args['page'] = $page;
256
+ $args['tab'] = $this->pages[$page]->sections[ $section ]->get_page_slug();
257
+ }
258
+
259
+ $class = $this->get_setting_classname( $type );
260
+ if ( ( $class && class_exists( $class ) ) && is_subclass_of( $class, $this->get_versioned_classname( 'sapAdminPageSetting' ) ) ) {
261
+ $this->pages[ $page ]->sections[ $section ]->add_setting( new $class( $args ) );
262
+ }
263
+
264
+ }
265
+
266
+ /**
267
+ * Register all page, section and settings content with WordPress
268
+ * @since 1.0
269
+ */
270
+ public function add_admin_menus() {
271
+
272
+ // If the library is run in debug mode, check for any errors in content,
273
+ // print any errors found, and don't add the menu if there are errors
274
+ if ( $this->debug_mode ) {
275
+ $errors = array();
276
+ foreach ( $this->pages as $page ) {
277
+ foreach ( $page->sections as $section ) {
278
+ if ( count( $section->errors ) ) {
279
+ array_merge( $errors, $section->errors );
280
+ }
281
+ foreach ( $section->settings as $setting ) {
282
+ if ( count( $setting->errors ) ) {
283
+ $errors = array_merge( $errors, $setting->errors );
284
+ }
285
+ }
286
+ }
287
+ }
288
+ if ( count( $errors ) ) {
289
+ print_r( $errors );
290
+ return;
291
+ }
292
+ }
293
+
294
+ // Add the action hooks
295
+ foreach ( $this->pages as $id => $page ) {
296
+ add_action( 'admin_menu', array( $page, 'add_admin_menu' ) );
297
+ add_action( 'admin_init', array( $page, 'register_admin_menu' ) );
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Port data from a previous version to the current version
303
+ *
304
+ * Version 2.0 of the library changes the structure of how it stores data.
305
+ * In order to upgrade the version of the library your plugin/theme is
306
+ * using, this method must be called after all of your pages and settings
307
+ * have been declared but before you run add_admin_menus().
308
+ *
309
+ * This method will loop over all of the settings data and port any existing
310
+ * data to the new data structure. It will check if the data has been ported
311
+ * first before it updates the data. The old data will be removed to keep
312
+ * the database clean.
313
+ *
314
+ * @var int target_version Which data version the library should update to.
315
+ * @since 2.0
316
+ */
317
+ public function port_data( $target_version, $delete_old_data = true ) {
318
+
319
+ // Port data to the storage structure in version 2
320
+ if ( $target_version == 2 ) {
321
+
322
+ foreach ( $this->pages as $page_id => $page ) {
323
+
324
+ // Skip if this page has already been ported
325
+ if ( get_option( $page_id ) !== false ) {
326
+ continue;
327
+ }
328
+
329
+ $page_values = array();
330
+
331
+ foreach ( $page->sections as $section ) {
332
+ foreach ( $section->settings as $setting ) {
333
+ $value = get_option( $setting->id );
334
+ if ( $value !== false ) {
335
+ $page_values[ $setting->id ] = $value;
336
+ }
337
+ }
338
+ }
339
+
340
+ if ( count( $page_values ) ) {
341
+ $result = add_option( $page_id, $page_values );
342
+
343
+ // Delete old data if the flag is set and the new data was
344
+ // saved successfully.
345
+ if ( $delete_old_data === true && $result !== false ) {
346
+ foreach( $page_values as $setting_id => $setting_value ) {
347
+ delete_option( $setting_id );
348
+ }
349
+ }
350
+
351
+ // Reset settings values
352
+ if ( $result === true ) {
353
+
354
+ foreach ( $page->sections as $section ) {
355
+ foreach ( $section->settings as $setting ) {
356
+ $setting->set_value();
357
+ }
358
+ }
359
+
360
+ }
361
+ }
362
+ }
363
+ }
364
+
365
+ }
366
+
367
+ /**
368
+ * Enqueue the CSS stylesheet
369
+ * @since 1.0
370
+ * @todo complex settings should enqueue their assets only when loaded
371
+ */
372
+ public function enqueue_scripts() {
373
+
374
+ // Load the pickadate library
375
+ wp_enqueue_style( 'pickadate-default', $this->lib_url . 'lib/pickadate/themes/default.css' );
376
+ wp_enqueue_style( 'pickadate-date', $this->lib_url . 'lib/pickadate/themes/default.date.css' );
377
+ wp_enqueue_style( 'pickadate-time', $this->lib_url . 'lib/pickadate/themes/default.time.css' );
378
+ wp_enqueue_script( 'pickadate', $this->lib_url . 'lib/pickadate/picker.js', array( 'jquery' ), '', true );
379
+ wp_enqueue_script( 'pickadate-date', $this->lib_url . 'lib/pickadate/picker.date.js', array( 'jquery' ), '', true );
380
+ wp_enqueue_script( 'pickadate-time', $this->lib_url . 'lib/pickadate/picker.time.js', array( 'jquery' ), '', true );
381
+ wp_enqueue_script( 'pickadate-legacy', $this->lib_url . 'lib/pickadate/legacy.js', array( 'jquery' ), '', true );
382
+ // @todo is there some way I can enqueue this for RTL languages
383
+ // wp_enqueue_style( 'pickadate-rtl', $this->lib_url . 'lib/pickadate/themes/rtl.css' );
384
+
385
+ // Default styles and scripts
386
+ wp_enqueue_style( 'sap-admin-style', $this->lib_url . 'css/admin.css' );
387
+ wp_enqueue_script( 'sap-admin-script', $this->lib_url . 'js/admin.js', array( 'jquery' ), '1.0', true );
388
+ }
389
+
390
+ /**
391
+ * Set an error
392
+ * @since 1.0
393
+ */
394
+ public function set_error( $error ) {
395
+ $this->errors[] = array_merge(
396
+ $error,
397
+ array(
398
+ 'class' => get_class( $this ),
399
+ 'id' => $this->id,
400
+ 'backtrace' => debug_backtrace()
401
+ )
402
+ );
403
+ }
404
+
405
+ }
406
+ } // endif;
lib/simple-admin-pages/css/admin.css ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * CSS Stylesheet for Simple Admin Pages library
3
+ *
4
+ * This stylesheet formats the display components placed into the WordPress
5
+ * admin menu. It attempts to replicate WordPress's existing admin menu style.
6
+ *
7
+ * @package Simple Admin Pages
8
+ */
9
+
10
+ /**
11
+ * Clear floats
12
+ */
13
+ .clearfix:before,
14
+ .clearfix:after {
15
+ content: " ";
16
+ display: table;
17
+ }
18
+ .clearfix:after {
19
+ clear: both;
20
+ }
21
+
22
+ /**
23
+ * Textarea
24
+ */
25
+ textarea.small-text {
26
+ width: 25em;
27
+ height: 10em;
28
+ }
29
+ textarea.large-text {
30
+ height: 30em;
31
+ }
32
+
33
+ /*
34
+ * Opening Hours
35
+ */
36
+ .sap-opening-hours td {
37
+ padding-left: 0;
38
+ padding-right: 1em;
39
+ padding-top: 0;
40
+ }
41
+ input.sap-opening-hours-day {
42
+ width: 15em;
43
+ }
44
+ input.sap-opening-hours-hours {
45
+ width: 9em;
46
+ }
47
+
48
+ /*
49
+ * Scheduler
50
+ */
51
+ .sap-add-scheduler {
52
+ margin-top: 1em;
53
+ }
54
+ .sap-scheduler-rule {
55
+ position: relative;
56
+ padding: 1em;
57
+ margin: 1em 0;
58
+ max-width: 800px;
59
+ background: #fff;
60
+ -webkit-box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
61
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
62
+ }
63
+ .sap-selector {
64
+ display: inline-block;
65
+ margin-top: 0;
66
+ margin-right: 1em;
67
+ border-bottom: 1px solid #ccc;
68
+ }
69
+ .sap-selector li {
70
+ display: inline-block;
71
+ margin-right: 1em;
72
+ line-height: 20px;
73
+ }
74
+ .sap-selector a {
75
+ text-decoration: none;
76
+ }
77
+ .sap-selector .selected {
78
+ font-weight: 600;
79
+ color: #666;
80
+ }
81
+ .sap-scheduler-weekdays li,
82
+ .sap-scheduler-weeks li {
83
+ display: inline-block;
84
+ margin-right: 0.5em;
85
+ text-align: center;
86
+ }
87
+ .sap-scheduler-date .label,
88
+ .sap-scheduler-date-input label {
89
+ display: block;
90
+ font-style: italic;
91
+ text-align: left;
92
+ margin-bottom: 0;
93
+ }
94
+ .sap-scheduler-weekdays input,
95
+ .sap-scheduler-weeks input {
96
+ margin-top: 6px;
97
+ margin-bottom: 7px;
98
+ }
99
+ .sap-scheduler-date label {
100
+ display: block;
101
+ }
102
+ .sap-scheduler-date-input {
103
+ display: none;
104
+ margin-top: 1em;
105
+ }
106
+ .sap-scheduler-date.date .sap-scheduler-weeks,
107
+ .sap-scheduler-date.date .sap-scheduler-weekdays {
108
+ display: none;
109
+ }
110
+ .sap-scheduler-date.weekly .sap-scheduler-weeks,
111
+ .sap-scheduler-date.date .sap-scheduler-weeks {
112
+ display: none;
113
+ }
114
+ .sap-scheduler-date.date .sap-scheduler-date-input {
115
+ display: block;
116
+ }
117
+ .sap-scheduler-time {
118
+ margin-top: 4em;
119
+ }
120
+ .sap-scheduler-time.all-day .sap-scheduler-time-input {
121
+ display: none;
122
+ }
123
+ .sap-scheduler-time-input .start,
124
+ .sap-scheduler-time-input .end {
125
+ margin-top: 1em;
126
+ display: inline-block;
127
+ }
128
+ .sap-scheduler-time-input .start {
129
+ margin-right: 1em;
130
+ }
131
+ .sap-scheduler-time-input label {
132
+ display: block;
133
+ font-style: italic;
134
+ }
135
+ .sap-scheduler-time-input input {
136
+ max-width: 8em;
137
+ }
138
+ .sap-scheduler-all-day {
139
+ display: inline-block;
140
+ margin-top: 1.7em;
141
+ font-style: italic;
142
+ padding: 1em;
143
+ background: #eee;
144
+ }
145
+ .sap-scheduler-all-day p {
146
+ margin-top: 0;
147
+ }
148
+ .sap-scheduler-time.time-slot .sap-scheduler-all-day {
149
+ display: none;
150
+ }
151
+ .sap-scheduler-control {
152
+ clear: both;
153
+ line-height: 1.5em;
154
+ }
155
+ .sap-scheduler-control {
156
+ position: absolute;
157
+ top: 1em;
158
+ right: 1em;
159
+ }
160
+ .sap-scheduler-control a {
161
+ text-decoration: none;
162
+ }
163
+ .sap-scheduler-control .delete {
164
+ color: #a00;
165
+ }
166
+ .sap-scheduler-control .delete:hover,
167
+ .sap-scheduler-control .delete:focus {
168
+ color: red;
169
+ }
170
+ .sap-scheduler-rule.list .sap-scheduler-date,
171
+ .sap-scheduler-rule.list .sap-scheduler-time {
172
+ display: none;
173
+ }
174
+ .sap-scheduler-rule .sap-scheduler-brief {
175
+ display: none;
176
+ clear: both;
177
+ }
178
+ .sap-scheduler-rule.list .sap-scheduler-brief {
179
+ display: block;
180
+ }
181
+ .sap-scheduler-brief {
182
+ margin-right: 4em;
183
+ line-height: 1.5em;
184
+ }
185
+ .sap-scheduler-brief .date,
186
+ .sap-scheduler-brief .time {
187
+ display: inline-block;
188
+ }
189
+ .sap-scheduler-brief .date {
190
+ margin-right: 1em;
191
+ }
192
+ @media (min-width: 783px) {
193
+ .sap-selector li {
194
+ font-size: 13px;
195
+ }
196
+ .sap-scheduler-time {
197
+ margin-top: 0;
198
+ }
199
+ .sap-scheduler-date {
200
+ float: left;
201
+ width: 50%;
202
+ }
203
+ .sap-scheduler-date.full-width {
204
+ float: none;
205
+ width: 100%;
206
+ }
207
+ .sap-scheduler-time {
208
+ float: right;
209
+ width: 50%;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Adjustements to pickadate library css to override WP admin styles
215
+ */
216
+ .picker__table {
217
+ table-layout: auto;
218
+ }
219
+ .picker__table th,
220
+ .picker__table td {
221
+ text-align: center;
222
+ display: table-cell;
223
+ padding: 0.5em;
224
+ font-size: 1em;
225
+ }
lib/simple-admin-pages/js/admin.js ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Javascript functions for the admin interface components of Simple Admin Pages
3
+ *
4
+ * @package Simple Admin Pages
5
+ */
6
+
7
+ jQuery(document).ready(function ($) {
8
+
9
+ /**
10
+ * Opening Hours
11
+ ***************/
12
+
13
+ /**
14
+ * Update the name of each day when the select option is changed
15
+ */
16
+ $( '.sap-opening-hours-day' ).change( function() {
17
+ $( $(this).data( 'target' ) ).val( $(this).children( 'option:selected' ).data( 'name' ) );
18
+ });
19
+
20
+ /**
21
+ * Scheduler
22
+ ***********/
23
+
24
+ if ( typeof sap_scheduler != 'undefined' ) {
25
+
26
+ /**
27
+ * Register click events on load
28
+ */
29
+ sap_scheduler_register_events();
30
+
31
+ /**
32
+ * Enable datepickers on load
33
+ */
34
+ if ( typeof sap_scheduler.settings != 'undefined' ) {
35
+ for ( var key in sap_scheduler.settings ) {
36
+ var obj = sap_scheduler.settings[key];
37
+ $( '#' + key + ' .sap-scheduler-date-input input' ).pickadate({
38
+ format: obj.date_format,
39
+ });
40
+ $( '#' + key + ' .sap-scheduler-time-input input' ).pickatime({
41
+ interval: obj.time_interval,
42
+ format: obj.time_format,
43
+ });
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Add a new scheduler panel
49
+ */
50
+ $( '.sap-add-scheduler a' ).click( function() {
51
+ var scheduler = $(this).parent().siblings( '.sap-scheduler' );
52
+ var scheduler_id = scheduler.attr( 'id' );
53
+ var scheduler_settings = sap_scheduler.settings[ scheduler_id ];
54
+ scheduler.append( scheduler_settings.template.replace( /\[0\]/g, '[' + scheduler.children( '.sap-scheduler-rule' ).length + ']' ) );
55
+ scheduler.last( '#' + scheduler_id + ' .sap-scheduler-rule' ).find( '.sap-scheduler-date-input input' ).pickadate({
56
+ format: scheduler_settings.date_format,
57
+ });
58
+ scheduler.last( '#' + scheduler_id + ' .sap-scheduler-rule' ).find( '.sap-scheduler-time-input input' ).pickatime({
59
+ interval: scheduler_settings.time_interval,
60
+ format: scheduler_settings.time_format,
61
+ });
62
+ sap_scheduler_register_events();
63
+
64
+ return false;
65
+ });
66
+
67
+ /**
68
+ * Register event handlers on the scheduler rules. This is run on page load
69
+ * and every time a rule is added.
70
+ */
71
+ function sap_scheduler_register_events() {
72
+
73
+ /**
74
+ * Open and close the full view of a scheduling rule
75
+ */
76
+ $( '.sap-scheduler-rule .toggle' ).off( 'click' ).click( function() {
77
+
78
+ var controls = $(this).parent();
79
+
80
+ if ( $(this).parent().parent().hasClass( 'list' ) ) {
81
+ controls.hide();
82
+ $(this).children( '.dashicons-edit' ).removeClass( 'dashicons-edit' ).addClass( 'dashicons-arrow-up-alt2' );
83
+ controls.siblings( '.sap-scheduler-brief' ).hide();
84
+ controls.siblings( '.sap-scheduler-date, .sap-scheduler-time' ).slideDown( function() {
85
+ $(this).parent().removeClass( 'list' );
86
+ controls.fadeIn();
87
+ });
88
+
89
+ } else {
90
+ controls.hide();
91
+ $(this).children( '.dashicons-arrow-up-alt2' ).removeClass( 'dashicons-arrow-up-alt2' ).addClass( 'dashicons-edit' );
92
+ controls.siblings( '.sap-scheduler-brief' ).fadeIn();
93
+ controls.siblings( '.sap-scheduler-time' ).slideUp();
94
+ controls.siblings( '.sap-scheduler-date' ).slideUp( function() {
95
+
96
+ var scheduler_rule = $(this).parent();
97
+ var scheduler_id = scheduler_rule.parent().attr( 'id' );
98
+
99
+ scheduler_rule.addClass( 'list' );
100
+ controls.fadeIn();
101
+
102
+ sap_scheduler_set_date_phrase( scheduler_rule, scheduler_id );
103
+ sap_scheduler_set_time_phrase( scheduler_rule, scheduler_id );
104
+ });
105
+ }
106
+
107
+ return false;
108
+ });
109
+
110
+ /**
111
+ * Update current selection for selector lists
112
+ */
113
+ $( '.sap-selector a' ).off( 'switch.sap' ).on( 'switch.sap', function() {
114
+ $(this).parent().parent().find( 'a' ).removeClass( 'selected' );
115
+ $(this).addClass( 'selected' );
116
+
117
+ return false;
118
+ });
119
+
120
+ /**
121
+ * Switch between weekly, monthly and date options
122
+ */
123
+ $( '.sap-scheduler-date .sap-selector a' ).off( 'click' ).click( function() {
124
+
125
+ $(this).trigger( 'switch.sap' );
126
+
127
+ var date = $(this).closest( '.sap-scheduler-date' );
128
+
129
+ if ( $(this).data( 'format' ) == 'weekly' && date.hasClass( 'weekly' ) === false ) {
130
+ date.children( '.sap-scheduler-weeks' ).slideUp( function() {
131
+ $(this).find( 'input' ).prop('checked', false);
132
+ });
133
+ date.children( '.sap-scheduler-date-input' ).slideUp( function() {
134
+ $(this).find( 'input' ).val( '' );
135
+ });
136
+ date.children( '.sap-scheduler-weekdays' ).slideDown( function() {
137
+ date.removeClass( 'monthly date' );
138
+ date.addClass( 'weekly' );
139
+ });
140
+
141
+ } else if ( $(this).data( 'format' ) == 'monthly' && date.hasClass( 'monthly' ) === false ) {
142
+ date.children( '.sap-scheduler-date-input' ).slideUp( function() {
143
+ $(this).find( 'input' ).val( '' );
144
+ });
145
+ date.children( '.sap-scheduler-weekdays' ).slideDown();
146
+ date.children( '.sap-scheduler-weeks' ).slideDown( function() {
147
+ date.removeClass( 'weekly date' );
148
+ date.addClass( 'monthly' );
149
+ });
150
+
151
+ } else if ( $(this).data( 'format' ) == 'date' && date.hasClass( 'date' ) === false ) {
152
+ date.children( '.sap-scheduler-weekdays' ).slideUp( function() {
153
+ $(this).find( 'input' ).prop('checked', false);
154
+ });
155
+ date.children( '.sap-scheduler-weeks' ).slideUp( function() {
156
+ $(this).find( 'input' ).prop('checked', false);
157
+ });
158
+ date.children( '.sap-scheduler-date-input' ).slideDown( function() {
159
+ date.removeClass( 'weekly monthly' );
160
+ date.addClass( 'date' );
161
+ });
162
+ }
163
+
164
+ return false;
165
+ });
166
+
167
+ /**
168
+ * Show or hide time slot options
169
+ */
170
+ $( '.sap-scheduler-time .sap-selector a' ).off( 'click' ).click( function() {
171
+
172
+ $(this).trigger( 'switch.sap' );
173
+
174
+ var time = $(this).closest( '.sap-scheduler-time' );
175
+
176
+ if ( $(this).data( 'format' ) == 'time-slot' && time.hasClass( 'time-slot' ) === false ) {
177
+ time.children( '.sap-scheduler-time-input' ).slideDown();
178
+ time.children( '.sap-scheduler-all-day' ).slideUp( function() {
179
+ time.removeClass( 'all-day' );
180
+ time.addClass( 'time-slot' );
181
+ });
182
+
183
+ } else if ( $(this).data( 'format' ) == 'all-day' && time.hasClass( 'all-day' ) === false ) {
184
+ time.children( '.sap-scheduler-all-day' ).slideDown();
185
+ time.children( '.sap-scheduler-time-input' ).slideUp( function() {
186
+ time.removeClass( 'time-slot' );
187
+ time.addClass( 'all-day' );
188
+ time.find( 'input' ).val( '' );
189
+ });
190
+ }
191
+
192
+ return false;
193
+ });
194
+
195
+ /**
196
+ * Show time slot options from the link in the all-day notice
197
+ */
198
+ $( '.sap-scheduler-all-day a' ).off( 'click' ).click( function() {
199
+ $(this).closest( '.sap-scheduler-time' ).children( '.sap-selector' ).find( 'a[data-format="time-slot"]' ).trigger( 'click' );
200
+
201
+ return false;
202
+ });
203
+
204
+ /**
205
+ * Delete a scheduling rule panel
206
+ */
207
+ $( '.sap-scheduler-control .delete' ).off( 'click' ).click( function() {
208
+ var scheduler = $(this).closest( '.sap-scheduler' );
209
+ $(this).parent().parent().fadeOut( function() {
210
+ $(this).remove();
211
+
212
+ // Reset the index of each rule
213
+ // @todo optimize this excessive use of regex (32x per rule).
214
+ // maybe set a data-slug attribute on .sap-scheduler-rule and a
215
+ // data-slug prop on each input/select/label, then use these to
216
+ // construct the new attributes: rule-slug[index][input-slug]
217
+ scheduler.children( '.sap-scheduler-rule' ).each( function( i ) {
218
+ var index = i.toString();
219
+ $(this).find( 'input' ).each( function() {
220
+ var name = $(this).attr( 'name' ).replace( /\[\d*\]/g, '[' + index + ']' );
221
+ $(this).attr( 'name', name );
222
+ $(this).attr( 'id', name );
223
+ var aria_owns = $(this).attr( 'aria-owns' );
224
+ if ( typeof aria_owns !== 'undefined' && aria_owns !== false) {
225
+ $(this).attr( 'aria-owns', name );
226
+ }
227
+ });
228
+ $(this).find( 'label' ).each( function() {
229
+ var name = $(this).attr( 'for' ).replace( /\[\d*\]/g, '[' + index + ']' );
230
+ $(this).attr( 'for', name );
231
+ });
232
+ $(this).find( '.picker' ).each( function() {
233
+ var name = $(this).attr( 'id' ).replace( /\[\d*\]/g, '[' + index + ']' );
234
+ $(this).attr( 'id', name );
235
+ });
236
+ });
237
+ });
238
+
239
+ return false;
240
+ });
241
+ }
242
+
243
+ /**
244
+ * Set the summary phrase for a scheduler rule's date. This phrase is shown
245
+ * when the rule's view is minimized.
246
+ */
247
+ function sap_scheduler_set_date_phrase( scheduler_rule, scheduler_id ) {
248
+
249
+ var date_value = scheduler_rule.find( '.sap-scheduler-date-input input' ).val();
250
+ if ( typeof date_value !== 'undefined' && date_value != '' ) {
251
+ scheduler_rule.find( '.sap-scheduler-brief .date .value' ).html( date_value );
252
+
253
+ return;
254
+ }
255
+
256
+ var weekdays = 0;
257
+ var weekday_arr = new Array();
258
+ scheduler_rule.find( '.sap-scheduler-weekdays input' ).each( function() {
259
+ if ( $(this).prop( 'checked' ) !== false ) {
260
+ weekdays += 1;
261
+ weekday_arr.push( sap_scheduler.settings[scheduler_id]['weekdays'][ $(this).data( 'day' ) ] );
262
+ }
263
+ });
264
+
265
+ if ( weekdays == 0 && sap_scheduler.settings[ scheduler_id ].disable_weekdays === false ) {
266
+ scheduler_rule.find( '.sap-scheduler-brief .date .value' ).html( sap_scheduler.settings[scheduler_id].summaries['never'] );
267
+
268
+ return;
269
+
270
+ } else if ( weekdays == 7 ) {
271
+ var weekday_string = sap_scheduler.settings[scheduler_id].summaries['weekly_always'];
272
+
273
+ } else {
274
+ var weekday_string = weekday_arr.join( ', ' );
275
+ }
276
+
277
+ var weeks = 0;
278
+ var weeks_arr = new Array();
279
+ scheduler_rule.find( '.sap-scheduler-weeks input' ).each( function() {
280
+ if ( $(this).prop( 'checked' ) !== false ) {
281
+ weeks +=1;
282
+ weeks_arr.push( sap_scheduler.settings[scheduler_id]['weeks'][ $(this).data( 'week' ) ] );
283
+ }
284
+ });
285
+
286
+ if ( ( weeks == 0 || weeks == 5 ) && sap_scheduler.settings[ scheduler_id ].disable_weekdays === false ) {
287
+ scheduler_rule.find( '.sap-scheduler-brief .date .value' ).html( weekday_string );
288
+
289
+ return;
290
+ }
291
+
292
+ if ( weeks == 0 ) {
293
+ scheduler_rule.find( '.sap-scheduler-brief .date .value' ).html( sap_scheduler.settings[scheduler_id].summaries['never'] );
294
+
295
+ return;
296
+ }
297
+
298
+ if ( weekday_string != '' ) {
299
+ scheduler_rule.find( '.sap-scheduler-brief .date .value' ).html( sap_scheduler.settings[scheduler_id].summaries['monthly_weekdays'].replace( '{days}', weekday_arr.join( ', ' ) ).replace( '{weeks}', weeks_arr.join( ', ' ) ) );
300
+ } else {
301
+ scheduler_rule.find( '.sap-scheduler-brief .date .value' ).html( sap_scheduler.settings[scheduler_id].summaries['monthly_weeks'].replace( '{weeks}', weeks_arr.join( ', ' ) ) );
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Set the summary phrase for a scheduler rule's time. This phrase is shown
307
+ * when the rule's view is minimized.
308
+ */
309
+ function sap_scheduler_set_time_phrase( scheduler_rule, scheduler_id ) {
310
+
311
+ var start = scheduler_rule.find( '.sap-scheduler-time-input .start input' ).val();
312
+ var end = scheduler_rule.find( '.sap-scheduler-time-input .end input' ).val();
313
+
314
+ if ( start == '' && ( end == '' || typeof end == 'undefined' ) ) {
315
+ scheduler_rule.find( '.sap-scheduler-brief .time .value' ).html( sap_scheduler.settings[scheduler_id].summaries['all_day'] );
316
+
317
+ return;
318
+ }
319
+
320
+ if ( start == '' ) {
321
+ scheduler_rule.find( '.sap-scheduler-brief .time .value' ).html( sap_scheduler.settings[scheduler_id].summaries['before'] + ' ' + end );
322
+
323
+ return;
324
+ }
325
+
326
+ if ( end == '' || typeof end == 'undefined' ) {
327
+ scheduler_rule.find( '.sap-scheduler-brief .time .value' ).html( sap_scheduler.settings[scheduler_id].summaries['after'] + ' ' + start );
328
+
329
+ return;
330
+ }
331
+
332
+ if ( typeof end == 'undefined' ) {
333
+ return scheduler_rule.find( '.sap-scheduler-brief .time .value' ).html( start );
334
+ } else {
335
+ return scheduler_rule.find( '.sap-scheduler-brief .time .value' ).html( start + sap_scheduler.settings[scheduler_id].summaries['separator'] + end );
336
+ }
337
+ }
338
+ }
339
+
340
+ });
lib/simple-admin-pages/lib/pickadate/legacy.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Legacy browser support
3
+ */
4
+ [].map||(Array.prototype.map=function(a,b){for(var c=this,d=c.length,e=new Array(d),f=0;d>f;f++)f in c&&(e[f]=a.call(b,c[f],f,c));return e}),[].filter||(Array.prototype.filter=function(a){if(null==this)throw new TypeError;var b=Object(this),c=b.length>>>0;if("function"!=typeof a)throw new TypeError;for(var d=[],e=arguments[1],f=0;c>f;f++)if(f in b){var g=b[f];a.call(e,g,f,b)&&d.push(g)}return d}),[].indexOf||(Array.prototype.indexOf=function(a){if(null==this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1});/*!
5
+ * Cross-Browser Split 1.1.1
6
+ * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
7
+ * Available under the MIT License
8
+ * http://blog.stevenlevithan.com/archives/cross-browser-split
9
+ */
10
+ var nativeSplit=String.prototype.split,compliantExecNpcg=void 0===/()??/.exec("")[1];String.prototype.split=function(a,b){var c=this;if("[object RegExp]"!==Object.prototype.toString.call(a))return nativeSplit.call(c,a,b);var d,e,f,g,h=[],i=(a.ignoreCase?"i":"")+(a.multiline?"m":"")+(a.extended?"x":"")+(a.sticky?"y":""),j=0;for(a=new RegExp(a.source,i+"g"),c+="",compliantExecNpcg||(d=new RegExp("^"+a.source+"$(?!\\s)",i)),b=void 0===b?-1>>>0:b>>>0;(e=a.exec(c))&&(f=e.index+e[0].length,!(f>j&&(h.push(c.slice(j,e.index)),!compliantExecNpcg&&e.length>1&&e[0].replace(d,function(){for(var a=1;a<arguments.length-2;a++)void 0===arguments[a]&&(e[a]=void 0)}),e.length>1&&e.index<c.length&&Array.prototype.push.apply(h,e.slice(1)),g=e[0].length,j=f,h.length>=b)));)a.lastIndex===e.index&&a.lastIndex++;return j===c.length?(g||!a.test(""))&&h.push(""):h.push(c.slice(j)),h.length>b?h.slice(0,b):h};
lib/simple-admin-pages/lib/pickadate/picker.date.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ /*!
2
+ * Date picker for pickadate.js v3.5.0
3
+ * http://amsul.github.io/pickadate.js/date.htm
4
+ */
5
+ !function(a){"function"==typeof define&&define.amd?define(["picker","jquery"],a):a(Picker,jQuery)}(function(a,b){function c(a,b){var c=this,d=a.$node[0].value,e=a.$node.data("value"),f=e||d,g=e?b.formatSubmit:b.format,h=function(){return"rtl"===getComputedStyle(a.$root[0]).direction};c.settings=b,c.$node=a.$node,c.queue={min:"measure create",max:"measure create",now:"now create",select:"parse create validate",highlight:"parse navigate create validate",view:"parse create validate viewset",disable:"deactivate",enable:"activate"},c.item={},c.item.clear=null,c.item.disable=(b.disable||[]).slice(0),c.item.enable=-function(a){return a[0]===!0?a.shift():-1}(c.item.disable),c.set("min",b.min).set("max",b.max).set("now"),f?c.set("select",f,{format:g}):c.set("select",null).set("highlight",c.item.now),c.key={40:7,38:-7,39:function(){return h()?-1:1},37:function(){return h()?1:-1},go:function(a){var b=c.item.highlight,d=new Date(b.year,b.month,b.date+a);c.set("highlight",[d.getFullYear(),d.getMonth(),d.getDate()],{interval:a}),this.render()}},a.on("render",function(){a.$root.find("."+b.klass.selectMonth).on("change",function(){var c=this.value;c&&(a.set("highlight",[a.get("view").year,c,a.get("highlight").date]),a.$root.find("."+b.klass.selectMonth).trigger("focus"))}),a.$root.find("."+b.klass.selectYear).on("change",function(){var c=this.value;c&&(a.set("highlight",[c,a.get("view").month,a.get("highlight").date]),a.$root.find("."+b.klass.selectYear).trigger("focus"))})}).on("open",function(){a.$root.find("button, select").attr("disabled",!1)}).on("close",function(){a.$root.find("button, select").attr("disabled",!0)})}var d=7,e=6,f=a._;c.prototype.set=function(a,b,c){var d=this,e=d.item;return null===b?("clear"==a&&(a="select"),e[a]=b,d):(e["enable"==a?"disable":"flip"==a?"enable":a]=d.queue[a].split(" ").map(function(e){return b=d[e](a,b,c)}).pop(),"select"==a?d.set("highlight",e.select,c):"highlight"==a?d.set("view",e.highlight,c):a.match(/^(flip|min|max|disable|enable)$/)&&(e.select&&d.disabled(e.select)&&d.set("select",e.select,c),e.highlight&&d.disabled(e.highlight)&&d.set("highlight",e.highlight,c)),d)},c.prototype.get=function(a){return this.item[a]},c.prototype.create=function(a,c,d){var e,g=this;return c=void 0===c?a:c,c==-1/0||1/0==c?e=c:b.isPlainObject(c)&&f.isInteger(c.pick)?c=c.obj:b.isArray(c)?(c=new Date(c[0],c[1],c[2]),c=f.isDate(c)?c:g.create().obj):c=f.isInteger(c)||f.isDate(c)?g.normalize(new Date(c),d):g.now(a,c,d),{year:e||c.getFullYear(),month:e||c.getMonth(),date:e||c.getDate(),day:e||c.getDay(),obj:e||c,pick:e||c.getTime()}},c.prototype.createRange=function(a,c){var d=this,e=function(a){return a===!0||b.isArray(a)||f.isDate(a)?d.create(a):a};return f.isInteger(a)||(a=e(a)),f.isInteger(c)||(c=e(c)),f.isInteger(a)&&b.isPlainObject(c)?a=[c.year,c.month,c.date+a]:f.isInteger(c)&&b.isPlainObject(a)&&(c=[a.year,a.month,a.date+c]),{from:e(a),to:e(c)}},c.prototype.withinRange=function(a,b){return a=this.createRange(a.from,a.to),b.pick>=a.from.pick&&b.pick<=a.to.pick},c.prototype.overlapRanges=function(a,b){var c=this;return a=c.createRange(a.from,a.to),b=c.createRange(b.from,b.to),c.withinRange(a,b.from)||c.withinRange(a,b.to)||c.withinRange(b,a.from)||c.withinRange(b,a.to)},c.prototype.now=function(a,b,c){return b=new Date,c&&c.rel&&b.setDate(b.getDate()+c.rel),this.normalize(b,c)},c.prototype.navigate=function(a,c,d){var e,f,g,h,i=b.isArray(c),j=b.isPlainObject(c),k=this.item.view;if(i||j){for(j?(f=c.year,g=c.month,h=c.date):(f=+c[0],g=+c[1],h=+c[2]),d&&d.nav&&k&&k.month!==g&&(f=k.year,g=k.month),e=new Date(f,g+(d&&d.nav?d.nav:0),1),f=e.getFullYear(),g=e.getMonth();new Date(f,g,h).getMonth()!==g;)h-=1;c=[f,g,h]}return c},c.prototype.normalize=function(a){return a.setHours(0,0,0,0),a},c.prototype.measure=function(a,b){var c=this;return b?f.isInteger(b)&&(b=c.now(a,b,{rel:b})):b="min"==a?-1/0:1/0,b},c.prototype.viewset=function(a,b){return this.create([b.year,b.month,1])},c.prototype.validate=function(a,c,d){var e,g,h,i,j=this,k=c,l=d&&d.interval?d.interval:1,m=-1===j.item.enable,n=j.item.min,o=j.item.max,p=m&&j.item.disable.filter(function(a){if(b.isArray(a)){var d=j.create(a).pick;d<c.pick?e=!0:d>c.pick&&(g=!0)}return f.isInteger(a)}).length;if((!d||!d.nav)&&(!m&&j.disabled(c)||m&&j.disabled(c)&&(p||e||g)||!m&&(c.pick<=n.pick||c.pick>=o.pick)))for(m&&!p&&(!g&&l>0||!e&&0>l)&&(l*=-1);j.disabled(c)&&(Math.abs(l)>1&&(c.month<k.month||c.month>k.month)&&(c=k,l=l>0?1:-1),c.pick<=n.pick?(h=!0,l=1,c=j.create([n.year,n.month,n.date+(c.pick===n.pick?0:-1)])):c.pick>=o.pick&&(i=!0,l=-1,c=j.create([o.year,o.month,o.date+(c.pick===o.pick?0:1)])),!h||!i);)c=j.create([c.year,c.month,c.date+l]);return c},c.prototype.disabled=function(a){var c=this,d=c.item.disable.filter(function(d){return f.isInteger(d)?a.day===(c.settings.firstDay?d:d-1)%7:b.isArray(d)||f.isDate(d)?a.pick===c.create(d).pick:b.isPlainObject(d)?c.withinRange(d,a):void 0});return d=d.length&&!d.filter(function(a){return b.isArray(a)&&"inverted"==a[3]||b.isPlainObject(a)&&a.inverted}).length,-1===c.item.enable?!d:d||a.pick<c.item.min.pick||a.pick>c.item.max.pick},c.prototype.parse=function(a,b,c){var d=this,e={};return b&&"string"==typeof b?(c&&c.format||(c=c||{},c.format=d.settings.format),d.formats.toArray(c.format).map(function(a){var c=d.formats[a],g=c?f.trigger(c,d,[b,e]):a.replace(/^!/,"").length;c&&(e[a]=b.substr(0,g)),b=b.substr(g)}),[e.yyyy||e.yy,+(e.mm||e.m)-1,e.dd||e.d]):b},c.prototype.formats=function(){function a(a,b,c){var d=a.match(/\w+/)[0];return c.mm||c.m||(c.m=b.indexOf(d)+1),d.length}function b(a){return a.match(/\w+/)[0].length}return{d:function(a,b){return a?f.digits(a):b.date},dd:function(a,b){return a?2:f.lead(b.date)},ddd:function(a,c){return a?b(a):this.settings.weekdaysShort[c.day]},dddd:function(a,c){return a?b(a):this.settings.weekdaysFull[c.day]},m:function(a,b){return a?f.digits(a):b.month+1},mm:function(a,b){return a?2:f.lead(b.month+1)},mmm:function(b,c){var d=this.settings.monthsShort;return b?a(b,d,c):d[c.month]},mmmm:function(b,c){var d=this.settings.monthsFull;return b?a(b,d,c):d[c.month]},yy:function(a,b){return a?2:(""+b.year).slice(2)},yyyy:function(a,b){return a?4:b.year},toArray:function(a){return a.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g)},toString:function(a,b){var c=this;return c.formats.toArray(a).map(function(a){return f.trigger(c.formats[a],c,[0,b])||a.replace(/^!/,"")}).join("")}}}(),c.prototype.isDateExact=function(a,c){var d=this;return f.isInteger(a)&&f.isInteger(c)||"boolean"==typeof a&&"boolean"==typeof c?a===c:(f.isDate(a)||b.isArray(a))&&(f.isDate(c)||b.isArray(c))?d.create(a).pick===d.create(c).pick:b.isPlainObject(a)&&b.isPlainObject(c)?d.isDateExact(a.from,c.from)&&d.isDateExact(a.to,c.to):!1},c.prototype.isDateOverlap=function(a,c){var d=this;return f.isInteger(a)&&(f.isDate(c)||b.isArray(c))?a===d.create(c).day+1:f.isInteger(c)&&(f.isDate(a)||b.isArray(a))?c===d.create(a).day+1:b.isPlainObject(a)&&b.isPlainObject(c)?d.overlapRanges(a,c):!1},c.prototype.flipEnable=function(a){var b=this.item;b.enable=a||(-1==b.enable?1:-1)},c.prototype.deactivate=function(a,c){var d=this,e=d.item.disable.slice(0);return"flip"==c?d.flipEnable():c===!1?(d.flipEnable(1),e=[]):c===!0?(d.flipEnable(-1),e=[]):c.map(function(a){for(var c,g=0;g<e.length;g+=1)if(d.isDateExact(a,e[g])){c=!0;break}c||(f.isInteger(a)||f.isDate(a)||b.isArray(a)||b.isPlainObject(a)&&a.from&&a.to)&&e.push(a)}),e},c.prototype.activate=function(a,c){var d=this,e=d.item.disable,g=e.length;return"flip"==c?d.flipEnable():c===!0?(d.flipEnable(1),e=[]):c===!1?(d.flipEnable(-1),e=[]):c.map(function(a){var c,h,i,j;for(i=0;g>i;i+=1){if(h=e[i],d.isDateExact(h,a)){c=e[i]=null,j=!0;break}if(d.isDateOverlap(h,a)){b.isPlainObject(a)?(a.inverted=!0,c=a):b.isArray(a)?(c=a,c[3]||c.push("inverted")):f.isDate(a)&&(c=[a.getFullYear(),a.getMonth(),a.getDate(),"inverted"]);break}}if(c)for(i=0;g>i;i+=1)if(d.isDateExact(e[i],a)){e[i]=null;break}if(j)for(i=0;g>i;i+=1)if(d.isDateOverlap(e[i],a)){e[i]=null;break}c&&e.push(c)}),e.filter(function(a){return null!=a})},c.prototype.nodes=function(a){var b=this,c=b.settings,g=b.item,h=g.now,i=g.select,j=g.highlight,k=g.view,l=g.disable,m=g.min,n=g.max,o=function(a,b){return c.firstDay&&(a.push(a.shift()),b.push(b.shift())),f.node("thead",f.node("tr",f.group({min:0,max:d-1,i:1,node:"th",item:function(d){return[a[d],c.klass.weekdays,'scope=col title="'+b[d]+'"']}})))}((c.showWeekdaysFull?c.weekdaysFull:c.weekdaysShort).slice(0),c.weekdaysFull.slice(0)),p=function(a){return f.node("div"," ",c.klass["nav"+(a?"Next":"Prev")]+(a&&k.year>=n.year&&k.month>=n.month||!a&&k.year<=m.year&&k.month<=m.month?" "+c.klass.navDisabled:""),"data-nav="+(a||-1)+" "+f.ariaAttr({role:"button",controls:b.$node[0].id+"_table"})+' title="'+(a?c.labelMonthNext:c.labelMonthPrev)+'"')},q=function(){var d=c.showMonthsShort?c.monthsShort:c.monthsFull;return c.selectMonths?f.node("select",f.group({min:0,max:11,i:1,node:"option",item:function(a){return[d[a],0,"value="+a+(k.month==a?" selected":"")+(k.year==m.year&&a<m.month||k.year==n.year&&a>n.month?" disabled":"")]}}),c.klass.selectMonth,(a?"":"disabled")+" "+f.ariaAttr({controls:b.$node[0].id+"_table"})+' title="'+c.labelMonthSelect+'"'):f.node("div",d[k.month],c.klass.month)},r=function(){var d=k.year,e=c.selectYears===!0?5:~~(c.selectYears/2);if(e){var g=m.year,h=n.year,i=d-e,j=d+e;if(g>i&&(j+=g-i,i=g),j>h){var l=i-g,o=j-h;i-=l>o?o:l,j=h}return f.node("select",f.group({min:i,max:j,i:1,node:"option",item:function(a){return[a,0,"value="+a+(d==a?" selected":"")]}}),c.klass.selectYear,(a?"":"disabled")+" "+f.ariaAttr({controls:b.$node[0].id+"_table"})+' title="'+c.labelYearSelect+'"')}return f.node("div",d,c.klass.year)};return f.node("div",(c.selectYears?r()+q():q()+r())+p()+p(1),c.klass.header)+f.node("table",o+f.node("tbody",f.group({min:0,max:e-1,i:1,node:"tr",item:function(a){var e=c.firstDay&&0===b.create([k.year,k.month,1]).day?-7:0;return[f.group({min:d*a-k.day+e+1,max:function(){return this.min+d-1},i:1,node:"td",item:function(a){a=b.create([k.year,k.month,a+(c.firstDay?1:0)]);var d=i&&i.pick==a.pick,e=j&&j.pick==a.pick,g=l&&b.disabled(a)||a.pick<m.pick||a.pick>n.pick;return[f.node("div",a.date,function(b){return b.push(k.month==a.month?c.klass.infocus:c.klass.outfocus),h.pick==a.pick&&b.push(c.klass.now),d&&b.push(c.klass.selected),e&&b.push(c.klass.highlighted),g&&b.push(c.klass.disabled),b.join(" ")}([c.klass.day]),"data-pick="+a.pick+" "+f.ariaAttr({role:"gridcell",selected:d&&b.$node.val()===f.trigger(b.formats.toString,b,[c.format,a])?!0:null,activedescendant:e?!0:null,disabled:g?!0:null})),"",f.ariaAttr({role:"presentation"})]}})]}})),c.klass.table,'id="'+b.$node[0].id+'_table" '+f.ariaAttr({role:"grid",controls:b.$node[0].id,readonly:!0}))+f.node("div",f.node("button",c.today,c.klass.buttonToday,"type=button data-pick="+h.pick+(a?"":" disabled")+" "+f.ariaAttr({controls:b.$node[0].id}))+f.node("button",c.clear,c.klass.buttonClear,"type=button data-clear=1"+(a?"":" disabled")+" "+f.ariaAttr({controls:b.$node[0].id})),c.klass.footer)},c.defaults=function(a){return{labelMonthNext:"Next month",labelMonthPrev:"Previous month",labelMonthSelect:"Select a month",labelYearSelect:"Select a year",monthsFull:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdaysFull:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],today:"Today",clear:"Clear",format:"d mmmm, yyyy",klass:{table:a+"table",header:a+"header",navPrev:a+"nav--prev",navNext:a+"nav--next",navDisabled:a+"nav--disabled",month:a+"month",year:a+"year",selectMonth:a+"select--month",selectYear:a+"select--year",weekdays:a+"weekday",day:a+"day",disabled:a+"day--disabled",selected:a+"day--selected",highlighted:a+"day--highlighted",now:a+"day--today",infocus:a+"day--infocus",outfocus:a+"day--outfocus",footer:a+"footer",buttonClear:a+"button--clear",buttonToday:a+"button--today"}}}(a.klasses().picker+"__"),a.extend("pickadate",c)});
lib/simple-admin-pages/lib/pickadate/picker.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ /*!
2
+ * pickadate.js v3.5.0, 2014/04/13
3
+ * By Amsul, http://amsul.ca
4
+ * Hosted on http://amsul.github.io/pickadate.js
5
+ * Licensed under MIT
6
+ */
7
+ !function(a){"function"==typeof define&&define.amd?define("picker",["jquery"],a):this.Picker=a(jQuery)}(function(a){function b(f,g,i,j){function k(){return b._.node("div",b._.node("div",b._.node("div",b._.node("div",v.component.nodes(q.open),s.box),s.wrap),s.frame),s.holder)}function l(){t.data(g,v).addClass(s.input).val(t.data("value")?v.get("select",r.format):f.value).on("focus."+q.id+" click."+q.id,o),r.editable||t.on("keydown."+q.id,function(a){var b=a.keyCode,c=/^(8|46)$/.test(b);return 27==b?(v.close(),!1):void((32==b||c||!q.open&&v.component.key[b])&&(a.preventDefault(),a.stopPropagation(),c?v.clear().close():v.open()))}),e(f,{haspopup:!0,expanded:!1,readonly:!1,owns:f.id+"_root"+(v._hidden?" "+v._hidden.id:"")})}function m(){v.$root.on({focusin:function(a){v.$root.removeClass(s.focused),a.stopPropagation()},"mousedown click":function(b){var c=b.target;c!=v.$root.children()[0]&&(b.stopPropagation(),"mousedown"!=b.type||a(c).is(":input")||"OPTION"==c.nodeName||(b.preventDefault(),f.focus()))}}).on("click","[data-pick], [data-nav], [data-clear]",function(){var c=a(this),d=c.data(),e=c.hasClass(s.navDisabled)||c.hasClass(s.disabled),g=document.activeElement;g=g&&(g.type||g.href)&&g,(e||g&&!a.contains(v.$root[0],g))&&f.focus(),d.nav&&!e?v.set("highlight",v.component.item.highlight,{nav:d.nav}):b._.isInteger(d.pick)&&!e?v.set("select",d.pick).close(!0):d.clear&&v.clear().close(!0)}),e(v.$root[0],"hidden",!0)}function n(){var b,c;r.hiddenName===!0?(b=f.name+"_hidden",c=f.name,f.name=""):(c=["string"==typeof r.hiddenPrefix?r.hiddenPrefix:"","string"==typeof r.hiddenSuffix?r.hiddenSuffix:"_submit"],c=b=c[0]+f.name+c[1]),v._hidden=a('<input type=hidden name="'+c+'"id="'+b+'"'+(t.data("value")||f.value?' value="'+v.get("select",r.formatSubmit)+'"':"")+">")[0],t.on("change."+q.id,function(){v._hidden.value=f.value?v.get("select",r.formatSubmit):""}).after(v._hidden)}function o(a){a.stopPropagation(),"focus"==a.type&&v.$root.addClass(s.focused),v.open()}if(!f)return b;var p=!1,q={id:f.id||"P"+Math.abs(~~(Math.random()*new Date))},r=i?a.extend(!0,{},i.defaults,j):j||{},s=a.extend({},b.klasses(),r.klass),t=a(f),u=function(){return this.start()},v=u.prototype={constructor:u,$node:t,start:function(){return q&&q.start?v:(q.methods={},q.start=!0,q.open=!1,q.type=f.type,f.autofocus=f==document.activeElement,f.type="text",f.readOnly=!r.editable,f.id=f.id||q.id,v.component=new i(v,r),v.$root=a(b._.node("div",k(),s.picker,'id="'+f.id+'_root"')),m(),r.formatSubmit&&n(),l(),r.container?a(r.container).append(v.$root):t.after(v.$root),v.on({start:v.component.onStart,render:v.component.onRender,stop:v.component.onStop,open:v.component.onOpen,close:v.component.onClose,set:v.component.onSet}).on({start:r.onStart,render:r.onRender,stop:r.onStop,open:r.onOpen,close:r.onClose,set:r.onSet}),p=c(v.$root.children()[0]),f.autofocus&&v.open(),v.trigger("start").trigger("render"))},render:function(a){return a?v.$root.html(k()):v.$root.find("."+s.box).html(v.component.nodes(q.open)),v.trigger("render")},stop:function(){return q.start?(v.close(),v._hidden&&v._hidden.parentNode.removeChild(v._hidden),v.$root.remove(),t.removeClass(s.input).removeData(g),setTimeout(function(){t.off("."+q.id)},0),f.type=q.type,f.readOnly=!1,v.trigger("stop"),q.methods={},q.start=!1,v):v},open:function(c){return q.open?v:(t.addClass(s.active),e(f,"expanded",!0),setTimeout(function(){v.$root.addClass(s.opened),e(v.$root[0],"hidden",!1)},0),c!==!1&&(q.open=!0,p&&a("html").css("overflow","hidden").css("padding-right","+="+d()),t.trigger("focus"),h.on("click."+q.id+" focusin."+q.id,function(a){var b=a.target;b!=f&&b!=document&&3!=a.which&&v.close(b===v.$root.children()[0])}).on("keydown."+q.id,function(c){var d=c.keyCode,e=v.component.key[d],g=c.target;27==d?v.close(!0):g!=f||!e&&13!=d?a.contains(v.$root[0],g)&&13==d&&(c.preventDefault(),g.click()):(c.preventDefault(),e?b._.trigger(v.component.key.go,v,[b._.trigger(e)]):v.$root.find("."+s.highlighted).hasClass(s.disabled)||v.set("select",v.component.item.highlight).close())})),v.trigger("open"))},close:function(b){return b&&(t.off("focus."+q.id).trigger("focus"),setTimeout(function(){t.on("focus."+q.id,o)},0)),t.removeClass(s.active),e(f,"expanded",!1),setTimeout(function(){v.$root.removeClass(s.opened+" "+s.focused),e(v.$root[0],"hidden",!0)},0),q.open?(q.open=!1,p&&a("html").css("overflow","").css("padding-right","-="+d()),h.off("."+q.id),v.trigger("close")):v},clear:function(){return v.set("clear")},set:function(b,c,d){var e,f,g=a.isPlainObject(b),h=g?b:{};if(d=g&&a.isPlainObject(c)?c:d||{},b){g||(h[b]=c);for(e in h)f=h[e],e in v.component.item&&(void 0===f&&(f=null),v.component.set(e,f,d)),("select"==e||"clear"==e)&&t.val("clear"==e?"":v.get(e,r.format)).trigger("change");v.render()}return d.muted?v:v.trigger("set",h)},get:function(a,c){return a=a||"value",null!=q[a]?q[a]:"value"==a?f.value:a in v.component.item?"string"==typeof c?b._.trigger(v.component.formats.toString,v.component,[c,v.component.get(a)]):v.component.get(a):void 0},on:function(b,c){var d,e,f=a.isPlainObject(b),g=f?b:{};if(b){f||(g[b]=c);for(d in g)e=g[d],q.methods[d]=q.methods[d]||[],q.methods[d].push(e)}return v},off:function(){var a,b,c=arguments;for(a=0,namesCount=c.length;namesCount>a;a+=1)b=c[a],b in q.methods&&delete q.methods[b];return v},trigger:function(a,c){var d=q.methods[a];return d&&d.map(function(a){b._.trigger(a,v,[c])}),v}};return new u}function c(a){var b,c="position";return a.currentStyle?b=a.currentStyle[c]:window.getComputedStyle&&(b=getComputedStyle(a)[c]),"fixed"==b}function d(){var b=a('<div style="visibility:hidden;width:100px" />').appendTo("body"),c=b[0].offsetWidth;b.css("overflow","scroll");var d=a('<div style="width:100%" />').appendTo(b),e=d[0].offsetWidth;return b.remove(),c-e}function e(b,c,d){if(a.isPlainObject(c))for(var e in c)f(b,e,c[e]);else f(b,c,d)}function f(a,b,c){a.setAttribute(("role"==b?"":"aria-")+b,c)}function g(b,c){a.isPlainObject(b)||(b={attribute:c}),c="";for(var d in b){var e=("role"==d?"":"aria-")+d,f=b[d];c+=null==f?"":e+'="'+b[d]+'"'}return c}var h=a(document);return b.klasses=function(a){return a=a||"picker",{picker:a,opened:a+"--opened",focused:a+"--focused",input:a+"__input",active:a+"__input--active",holder:a+"__holder",frame:a+"__frame",wrap:a+"__wrap",box:a+"__box"}},b._={group:function(a){for(var c,d="",e=b._.trigger(a.min,a);e<=b._.trigger(a.max,a,[e]);e+=a.i)c=b._.trigger(a.item,a,[e]),d+=b._.node(a.node,c[0],c[1],c[2]);return d},node:function(b,c,d,e){return c?(c=a.isArray(c)?c.join(""):c,d=d?' class="'+d+'"':"",e=e?" "+e:"","<"+b+d+e+">"+c+"</"+b+">"):""},lead:function(a){return(10>a?"0":"")+a},trigger:function(a,b,c){return"function"==typeof a?a.apply(b,c||[]):a},digits:function(a){return/\d/.test(a[1])?2:1},isDate:function(a){return{}.toString.call(a).indexOf("Date")>-1&&this.isInteger(a.getDate())},isInteger:function(a){return{}.toString.call(a).indexOf("Number")>-1&&a%1===0},ariaAttr:g},b.extend=function(c,d){a.fn[c]=function(e,f){var g=this.data(c);return"picker"==e?g:g&&"string"==typeof e?b._.trigger(g[e],g,[f]):this.each(function(){var f=a(this);f.data(c)||new b(this,c,d,e)})},a.fn[c].defaults=d.defaults},b});
lib/simple-admin-pages/lib/pickadate/picker.time.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ /*!
2
+ * Time picker for pickadate.js v3.5.0
3
+ * http://amsul.github.io/pickadate.js/time.htm
4
+ */
5
+ !function(a){"function"==typeof define&&define.amd?define(["picker","jquery"],a):a(Picker,jQuery)}(function(a,b){function c(a,b){var c=this,d=a.$node[0].value,e=a.$node.data("value"),f=e||d,g=e?b.formatSubmit:b.format;c.settings=b,c.$node=a.$node,c.queue={interval:"i",min:"measure create",max:"measure create",now:"now create",select:"parse create validate",highlight:"parse create validate",view:"parse create validate",disable:"deactivate",enable:"activate"},c.item={},c.item.clear=null,c.item.interval=b.interval||30,c.item.disable=(b.disable||[]).slice(0),c.item.enable=-function(a){return a[0]===!0?a.shift():-1}(c.item.disable),c.set("min",b.min).set("max",b.max).set("now"),f?c.set("select",f,{format:g,fromValue:!!d}):c.set("select",null).set("highlight",c.item.now),c.key={40:1,38:-1,39:1,37:-1,go:function(a){c.set("highlight",c.item.highlight.pick+a*c.item.interval,{interval:a*c.item.interval}),this.render()}},a.on("render",function(){var c=a.$root.children(),d=c.find("."+b.klass.viewset);d.length&&(c[0].scrollTop=~~d.position().top-2*d[0].clientHeight)}).on("open",function(){a.$root.find("button").attr("disabled",!1)}).on("close",function(){a.$root.find("button").attr("disabled",!0)})}var d=24,e=60,f=12,g=d*e,h=a._;c.prototype.set=function(a,b,c){var d=this,e=d.item;return null===b?("clear"==a&&(a="select"),e[a]=b,d):(e["enable"==a?"disable":"flip"==a?"enable":a]=d.queue[a].split(" ").map(function(e){return b=d[e](a,b,c)}).pop(),"select"==a?d.set("highlight",e.select,c):"highlight"==a?d.set("view",e.highlight,c):"interval"==a?d.set("min",e.min,c).set("max",e.max,c):a.match(/^(flip|min|max|disable|enable)$/)&&("min"==a&&d.set("max",e.max,c),e.select&&d.disabled(e.select)&&d.set("select",e.select,c),e.highlight&&d.disabled(e.highlight)&&d.set("highlight",e.highlight,c)),d)},c.prototype.get=function(a){return this.item[a]},c.prototype.create=function(a,c,f){var i=this;return c=void 0===c?a:c,h.isDate(c)&&(c=[c.getHours(),c.getMinutes()]),b.isPlainObject(c)&&h.isInteger(c.pick)?c=c.pick:b.isArray(c)?c=+c[0]*e+ +c[1]:h.isInteger(c)||(c=i.now(a,c,f)),"max"==a&&c<i.item.min.pick&&(c+=g),"min"!=a&&"max"!=a&&(c-i.item.min.pick)%i.item.interval!==0&&(c+=i.item.interval),c=i.normalize(a,c,f),{hour:~~(d+c/e)%d,mins:(e+c%e)%e,time:(g+c)%g,pick:c}},c.prototype.createRange=function(a,c){var d=this,e=function(a){return a===!0||b.isArray(a)||h.isDate(a)?d.create(a):a};return h.isInteger(a)||(a=e(a)),h.isInteger(c)||(c=e(c)),h.isInteger(a)&&b.isPlainObject(c)?a=[c.hour,c.mins+a*d.settings.interval]:h.isInteger(c)&&b.isPlainObject(a)&&(c=[a.hour,a.mins+c*d.settings.interval]),{from:e(a),to:e(c)}},c.prototype.withinRange=function(a,b){return a=this.createRange(a.from,a.to),b.pick>=a.from.pick&&b.pick<=a.to.pick},c.prototype.overlapRanges=function(a,b){var c=this;return a=c.createRange(a.from,a.to),b=c.createRange(b.from,b.to),c.withinRange(a,b.from)||c.withinRange(a,b.to)||c.withinRange(b,a.from)||c.withinRange(b,a.to)},c.prototype.now=function(a,b){var c,d=this.item.interval,f=new Date,g=f.getHours()*e+f.getMinutes(),i=h.isInteger(b);return g-=g%d,c=0>b&&-d>=d*b+g,g+="min"==a&&c?0:d,i&&(g+=d*(c&&"max"!=a?b+1:b)),g},c.prototype.normalize=function(a,b){var c=this.item.interval,d=this.item.min&&this.item.min.pick||0;return b-="min"==a?0:(b-d)%c},c.prototype.measure=function(a,c,f){var g=this;return c?c===!0||h.isInteger(c)?c=g.now(a,c,f):b.isPlainObject(c)&&h.isInteger(c.pick)&&(c=g.normalize(a,c.pick,f)):c="min"==a?[0,0]:[d-1,e-1],c},c.prototype.validate=function(a,b,c){var d=this,e=c&&c.interval?c.interval:d.item.interval;return d.disabled(b)&&(b=d.shift(b,e)),b=d.scope(b),d.disabled(b)&&(b=d.shift(b,-1*e)),b},c.prototype.disabled=function(a){var c=this,d=c.item.disable.filter(function(d){return h.isInteger(d)?a.hour==d:b.isArray(d)||h.isDate(d)?a.pick==c.create(d).pick:b.isPlainObject(d)?c.withinRange(d,a):void 0});return d=d.length&&!d.filter(function(a){return b.isArray(a)&&"inverted"==a[2]||b.isPlainObject(a)&&a.inverted}).length,-1===c.item.enable?!d:d||a.pick<c.item.min.pick||a.pick>c.item.max.pick},c.prototype.shift=function(a,b){var c=this,d=c.item.min.pick,e=c.item.max.pick;for(b=b||c.item.interval;c.disabled(a)&&(a=c.create(a.pick+=b),!(a.pick<=d||a.pick>=e)););return a},c.prototype.scope=function(a){var b=this.item.min.pick,c=this.item.max.pick;return this.create(a.pick>c?c:a.pick<b?b:a)},c.prototype.parse=function(a,b,c){var d,f,g,i,j,k=this,l={};if(!b||"string"!=typeof b)return b;c&&c.format||(c=c||{},c.format=k.settings.format),k.formats.toArray(c.format).map(function(a){var c,d=k.formats[a],e=d?h.trigger(d,k,[b,l]):a.replace(/^!/,"").length;d&&(c=b.substr(0,e),l[a]=c.match(/^\d+$/)?+c:c),b=b.substr(e)});for(i in l)j=l[i],h.isInteger(j)?i.match(/^(h|hh)$/i)?(d=j,("h"==i||"hh"==i)&&(d%=12)):"i"==i&&(f=j):i.match(/^a$/i)&&j.match(/^p/i)&&("h"in l||"hh"in l)&&(g=!0);return(g?d+12:d)*e+f},c.prototype.formats={h:function(a,b){return a?h.digits(a):b.hour%f||f},hh:function(a,b){return a?2:h.lead(b.hour%f||f)},H:function(a,b){return a?h.digits(a):""+b.hour%24},HH:function(a,b){return a?h.digits(a):h.lead(b.hour%24)},i:function(a,b){return a?2:h.lead(b.mins)},a:function(a,b){return a?4:g/2>b.time%g?"a.m.":"p.m."},A:function(a,b){return a?2:g/2>b.time%g?"AM":"PM"},toArray:function(a){return a.split(/(h{1,2}|H{1,2}|i|a|A|!.)/g)},toString:function(a,b){var c=this;return c.formats.toArray(a).map(function(a){return h.trigger(c.formats[a],c,[0,b])||a.replace(/^!/,"")}).join("")}},c.prototype.isTimeExact=function(a,c){var d=this;return h.isInteger(a)&&h.isInteger(c)||"boolean"==typeof a&&"boolean"==typeof c?a===c:(h.isDate(a)||b.isArray(a))&&(h.isDate(c)||b.isArray(c))?d.create(a).pick===d.create(c).pick:b.isPlainObject(a)&&b.isPlainObject(c)?d.isTimeExact(a.from,c.from)&&d.isTimeExact(a.to,c.to):!1},c.prototype.isTimeOverlap=function(a,c){var d=this;return h.isInteger(a)&&(h.isDate(c)||b.isArray(c))?a===d.create(c).hour:h.isInteger(c)&&(h.isDate(a)||b.isArray(a))?c===d.create(a).hour:b.isPlainObject(a)&&b.isPlainObject(c)?d.overlapRanges(a,c):!1},c.prototype.flipEnable=function(a){var b=this.item;b.enable=a||(-1==b.enable?1:-1)},c.prototype.deactivate=function(a,c){var d=this,e=d.item.disable.slice(0);return"flip"==c?d.flipEnable():c===!1?(d.flipEnable(1),e=[]):c===!0?(d.flipEnable(-1),e=[]):c.map(function(a){for(var c,f=0;f<e.length;f+=1)if(d.isTimeExact(a,e[f])){c=!0;break}c||(h.isInteger(a)||h.isDate(a)||b.isArray(a)||b.isPlainObject(a)&&a.from&&a.to)&&e.push(a)}),e},c.prototype.activate=function(a,c){var d=this,e=d.item.disable,f=e.length;return"flip"==c?d.flipEnable():c===!0?(d.flipEnable(1),e=[]):c===!1?(d.flipEnable(-1),e=[]):c.map(function(a){var c,g,i,j;for(i=0;f>i;i+=1){if(g=e[i],d.isTimeExact(g,a)){c=e[i]=null,j=!0;break}if(d.isTimeOverlap(g,a)){b.isPlainObject(a)?(a.inverted=!0,c=a):b.isArray(a)?(c=a,c[2]||c.push("inverted")):h.isDate(a)&&(c=[a.getFullYear(),a.getMonth(),a.getDate(),"inverted"]);break}}if(c)for(i=0;f>i;i+=1)if(d.isTimeExact(e[i],a)){e[i]=null;break}if(j)for(i=0;f>i;i+=1)if(d.isTimeOverlap(e[i],a)){e[i]=null;break}c&&e.push(c)}),e.filter(function(a){return null!=a})},c.prototype.i=function(a,b){return h.isInteger(b)&&b>0?b:this.item.interval},c.prototype.nodes=function(a){var b=this,c=b.settings,d=b.item.select,e=b.item.highlight,f=b.item.view,g=b.item.disable;return h.node("ul",h.group({min:b.item.min.pick,max:b.item.max.pick,i:b.item.interval,node:"li",item:function(a){a=b.create(a);var i=a.pick,j=d&&d.pick==i,k=e&&e.pick==i,l=g&&b.disabled(a);return[h.trigger(b.formats.toString,b,[h.trigger(c.formatLabel,b,[a])||c.format,a]),function(a){return j&&a.push(c.klass.selected),k&&a.push(c.klass.highlighted),f&&f.pick==i&&a.push(c.klass.viewset),l&&a.push(c.klass.disabled),a.join(" ")}([c.klass.listItem]),"data-pick="+a.pick+" "+h.ariaAttr({role:"option",selected:j&&b.$node.val()===h.trigger(b.formats.toString,b,[c.format,a])?!0:null,activedescendant:k?!0:null,disabled:l?!0:null})]}})+h.node("li",h.node("button",c.clear,c.klass.buttonClear,"type=button data-clear=1"+(a?"":" disabled")+" "+h.ariaAttr({controls:b.$node[0].id})),"",h.ariaAttr({role:"presentation"})),c.klass.list,h.ariaAttr({role:"listbox",controls:b.$node[0].id}))},c.defaults=function(a){return{clear:"Clear",format:"h:i A",interval:30,klass:{picker:a+" "+a+"--time",holder:a+"__holder",list:a+"__list",listItem:a+"__list-item",disabled:a+"__list-item--disabled",selected:a+"__list-item--selected",highlighted:a+"__list-item--highlighted",viewset:a+"__list-item--viewset",now:a+"__list-item--now",buttonClear:a+"__button--clear"}}}(a.klasses().picker),a.extend("pickatime",c)});
lib/simple-admin-pages/lib/pickadate/themes/default.css ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ .picker{font-size:16px;text-align:left;line-height:1.2;color:#000;position:absolute;z-index:10000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.picker__input{cursor:default}.picker__input.picker__input--active{border-color:#0089ec}.picker__holder{width:100%;overflow-y:auto;-webkit-overflow-scrolling:touch}/*!
2
+ * Default mobile-first, responsive styling for pickadate.js
3
+ * Demo: http://amsul.github.io/pickadate.js
4
+ */.picker__frame,.picker__holder{bottom:0;left:0;right:0;top:100%}.picker__holder{position:fixed;-webkit-transition:background .15s ease-out,top 0s .15s;-moz-transition:background .15s ease-out,top 0s .15s;transition:background .15s ease-out,top 0s .15s}.picker__frame{position:absolute;margin:0 auto;min-width:256px;max-width:666px;width:100%;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);-moz-opacity:0;opacity:0;-webkit-transition:all .15s ease-out;-moz-transition:all .15s ease-out;transition:all .15s ease-out}@media (min-height:33.875em){.picker__frame{overflow:visible;top:auto;bottom:-100%;max-height:80%}}@media (min-height:40.125em){.picker__frame{margin-bottom:7.5%}}.picker__wrap{display:table;width:100%;height:100%}@media (min-height:33.875em){.picker__wrap{display:block}}.picker__box{background:#fff;display:table-cell;vertical-align:middle}@media (min-height:26.5em){.picker__box{font-size:1.25em}}@media (min-height:33.875em){.picker__box{display:block;font-size:1.33em;border:1px solid #777;border-top-color:#898989;border-bottom-width:0;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;-webkit-box-shadow:0 12px 36px 16px rgba(0,0,0,.24);-moz-box-shadow:0 12px 36px 16px rgba(0,0,0,.24);box-shadow:0 12px 36px 16px rgba(0,0,0,.24)}}@media (min-height:40.125em){.picker__box{font-size:1.5em;border-bottom-width:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}}.picker--opened .picker__holder{top:0;zoom:1;background:rgba(0,0,0,.32);-webkit-transition:background .15s ease-out;-moz-transition:background .15s ease-out;transition:background .15s ease-out}.picker--opened .picker__frame{top:0;-ms-filter:"alpha(Opacity=100)";filter:alpha(opacity=100);-moz-opacity:1;opacity:1}@media (min-height:33.875em){.picker--opened .picker__frame{top:auto;bottom:0}}
lib/simple-admin-pages/lib/pickadate/themes/default.date.css ADDED
@@ -0,0 +1 @@
 
1
+ .picker__box{padding:0 1em}.picker__header{text-align:center;position:relative;margin-top:.75em}.picker__month,.picker__year{font-weight:500;display:inline-block;margin-left:.25em;margin-right:.25em}.picker__year{color:#999;font-size:.8em;font-style:italic}.picker__select--month,.picker__select--year{border:1px solid #b7b7b7;height:2em;padding:.5em;margin-left:.25em;margin-right:.25em}@media (min-width:24.5em){.picker__select--month,.picker__select--year{margin-top:-.5em}}.picker__select--month{width:35%}.picker__select--year{width:22.5%}.picker__select--month:focus,.picker__select--year:focus{border-color:#0089ec}.picker__nav--next,.picker__nav--prev{position:absolute;padding:.5em 1.25em;width:1em;height:1em;top:-.25em}@media (min-width:24.5em){.picker__nav--next,.picker__nav--prev{top:-.33em}}.picker__nav--prev{left:-1em;padding-right:1.25em}@media (min-width:24.5em){.picker__nav--prev{padding-right:1.5em}}.picker__nav--next{right:-1em;padding-left:1.25em}@media (min-width:24.5em){.picker__nav--next{padding-left:1.5em}}.picker__nav--next:before,.picker__nav--prev:before{content:" ";border-top:.5em solid transparent;border-bottom:.5em solid transparent;border-right:.75em solid #000;width:0;height:0;display:block;margin:0 auto}.picker__nav--next:before{border-right:0;border-left:.75em solid #000}.picker__nav--next:hover,.picker__nav--prev:hover{cursor:pointer;color:#000;background:#b1dcfb}.picker__nav--disabled,.picker__nav--disabled:before,.picker__nav--disabled:before:hover,.picker__nav--disabled:hover{cursor:default;background:0 0;border-right-color:#f5f5f5;border-left-color:#f5f5f5}.picker__table{text-align:center;border-collapse:collapse;border-spacing:0;table-layout:fixed;font-size:inherit;width:100%;margin-top:.75em;margin-bottom:.5em}@media (min-height:33.875em){.picker__table{margin-bottom:.75em}}.picker__table td{margin:0;padding:0}.picker__weekday{width:14.285714286%;font-size:.75em;padding-bottom:.25em;color:#999;font-weight:500}@media (min-height:33.875em){.picker__weekday{padding-bottom:.5em}}.picker__day{padding:.3125em 0;font-weight:200;border:1px solid transparent}.picker__day--today{color:#0089ec;position:relative}.picker__day--today:before{content:" ";position:absolute;top:2px;right:2px;width:0;height:0;border-top:.5em solid #0059bc;border-left:.5em solid transparent}.picker__day--selected,.picker__day--selected:hover{border-color:#0089ec}.picker__day--highlighted{background:#b1dcfb}.picker__day--disabled:before{border-top-color:#aaa}.picker__day--outfocus{color:#ddd}.picker__day--infocus:hover,.picker__day--outfocus:hover{cursor:pointer;color:#000;background:#b1dcfb}.picker--focused .picker__day--highlighted,.picker__day--highlighted:hover{background:#0089ec;color:#fff}.picker__day--disabled,.picker__day--disabled:hover{background:#f5f5f5;border-color:#f5f5f5;color:#ddd;cursor:default}.picker__day--highlighted.picker__day--disabled,.picker__day--highlighted.picker__day--disabled:hover{background:#bbb}.picker__footer{text-align:center}.picker__button--clear,.picker__button--today{border:1px solid #fff;background:#fff;font-size:.8em;padding:.66em 0;font-weight:700;width:50%;display:inline-block;vertical-align:bottom}.picker__button--clear:hover,.picker__button--today:hover{cursor:pointer;color:#000;background:#b1dcfb;border-bottom-color:#b1dcfb}.picker__button--clear:focus,.picker__button--today:focus{background:#b1dcfb;border-color:#0089ec;outline:0}.picker__button--clear:before,.picker__button--today:before{position:relative;display:inline-block;height:0}.picker__button--today:before{content:" ";margin-right:.45em;top:-.05em;width:0;border-top:.66em solid #0059bc;border-left:.66em solid transparent}.picker__button--clear:before{content:"\D7";margin-right:.35em;top:-.1em;color:#e20;vertical-align:top;font-size:1.1em}
lib/simple-admin-pages/lib/pickadate/themes/default.time.css ADDED
@@ -0,0 +1 @@
 
1
+ .picker__list{list-style:none;padding:.75em 0 4.2em;margin:0}.picker__list-item{border-bottom:1px solid #ddd;border-top:1px solid #ddd;margin-bottom:-1px;position:relative;background:#fff;padding:.75em 1.25em}@media (min-height:46.75em){.picker__list-item{padding:.5em 1em}}.picker__list-item:hover{cursor:pointer;color:#000;background:#b1dcfb;border-color:#0089ec;z-index:10}.picker__list-item--selected,.picker__list-item--selected:hover{border-color:#0089ec;z-index:10}.picker__list-item--highlighted{background:#b1dcfb}.picker--focused .picker__list-item--highlighted,.picker__list-item--highlighted:hover{background:#0089ec;color:#fff}.picker--focused .picker__list-item--disabled,.picker__list-item--disabled,.picker__list-item--disabled:hover{background:#f5f5f5;color:#ddd;cursor:default;border-color:#ddd;z-index:auto}.picker--time .picker__button--clear{display:block;width:80%;margin:1em auto 0;padding:1em 1.25em;background:0 0;border:0;font-weight:500;font-size:.67em;text-align:center;text-transform:uppercase;color:#666}.picker--time .picker__button--clear:focus,.picker--time .picker__button--clear:hover{background:#b1dcfb;background:#e20;border-color:#e20;cursor:pointer;color:#fff;outline:0}.picker--time .picker__button--clear:before{top:-.25em;color:#666;font-size:1.25em;font-weight:700}.picker--time .picker__button--clear:focus:before,.picker--time .picker__button--clear:hover:before{color:#fff}.picker--time .picker__frame{min-width:256px;max-width:320px}.picker--time .picker__box{font-size:1em;background:#f2f2f2;padding:0}@media (min-height:40.125em){.picker--time .picker__box{margin-bottom:5em}}
lib/simple-admin-pages/lib/pickadate/themes/rtl.css ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ /*!
2
+ * Styling for RTL (right-to-left) languages using pickadate.js
3
+ */.picker{direction:rtl}.picker__nav--next{right:auto;left:-1em}.picker__nav--prev{left:auto;right:-1em}.picker__nav--next:before{border-left:0;border-right:.75em solid #000}.picker__nav--prev:before{border-right:0;border-left:.75em solid #000}
lib/simple-admin-pages/lib/pickadate/translations/ar.js ADDED
@@ -0,0 +1 @@
 
1
+ $.extend($.fn.pickadate.defaults,{monthsFull:["يناير","فبراير","مارس","ابريل","مايو","يونيو","يوليو","اغسطس","سبتمبر","اكتوبر","نوفمبر","ديسمبر"],monthsShort:["يناير","فبراير","مارس","ابريل","مايو","يونيو","يوليو","اغسطس","سبتمبر","اكتوبر","نوفمبر","ديسمبر"],weekdaysFull:["الاحد","الاثنين","الثلاثاء","الاربعاء","الخميس","الجمعة","السبت"],weekdaysShort:["الاحد","الاثنين","الثلاثاء","الاربعاء","الخميس","الجمعة","السبت"],today:"اليوم",clear:"مسح",format:"yyyy mmmm dd",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/bg_BG.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["януари","февруари","март","април","май","юни","юли","август","септември","октомври","ноември","декември"],monthsShort:["янр","фев","мар","апр","май","юни","юли","авг","сеп","окт","ное","дек"],weekdaysFull:["неделя","понеделник","вторник","сряда","четвъртък","петък","събота"],weekdaysShort:["нд","пн","вт","ср","чт","пт","сб"],today:"днес",clear:"изтривам",firstDay:1,format:"d mmmm yyyy г.",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/bs_BA.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["januar","februar","mart","april","maj","juni","juli","august","septembar","oktobar","novembar","decembar"],monthsShort:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],weekdaysFull:["nedjelja","ponedjeljak","utorak","srijeda","cetvrtak","petak","subota"],weekdaysShort:["ne","po","ut","sr","če","pe","su"],today:"danas",clear:"izbrisati",firstDay:1,format:"dd. mmmm yyyy.",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/ca_ES.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["Gener","Febrer","Març","Abril","Maig","juny","Juliol","Agost","Setembre","Octubre","Novembre","Desembre"],monthsShort:["Gen","Feb","Mar","Abr","Mai","Jun","Jul","Ago","Set","Oct","Nov","Des"],weekdaysFull:["diumenge","dilluns","dimarts","dimecres","dijous","divendres","dissabte"],weekdaysShort:["diu","dil","dim","dmc","dij","div","dis"],today:"avui",clear:"esborrar",firstDay:1,format:"dddd d !de mmmm !de yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/cs_CZ.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["leden","únor","březen","duben","květen","červen","červenec","srpen","září","říjen","listopad","prosinec"],monthsShort:["led","úno","bře","dub","kvě","čer","čvc","srp","zář","říj","lis","pro"],weekdaysFull:["neděle","pondělí","úterý","středa","čtvrtek","pátek","sobota"],weekdaysShort:["ne","po","út","st","čt","pá","so"],today:"dnes",clear:"vymazat",firstDay:1,format:"d. mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/da_DK.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["januar","februar","marts","april","maj","juni","juli","august","september","oktober","november","december"],monthsShort:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],weekdaysFull:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],weekdaysShort:["søn","man","tir","ons","tor","fre","lør"],today:"i dag",clear:"slet",firstDay:1,format:"d. mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/de_DE.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthsShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],weekdaysFull:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],weekdaysShort:["So","Mo","Di","Mi","Do","Fr","Sa"],today:"Heute",clear:"Löschen",firstDay:1,format:"dddd, dd. mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/el_GR.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],monthsShort:["Ιαν","Φεβ","Μαρ","Απρ","Μαι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ"],weekdaysFull:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο"],weekdaysShort:["Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ"],today:"σήμερα",clear:"Διαγραφή",firstDay:1,format:"d mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/es_ES.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthsShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],weekdaysFull:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],weekdaysShort:["dom","lun","mar","mié","jue","vie","sáb"],today:"hoy",clear:"borrar",firstDay:1,format:"dddd d !de mmmm !de yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/et_EE.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["jaanuar","veebruar","märts","aprill","mai","juuni","juuli","august","september","oktoober","november","detsember"],monthsShort:["jaan","veebr","märts","apr","mai","juuni","juuli","aug","sept","okt","nov","dets"],weekdaysFull:["pühapäev","esmaspäev","teisipäev","kolmapäev","neljapäev","reede","laupäev"],weekdaysShort:["püh","esm","tei","kol","nel","ree","lau"],today:"täna",clear:"kustutama",firstDay:1,format:"d. mmmm yyyy. a",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/eu_ES.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["urtarrila","otsaila","martxoa","apirila","maiatza","ekaina","uztaila","abuztua","iraila","urria","azaroa","abendua"],monthsShort:["urt","ots","mar","api","mai","eka","uzt","abu","ira","urr","aza","abe"],weekdaysFull:["igandea","astelehena","asteartea","asteazkena","osteguna","ostirala","larunbata"],weekdaysShort:["ig.","al.","ar.","az.","og.","or.","lr."],today:"gaur",clear:"garbitu",firstDay:1,format:"dddd, yyyy(e)ko mmmmren da",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/fi_FI.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["tammikuu","helmikuu","maaliskuu","huhtikuu","toukokuu","kesäkuu","heinäkuu","elokuu","syyskuu","lokakuu","marraskuu","joulukuu"],monthsShort:["tammi","helmi","maalis","huhti","touko","kesä","heinä","elo","syys","loka","marras","joulu"],weekdaysFull:["sunnuntai","maanantai","tiistai","keskiviikko","torstai","perjantai","lauantai"],weekdaysShort:["su","ma","ti","ke","to","pe","la"],today:"tänään",clear:"tyhjennä",firstDay:1,format:"d.m.yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/fr_FR.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"],monthsShort:["Jan","Fev","Mar","Avr","Mai","Juin","Juil","Aou","Sep","Oct","Nov","Dec"],weekdaysFull:["Dimanche","Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi"],weekdaysShort:["Dim","Lun","Mar","Mer","Jeu","Ven","Sam"],today:"Aujourd'hui",clear:"Effacer",firstDay:1,format:"dd mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/gl_ES.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["Xaneiro","Febreiro","Marzo","Abril","Maio","Xuño","Xullo","Agosto","Setembro","Outubro","Novembro","Decembro"],monthsShort:["xan","feb","mar","abr","mai","xun","xul","ago","sep","out","nov","dec"],weekdaysFull:["domingo","luns","martes","mércores","xoves","venres","sábado"],weekdaysShort:["dom","lun","mar","mér","xov","ven","sab"],today:"hoxe",clear:"borrar",firstDay:1,format:"dddd d !de mmmm !de yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/he_IL.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["ינואר","פברואר","מרץ","אפריל","מאי","יוני","יולי","אוגוסט","ספטמבר","אוקטובר","נובמבר","דצמבר"],monthsShort:["ינו","פבר","מרץ","אפר","מאי","יונ","יול","אוג","ספט","אוק","נוב","דצמ"],weekdaysFull:["יום ראשון","יום שני","יום שלישי","יום רביעי","יום חמישי","יום ששי","יום שבת"],weekdaysShort:["א","ב","ג","ד","ה","ו","ש"],today:"היום",clear:"למחוק",format:"yyyy mmmmב d dddd",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/hr_HR.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["sijećanj","veljača","ožujak","travanj","svibanj","lipanj","srpanj","kolovoz","rujan","listopad","studeni","prosinac"],monthsShort:["sij","velj","ožu","tra","svi","lip","srp","kol","ruj","lis","stu","pro"],weekdaysFull:["nedjelja","ponedjeljak","utorak","srijeda","četvrtak","petak","subota"],weekdaysShort:["ned","pon","uto","sri","čet","pet","sub"],today:"danas",clear:"izbrisati",firstDay:1,format:"d. mmmm yyyy.",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/hu_HU.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["január","február","március","aprilis","május","június","július","augusztus","szeptember","október","november","december"],monthsShort:["jan","febr","márc","apr","máj","jún","júl","aug","szept","okt","nov","dec"],weekdaysFull:["vasámap","hétfö","kedd","szerda","csütörtök","péntek","szombat"],weekdaysShort:["V","H","K","SZ","CS","P","SZ"],today:"ma",clear:"töröl",firstDay:1,format:"yyyy. mmmm dd.",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/id_ID.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agu","Sep","Okt","Nov","Des"],weekdaysFull:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],weekdaysShort:["Min","Sen","Sel","Rab","Kam","Jum","Sab"],today:"hari ini",clear:"menghapus",firstDay:1,format:"d mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/is_IS.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["janúar","febrúar","mars","apríl","maí","júní","júlí","ágúst","september","október","nóvember","desember"],monthsShort:["jan","feb","mar","apr","maí","jún","júl","ágú","sep","okt","nóv","des"],weekdaysFull:["sunnudagur","mánudagur","þriðjudagur","miðvikudagur","fimmtudagur","föstudagur","laugardagur"],weekdaysShort:["sun","mán","þri","mið","fim","fös","lau"],today:"Í dag",clear:"Hreinsa",firstDay:1,format:"dd. mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/it_IT.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["gennaio","febbraio","marzo","aprile","maggio","giugno","luglio","agosto","settembre","ottobre","novembre","dicembre"],monthsShort:["gen","feb","mar","apr","mag","giu","lug","ago","set","ott","nov","dic"],weekdaysFull:["domenica","lunedì","martedì","mercoledì","giovedì","venerdì","sabato"],weekdaysShort:["dom","lun","mar","mer","gio","ven","sab"],today:"oggi",clear:"cancellare",firstDay:1,format:"dddd d mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/ja_JP.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],weekdaysFull:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],weekdaysShort:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],today:"今日",clear:"消去",firstDay:1,format:"yyyy mm dd",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/ko_KR.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],monthsShort:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],weekdaysFull:["일요일","월요일","화요일","수요일","목요일","금요일","토요일"],weekdaysShort:["일","월","화","수","목","금","토"],today:"오늘",clear:"취소",firstDay:1,format:"yyyy 년 mm 월 dd 일",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/nl_NL.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthsShort:["jan","feb","maa","apr","mei","jun","jul","aug","sep","okt","nov","dec"],weekdaysFull:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],weekdaysShort:["zo","ma","di","wo","do","vr","za"],today:"vandaag",clear:"verwijderen",firstDay:1,format:"dddd d mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/no_NO.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthsShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],weekdaysFull:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],weekdaysShort:["søn","man","tir","ons","tor","fre","lør"],today:"i dag",clear:"nullstill",firstDay:1,format:"dd. mmm. yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/pl_PL.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["styczeń","luty","marzec","kwiecień","maj","czerwiec","lipiec","sierpień","wrzesień","październik","listopad","grudzień"],monthsShort:["sty","lut","mar","kwi","maj","cze","lip","sie","wrz","paź","lis","gru"],weekdaysFull:["niedziela","poniedziałek","wtorek","środa","czwartek","piątek","sobota"],weekdaysShort:["N","Pn","Wt","Śr","Cz","Pt","So"],today:"dzisiaj",clear:"usunąć",firstDay:1,format:"d mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/pt_BR.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["janeiro","fevereiro","março","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro"],monthsShort:["jan","fev","mar","abr","mai","jun","jul","ago","set","out","nov","dez"],weekdaysFull:["domingo","segunda-feira","terça-feira","quarta-feira","quinta-feira","sexta-feira","sábado"],weekdaysShort:["dom","seg","ter","qua","qui","sex","sab"],today:"hoje",clear:"excluir",format:"dddd, d !de mmmm !de yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/pt_PT.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["janeiro","fevereiro","março","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro"],monthsShort:["jan","fev","mar","abr","mai","jun","jul","ago","set","out","nov","dez"],weekdaysFull:["domingo","segunda","terça","quarta","quinta","sexta","sábado"],weekdaysShort:["dom","seg","ter","qua","qui","sex","sab"],today:"hoje",clear:"excluir",format:"d !de mmmm !de yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/ro_RO.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["ianuarie","februarie","martie","aprilie","mai","iunie","iulie","august","septembrie","octombrie","noiembrie","decembrie"],monthsShort:["ian","feb","mar","apr","mai","iun","iul","aug","sep","oct","noi","dec"],weekdaysFull:["duminică","luni","marţi","miercuri","joi","vineri","sâmbătă"],weekdaysShort:["D","L","Ma","Mi","J","V","S"],today:"azi",clear:"șterge",firstDay:1,format:"dd mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/ru_RU.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["Января","Февраля","Марта","Апреля","Мая","Июня","Июля","Августа","Сентября","Октября","Ноября","Декабря"],monthsShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],weekdaysFull:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],weekdaysShort:["вс","пн","вт","ср","чт","пт","сб"],today:"сегодня",clear:"удалить",firstDay:1,format:"d mmmm yyyy г.",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/sk_SK.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["január","február","marec","apríl","máj","jún","júl","august","september","október","november","december"],monthsShort:["I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII"],weekdaysFull:["nedeļľa","pondelok","utorok","streda","š̌švrtok","piatok","sobota"],weekdaysShort:["Ne","Po","Ut","St","Št","Pi","So"],today:"dnes",clear:"vymazať",firstDay:1,format:"d. mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/sl_SI.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["januar","februar","marec","april","maj","junij","julij","avgust","september","oktober","november","december"],monthsShort:["jan","feb","mar","apr","maj","jun","jul","avg","sep","okt","nov","dec"],weekdaysFull:["nedelja","ponedeljek","torek","sreda","četrtek","petek","sobota"],weekdaysShort:["ned","pon","tor","sre","čet","pet","sob"],today:"danes",clear:"izbriši",firstDay:1,format:"d. mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/sv_SE.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["januari","februari","mars","april","maj","juni","juli","augusti","september","oktober","november","december"],monthsShort:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],weekdaysFull:["söndag","måndag","tisdag","onsdag","torsdag","fredag","lördag"],weekdaysShort:["sön","mån","tis","ons","tor","fre","lör"],today:"i dag",clear:"bort",firstDay:1,format:"d/m yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/th_TH.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthsShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],weekdaysFull:["อาทติย","จันทร","องัคาร","พุธ","พฤหสั บดี","ศกุร","เสาร"],weekdaysShort:["อ.","จ.","อ.","พ.","พฤ.","ศ.","ส."],today:"วันนี้",clear:"ลบ",format:"d mmmm yyyy",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/tr_TR.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthsShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],weekdaysFull:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],weekdaysShort:["Pzr","Pzt","Sal","Çrş","Prş","Cum","Cmt"],today:"bugün",clear:"sil",firstDay:1,format:"dd mmmm yyyy dddd",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/uk_UA.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["січень","лютий","березень","квітень","травень","червень","липень","серпень","вересень","жовтень","листопад","грудень"],monthsShort:["січ","лют","бер","кві","тра","чер","лип","сер","вер","жов","лис","гру"],weekdaysFull:["неділя","понеділок","вівторок","середа","четвер","п‘ятниця","субота"],weekdaysShort:["нд","пн","вт","ср","чт","пт","сб"],today:"сьогодні",clear:"викреслити",firstDay:1,format:"dd mmmm yyyy p.",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/zh_CN.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["一","二","三","四","五","六","七","八","九","十","十一","十二"],weekdaysFull:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],weekdaysShort:["日","一","二","三","四","五","六"],today:"今日",clear:"删",firstDay:1,format:"yyyy 年 mm 月 dd 日",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/lib/pickadate/translations/zh_TW.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery.extend(jQuery.fn.pickadate.defaults,{monthsFull:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["一","二","三","四","五","六","七","八","九","十","十一","十二"],weekdaysFull:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],weekdaysShort:["日","一","二","三","四","五","六"],today:"今天",clear:"清除",firstDay:1,format:"yyyy 年 mm 月 dd 日",formatSubmit:"yyyy/mm/dd"});
lib/simple-admin-pages/simple-admin-pages.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Simple Admin Pages
5
+ *
6
+ * This is a very small utility library to easily add new admin pages to the
7
+ * WordPress admin interface. It simply collects WordPress' useful Settings API
8
+ * into reuseable classes.
9
+ *
10
+ * Created by Nate Wright
11
+ *
12
+ *
13
+ * @since 1.0
14
+ * @package Simple Admin Pages
15
+ * @license GNU GPL 2 or later
16
+ */
17
+
18
+ /**
19
+ * Always load the library files attached to this copy of SAP
20
+ *
21
+ * This fixes a compatibility bug if two versions of the library are being
22
+ * loaded by different plugins/themes. However, versions prior to 2.0 do not
23
+ * include this and can cause compatibility issues.
24
+ */
25
+ require_once( 'classes/Library.class.php' );
26
+
27
+ /**
28
+ * Initialize the appropriate version of the libary.
29
+ *
30
+ * This function should remain backwards compatible at all times, so that the
31
+ * initialization function from version 1.0 will still be able to initialize
32
+ * the library appropriately for version 2.0. This way, if two plugins exist
33
+ * with different versions, the plugin creator will still be able to initialize
34
+ * their library.
35
+ *
36
+ * @since 1.0
37
+ */
38
+ if ( !function_exists( 'sap_initialize_library' ) ) {
39
+
40
+ function sap_initialize_library( $args = array() ) {
41
+
42
+ // Exit early if no version was provided
43
+ if ( !isset( $args['version'] ) ) {
44
+ return null;
45
+ }
46
+
47
+ // Set the textdomain for translation
48
+ if ( !defined( 'SAP_TEXTDOMAIN' ) ) {
49
+ define( 'SAP_TEXTDOMAIN', 'sapdomain' );
50
+ }
51
+
52
+ $lib_class_name = 'sapLibrary_' . str_replace( '.', '_', $args['version'] );
53
+
54
+ return new $lib_class_name( $args );
55
+ }
56
+
57
+ }
readme.txt ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Restaurant Reservations ===
2
+ Contributors: NateWr
3
+ Author URI: https://github.com/NateWr
4
+ Plugin URL: http://themeofthecrop.com
5
+ Requires at Least: 3.8
6
+ Tested Up To: 3.9
7
+ Tags: restaurant, reservations, bookings, table bookings, restaurant reservation, table reservation
8
+ Stable tag: 1.0.1
9
+ License: GPLv2 or later
10
+ Donate link: http://themeofthecrop.com
11
+
12
+ Accept restaurant reservations and table bookings online. Quickly confirm or reject bookings, send email notifications, set booking times and more.
13
+
14
+ == Description ==
15
+
16
+ Accept restaurant reservations and table bookings online. Quickly confirm or reject bookings, send out custom email notifications, restrict booking times and more.
17
+
18
+ * Quickly confirm or reject a booking
19
+ * Admin email notification when a booking request is made
20
+ * Customer email notifications when their request is confirmed or rejected
21
+ * Limit available booking times
22
+ * Custom user role to manage bookings
23
+ * Add your booking form to any page, post or widget area
24
+ * Customize all notification messages, and date and time formats
25
+
26
+ This plugin is under active development. More features will be added to this plugin and addons will be created which extend the functionality or integrate with third-party services. Follow future developments at [Theme of the Crop](http://themeofthecrop.com/?utm_source=Plugin&utm_medium=Plugin%20Description&utm_campaign=Restaurant%20Reservations) or read the Upgrade Notices when you see updates for this plugin in your WordPress admin panel.
27
+
28
+ = How to use =
29
+
30
+ There is a short guide to using the plugin in the /docs/ folder. It can be accessed by following the Help link listed under the plugin on the Plugins page in your WordPress admin area. Not sure where that is? One of the screenshots for this plugin will show you where to find it.
31
+
32
+ = Developers =
33
+
34
+ This plugin is packed with hooks so you can extend it, customize it and rebrand it as needed. Development takes place on [GitHub](https://github.com/NateWr/restaurant-reservations), so fork it up.
35
+
36
+ == Installation ==
37
+
38
+ 1. Unzip `restaurant-reservations.zip`
39
+ 2. Upload the contents of `restaurant-reservations.zip` to the `/wp-content/plugins/` directory
40
+ 3. Activate the plugin through the 'Plugins' menu in WordPress
41
+ 4. Go to Bookings > Settings to set up the page to display your booking form.
42
+
43
+ == Screenshots ==
44
+
45
+ 1. Easily manage bookings. View today's bookings or upcoming bookings at-a-glance. Confirm or reject bookings quickly.
46
+ 2. Easy-to-use booking form with a simple, clear style that is compatible with many themes. Minimal CSS is used to make it easier to style.
47
+ 3. Great, mobile-friendly date and time pickers to make it easy for your customers.
48
+ 4. Clear settings page to get it working how you need.
49
+ 5. Easily adjust when customers can request a booking.
50
+ 6. Customize the admin notification email when a new booking request is made. Add a quick link to confirm or reject a request straight from the email.
51
+ 7. Customize the notification email sent to a user when they make a new booking request.
52
+ 8. Customize the notification email sent to a user when their booking is confirmed. You can also customize the email sent when a booking is rejected.
53
+ 9. Access a short guide from your Plugins list to help you get started quickly.
54
+
55
+ == Changelog ==
56
+
57
+ = 1.0.1 (2014-05-08) =
58
+ * Replace dashicons caret with CSS-only caret in booking form
59
+
60
+ = 1.0 (2014-05-07) =
61
+ * Initial release
restaurant-reservations.php ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Plugin Name: Restaurant Reservations
4
+ * Plugin URI: http://themeofthecrop.com
5
+ * Description: Accept restaurant reservations and bookings online.
6
+ * Version: 1.0.1
7
+ * Author: Theme of the Crop
8
+ * Author URI: http://themeofthecrop.com
9
+ * License: GNU General Public License v2.0 or later
10
+ * License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
+ * Requires at least: 3.8
12
+ * Tested up to: 3.9
13
+ *
14
+ * Text Domain: rtbdomain
15
+ * Domain Path: /languages/
16
+ *
17
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
18
+ * General Public License as published by the Free Software Foundation; either version 2 of the License,
19
+ * or (at your option) any later version.
20
+ *
21
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
22
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23
+ *
24
+ * You should have received a copy of the GNU General Public License along with this program; if not, write
25
+ * to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
+ */
27
+ if ( ! defined( 'ABSPATH' ) )
28
+ exit;
29
+
30
+ if ( !class_exists( 'rtbInit' ) ) {
31
+ class rtbInit {
32
+
33
+ /**
34
+ * Set a flag which tracks whether the form has already been rendered on
35
+ * the page. Only one form per page for now.
36
+ * @todo support multiple forms per page
37
+ */
38
+ public $form_rendered = false;
39
+
40
+ /**
41
+ * Initialize the plugin and register hooks
42
+ */
43
+ public function __construct() {
44
+
45
+ // Common strings
46
+ define( 'RTB_TEXTDOMAIN', 'rtbdomain' );
47
+ define( 'RTB_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
48
+ define( 'RTB_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
49
+ define( 'RTB_PLUGIN_FNAME', plugin_basename( __FILE__ ) );
50
+ define( 'RTB_BOOKING_POST_TYPE', 'rtb-booking' );
51
+ define( 'RTB_BOOKING_POST_TYPE_SLUG', 'booking' );
52
+ define( 'RTB_LOAD_FRONTEND_ASSETS', apply_filters( 'rtb-load-frontend-assets', true ) );
53
+
54
+
55
+ // Initialize the plugin
56
+ add_action( 'init', array( $this, 'load_textdomain' ) );
57
+
58
+ // Add custom roles and capabilities
59
+ add_action( 'init', array( $this, 'add_roles' ) );
60
+
61
+ // Load custom post types
62
+ require_once( RTB_PLUGIN_DIR . '/includes/CustomPostTypes.class.php' );
63
+ $this->cpts = new rtbCustomPostTypes();
64
+
65
+ // Add the admin menu
66
+ add_action( 'admin_menu', array( $this, 'add_menu_page' ) );
67
+
68
+ // Flush the rewrite rules for the custom post types
69
+ register_activation_hook( __FILE__, array( $this, 'rewrite_flush' ) );
70
+
71
+ // Load the template functions which print the booking form, etc
72
+ require_once( RTB_PLUGIN_DIR . '/includes/template-functions.php' );
73
+
74
+ // Load assets
75
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
76
+ add_action( 'wp_enqueue_scripts', array( $this, 'register_assets' ) );
77
+
78
+ // Handle notifications
79
+ require_once( RTB_PLUGIN_DIR . '/includes/Notifications.class.php' );
80
+ $this->notifications = new rtbNotifications();
81
+
82
+ // Load settings
83
+ require_once( RTB_PLUGIN_DIR . '/includes/Settings.class.php' );
84
+ $this->settings = new rtbSettings();
85
+
86
+ // Append booking form to a post's $content variable
87
+ add_filter( 'the_content', array( $this, 'append_to_content' ) );
88
+
89
+ // Register the widget
90
+ add_action( 'widgets_init', array( $this, 'register_widgets' ) );
91
+
92
+ // Add links to plugin listing
93
+ add_filter('plugin_action_links', array( $this, 'plugin_action_links' ), 10, 2);
94
+
95
+ // Development tool
96
+ // @todo maybe split off this sort of thing to another file
97
+ add_action( 'init', array( $this, 'dev_add_bookings_data' ) );
98
+
99
+ }
100
+
101
+ /**
102
+ * Flush the rewrite rules when this plugin is activated to update with
103
+ * custom post types
104
+ * @since 0.0.1
105
+ */
106
+ public function rewrite_flush() {
107
+ $this->cpts->load_cpts();
108
+ flush_rewrite_rules();
109
+ }
110
+
111
+ /**
112
+ * Load the plugin textdomain for localistion
113
+ * @since 0.0.1
114
+ */
115
+ public function load_textdomain() {
116
+ load_plugin_textdomain( RTB_TEXTDOMAIN, false, plugin_basename( dirname( __FILE__ ) ) . "/languages" );
117
+ }
118
+
119
+ /**
120
+ * Add a role to manage the bookings and add the capability to Editors,
121
+ * Administrators and Super Admins
122
+ * @since 0.0.1
123
+ */
124
+ public function add_roles() {
125
+
126
+ // The booking manager should be able to access the bookings list and
127
+ // update booking statuses, but shouldn't be able to touch anything else
128
+ // in the account.
129
+ $booking_manager = add_role(
130
+ 'rtb_booking_manager',
131
+ __( 'Booking Manager', RTB_TEXTDOMAIN ),
132
+ array(
133
+ 'read' => true,
134
+ 'manage_bookings' => true,
135
+ )
136
+ );
137
+
138
+ $manage_bookings_roles = apply_filters(
139
+ 'rtb_manage_bookings_roles',
140
+ array(
141
+ 'administrator',
142
+ 'editor',
143
+ )
144
+ );
145
+
146
+ global $wp_roles;
147
+ foreach ( $manage_bookings_roles as $role ) {
148
+ $wp_roles->add_cap( $role, 'manage_bookings' );
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Add the top-level admin menu page
154
+ * @since 0.0.1
155
+ */
156
+ public function add_menu_page() {
157
+
158
+ add_menu_page(
159
+ _x( 'Bookings', 'Title of admin page that lists bookings', RTB_TEXTDOMAIN ),
160
+ _x( 'Bookings', 'Title of bookings admin menu item', RTB_TEXTDOMAIN ),
161
+ 'manage_bookings',
162
+ 'rtb-bookings',
163
+ array( $this, 'show_admin_bookings_page' ),
164
+ 'dashicons-calendar',
165
+ '26.2987'
166
+ );
167
+
168
+ }
169
+
170
+ /**
171
+ * Display the admin bookings page
172
+ * @since 0.0.1
173
+ */
174
+ public function show_admin_bookings_page() {
175
+
176
+ require_once( RTB_PLUGIN_DIR . '/includes/WP_List_Table.BookingsTable.class.php' );
177
+ $bookings_table = new rtbBookingsTable();
178
+ $bookings_table->prepare_items();
179
+ ?>
180
+
181
+ <div class="wrap">
182
+ <h2><?php _e( 'Restaurant Bookings', RTB_TEXTDOMAIN ); ?></h2>
183
+ <?php do_action( 'rtb_bookings_table_top' ); ?>
184
+ <form id="rtb-bookings-table" method="POST" action="">
185
+ <input type="hidden" name="post_type" value="<?php echo RTB_BOOKING_POST_TYPE; ?>" />
186
+ <input type="hidden" name="page" value="rtb-bookings">
187
+
188
+ <?php $bookings_table->views(); ?>
189
+ <?php $bookings_table->advanced_filters(); ?>
190
+ <?php $bookings_table->display(); ?>
191
+ </form>
192
+ <?php do_action( 'rtb_bookings_table_btm' ); ?>
193
+ </div>
194
+
195
+ <?php
196
+ }
197
+
198
+ /**
199
+ * Append booking form to a post's $content variable
200
+ * @since 0.0.1
201
+ */
202
+ function append_to_content( $content ) {
203
+
204
+ if ( !is_main_query() || !in_the_loop() ) {
205
+ return $content;
206
+ }
207
+
208
+ $booking_page = $this->settings->get_setting( 'booking-page' );
209
+ if ( empty( $booking_page ) ) {
210
+ return $content;
211
+ }
212
+
213
+ global $post;
214
+ if ( $post->ID !== $this->settings->get_setting( 'booking-page' ) ) {
215
+ return $content;
216
+ }
217
+
218
+ return $content . rtb_print_booking_form();
219
+ }
220
+
221
+ /**
222
+ * Development tool to populate the database with lots of bookings
223
+ * @since 0.0.1
224
+ */
225
+ public function dev_add_bookings_data() {
226
+
227
+ if ( !WP_DEBUG || !isset( $_GET['rtb_devmode'] ) || $_GET['rtb_devmode'] !== 'add_bookings' ) {
228
+ return;
229
+ }
230
+
231
+ $lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat consequat diam, in tincidunt purus convallis vel. Morbi sed dapibus diam. Vestibulum laoreet mi at neque varius consequat. Nam non mi erat. Donec nec semper velit. Maecenas id tortor orci. Aenean viverra suscipit urna, egestas adipiscing felis varius vitae. Curabitur et accumsan turpis. Suspendisse sed risus ac mi lobortis aliquam vel vel dolor. Nulla facilisi. In feugiat tempus massa, sed pulvinar neque bibendum ut. Nullam nibh eros, consectetur et orci non, condimentum tempor nunc. Maecenas sit amet libero sed diam pulvinar iaculis eget vitae odio. Quisque ac luctus metus, sit amet fringilla magna. Aliquam commodo odio eu eros imperdiet, ut auctor odio faucibus.';
232
+ $words = explode( ' ', str_replace( array( ',', '.'), '', $lorem ) );
233
+ for ( $i = 0; $i < 100; $i++ ) {
234
+
235
+ shuffle( $words );
236
+
237
+ $phone = '(';
238
+ for( $p = 0; $p < 3; $p++ ) {
239
+ $phone .= rand(0,9);
240
+ }
241
+ $phone .= ') ';
242
+ for( $p = 0; $p < 3; $p++ ) {
243
+ $phone .= rand(0,9);
244
+ }
245
+ $phone .= '-';
246
+ for( $p = 0; $p < 4; $p++ ) {
247
+ $phone .= rand(0,9);
248
+ }
249
+
250
+ $status = rand(0, 100) > 30 ? 'confirmed' : 'pending';
251
+ $status = rand(0, 100) > 90 ? 'closed' : $status;
252
+
253
+ // Get the date formatted for MySQL
254
+ $date = new DateTime( date('Y-m-d H:i:s', current_time() + rand( 0, 7776000 ) ) ); // 90 days in advance
255
+
256
+ $id = wp_insert_post(
257
+ array(
258
+ 'post_type' => RTB_BOOKING_POST_TYPE,
259
+ 'post_title' => $words[0] . ' ' . $words[1],
260
+ 'post_content' => rand(0,10) < 3 ? $lorem : '',
261
+ 'post_date' => $date->format( 'Y-m-d H:i:s' ),
262
+ 'post_status' => $status,
263
+ )
264
+ );
265
+
266
+ $meta = array(
267
+ 'party' => rand(1, 20),
268
+ 'email' => $words[2] . '@email.com',
269
+ 'phone' => $phone,
270
+ 'date_submission' => current_time(), // keep track of when it was submitted for logs
271
+ );
272
+
273
+ $meta = apply_filters( 'rtb_sanitize_post_metadata_devmode', $meta, $id );
274
+
275
+ update_post_meta( $id, 'rtb', $meta );
276
+
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Enqueue the admin-only CSS and Javascript
282
+ * @since 0.0.1
283
+ */
284
+ public function enqueue_admin_assets() {
285
+
286
+ $screen = get_current_screen();
287
+ if ( $screen->base == 'toplevel_page_rtb-bookings' || $screen->base == 'bookings_page_rtb-settings' ) {
288
+ wp_enqueue_style( 'rtb-admin', RTB_PLUGIN_URL . '/assets/css/admin.css' );
289
+ wp_enqueue_script( 'rtb-admin', RTB_PLUGIN_URL . '/assets/js/admin.js', array( 'jquery' ), '', true );
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Register the front-end CSS and Javascript for the booking form
295
+ * @since 0.0.1
296
+ */
297
+ function register_assets() {
298
+
299
+ if ( !RTB_LOAD_FRONTEND_ASSETS ) {
300
+ return;
301
+ }
302
+
303
+ wp_register_style( 'rtb-booking-form', RTB_PLUGIN_URL . '/assets/css/booking-form.css' );
304
+ wp_register_script( 'rtb-booking-form', RTB_PLUGIN_URL . '/assets/js/booking-form.js', array( 'jquery' ) );
305
+ }
306
+
307
+ /**
308
+ * Register the widgets
309
+ * @since 0.0.1
310
+ */
311
+ public function register_widgets() {
312
+ require_once( RTB_PLUGIN_DIR . '/includes/WP_Widget.BookingFormWidget.class.php' );
313
+ register_widget( 'rtbBookingFormWidget' );
314
+ }
315
+
316
+ /**
317
+ * Add links to the plugin listing on the installed plugins page
318
+ * @since 0.0.1
319
+ */
320
+ public function plugin_action_links( $links, $plugin ) {
321
+
322
+ if ( $plugin == RTB_PLUGIN_FNAME ) {
323
+
324
+ $links['help'] = '<a href="' . RTB_PLUGIN_URL . '/docs" title="' . __( 'View the help documentation for Restaurant Reservations', RTB_TEXTDOMAIN ) . '">' . __( 'Help', RTB_TEXTDOMAIN ) . '</a>';
325
+ }
326
+
327
+ return $links;
328
+
329
+ }
330
+
331
+ }
332
+ } // endif;
333
+
334
+ $rtb_controller = new rtbInit();
screenshot-1.PNG ADDED
Binary file
screenshot-2.PNG ADDED
Binary file
screenshot-3.PNG ADDED
Binary file
screenshot-4.PNG ADDED
Binary file
screenshot-5.PNG ADDED
Binary file
screenshot-6.PNG ADDED
Binary file
screenshot-7.PNG ADDED
Binary file
screenshot-8.PNG ADDED
Binary file
screenshot-9.PNG ADDED
Binary file