Simple Lightbox - Version 2.9.0

Version Description

  • Add: Support WebP image format
  • Add: Support AVIF image format
  • Add: Documentation link to readme file
  • Optimize: Code cleanup/refactoring
  • Optimize: WPCS validation (Phase 1)
  • Optimize: Activate links after all other filters
  • Optimize: Plugin metadata retrieval
  • Update: Confirm WordPress 6.0 compatibility
  • Update: Build dependencies
  • Update: GitHub issue templates
Download this release

Release Info

Developer Archetyped
Plugin Icon wp plugin Simple Lightbox
Version 2.9.0
Comparing to
See all releases

Code changes from version 2.8.1 to 2.9.0

Files changed (51) hide show
  1. .gitignore +4 -3
  2. CONTRIBUTING.md +28 -28
  3. COPYING +339 -339
  4. Gruntfile.js +49 -49
  5. README.md +11 -11
  6. changelog.txt +356 -343
  7. client/css/admin.css +1 -1
  8. client/css/app.css +1 -1
  9. client/js/dev/lib.admin.js +26 -26
  10. client/js/dev/lib.core.js +933 -933
  11. client/js/dev/lib.view.js +4740 -4740
  12. client/js/prod/lib.admin.js +1 -1
  13. client/js/prod/lib.core.js +1 -1
  14. client/js/prod/lib.view.js +1 -1
  15. client/sass/admin.scss +53 -53
  16. client/sass/app.scss +9 -9
  17. composer.json +8 -8
  18. composer.lock +342 -340
  19. content-handlers/image/js/dev/handler.image.js +32 -32
  20. content-handlers/image/js/prod/handler.image.js +1 -1
  21. controller.php +1891 -1705
  22. functions.php +24 -24
  23. grunt/jshint.js +37 -37
  24. grunt/phplint.js +13 -13
  25. grunt/sass.js +36 -36
  26. grunt/uglify.js +20 -20
  27. grunt/watch.js +56 -56
  28. includes/class-requirements-check.php +168 -168
  29. includes/class.admin.php +686 -661
  30. includes/class.admin_action.php +110 -109
  31. includes/class.admin_menu.php +41 -40
  32. includes/class.admin_page.php +188 -187
  33. includes/class.admin_section.php +53 -51
  34. includes/class.admin_view.php +553 -529
  35. includes/class.base.php +561 -555
  36. includes/class.base_collection.php +369 -369
  37. includes/class.base_object.php +343 -337
  38. includes/class.collection_controller.php +57 -57
  39. includes/class.component.php +135 -135
  40. includes/class.content_handler.php +82 -82
  41. includes/class.content_handlers.php +280 -277
  42. includes/class.field.php +2 -2
  43. includes/class.field_base.php +1202 -1118
  44. includes/class.field_collection.php +842 -768
  45. includes/class.field_type.php +462 -445
  46. includes/class.fields.php +518 -446
  47. includes/class.option.php +185 -179
  48. includes/class.options.php +696 -679
  49. includes/class.template_tag.php +9 -9
  50. includes/class.template_tags.php +122 -122
  51. includes/class.theme.php +0 -54
.gitignore CHANGED
@@ -1,4 +1,5 @@
1
- .vscode
2
- .sass-cache
3
- node_modules/
 
4
  vendor/
1
+ .ds_store
2
+ .vscode
3
+ .sass-cache
4
+ node_modules/
5
  vendor/
CONTRIBUTING.md CHANGED
@@ -1,28 +1,28 @@
1
- # How to contribute
2
-
3
- User feedback is huge. You're experiencing an issue, want a new feature, or just want to shout to the mountains about how much you love Simple Lightbox? Here's how to do it and get the best outcome.
4
-
5
- ## Getting Started
6
-
7
- * Install the [latest version of SLB][latest]
8
- * Get a [GitHub account][gh] if you don't already have one.
9
-
10
- Now you're ready to contribute!
11
-
12
- ## Reporting Issues
13
-
14
- Because of the vast number and variety of sites WordPress powers, your reports are essential to making sure that SLB works everywhere. Sometimes an issue may be particular to your site's setup and sometimes it might be universal to all users. Either way, you can report your issue here.
15
-
16
- Before reporting an issue, please read [Reporting Issues][report-issue] to ensure that you've provided the necessary information for your issue to be properly investigated.
17
-
18
-
19
- ## Additional Resources
20
-
21
- * [Simple Lightbox's Official Page][slb]
22
- * [Simple Lightbox on WordPress.org][slb-wp]
23
-
24
- [slb]: http://archetyped.com/tools/simple-lightbox/ "Simple Lightbox"
25
- [slb-wp]: http://wordpress.org/plugins/simple-lightbox
26
- [latest]: https://github.com/archetyped/simple-lightbox "Simple Lightbox"
27
- [gh]: https://github.com/signup/free "GitHub Signup"
28
- [report-issue]: https://github.com/archetyped/simple-lightbox/wiki/Reporting-Issues "Reporting Issues"
1
+ # How to contribute
2
+
3
+ User feedback is huge. You're experiencing an issue, want a new feature, or just want to shout to the mountains about how much you love Simple Lightbox? Here's how to do it and get the best outcome.
4
+
5
+ ## Getting Started
6
+
7
+ * Install the [latest version of SLB][latest]
8
+ * Get a [GitHub account][gh] if you don't already have one.
9
+
10
+ Now you're ready to contribute!
11
+
12
+ ## Reporting Issues
13
+
14
+ Because of the vast number and variety of sites WordPress powers, your reports are essential to making sure that SLB works everywhere. Sometimes an issue may be particular to your site's setup and sometimes it might be universal to all users. Either way, you can report your issue here.
15
+
16
+ Before reporting an issue, please read [Reporting Issues][report-issue] to ensure that you've provided the necessary information for your issue to be properly investigated.
17
+
18
+
19
+ ## Additional Resources
20
+
21
+ * [Simple Lightbox's Official Page][slb]
22
+ * [Simple Lightbox on WordPress.org][slb-wp]
23
+
24
+ [slb]: http://archetyped.com/tools/simple-lightbox/ "Simple Lightbox"
25
+ [slb-wp]: http://wordpress.org/plugins/simple-lightbox
26
+ [latest]: https://github.com/archetyped/simple-lightbox "Simple Lightbox"
27
+ [gh]: https://github.com/signup/free "GitHub Signup"
28
+ [report-issue]: https://github.com/archetyped/simple-lightbox/wiki/Reporting-Issues "Reporting Issues"
COPYING CHANGED
@@ -1,339 +1,339 @@
1
- GNU GENERAL PUBLIC LICENSE
2
- Version 2, June 1991
3
-
4
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
- Everyone is permitted to copy and distribute verbatim copies
7
- of this license document, but changing it is not allowed.
8
-
9
- Preamble
10
-
11
- The licenses for most software are designed to take away your
12
- freedom to share and change it. By contrast, the GNU General Public
13
- License is intended to guarantee your freedom to share and change free
14
- software--to make sure the software is free for all its users. This
15
- General Public License applies to most of the Free Software
16
- Foundation's software and to any other program whose authors commit to
17
- using it. (Some other Free Software Foundation software is covered by
18
- the GNU Lesser General Public License instead.) You can apply it to
19
- your programs, too.
20
-
21
- When we speak of free software, we are referring to freedom, not
22
- price. Our General Public Licenses are designed to make sure that you
23
- have the freedom to distribute copies of free software (and charge for
24
- this service if you wish), that you receive source code or can get it
25
- if you want it, that you can change the software or use pieces of it
26
- in new free programs; and that you know you can do these things.
27
-
28
- To protect your rights, we need to make restrictions that forbid
29
- anyone to deny you these rights or to ask you to surrender the rights.
30
- These restrictions translate to certain responsibilities for you if you
31
- distribute copies of the software, or if you modify it.
32
-
33
- For example, if you distribute copies of such a program, whether
34
- gratis or for a fee, you must give the recipients all the rights that
35
- you have. You must make sure that they, too, receive or can get the
36
- source code. And you must show them these terms so they know their
37
- rights.
38
-
39
- We protect your rights with two steps: (1) copyright the software, and
40
- (2) offer you this license which gives you legal permission to copy,
41
- distribute and/or modify the software.
42
-
43
- Also, for each author's protection and ours, we want to make certain
44
- that everyone understands that there is no warranty for this free
45
- software. If the software is modified by someone else and passed on, we
46
- want its recipients to know that what they have is not the original, so
47
- that any problems introduced by others will not reflect on the original
48
- authors' reputations.
49
-
50
- Finally, any free program is threatened constantly by software
51
- patents. We wish to avoid the danger that redistributors of a free
52
- program will individually obtain patent licenses, in effect making the
53
- program proprietary. To prevent this, we have made it clear that any
54
- patent must be licensed for everyone's free use or not licensed at all.
55
-
56
- The precise terms and conditions for copying, distribution and
57
- modification follow.
58
-
59
- GNU GENERAL PUBLIC LICENSE
60
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
-
62
- 0. This License applies to any program or other work which contains
63
- a notice placed by the copyright holder saying it may be distributed
64
- under the terms of this General Public License. The "Program", below,
65
- refers to any such program or work, and a "work based on the Program"
66
- means either the Program or any derivative work under copyright law:
67
- that is to say, a work containing the Program or a portion of it,
68
- either verbatim or with modifications and/or translated into another
69
- language. (Hereinafter, translation is included without limitation in
70
- the term "modification".) Each licensee is addressed as "you".
71
-
72
- Activities other than copying, distribution and modification are not
73
- covered by this License; they are outside its scope. The act of
74
- running the Program is not restricted, and the output from the Program
75
- is covered only if its contents constitute a work based on the
76
- Program (independent of having been made by running the Program).
77
- Whether that is true depends on what the Program does.
78
-
79
- 1. You may copy and distribute verbatim copies of the Program's
80
- source code as you receive it, in any medium, provided that you
81
- conspicuously and appropriately publish on each copy an appropriate
82
- copyright notice and disclaimer of warranty; keep intact all the
83
- notices that refer to this License and to the absence of any warranty;
84
- and give any other recipients of the Program a copy of this License
85
- along with the Program.
86
-
87
- You may charge a fee for the physical act of transferring a copy, and
88
- you may at your option offer warranty protection in exchange for a fee.
89
-
90
- 2. You may modify your copy or copies of the Program or any portion
91
- of it, thus forming a work based on the Program, and copy and
92
- distribute such modifications or work under the terms of Section 1
93
- above, provided that you also meet all of these conditions:
94
-
95
- a) You must cause the modified files to carry prominent notices
96
- stating that you changed the files and the date of any change.
97
-
98
- b) You must cause any work that you distribute or publish, that in
99
- whole or in part contains or is derived from the Program or any
100
- part thereof, to be licensed as a whole at no charge to all third
101
- parties under the terms of this License.
102
-
103
- c) If the modified program normally reads commands interactively
104
- when run, you must cause it, when started running for such
105
- interactive use in the most ordinary way, to print or display an
106
- announcement including an appropriate copyright notice and a
107
- notice that there is no warranty (or else, saying that you provide
108
- a warranty) and that users may redistribute the program under
109
- these conditions, and telling the user how to view a copy of this
110
- License. (Exception: if the Program itself is interactive but
111
- does not normally print such an announcement, your work based on
112
- the Program is not required to print an announcement.)
113
-
114
- These requirements apply to the modified work as a whole. If
115
- identifiable sections of that work are not derived from the Program,
116
- and can be reasonably considered independent and separate works in
117
- themselves, then this License, and its terms, do not apply to those
118
- sections when you distribute them as separate works. But when you
119
- distribute the same sections as part of a whole which is a work based
120
- on the Program, the distribution of the whole must be on the terms of
121
- this License, whose permissions for other licensees extend to the
122
- entire whole, and thus to each and every part regardless of who wrote it.
123
-
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
281
-
282
- How to Apply These Terms to Your New Programs
283
-
284
- If you develop a new program, and you want it to be of the greatest
285
- possible use to the public, the best way to achieve this is to make it
286
- free software which everyone can redistribute and change under these terms.
287
-
288
- To do so, attach the following notices to the program. It is safest
289
- to attach them to the start of each source file to most effectively
290
- convey the exclusion of warranty; and each file should have at least
291
- the "copyright" line and a pointer to where the full notice is found.
292
-
293
- <one line to give the program's name and a brief idea of what it does.>
294
- Copyright (C) <year> <name of author>
295
-
296
- This program is free software; you can redistribute it and/or modify
297
- it under the terms of the GNU General Public License as published by
298
- the Free Software Foundation; either version 2 of the License, or
299
- (at your option) any later version.
300
-
301
- This program is distributed in the hope that it will be useful,
302
- but WITHOUT ANY WARRANTY; without even the implied warranty of
303
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304
- GNU General Public License for more details.
305
-
306
- You should have received a copy of the GNU General Public License along
307
- with this program; if not, write to the Free Software Foundation, Inc.,
308
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
-
310
- Also add information on how to contact you by electronic and paper mail.
311
-
312
- If the program is interactive, make it output a short notice like this
313
- when it starts in an interactive mode:
314
-
315
- Gnomovision version 69, Copyright (C) year name of author
316
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317
- This is free software, and you are welcome to redistribute it
318
- under certain conditions; type `show c' for details.
319
-
320
- The hypothetical commands `show w' and `show c' should show the appropriate
321
- parts of the General Public License. Of course, the commands you use may
322
- be called something other than `show w' and `show c'; they could even be
323
- mouse-clicks or menu items--whatever suits your program.
324
-
325
- You should also get your employer (if you work as a programmer) or your
326
- school, if any, to sign a "copyright disclaimer" for the program, if
327
- necessary. Here is a sample; alter the names:
328
-
329
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
-
332
- <signature of Ty Coon>, 1 April 1989
333
- Ty Coon, President of Vice
334
-
335
- This General Public License does not permit incorporating your program into
336
- proprietary programs. If your program is a subroutine library, you may
337
- consider it more useful to permit linking proprietary applications with the
338
- library. If this is what you want to do, use the GNU Lesser General
339
- Public License instead of this License.
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 2, June 1991
3
+
4
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
+ Everyone is permitted to copy and distribute verbatim copies
7
+ of this license document, but changing it is not allowed.
8
+
9
+ Preamble
10
+
11
+ The licenses for most software are designed to take away your
12
+ freedom to share and change it. By contrast, the GNU General Public
13
+ License is intended to guarantee your freedom to share and change free
14
+ software--to make sure the software is free for all its users. This
15
+ General Public License applies to most of the Free Software
16
+ Foundation's software and to any other program whose authors commit to
17
+ using it. (Some other Free Software Foundation software is covered by
18
+ the GNU Lesser General Public License instead.) You can apply it to
19
+ your programs, too.
20
+
21
+ When we speak of free software, we are referring to freedom, not
22
+ price. Our General Public Licenses are designed to make sure that you
23
+ have the freedom to distribute copies of free software (and charge for
24
+ this service if you wish), that you receive source code or can get it
25
+ if you want it, that you can change the software or use pieces of it
26
+ in new free programs; and that you know you can do these things.
27
+
28
+ To protect your rights, we need to make restrictions that forbid
29
+ anyone to deny you these rights or to ask you to surrender the rights.
30
+ These restrictions translate to certain responsibilities for you if you
31
+ distribute copies of the software, or if you modify it.
32
+
33
+ For example, if you distribute copies of such a program, whether
34
+ gratis or for a fee, you must give the recipients all the rights that
35
+ you have. You must make sure that they, too, receive or can get the
36
+ source code. And you must show them these terms so they know their
37
+ rights.
38
+
39
+ We protect your rights with two steps: (1) copyright the software, and
40
+ (2) offer you this license which gives you legal permission to copy,
41
+ distribute and/or modify the software.
42
+
43
+ Also, for each author's protection and ours, we want to make certain
44
+ that everyone understands that there is no warranty for this free
45
+ software. If the software is modified by someone else and passed on, we
46
+ want its recipients to know that what they have is not the original, so
47
+ that any problems introduced by others will not reflect on the original
48
+ authors' reputations.
49
+
50
+ Finally, any free program is threatened constantly by software
51
+ patents. We wish to avoid the danger that redistributors of a free
52
+ program will individually obtain patent licenses, in effect making the
53
+ program proprietary. To prevent this, we have made it clear that any
54
+ patent must be licensed for everyone's free use or not licensed at all.
55
+
56
+ The precise terms and conditions for copying, distribution and
57
+ modification follow.
58
+
59
+ GNU GENERAL PUBLIC LICENSE
60
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
+
62
+ 0. This License applies to any program or other work which contains
63
+ a notice placed by the copyright holder saying it may be distributed
64
+ under the terms of this General Public License. The "Program", below,
65
+ refers to any such program or work, and a "work based on the Program"
66
+ means either the Program or any derivative work under copyright law:
67
+ that is to say, a work containing the Program or a portion of it,
68
+ either verbatim or with modifications and/or translated into another
69
+ language. (Hereinafter, translation is included without limitation in
70
+ the term "modification".) Each licensee is addressed as "you".
71
+
72
+ Activities other than copying, distribution and modification are not
73
+ covered by this License; they are outside its scope. The act of
74
+ running the Program is not restricted, and the output from the Program
75
+ is covered only if its contents constitute a work based on the
76
+ Program (independent of having been made by running the Program).
77
+ Whether that is true depends on what the Program does.
78
+
79
+ 1. You may copy and distribute verbatim copies of the Program's
80
+ source code as you receive it, in any medium, provided that you
81
+ conspicuously and appropriately publish on each copy an appropriate
82
+ copyright notice and disclaimer of warranty; keep intact all the
83
+ notices that refer to this License and to the absence of any warranty;
84
+ and give any other recipients of the Program a copy of this License
85
+ along with the Program.
86
+
87
+ You may charge a fee for the physical act of transferring a copy, and
88
+ you may at your option offer warranty protection in exchange for a fee.
89
+
90
+ 2. You may modify your copy or copies of the Program or any portion
91
+ of it, thus forming a work based on the Program, and copy and
92
+ distribute such modifications or work under the terms of Section 1
93
+ above, provided that you also meet all of these conditions:
94
+
95
+ a) You must cause the modified files to carry prominent notices
96
+ stating that you changed the files and the date of any change.
97
+
98
+ b) You must cause any work that you distribute or publish, that in
99
+ whole or in part contains or is derived from the Program or any
100
+ part thereof, to be licensed as a whole at no charge to all third
101
+ parties under the terms of this License.
102
+
103
+ c) If the modified program normally reads commands interactively
104
+ when run, you must cause it, when started running for such
105
+ interactive use in the most ordinary way, to print or display an
106
+ announcement including an appropriate copyright notice and a
107
+ notice that there is no warranty (or else, saying that you provide
108
+ a warranty) and that users may redistribute the program under
109
+ these conditions, and telling the user how to view a copy of this
110
+ License. (Exception: if the Program itself is interactive but
111
+ does not normally print such an announcement, your work based on
112
+ the Program is not required to print an announcement.)
113
+
114
+ These requirements apply to the modified work as a whole. If
115
+ identifiable sections of that work are not derived from the Program,
116
+ and can be reasonably considered independent and separate works in
117
+ themselves, then this License, and its terms, do not apply to those
118
+ sections when you distribute them as separate works. But when you
119
+ distribute the same sections as part of a whole which is a work based
120
+ on the Program, the distribution of the whole must be on the terms of
121
+ this License, whose permissions for other licensees extend to the
122
+ entire whole, and thus to each and every part regardless of who wrote it.
123
+
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
281
+
282
+ How to Apply These Terms to Your New Programs
283
+
284
+ If you develop a new program, and you want it to be of the greatest
285
+ possible use to the public, the best way to achieve this is to make it
286
+ free software which everyone can redistribute and change under these terms.
287
+
288
+ To do so, attach the following notices to the program. It is safest
289
+ to attach them to the start of each source file to most effectively
290
+ convey the exclusion of warranty; and each file should have at least
291
+ the "copyright" line and a pointer to where the full notice is found.
292
+
293
+ <one line to give the program's name and a brief idea of what it does.>
294
+ Copyright (C) <year> <name of author>
295
+
296
+ This program is free software; you can redistribute it and/or modify
297
+ it under the terms of the GNU General Public License as published by
298
+ the Free Software Foundation; either version 2 of the License, or
299
+ (at your option) any later version.
300
+
301
+ This program is distributed in the hope that it will be useful,
302
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
303
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304
+ GNU General Public License for more details.
305
+
306
+ You should have received a copy of the GNU General Public License along
307
+ with this program; if not, write to the Free Software Foundation, Inc.,
308
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
+
310
+ Also add information on how to contact you by electronic and paper mail.
311
+
312
+ If the program is interactive, make it output a short notice like this
313
+ when it starts in an interactive mode:
314
+
315
+ Gnomovision version 69, Copyright (C) year name of author
316
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317
+ This is free software, and you are welcome to redistribute it
318
+ under certain conditions; type `show c' for details.
319
+
320
+ The hypothetical commands `show w' and `show c' should show the appropriate
321
+ parts of the General Public License. Of course, the commands you use may
322
+ be called something other than `show w' and `show c'; they could even be
323
+ mouse-clicks or menu items--whatever suits your program.
324
+
325
+ You should also get your employer (if you work as a programmer) or your
326
+ school, if any, to sign a "copyright disclaimer" for the program, if
327
+ necessary. Here is a sample; alter the names:
328
+
329
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
+
332
+ <signature of Ty Coon>, 1 April 1989
333
+ Ty Coon, President of Vice
334
+
335
+ This General Public License does not permit incorporating your program into
336
+ proprietary programs. If your program is a subroutine library, you may
337
+ consider it more useful to permit linking proprietary applications with the
338
+ library. If this is what you want to do, use the GNU Lesser General
339
+ Public License instead of this License.
Gruntfile.js CHANGED
@@ -1,49 +1,49 @@
1
- module.exports = function(grunt) {
2
- // Load tasks
3
- require('load-grunt-tasks')(grunt);
4
- // Display task timing
5
- require('time-grunt')(grunt);
6
- // Project configuration.
7
- grunt.initConfig({
8
- // Metadata
9
- pkg : grunt.file.readJSON('package.json'),
10
- // Variables
11
- paths : {
12
- // Base dir assets dir
13
- base : 'client',
14
-
15
- // PHP assets
16
- php : {
17
- files_std : ['*.php', '**/*.php', '!node_modules/**/*.php'], // Standard file match
18
- files : '<%= paths.php.files_std %>' // Dynamic file match
19
- },
20
-
21
- // JavaScript assets
22
- js : {
23
- base : 'js', // Base dir
24
- src : '<%= paths.js.base %>/dev', // Development code
25
- dest : '<%= paths.js.base %>/prod', // Production code
26
- files_std : '**/<%= paths.js.src %>/**/*.js', // Standard file match
27
- files : '<%= paths.js.files_std %>' // Dynamic file match
28
- },
29
-
30
- // Sass assets
31
- sass : {
32
- src : 'sass', // Source files dir
33
- dest : 'css', // Compiled files dir
34
- ext : '.css', // Compiled extension
35
- target : '*.scss', // Only Sass files in CWD
36
- exclude : '!_*.scss', // Do not process partials
37
- base_src : '<%= paths.base %>/<%= paths.sass.src %>', // Base source dir
38
- base_dest : '<%= paths.base %>/<%= paths.sass.dest %>', // Base compile dir
39
- }
40
- },
41
- });
42
-
43
- // Load task configurations
44
- grunt.loadTasks('grunt');
45
-
46
- // Default Tasks
47
- grunt.registerTask('build', ['phplint', 'jshint:all', 'uglify', 'sass']);
48
- grunt.registerTask('watch_all', ['watch:js', 'watch:sass']);
49
- };
1
+ module.exports = function(grunt) {
2
+ // Load tasks
3
+ require('load-grunt-tasks')(grunt);
4
+ // Display task timing
5
+ require('time-grunt')(grunt);
6
+ // Project configuration.
7
+ grunt.initConfig({
8
+ // Metadata
9
+ pkg : grunt.file.readJSON('package.json'),
10
+ // Variables
11
+ paths : {
12
+ // Base dir assets dir
13
+ base : 'client',
14
+
15
+ // PHP assets
16
+ php : {
17
+ files_std : ['*.php', '**/*.php', '!node_modules/**/*.php'], // Standard file match
18
+ files : '<%= paths.php.files_std %>' // Dynamic file match
19
+ },
20
+
21
+ // JavaScript assets
22
+ js : {
23
+ base : 'js', // Base dir
24
+ src : '<%= paths.js.base %>/dev', // Development code
25
+ dest : '<%= paths.js.base %>/prod', // Production code
26
+ files_std : '**/<%= paths.js.src %>/**/*.js', // Standard file match
27
+ files : '<%= paths.js.files_std %>' // Dynamic file match
28
+ },
29
+
30
+ // Sass assets
31
+ sass : {
32
+ src : 'sass', // Source files dir
33
+ dest : 'css', // Compiled files dir
34
+ ext : '.css', // Compiled extension
35
+ target : '*.scss', // Only Sass files in CWD
36
+ exclude : '!_*.scss', // Do not process partials
37
+ base_src : '<%= paths.base %>/<%= paths.sass.src %>', // Base source dir
38
+ base_dest : '<%= paths.base %>/<%= paths.sass.dest %>', // Base compile dir
39
+ }
40
+ },
41
+ });
42
+
43
+ // Load task configurations
44
+ grunt.loadTasks('grunt');
45
+
46
+ // Default Tasks
47
+ grunt.registerTask('build', ['jshint:all', 'uglify', 'sass']);
48
+ grunt.registerTask('watch_all', ['watch:js', 'watch:sass']);
49
+ };
README.md CHANGED
@@ -1,11 +1,11 @@
1
- Simple Lightbox
2
- ===============
3
-
4
- The highly customizable lightbox for WordPress
5
-
6
- http://archetyped.com/tools/simple-lightbox/
7
-
8
- ## Support
9
- Found a bug or otherwise experiencing an issue with Simple Lightbox? [Report the issue here][issue-report]
10
-
11
- [issue-report]: https://github.com/archetyped/simple-lightbox/wiki/Reporting-Issues "Report an issue"
1
+ Simple Lightbox
2
+ ===============
3
+
4
+ The highly customizable lightbox for WordPress
5
+
6
+ http://archetyped.com/tools/simple-lightbox/
7
+
8
+ ## Support
9
+ Found a bug or otherwise experiencing an issue with Simple Lightbox? [Report the issue here][issue-report]
10
+
11
+ [issue-report]: https://github.com/archetyped/simple-lightbox/wiki/Reporting-Issues "Report an issue"
changelog.txt CHANGED
@@ -1,344 +1,357 @@
1
- = 2.8.1 =
2
-
3
- * Update: PHP 5.6 Compatibility
4
- * Add: PHPCS configuration
5
- * Add: GitHub Issue templates
6
-
7
- = 2.8.0 =
8
-
9
- * Update: WordPress 5.3+ required.
10
- * Update: PHP 7.2+ required.
11
- * Optimize: Link detection up to 2x faster.
12
- * Optimize: Options data handling.
13
- * Optimize: Default title filtering.
14
- * Optimize: Standardize media item data structure to avoid conflicts with third-party data.
15
- * Optimize: Load only necessary media item properties in browser.
16
- * Optimize: Filter all media items (instead of each individual item).
17
- * Filter Removed: `media_item_properties` (single item).
18
- * Filter Added: `media_items` (all items).
19
- * Fix: `area` elements included in link detection (This is Jim's Area).
20
-
21
- = 2.7.1 =
22
-
23
- * Update: Confirm compatibility with WordPress 5.0+
24
- * Optimize: Improved support for captions generated by Block Editor.
25
-
26
- = 2.7.0 =
27
-
28
- * Fix: Remove reference to deprecated `screen_icon()` function (The Icon of Finnegan Island)
29
- * Add: Validate requirements before initialization.
30
- * Optimize: PHP 7.2+ Compatibility
31
- * Optimize: Internal code optimizations
32
- * Themes
33
- * Add: RTL Support
34
- * Update: Load font locally
35
-
36
- = 2.6.0 =
37
-
38
- * Add: Activate links in native WordPress navigation menus (enable in admin settings)
39
- * Add: Group menu links separately (enable in admin settings)
40
- * Optimize: Fallback lightbox title text retrieval (link text)
41
- * Fix: Undefined variable in `Utilities::get_plugin_base_file()` (The Lost Temple of Xavivars)
42
-
43
- = 2.5.3 =
44
-
45
- * Optimize: Entity handling in URIs for different server environments
46
-
47
- = 2.5.2 =
48
-
49
- * Fix: Activation when Home page set to static page (Lyra's Static Cling)
50
- * Optimize: Prep for WordPress language packs
51
-
52
- = 2.5.1 =
53
-
54
- * Update: Client-side Utilities library
55
- * Optimize: Request processing
56
-
57
- = 2.5.0 =
58
-
59
- * Fix: Query string removed from URI (A Stern Query)
60
- * Optimize: Key-based asset data storage/retrieval
61
- * Optimize: Improved cache usage when processing links
62
- * Optimize: Refactor image URI detection
63
-
64
- = 2.4.1 =
65
-
66
- * Fix: Ungrouped items in empty group (Robert & The Lost Group)
67
- * Fix: IE8 Support (S.Franzis' Legacy)
68
- * Optimize: Widget support
69
- * Optimize: Relative and internal URI handling
70
- * Optimize: Link activation performance
71
-
72
- = 2.4.0 =
73
-
74
- * Update: WordPress version compatibility (v4.2.1)
75
- * Optimize: Standardize code
76
- * Optimize: Do not process excerpt content
77
- * Optimize: Client-side libraries (Phase 1)
78
- * Add: Set group via `slb_activate()`
79
- * Add: Set group via `activate_links()`
80
- * Add: `slb_is_enabled` filter
81
-
82
- = 2.3.1 =
83
-
84
- * Fix: WordPress version requirement
85
- * Optimize: Field collection group parsing
86
-
87
- = 2.3.0 =
88
- [Full Release Notes](http://archetyped.com/lab/slb-2-3-0 "Simple Lightbox 2.3.0")
89
-
90
- * Update: WordPress 3.9 support
91
- * Update: Support URI, content
92
- * Add: Enhanced grouping support
93
- * Add: Shortcode: `[slb_group]`
94
- * Add: Shortcode: `[slb_exclude]`
95
- * Add: Filter: `slb_pre_process_links`
96
- * Add: Filter: `slb_post_process_links`
97
- * Add: Filter: `slb_process_link_attributes`
98
- * Add: Filter: `slb_media_item_properties`
99
- * Add: Filter: `slb_pre_exclude_content`
100
- * Add: Filter: `slb_exclude_shortcodes`
101
- * Add: Filter: `slb_group_shortcodes`
102
- * Add: Template Tag: `slb_activate()` - Manually activate content
103
- * Add: Option to enable/disable usage of WordPress-generated media title
104
- * Add: Dev mode
105
- * Add: Theme breakpoints
106
- * Optimize: Remove deprecated code
107
- * Optimize: Remove deprecated legacy support
108
- * Optimize: Content exclusion performance
109
- * Optimize: Content grouping performance
110
- * Optimize: Harden code against third-party post query modifications
111
- * Optimize: Utility code
112
- * Optimize: Loading process
113
- * Optimize: Client-side code
114
- * Optimize: Client-side: Code loading
115
- * Optimize: Client-side: Simplified dependency detection
116
- * Optimize: Client-side: Default Theme transitions
117
- * Optimize: Grunt: Cleanup
118
- * Optimize: Grunt: Path abstraction
119
- * Optimize: Grunt: Task loading
120
- * Optimize: Grunt: Selective file compilation
121
-
122
- = 2.2.2 =
123
-
124
- * Optimize: Widget processing
125
- * Optimize: Remove call-time-pass-by-references
126
-
127
- = 2.2.1 =
128
-
129
- * Fix: Enable/Disable lightbox on certain requests (Danny the Enabler)
130
- * Fix: Widget links grouped with post links (Rafa's Widgetarian Adventure)
131
- * Optimize: Client-side loading
132
- * Optimize: Theme validation
133
- * Optimize: Widget processing
134
-
135
- = 2.2.0 =
136
-
137
- * Update: WordPress 3.8 support
138
- * Add: Add-on support
139
- * Add: Load external data for item
140
- * Add: Unloading process for viewer
141
- * Add: Relative links marked as "internal"
142
- * Add: Grunt build workflow
143
- * Optimize: Initialization process
144
- * Optimize: Client-side output (JavaScript, CSS)
145
- * Optimize: Improved URI handling (variants, query strings, etc.)
146
- * Optimize: Improved support for content types (video, etc.)
147
- * Optimize: Improved File contents retrieval
148
- * Optimize: Plugin metadata cleanup
149
- * Optimize: Use absolute paths for file includes (props k3davis)
150
-
151
- = 2.1.3 =
152
-
153
- * Fix: PHP configuration issue on some web hosts (Tim's got (config) issues)
154
- * Optimize: Hide overlapping elements when lightbox is displayed (e.g. Flash, etc.)
155
-
156
- = 2.1.2 =
157
-
158
- * Fix: Incorrect paths when WP in subdirectory (Kim's Van Repair)
159
-
160
- = 2.1.1 =
161
-
162
- * Fix: Automatic resizing
163
- * Fix: Compatibility with non-standard wp-content location (On the Path of the Wijdemans)
164
- * Optimize: jQuery dependency handling
165
- * Optimize: Plugin initialization
166
- * Optimize: Deferred component stylesheet loading
167
- * Optimize: Code cleanup
168
-
169
- = 2.1 =
170
-
171
- * Update: Finalized Theme API
172
- * Update: Finalized Content Handler API
173
- * Update: Finalized Template Tag API
174
- * Update: Administration framework
175
- * Add: Baseline theme
176
- * Add: Hook for extending image link matching
177
- * Optimize: Link validation
178
- * Optimize: Intelligent client-side loading
179
- * Optimize: Server-side processing
180
- * Optimize: Default theme display
181
- * Fix: False positive link activation (What's eating Gilbert's links?)
182
- * Fix: Gallery post format compatibility (Just Juan problem with galleries)
183
-
184
- = 2.0 =
185
-
186
- * Completely rewritten lightbox code
187
- * Add: Automatically resize lightbox to fit window
188
- * Add: APIs for third-party add-ons
189
- * Add: Flexible theme support
190
- * Add: Flexible content handler support
191
- * Add: Mobile-optimized responsive themes (2)
192
- * Optimize: PHP class autoloading
193
- * Optimize: Improved performance and compatibility
194
- * Optimize: Full internationalization support
195
-
196
- = 1.6 =
197
-
198
- * Add: Widget support
199
- * Add: WordPress 3.3 support
200
- * Add: Localization support
201
- * Add: Option to group gallery links separately (supports WordPress & NextGen galleries)
202
- * Add: Upgrade notice
203
- * Optimize: WP 3.3 compatibility
204
- * Optimize: Improved compatibility with URI case-sensitivity
205
- * Optimize: Activation processing
206
- * Optimize: Image grouping
207
- * Optimize: Image metadata loading performance
208
- * Optimize: File loading
209
- * Optimize: Improved safeguards against interference by bugs in other plugins
210
- * Optimize: Link processing performance
211
- * Optimize: Lightbox styling isolated from site styles
212
- * Optimize: Improved link processing performance
213
- * Optimize: Improved image metadata support
214
- * Optimize: Improved support for HTTP/HTTPS requests
215
- * Fix: SLB is not defined in JS (Jezz Hands)
216
- * Fix: Boolean case-sensitivity (78 Truths)
217
- * Fix: YouTube embed using iFrame overlaps lightbox (Elena in Hiding)
218
- * Fix: Issue when scanning links without valid URLs (McCloskey Iteration)
219
- * Fix: Image activation is case-sensitive (Sensitive Tanya)
220
- * Fix: Visible lightbox overlay edges when image larger than browser window (Chibi Overlay)
221
- * Fix: Options availability for some users
222
- * Fix: Inconsistent loading of image metadata
223
- * Fix: Links not fully processed when group is set manually
224
-
225
- = 1.5.6 =
226
-
227
- * Add: Display image description in lightbox (with HTML support)
228
- * Add: Support for W3 Total Cache plugin
229
- * Add: Initial support for NextGEN galleries
230
- * Update: **Important:** [System Requirements](http://wordpress.org/about/requirements/) aligned with WP 3.2.1
231
- * Optimize: Improved support for small images in default template
232
- * Optimize: Support for non-English text in user options
233
- * Optimize: Improved IE compatibility
234
- * Optimize: Improved data handling
235
- * Optimize: Skin loading performance
236
- * Optimize: Skin CSS Cleanup
237
- * Optimize: Caption support for galleries
238
- * Optimize: Options code cleanup (Juga Sweep)
239
- * Fix: User-defined UI text not used (Ivan gets Even (cooler))
240
- * Fix: Options reset after update (KRazy Donna)
241
-
242
- = 1.5.5.1 =
243
-
244
- * Fix: Disabled links not being disabled (Disabling Sascha)
245
-
246
- = 1.5.5 =
247
-
248
- * Add: Distinct link activation (will not affect other lightboxes)
249
- * Add: Backwards compatibility with legacy lightbox links (optional)
250
- * Add: Support for WordPress 3.2
251
- * Add: Support for links added after page load (e.g. via AJAX, etc.)
252
- * Add: Admin option to enable/disable attachment links
253
- * Add: Support for image attachment links
254
- * Update: Options management overhaul
255
- * Update: Additional WordPress 3.2 support (Gallery)
256
- * Update: Cache-management for enqueued files
257
- * Update: Improved UI consistency
258
- * Update: Improved compatibility for older versions of PHP
259
- * Update: Internal optimizations
260
- * Update: Improved URL handling
261
- * Fix: Improved options migration from old versions (Hutchison Migration)
262
- * Fix: XHTML Validation (Hajo Validation)
263
-
264
- = 1.5.4 =
265
-
266
- * Add: Optional Link validation
267
- * Add: Keyboard Navigation
268
- * Add: Option to enable/disable image caption
269
- * Add: `rel` attribute supported again
270
- * Add: Use `slb_off` in link's `rel` attribute to disable automatic activation for link
271
- * Fix: HTTPS compatibility (J&uuml;rgen Protocol)
272
- * Fix: Enabling SLB on Pages issue
273
- * Fix: Zmanu is_single
274
- * Fix: Image order is sometimes incorrect
275
- * Optimize: Filter double clicks
276
- * Optimize: Separate options to enable/disable SLB on Posts and Pages
277
- * Optimize: Better grouping support
278
-
279
- = 1.5.3 =
280
-
281
- * Fix: Caption may not display under certain circumstances (Caption Erin)
282
- * Fix: Images not grouped when "separate by post" option is activated (Logical Ross)
283
- * Update: Lightbox will not be activated for links that already have `rel` attribute set
284
-
285
- = 1.5.2 =
286
-
287
- * Fix: Slideshow loops out of control (Mirage of Wallentin)
288
- * Fix: Lightbox fails when group by posts disabled (Lange Find)
289
- * Add: Option to use the image's URI as caption when link title not set (Under UI options)
290
-
291
- = 1.5.1 =
292
-
293
- * Add: WP Gallery support
294
- * Fix: Navigation hidden when only one image
295
- * Fix: Use user-defined UI text
296
-
297
- = 1.5 =
298
-
299
- * Add: Theme support
300
- * Optimize: JavaScript cleanup and file size reductions
301
- * Optimize: CSS cleanup
302
-
303
- = 1.4 =
304
-
305
- * Update: Integrated with jQuery
306
- * Optimize: JavaScript file size 9x smaller
307
- * Add: Close lightbox by clicking to left/right outside of image (an oft-requested feature)
308
-
309
- = 1.3.2 =
310
-
311
- * Add: Option to enable/disable lightbox resizing animation (thanks Maria!)
312
-
313
- = 1.3.1 =
314
-
315
- * Update: Utilities code (internal)
316
-
317
- = 1.3 =
318
-
319
- * Add: Customizable UI label text (close, next, and previous button images can be replaced in `images` directory)
320
- * Add: Group image links by Post (separate slideshow for each post)
321
- * Add: Reset settings link on plugin listings page
322
- * Optimize: Organized settings page
323
-
324
- = 1.2.1 =
325
-
326
- * Fixed: Image title given higher precedence than Image alt (more compatible w/WP workflow)
327
-
328
- = 1.2 =
329
-
330
- * Added: Option to group automatically activated links
331
- * Optimized: Lightbox caption retrieval
332
-
333
- = 1.1 =
334
-
335
- * Added: Enable/disable lightbox functionality by page type (Home, Pages/Posts, Archive, etc.)
336
- * Added: Automatically activate lightbox functionality for image links
337
- * Added: Link to settings menu on plugin listing page
338
- * Optimized: Options menu field building
339
- * Optimized: Loading of default values for plugin options
340
- * Optimized: General code optimizations
341
-
342
- = 1.0 =
343
-
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  * Initial release
1
+ = 2.9.0 =
2
+
3
+ * Add: Support WebP image format
4
+ * Add: Support AVIF image format
5
+ * Add: Documentation link to readme file
6
+ * Optimize: Code cleanup/refactoring
7
+ * Optimize: WPCS validation (Phase 1)
8
+ * Optimize: Activate links after all other filters
9
+ * Optimize: Plugin metadata retrieval
10
+ * Update: Confirm WordPress 6.0 compatibility
11
+ * Update: Build dependencies
12
+ * Update: GitHub issue templates
13
+
14
+ = 2.8.1 =
15
+
16
+ * Update: PHP 5.6 Compatibility
17
+ * Add: PHPCS configuration
18
+ * Add: GitHub Issue templates
19
+
20
+ = 2.8.0 =
21
+
22
+ * Update: WordPress 5.3+ required.
23
+ * Update: PHP 7.2+ required.
24
+ * Optimize: Link detection up to 2x faster.
25
+ * Optimize: Options data handling.
26
+ * Optimize: Default title filtering.
27
+ * Optimize: Standardize media item data structure to avoid conflicts with third-party data.
28
+ * Optimize: Load only necessary media item properties in browser.
29
+ * Optimize: Filter all media items (instead of each individual item).
30
+ * Filter Removed: `media_item_properties` (single item).
31
+ * Filter Added: `media_items` (all items).
32
+ * Fix: `area` elements included in link detection (This is Jim's Area).
33
+
34
+ = 2.7.1 =
35
+
36
+ * Update: Confirm compatibility with WordPress 5.0+
37
+ * Optimize: Improved support for captions generated by Block Editor.
38
+
39
+ = 2.7.0 =
40
+
41
+ * Fix: Remove reference to deprecated `screen_icon()` function (The Icon of Finnegan Island)
42
+ * Add: Validate requirements before initialization.
43
+ * Optimize: PHP 7.2+ Compatibility
44
+ * Optimize: Internal code optimizations
45
+ * Themes
46
+ * Add: RTL Support
47
+ * Update: Load font locally
48
+
49
+ = 2.6.0 =
50
+
51
+ * Add: Activate links in native WordPress navigation menus (enable in admin settings)
52
+ * Add: Group menu links separately (enable in admin settings)
53
+ * Optimize: Fallback lightbox title text retrieval (link text)
54
+ * Fix: Undefined variable in `Utilities::get_plugin_base_file()` (The Lost Temple of Xavivars)
55
+
56
+ = 2.5.3 =
57
+
58
+ * Optimize: Entity handling in URIs for different server environments
59
+
60
+ = 2.5.2 =
61
+
62
+ * Fix: Activation when Home page set to static page (Lyra's Static Cling)
63
+ * Optimize: Prep for WordPress language packs
64
+
65
+ = 2.5.1 =
66
+
67
+ * Update: Client-side Utilities library
68
+ * Optimize: Request processing
69
+
70
+ = 2.5.0 =
71
+
72
+ * Fix: Query string removed from URI (A Stern Query)
73
+ * Optimize: Key-based asset data storage/retrieval
74
+ * Optimize: Improved cache usage when processing links
75
+ * Optimize: Refactor image URI detection
76
+
77
+ = 2.4.1 =
78
+
79
+ * Fix: Ungrouped items in empty group (Robert & The Lost Group)
80
+ * Fix: IE8 Support (S.Franzis' Legacy)
81
+ * Optimize: Widget support
82
+ * Optimize: Relative and internal URI handling
83
+ * Optimize: Link activation performance
84
+
85
+ = 2.4.0 =
86
+
87
+ * Update: WordPress version compatibility (v4.2.1)
88
+ * Optimize: Standardize code
89
+ * Optimize: Do not process excerpt content
90
+ * Optimize: Client-side libraries (Phase 1)
91
+ * Add: Set group via `slb_activate()`
92
+ * Add: Set group via `activate_links()`
93
+ * Add: `slb_is_enabled` filter
94
+
95
+ = 2.3.1 =
96
+
97
+ * Fix: WordPress version requirement
98
+ * Optimize: Field collection group parsing
99
+
100
+ = 2.3.0 =
101
+ [Full Release Notes](http://archetyped.com/lab/slb-2-3-0 "Simple Lightbox 2.3.0")
102
+
103
+ * Update: WordPress 3.9 support
104
+ * Update: Support URI, content
105
+ * Add: Enhanced grouping support
106
+ * Add: Shortcode: `[slb_group]`
107
+ * Add: Shortcode: `[slb_exclude]`
108
+ * Add: Filter: `slb_pre_process_links`
109
+ * Add: Filter: `slb_post_process_links`
110
+ * Add: Filter: `slb_process_link_attributes`
111
+ * Add: Filter: `slb_media_item_properties`
112
+ * Add: Filter: `slb_pre_exclude_content`
113
+ * Add: Filter: `slb_exclude_shortcodes`
114
+ * Add: Filter: `slb_group_shortcodes`
115
+ * Add: Template Tag: `slb_activate()` - Manually activate content
116
+ * Add: Option to enable/disable usage of WordPress-generated media title
117
+ * Add: Dev mode
118
+ * Add: Theme breakpoints
119
+ * Optimize: Remove deprecated code
120
+ * Optimize: Remove deprecated legacy support
121
+ * Optimize: Content exclusion performance
122
+ * Optimize: Content grouping performance
123
+ * Optimize: Harden code against third-party post query modifications
124
+ * Optimize: Utility code
125
+ * Optimize: Loading process
126
+ * Optimize: Client-side code
127
+ * Optimize: Client-side: Code loading
128
+ * Optimize: Client-side: Simplified dependency detection
129
+ * Optimize: Client-side: Default Theme transitions
130
+ * Optimize: Grunt: Cleanup
131
+ * Optimize: Grunt: Path abstraction
132
+ * Optimize: Grunt: Task loading
133
+ * Optimize: Grunt: Selective file compilation
134
+
135
+ = 2.2.2 =
136
+
137
+ * Optimize: Widget processing
138
+ * Optimize: Remove call-time-pass-by-references
139
+
140
+ = 2.2.1 =
141
+
142
+ * Fix: Enable/Disable lightbox on certain requests (Danny the Enabler)
143
+ * Fix: Widget links grouped with post links (Rafa's Widgetarian Adventure)
144
+ * Optimize: Client-side loading
145
+ * Optimize: Theme validation
146
+ * Optimize: Widget processing
147
+
148
+ = 2.2.0 =
149
+
150
+ * Update: WordPress 3.8 support
151
+ * Add: Add-on support
152
+ * Add: Load external data for item
153
+ * Add: Unloading process for viewer
154
+ * Add: Relative links marked as "internal"
155
+ * Add: Grunt build workflow
156
+ * Optimize: Initialization process
157
+ * Optimize: Client-side output (JavaScript, CSS)
158
+ * Optimize: Improved URI handling (variants, query strings, etc.)
159
+ * Optimize: Improved support for content types (video, etc.)
160
+ * Optimize: Improved File contents retrieval
161
+ * Optimize: Plugin metadata cleanup
162
+ * Optimize: Use absolute paths for file includes (props k3davis)
163
+
164
+ = 2.1.3 =
165
+
166
+ * Fix: PHP configuration issue on some web hosts (Tim's got (config) issues)
167
+ * Optimize: Hide overlapping elements when lightbox is displayed (e.g. Flash, etc.)
168
+
169
+ = 2.1.2 =
170
+
171
+ * Fix: Incorrect paths when WP in subdirectory (Kim's Van Repair)
172
+
173
+ = 2.1.1 =
174
+
175
+ * Fix: Automatic resizing
176
+ * Fix: Compatibility with non-standard wp-content location (On the Path of the Wijdemans)
177
+ * Optimize: jQuery dependency handling
178
+ * Optimize: Plugin initialization
179
+ * Optimize: Deferred component stylesheet loading
180
+ * Optimize: Code cleanup
181
+
182
+ = 2.1 =
183
+
184
+ * Update: Finalized Theme API
185
+ * Update: Finalized Content Handler API
186
+ * Update: Finalized Template Tag API
187
+ * Update: Administration framework
188
+ * Add: Baseline theme
189
+ * Add: Hook for extending image link matching
190
+ * Optimize: Link validation
191
+ * Optimize: Intelligent client-side loading
192
+ * Optimize: Server-side processing
193
+ * Optimize: Default theme display
194
+ * Fix: False positive link activation (What's eating Gilbert's links?)
195
+ * Fix: Gallery post format compatibility (Just Juan problem with galleries)
196
+
197
+ = 2.0 =
198
+
199
+ * Completely rewritten lightbox code
200
+ * Add: Automatically resize lightbox to fit window
201
+ * Add: APIs for third-party add-ons
202
+ * Add: Flexible theme support
203
+ * Add: Flexible content handler support
204
+ * Add: Mobile-optimized responsive themes (2)
205
+ * Optimize: PHP class autoloading
206
+ * Optimize: Improved performance and compatibility
207
+ * Optimize: Full internationalization support
208
+
209
+ = 1.6 =
210
+
211
+ * Add: Widget support
212
+ * Add: WordPress 3.3 support
213
+ * Add: Localization support
214
+ * Add: Option to group gallery links separately (supports WordPress & NextGen galleries)
215
+ * Add: Upgrade notice
216
+ * Optimize: WP 3.3 compatibility
217
+ * Optimize: Improved compatibility with URI case-sensitivity
218
+ * Optimize: Activation processing
219
+ * Optimize: Image grouping
220
+ * Optimize: Image metadata loading performance
221
+ * Optimize: File loading
222
+ * Optimize: Improved safeguards against interference by bugs in other plugins
223
+ * Optimize: Link processing performance
224
+ * Optimize: Lightbox styling isolated from site styles
225
+ * Optimize: Improved link processing performance
226
+ * Optimize: Improved image metadata support
227
+ * Optimize: Improved support for HTTP/HTTPS requests
228
+ * Fix: SLB is not defined in JS (Jezz Hands)
229
+ * Fix: Boolean case-sensitivity (78 Truths)
230
+ * Fix: YouTube embed using iFrame overlaps lightbox (Elena in Hiding)
231
+ * Fix: Issue when scanning links without valid URLs (McCloskey Iteration)
232
+ * Fix: Image activation is case-sensitive (Sensitive Tanya)
233
+ * Fix: Visible lightbox overlay edges when image larger than browser window (Chibi Overlay)
234
+ * Fix: Options availability for some users
235
+ * Fix: Inconsistent loading of image metadata
236
+ * Fix: Links not fully processed when group is set manually
237
+
238
+ = 1.5.6 =
239
+
240
+ * Add: Display image description in lightbox (with HTML support)
241
+ * Add: Support for W3 Total Cache plugin
242
+ * Add: Initial support for NextGEN galleries
243
+ * Update: **Important:** [System Requirements](http://wordpress.org/about/requirements/) aligned with WP 3.2.1
244
+ * Optimize: Improved support for small images in default template
245
+ * Optimize: Support for non-English text in user options
246
+ * Optimize: Improved IE compatibility
247
+ * Optimize: Improved data handling
248
+ * Optimize: Skin loading performance
249
+ * Optimize: Skin CSS Cleanup
250
+ * Optimize: Caption support for galleries
251
+ * Optimize: Options code cleanup (Juga Sweep)
252
+ * Fix: User-defined UI text not used (Ivan gets Even (cooler))
253
+ * Fix: Options reset after update (KRazy Donna)
254
+
255
+ = 1.5.5.1 =
256
+
257
+ * Fix: Disabled links not being disabled (Disabling Sascha)
258
+
259
+ = 1.5.5 =
260
+
261
+ * Add: Distinct link activation (will not affect other lightboxes)
262
+ * Add: Backwards compatibility with legacy lightbox links (optional)
263
+ * Add: Support for WordPress 3.2
264
+ * Add: Support for links added after page load (e.g. via AJAX, etc.)
265
+ * Add: Admin option to enable/disable attachment links
266
+ * Add: Support for image attachment links
267
+ * Update: Options management overhaul
268
+ * Update: Additional WordPress 3.2 support (Gallery)
269
+ * Update: Cache-management for enqueued files
270
+ * Update: Improved UI consistency
271
+ * Update: Improved compatibility for older versions of PHP
272
+ * Update: Internal optimizations
273
+ * Update: Improved URL handling
274
+ * Fix: Improved options migration from old versions (Hutchison Migration)
275
+ * Fix: XHTML Validation (Hajo Validation)
276
+
277
+ = 1.5.4 =
278
+
279
+ * Add: Optional Link validation
280
+ * Add: Keyboard Navigation
281
+ * Add: Option to enable/disable image caption
282
+ * Add: `rel` attribute supported again
283
+ * Add: Use `slb_off` in link's `rel` attribute to disable automatic activation for link
284
+ * Fix: HTTPS compatibility (J&uuml;rgen Protocol)
285
+ * Fix: Enabling SLB on Pages issue
286
+ * Fix: Zmanu is_single
287
+ * Fix: Image order is sometimes incorrect
288
+ * Optimize: Filter double clicks
289
+ * Optimize: Separate options to enable/disable SLB on Posts and Pages
290
+ * Optimize: Better grouping support
291
+
292
+ = 1.5.3 =
293
+
294
+ * Fix: Caption may not display under certain circumstances (Caption Erin)
295
+ * Fix: Images not grouped when "separate by post" option is activated (Logical Ross)
296
+ * Update: Lightbox will not be activated for links that already have `rel` attribute set
297
+
298
+ = 1.5.2 =
299
+
300
+ * Fix: Slideshow loops out of control (Mirage of Wallentin)
301
+ * Fix: Lightbox fails when group by posts disabled (Lange Find)
302
+ * Add: Option to use the image's URI as caption when link title not set (Under UI options)
303
+
304
+ = 1.5.1 =
305
+
306
+ * Add: WP Gallery support
307
+ * Fix: Navigation hidden when only one image
308
+ * Fix: Use user-defined UI text
309
+
310
+ = 1.5 =
311
+
312
+ * Add: Theme support
313
+ * Optimize: JavaScript cleanup and file size reductions
314
+ * Optimize: CSS cleanup
315
+
316
+ = 1.4 =
317
+
318
+ * Update: Integrated with jQuery
319
+ * Optimize: JavaScript file size 9x smaller
320
+ * Add: Close lightbox by clicking to left/right outside of image (an oft-requested feature)
321
+
322
+ = 1.3.2 =
323
+
324
+ * Add: Option to enable/disable lightbox resizing animation (thanks Maria!)
325
+
326
+ = 1.3.1 =
327
+
328
+ * Update: Utilities code (internal)
329
+
330
+ = 1.3 =
331
+
332
+ * Add: Customizable UI label text (close, next, and previous button images can be replaced in `images` directory)
333
+ * Add: Group image links by Post (separate slideshow for each post)
334
+ * Add: Reset settings link on plugin listings page
335
+ * Optimize: Organized settings page
336
+
337
+ = 1.2.1 =
338
+
339
+ * Fixed: Image title given higher precedence than Image alt (more compatible w/WP workflow)
340
+
341
+ = 1.2 =
342
+
343
+ * Added: Option to group automatically activated links
344
+ * Optimized: Lightbox caption retrieval
345
+
346
+ = 1.1 =
347
+
348
+ * Added: Enable/disable lightbox functionality by page type (Home, Pages/Posts, Archive, etc.)
349
+ * Added: Automatically activate lightbox functionality for image links
350
+ * Added: Link to settings menu on plugin listing page
351
+ * Optimized: Options menu field building
352
+ * Optimized: Loading of default values for plugin options
353
+ * Optimized: General code optimizations
354
+
355
+ = 1.0 =
356
+
357
  * Initial release
client/css/admin.css CHANGED
@@ -1 +1 @@
1
- .slb_section_head{display:block;padding:2em 0 0}.slb_option_item .block{display:inline-block}.slb_option_item label.title{width:200px;padding:10px}.slb_option_item .input{font-size:11px;line-height:20px;margin-bottom:9px;padding:8px 10px}.slb_option_item .input select{min-width:12em}.slb_notice{color:#f00;font-weight:bold}.slb .columns-2{margin-right:300px}.slb .columns-2 .postbox-container{float:left;width:100%}.slb .columns-2 .content-secondary{margin-right:-300px;width:280px;float:right}.slb_admin_action_reset{color:#a00}.slb_admin_action_reset:hover{color:#dc3232;border:none}
1
+ .slb_section_head{display:block;padding:2em 0 0}.slb_option_item .block{display:inline-block}.slb_option_item label.title{width:200px;padding:10px}.slb_option_item .input{font-size:11px;line-height:20px;margin-bottom:9px;padding:8px 10px}.slb_option_item .input select{min-width:12em}.slb_notice{color:#f00;font-weight:bold}.slb .columns-2{margin-right:300px}.slb .columns-2 .postbox-container{float:left;width:100%}.slb .columns-2 .content-secondary{margin-right:-300px;width:280px;float:right}.slb_admin_action_reset{color:#a00}.slb_admin_action_reset:hover{color:#dc3232;border:none}
client/css/app.css CHANGED
@@ -1 +1 @@
1
- html.slb_overlay object,html.slb_overlay embed,html.slb_overlay iframe{visibility:hidden}html.slb_overlay #slb_viewer_wrap object,html.slb_overlay #slb_viewer_wrap embed,html.slb_overlay #slb_viewer_wrap iframe{visibility:visible}
1
+ html.slb_overlay object,html.slb_overlay embed,html.slb_overlay iframe{visibility:hidden}html.slb_overlay #slb_viewer_wrap object,html.slb_overlay #slb_viewer_wrap embed,html.slb_overlay #slb_viewer_wrap iframe{visibility:visible}
client/js/dev/lib.admin.js CHANGED
@@ -1,27 +1,27 @@
1
- /**
2
- * Admin
3
- * @package Simple Lightbox
4
- * @subpackage Admin
5
- * @author Archetyped
6
- */
7
-
8
- /* global SLB, postboxes, pagenow */
9
-
10
- if ( !!window.SLB && !!SLB.attach ) { (function ($) {
11
-
12
- SLB.attach('Admin', {
13
- /**
14
- * Initialization routines
15
- */
16
- init: function() {
17
- if ( postboxes ) {
18
- postboxes.add_postbox_toggles(pagenow);
19
- }
20
- },
21
- });
22
-
23
- $(document).ready(function() {
24
- SLB.Admin.init();
25
- });
26
-
27
  })(jQuery);}
1
+ /**
2
+ * Admin
3
+ * @package Simple Lightbox
4
+ * @subpackage Admin
5
+ * @author Archetyped
6
+ */
7
+
8
+ /* global SLB, postboxes, pagenow */
9
+
10
+ if ( !!window.SLB && !!SLB.attach ) { (function ($) {
11
+
12
+ SLB.attach('Admin', {
13
+ /**
14
+ * Initialization routines
15
+ */
16
+ init: function() {
17
+ if ( postboxes ) {
18
+ postboxes.add_postbox_toggles(pagenow);
19
+ }
20
+ },
21
+ });
22
+
23
+ $(document).ready(function() {
24
+ SLB.Admin.init();
25
+ });
26
+
27
  })(jQuery);}
client/js/dev/lib.core.js CHANGED
@@ -1,934 +1,934 @@
1
- /**
2
- * Core
3
- * @package SLB
4
- * @author Archetyped
5
- */
6
- if ( window.jQuery ){(function($) {
7
- 'use strict';
8
-
9
- /**
10
- * Extendible class
11
- * Adapted from John Resig
12
- * @link http://ejohn.org/blog/simple-javascript-inheritance/
13
- */
14
- var c_init = false;
15
- var Class = function() {};
16
-
17
- /**
18
- * Create class that extends another class
19
- * @param object members Child class' properties
20
- * @return function New class
21
- */
22
- Class.extend = function(members) {
23
- var _super = this.prototype;
24
-
25
- // Copy instance to prototype
26
- c_init = true;
27
- var proto = new this();
28
- c_init = false;
29
-
30
- var val, name;
31
- // Scrub prototype objects (Decouple from super class)
32
- for ( name in proto ) {
33
- if ( $.isPlainObject(proto[name]) ) {
34
- val = $.extend({}, proto[name]);
35
- proto[name] = val;
36
- }
37
- }
38
-
39
- /**
40
- * Create class method with access to super class method
41
- * @param string nm Method name
42
- * @param function fn Class method
43
- * @return function Class method with access to super class method
44
- */
45
- var make_handler = function(nm, fn) {
46
- return function() {
47
- // Cache super variable
48
- var tmp = this._super;
49
- // Set variable to super class method
50
- this._super = _super[nm];
51
- // Call method
52
- var ret = fn.apply(this, arguments);
53
- // Restore super variable
54
- this._super = tmp;
55
- // Return value
56
- return ret;
57
- };
58
- };
59
- // Copy properties to Class
60
- for ( name in members ) {
61
- // Add access to super class method to methods
62
- if ( 'function' === typeof members[name] && 'function' === typeof _super[name] ) {
63
- proto[name] = make_handler(name, members[name]);
64
- } else {
65
- // Transfer properties
66
- // Objects are copied, not referenced
67
- proto[name] = ( $.isPlainObject(members[name]) ) ? $.extend({}, members[name]) : members[name];
68
- }
69
- }
70
-
71
- /**
72
- * Class constructor
73
- * Supports pre-construction initilization (`Class._init()`)
74
- * Supports passing constructor for new classes (`Class._c()`)
75
- */
76
- function Class() {
77
- if ( !c_init ) {
78
- // Private initialization
79
- if ( 'function' === typeof this._init ) {
80
- this._init.apply(this, arguments);
81
- }
82
- // Main Constructor
83
- if ( 'function' === typeof this._c ) {
84
- this._c.apply(this, arguments);
85
- }
86
- }
87
- }
88
-
89
-
90
- // Populate new prototype
91
- Class.prototype = proto;
92
-
93
- // Set constructor
94
- Class.prototype.constructor = Class;
95
-
96
- // Set extender
97
- Class.extend = this.extend;
98
-
99
- // Return function
100
- return Class;
101
- };
102
-
103
- /**
104
- * Base Class
105
- */
106
- var Base = {
107
- /* Properties */
108
-
109
- /**
110
- * Base object flag
111
- * @var bool
112
- */
113
- base: false,
114
- /**
115
- * Instance parent
116
- * @var object
117
- */
118
- _parent: null,
119
- /**
120
- * Class prefix
121
- * @var string
122
- */
123
- prefix: 'slb',
124
-
125
- /* Methods */
126
-
127
- /**
128
- * Constructor
129
- * Sets instance parent
130
- */
131
- _init: function() {
132
- this._set_parent();
133
- },
134
-
135
- /**
136
- * Set instance parent
137
- * Set utilities parent to current instance
138
- * @param obj p Parent instance
139
- */
140
- _set_parent: function(p) {
141
- if ( this.util.is_set(p) ) {
142
- this._parent = p;
143
- }
144
- this.util._parent = this;
145
- },
146
-
147
- /**
148
- * Attach new member to instance
149
- * Member can be property (value) or method
150
- * @param string name Member name
151
- * @param object data Member data
152
- * @param bool simple (optional) Save new member as data object or new class instance (Default: new instance)
153
- * @return obj Attached object
154
- */
155
- attach: function(member, data, simple) {
156
- var ret = data;
157
- // Validate
158
- simple = ( typeof simple === 'undefined' ) ? false : !!simple;
159
- // Add member to instance
160
- if ( 'string' === $.type(member) ) {
161
- // Prepare member value
162
- if ( $.isPlainObject(data) && !simple ) {
163
- // Set parent reference for attached instance
164
- data['_parent'] = this;
165
- // Define new class
166
- data = this.Class.extend(data);
167
- }
168
- // Save member to current instance
169
- // Initialize new instance if data is a class
170
- this[member] = ( 'function' === $.type(data) ) ? new data() : data;
171
- ret = this[member];
172
- }
173
- return ret;
174
- },
175
-
176
- /**
177
- * Check for child object
178
- * Child object can be multi-level (e.g. Child.Level2child.Level3child)
179
- *
180
- * @param string child Name of child object
181
- */
182
- has_child: function(child) {
183
- // Validate
184
- if ( !this.util.is_string(child) ) {
185
- return false;
186
- }
187
-
188
- var children = child.split('.');
189
- child = null;
190
- var o = this;
191
- var x;
192
- for ( x = 0; x < children.length; x++ ) {
193
- child = children[x];
194
- if ( "" === child ) {
195
- continue;
196
- }
197
- if ( this.util.is_obj(o) && o[child] ) {
198
- o = o[child];
199
- } else {
200
- return false;
201
- }
202
- }
203
- return true;
204
- },
205
-
206
- /**
207
- * Check if instance is set as a base
208
- * @uses base
209
- * @return bool TRUE if object is set as a base
210
- */
211
- is_base: function() {
212
- return !!this.base;
213
- },
214
-
215
- /**
216
- * Get parent instance
217
- * @uses `Base._parent` property
218
- * @return obj Parent instance
219
- */
220
- get_parent: function() {
221
- var p = this._parent;
222
- // Validate
223
- if ( !p ) {
224
- this._parent = {};
225
- }
226
- return this._parent;
227
- }
228
- };
229
-
230
- /**
231
- * Utility methods
232
- */
233
- var Utilities = {
234
- /* Properties */
235
-
236
- _base: null,
237
- _parent: null,
238
-
239
- /* Methods */
240
-
241
- /* Connections */
242
-
243
- /**
244
- * Get base ancestor
245
- * @return obj Base ancestor
246
- */
247
- get_base: function() {
248
- if ( !this._base ) {
249
- var p = this.get_parent();
250
- var p_prev = null;
251
- var methods = ['is_base', 'get_parent'];
252
- // Find base ancestor
253
- // Either oldest ancestor or object explicitly set as a base
254
- while ( ( p_prev !== p ) && this.is_method(p, methods) && !p.is_base() ) {
255
- // Save previous parent
256
- p_prev = p;
257
- // Get new parent
258
- p = p.get_parent();
259
- }
260
- // Set base
261
- this._base = p;
262
- }
263
- return this._base;
264
- },
265
-
266
- /**
267
- * Get parent object or parent property value
268
- * @param string prop (optional) Property to retrieve
269
- * @return obj Parent object or property value
270
- */
271
- get_parent: function(prop) {
272
- var ret = this._parent;
273
- // Validate
274
- if ( !ret ) {
275
- // Set default parent value
276
- ret = this._parent = {};
277
- }
278
- // Get parent property
279
- if ( this.is_string(prop) ) {
280
- ret = ( this.in_obj(ret, prop) ) ? ret[prop] : null;
281
- }
282
- return ret;
283
- },
284
-
285
- /* Prefix */
286
-
287
- /**
288
- * Retrieve valid separator
289
- * If supplied argument is not a valid separator, use default separator
290
- * @param string (optional) sep Separator text
291
- * @return string Separator text
292
- */
293
- get_sep: function(sep) {
294
- var sep_default = '_';
295
- return ( this.is_string(sep, false) ) ? sep : sep_default;
296
- },
297
-
298
- /**
299
- * Retrieve prefix
300
- * @return string Prefix
301
- */
302
- get_prefix: function() {
303
- var p = this.get_parent('prefix');
304
- return ( this.is_string(p, false) ) ? p : '';
305
- },
306
-
307
- /**
308
- * Check if string is prefixed
309
- */
310
- has_prefix: function(val, sep) {
311
- return ( this.is_string(val) && 0 === val.indexOf(this.get_prefix() + this.get_sep(sep)) );
312
- },
313
-
314
- /**
315
- * Add Prefix to a string
316
- * @param string val Value to add prefix to
317
- * @param string sep (optional) Separator (Default: `_`)
318
- * @param bool (optional) once If text should only be prefixed once (Default: TRUE)
319
- */
320
- add_prefix: function(val, sep, once) {
321
- // Validate
322
- if ( !this.is_string(val) ) {
323
- // Return prefix if value to add prefix to is empty
324
- return this.get_prefix();
325
- }
326
- sep = this.get_sep(sep);
327
- if ( !this.is_bool(once) ) {
328
- once = true;
329
- }
330
-
331
- return ( once && this.has_prefix(val, sep) ) ? val : [this.get_prefix(), val].join(sep);
332
- },
333
-
334
- /**
335
- * Remove Prefix from a string
336
- * @param string val Value to add prefix to
337
- * @param string sep (optional) Separator (Default: `_`)
338
- * @param bool (optional) once If text should only be prefixed once (Default: true)
339
- * @return string Original value with prefix removed
340
- */
341
- remove_prefix: function(val, sep, once) {
342
- // Validate parameters
343
- if ( !this.is_string(val, true) ) {
344
- return '';
345
- }
346
- // Default values
347
- sep = this.get_sep(sep);
348
- if ( !this.is_bool(once) ) {
349
- once = true;
350
- }
351
- // Check if string is prefixed
352
- if ( this.has_prefix(val, sep) ) {
353
- // Remove prefix
354
- var prfx = this.get_prefix() + sep;
355
- do {
356
- val = val.substr(prfx.length);
357
- } while ( !once && this.has_prefix(val, sep) );
358
- }
359
- return val;
360
- },
361
-
362
- /* Attributes */
363
-
364
- /*
365
- * Get attribute name
366
- * @param string attr_base Attribute's base name
367
- * @return string Fully-formed attribute name
368
- */
369
- get_attribute: function(attr_base) {
370
- // Setup
371
- var sep = '-';
372
- var top = 'data';
373
- // Validate
374
- var attr = [top, this.get_prefix()].join(sep);
375
- // Process
376
- if ( this.is_string(attr_base) && 0 !== attr_base.indexOf(attr + sep) ) {
377
- attr = [attr, attr_base].join(sep);
378
- }
379
- return attr;
380
- },
381
-
382
- /* Request */
383
-
384
- /**
385
- * Retrieve valid context
386
- * @return array Context
387
- */
388
- get_context: function() {
389
- // Validate
390
- var b = this.get_base();
391
- if ( !$.isArray(b.context) ) {
392
- b.context = [];
393
- }
394
- // Return context
395
- return b.context;
396
- },
397
-
398
- /**
399
- * Check if a context exists in current request
400
- * If multiple contexts are supplied, result will be TRUE if at least ONE context exists
401
- *
402
- * @param string|array ctx Context to check for
403
- * @return bool TRUE if context exists, FALSE otherwise
404
- */
405
- is_context: function(ctx) {
406
- // Validate context
407
- if ( this.is_string(ctx) ) {
408
- ctx = [ctx];
409
- }
410
- return ( this.is_array(ctx) && this.arr_intersect(this.get_context(), ctx).length > 0 );
411
- },
412
-
413
- /* Helpers */
414
-
415
- /**
416
- * Check if value is set/defined
417
- * @param mixed val Value to check
418
- * @return bool TRUE if value is defined
419
- */
420
- is_set: function(val) {
421
- return ( typeof val !== 'undefined' );
422
- },
423
-
424
- /**
425
- * Validate data type
426
- * @param mixed val Value to validate
427
- * @param mixed type Data type to compare with (function gets for instance, string checks data type)
428
- * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
429
- * @return bool TRUE if Value matches specified data type
430
- */
431
- is_type: function(val, type, nonempty) {
432
- var ret = false;
433
- if ( this.is_set(val) && null !== val && this.is_set(type) ) {
434
- switch ( $.type(type) ) {
435
- case 'function':
436
- ret = ( val instanceof type ) ? true : false;
437
- break;
438
- case 'string':
439
- ret = ( $.type(val) === type ) ? true : false;
440
- break;
441
- default:
442
- ret = false;
443
- break;
444
- }
445
- }
446
-
447
- // Validate empty values
448
- if ( ret && ( !this.is_set(nonempty) || !!nonempty ) ) {
449
- ret = !this.is_empty(val);
450
- }
451
- return ret;
452
- },
453
-
454
- /**
455
- * Check if value is a string
456
- * @uses is_type()
457
- * @param mixed value Value to check
458
- * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
459
- * @return bool TRUE if value is a valid string
460
- */
461
- is_string: function(value, nonempty) {
462
- return this.is_type(value, 'string', nonempty);
463
- },
464
-
465
- /**
466
- * Check if value is an array
467
- * @uses is_type()
468
- * @param mixed value Value to check
469
- * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
470
- * @return bool TRUE if value is a valid array
471
- */
472
- is_array: function(value, nonempty) {
473
- return ( this.is_type(value, 'array', nonempty) );
474
- },
475
-
476
- /**
477
- * Check if value is a boolean
478
- * @uses is_type()
479
- * @param mixed value Value to check
480
- * @return bool TRUE if value is a valid boolean
481
- */
482
- is_bool: function(value) {
483
- return this.is_type(value, 'boolean', false);
484
- },
485
-
486
- /**
487
- * Check if value is an object
488
- * @uses is_type()
489
- * @param mixed value Value to check
490
- * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
491
- * @return bool TRUE if value is a valid object
492
- */
493
- is_obj: function(value, nonempty) {
494
- return this.is_type(value, 'object', nonempty);
495
- },
496
-
497
- /**
498
- * Check if value is a function
499
- * @uses is_type()
500
- * @param mixed value Value to check
501
- * @return bool TRUE if value is a valid function
502
- */
503
- is_func: function(value) {
504
- return this.is_type(value, 'function', false);
505
- },
506
-
507
- /**
508
- * Checks if an object has a method
509
- * @param obj obj Object to check
510
- * @param string|array key Name(s) of methods to check for
511
- * @return bool TRUE if method(s) exist, FALSE otherwise
512
- */
513
- is_method: function(obj, key) {
514
- var ret = false;
515
- if ( this.is_string(key) ) {
516
- key = [key];
517
- }
518
- if ( this.in_obj(obj, key) ) {
519
- ret = true;
520
- var x = 0;
521
- while ( ret && x < key.length ) {
522
- ret = this.is_func(obj[key[x]]);
523
- x++;
524
- }
525
- }
526
- return ret;
527
- },
528
-
529
- /**
530
- * Check if object is instance of a class
531
- * @param obj obj Instance object
532
- * @param obj parent Class to compare with
533
- * @return bool TRUE if object is instance of class
534
- */
535
- is_instance: function(obj, parent) {
536
- if ( !this.is_func(parent) ) {
537
- return false;
538
- }
539
- return ( this.is_obj(obj) && ( obj instanceof parent ) );
540
- },
541
-
542
- /**
543
- * Check if object is class
544
- * Optionally check if class is sub-class of another class
545
- * @param func cls Class to check
546
- * @param func parent (optional) parent class
547
- * @return bool TRUE if object is valid class (and sub-class if parent is specified)
548
- */
549
- is_class: function(cls, parent) {
550
- // Validate class
551
- var ret = ( this.is_func(cls) && ( 'prototype' in cls ) );
552
- // Check parent class
553
- if ( ret && this.is_set(parent) ) {
554
- ret = this.is_instance(cls.prototype, parent);
555
- }
556
- return ret;
557
- },
558
-
559
- /**
560
- * Check if value is a number
561
- * @uses is_type()
562
- * @param mixed value Value to check
563
- * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
564
- * @return bool TRUE if value is a valid number
565
- */
566
- is_num: function(value, nonempty) {
567
- var f = {
568
- 'nan': ( Number.isNaN ) ? Number.isNaN : isNaN,
569
- 'finite': ( Number.isFinite ) ? Number.isFinite : isFinite
570
- };
571
- return ( this.is_type(value, 'number', nonempty) && !f.nan(value) && f.finite(value) );
572
- },
573
-
574
- /**
575
- * Check if value is a integer
576
- * @uses is_type()
577
- * @param mixed value Value to check
578
- * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
579
- * @return bool TRUE if value is a valid integer
580
- */
581
- is_int: function(value, nonempty) {
582
- return ( this.is_num(value, nonempty) && Math.floor(value) === value );
583
- },
584
-
585
- /**
586
- * Check if value is scalar (string, number, boolean)
587
- * @uses is_type()
588
- * @param mixed value Value to check
589
- * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
590
- * @return bool TRUE if value is scalar
591
- */
592
- is_scalar: function(value, nonempty) {
593
- return ( this.is_num(value, nonempty) || this.is_string(value, nonempty) || this.is_bool(value) );
594
- },
595
-
596
- /**
597
- * Checks if value is empty
598
- * @param mixed value Value to check
599
- * @param string type (optional) Data type
600
- * @return bool TRUE if value is empty
601
- */
602
- is_empty: function(value, type) {
603
- var ret = false;
604
- // Check Undefined
605
- if ( !this.is_set(value) ) {
606
- ret = true;
607
- } else {
608
- // Check standard values
609
- var empties = [null, "", false, 0];
610
- var x = 0;
611
- while ( !ret && x < empties.length ) {
612
- ret = ( empties[x] === value );
613
- x++;
614
- }
615
- }
616
-
617
- // Advanced check
618
- if ( !ret ) {
619
- // Validate type
620
- if ( !this.is_set(type) ) {
621
- type = $.type(value);
622
- }
623
- // Type-based check
624
- if ( this.is_type(value, type, false) ) {
625
- switch ( type ) {
626
- case 'string':
627
- case 'array':
628
- ret = ( value.length === 0 );
629
- break;
630
- case 'number':
631
- ret = ( value == 0 ); // jshint ignore:line
632
- break;
633
- case 'object':
634
- if ( !$.isPlainObject(value) ) {
635
- // Custom object. Unable to evaluate emptiness further
636
- ret = false;
637
- } else {
638
- // Evaluate plain object
639
- if ( Object.getOwnPropertyNames ) {
640
- // Modern browser check
641
- ret = ( Object.getOwnPropertyNames(value).length === 0 );
642
- } else if ( value.hasOwnProperty ) {
643
- // Legacy browser check
644
- ret = true;
645
- for ( var key in value ) {
646
- if ( value.hasOwnProperty(key) ) {
647
- ret = false;
648
- break;
649
- }
650
- }
651
- }
652
- }
653
- break;
654
- }
655
- } else {
656
- ret = true;
657
- }
658
- }
659
- return ret;
660
- },
661
-
662
- /**
663
- * Check if object is a jQuery.Promise instance
664
- * Will also match (but not guarantee) jQuery.Deferred instances
665
- * @uses is_method()
666
- * @param obj obj Object to check
667
- * @return bool TRUE if object is Promise/Deferred
668
- */
669
- is_promise: function(obj) {
670
- return ( this.is_method(obj, ['then', 'done', 'always', 'fail', 'pipe']) );
671
- },
672
-
673
- /**
674
- * Return formatted string
675
- * @param string fmt Format template
676
- * @param string val Replacement value (Multiple parameters may be set)
677
- * @return string Formatted string
678
- */
679
- format: function(fmt, val) {
680
- // Validate format
681
- if ( !this.is_string(fmt) ) {
682
- return '';
683
- }
684
- var params = [];
685
- var ph = '%s';
686
- /**
687
- * Clean string (remove placeholders)
688
- */
689
- var strip = function(txt) {
690
- return ( txt.indexOf(ph) !== -1 ) ? txt.replace(ph, '') : txt;
691
- };
692
- // Stop processing if no replacement values specified or format string contains no placeholders
693
- if ( arguments.length < 2 || fmt.indexOf(ph) === -1 ) {
694
- return strip(fmt);
695
- }
696
- // Get replacement values
697
- params = Array.prototype.slice.call(arguments, 1);
698
- val = null;
699
- // Clean parameters
700
- for ( var x = 0; x < params.length; x++ ) {
701
- if ( !this.is_scalar(params[x], false) ) {
702
- params[x] = '';
703
- }
704
- }
705
-
706
- // Replace all placeholders at once if single parameter set
707
- if ( params.length === 1 ) {
708
- fmt = fmt.replace(ph, params[0].toString());
709
- } else {
710
- var idx = 0; // Current replacement index
711
- var len = params.length; // Number of replacements
712
- var rlen = ph.length; // Placeholder length
713
- var pos = 0; // Current placeholder position (in format template)
714
- while ( ( pos = fmt.indexOf(ph) ) && pos !== -1 && idx < len ) {
715
- // Replace current placeholder with respective parameter
716
- fmt = fmt.substr(0, pos) + params[idx].toString() + fmt.substr(pos + rlen);
717
- idx++;
718
- }
719
- // Remove any remaining placeholders
720
- fmt = strip(fmt);
721
- }
722
- return fmt;
723
- },
724
-
725
- /**
726
- * Checks if key(s) exist in an object
727
- * @param object obj Object to check
728
- * @param string|array key Key(s) to check for in object
729
- * @param bool all (optional) All keys must exist in object? (Default: TRUE)
730
- * @return bool TRUE if key(s) exist in object
731
- */
732
- in_obj: function(obj, key, all) {
733
- // Validate
734
- if ( !this.is_bool(all) ) {
735
- all = true;
736
- }
737
- if ( this.is_string(key) ) {
738
- key = [key];
739
- }
740
- // Check for keys
741
- var ret = false;
742
- if ( this.is_obj(obj) && this.is_array(key) ) {
743
- var val;
744
- for ( var x = 0; x < key.length; x++ ) {
745
- val = key[x];
746
- ret = ( this.is_string(val) && ( val in obj ) ) ? true : false;
747
- // Stop processing if conditions have been met
748
- if ( ( !all && ret ) || ( all && !ret ) ) {
749
- break;
750
- }
751
- }
752
- }
753
- return ret;
754
- },
755
-
756
- /**
757
- * Retrieve an object's keys
758
- * @param obj Object to parse
759
- * @return array List of object's keys
760
- */
761
- obj_keys: function(obj) {
762
- var keys = [];
763
- // Validation
764
- if ( !this.is_obj(obj) ) {
765
- return keys;
766
- }
767
- if ( Object.keys ) {
768
- keys = Object.keys(obj);
769
- } else {
770
- var prop;
771
- for ( prop in obj ) {
772
- if ( obj.hasOwnProperty(prop) ) {
773
- keys.push(prop);
774
- }
775
- }
776
- }
777
- return keys;
778
- },
779
-
780
- /**
781
- * Find common elements of 2 or more arrays
782
- * @param array arr1 First array
783
- * @param array arr2 Second array (additional arrays can be passed as well)
784
- * @return array Elements common to all
785
- */
786
- arr_intersect: function(arr1, arr2) {
787
- var ret = [];
788
- // Get arrays
789
- var params = Array.prototype.slice.call(arguments);
790
- // Clean arrays
791
- var arrs = [];
792
- var x;
793
- for ( x = 0; x < params.length; x++ ) {
794
- if ( this.is_array(params[x], false) ) {
795
- arrs.push(params[x]);
796
- }
797
- }
798
- // Stop processing if no valid arrays to compare
799
- if ( arrs.length < 2 ) {
800
- return ret;
801
- }
802
- params = arr1 = arr2 = null;
803
- // Find common elements in arrays
804
- var base = arrs.shift();
805
- var add;
806
- var sub;
807
- for ( x = 0; x < base.length; x++ ) {
808
- add = true;
809
- // Check other arrays for element match
810
- for ( sub = 0; sub < arrs.length; sub++ ) {
811
- if ( arrs[sub].indexOf(base[x]) === -1 ) {
812
- add = false;
813
- break;
814
- }
815
- }
816
- if ( add ) {
817
- ret.push(base[x]);
818
- }
819
- }
820
- // Return intersection results
821
- return ret;
822
- },
823
-
824
- /**
825
- * Generates a GUID string.
826
- * @returns string The generated GUID.
827
- * @example af8a8416-6e18-a307-bd9c-f2c947bbb3aa
828
- * @author Slavik Meltser (slavik@meltser.info).
829
- * @link http://slavik.meltser.info/?p=142
830
- */
831
- guid: function() {
832
- function _p8(s) {
833
- var p = (Math.random().toString(16)+"000000000").substr(2,8);
834
- return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ;
835
- }
836
- return _p8() + _p8(true) + _p8(true) + _p8();
837
- },
838
-
839
- /**
840
- * Parse URI
841
- * @param string uri URI to parse
842
- * @return obj URI components (DOM anchor element)
843
- */
844
- parse_uri: function(uri) {
845
- return $('<a href="' + uri + '"/>').get(0);
846
- },
847
- /**
848
- * Parse URI query string
849
- * @param string uri URI with query string to parse
850
- * @return obj Query variables and values (empty if no query string)
851
- */
852
- parse_query: function(uri) {
853
- var delim = {
854
- 'vars': '&',
855
- 'val': '='
856
- };
857
- var query = {
858
- 'raw': [],
859
- 'parsed': {},
860
- 'string': ''
861
- };
862
- uri = this.parse_uri(uri);
863
- if ( 0 === uri.search.indexOf('?') ) {
864
- // Extract query string
865
- query.raw = uri.search.substr(1).split(delim.vars);
866
- var i, temp, key, val;
867
- // Build query object
868
- for ( i = 0; i < query.raw.length; i++ ) {
869
- // Split var and value
870
- temp = query.raw[i].split(delim.val);
871
- key = temp.shift();
872
- val = ( temp.length > 0 ) ? temp.join(delim.val) : null;
873
- query.parsed[key] = val;
874
- }
875
- }
876
- return query.parsed;
877
- },
878
- /**
879
- * Build query string from object
880
- * @param obj query Query data
881
- * @return string Query data formatted as HTTP query string
882
- */
883
- build_query: function(query) {
884
- var q = [];
885
- var delim = {
886
- 'vars': '&',
887
- 'val': '='
888
- };
889
- var val;
890
- for ( var key in query ) {
891
- val = ( null !== query[key] ) ? delim.val + query[key] : '';
892
- q.push(key + val);
893
- }
894
- return q.join(delim.vars);
895
- }
896
- };
897
-
898
- // Attach Utilities
899
- Base.attach('util', Utilities, true);
900
-
901
- /**
902
- * SLB Base Class
903
- */
904
- var SLB_Base = Class.extend(Base);
905
-
906
- /**
907
- * Core
908
- */
909
- var Core = {
910
- /* Properties */
911
-
912
- base: true,
913
- context: [],
914
-
915
- /**
916
- * New object initializer
917
- * @var obj
918
- */
919
- Class: SLB_Base,
920
-
921
- /* Methods */
922
-
923
- /**
924
- * Init
925
- * Set variables, DOM, etc.
926
- */
927
- _init: function() {
928
- this._super();
929
- $('html').addClass(this.util.get_prefix());
930
- }
931
- };
932
- var SLB_Core = SLB_Base.extend(Core);
933
- window.SLB = new SLB_Core();
934
  })(jQuery);}
1
+ /**
2
+ * Core
3
+ * @package SLB
4
+ * @author Archetyped
5
+ */
6
+ if ( window.jQuery ){(function($) {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Extendible class
11
+ * Adapted from John Resig
12
+ * @link http://ejohn.org/blog/simple-javascript-inheritance/
13
+ */
14
+ var c_init = false;
15
+ var Class = function() {};
16
+
17
+ /**
18
+ * Create class that extends another class
19
+ * @param object members Child class' properties
20
+ * @return function New class
21
+ */
22
+ Class.extend = function(members) {
23
+ var _super = this.prototype;
24
+
25
+ // Copy instance to prototype
26
+ c_init = true;
27
+ var proto = new this();
28
+ c_init = false;
29
+
30
+ var val, name;
31
+ // Scrub prototype objects (Decouple from super class)
32
+ for ( name in proto ) {
33
+ if ( $.isPlainObject(proto[name]) ) {
34
+ val = $.extend({}, proto[name]);
35
+ proto[name] = val;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Create class method with access to super class method
41
+ * @param string nm Method name
42
+ * @param function fn Class method
43
+ * @return function Class method with access to super class method
44
+ */
45
+ var make_handler = function(nm, fn) {
46
+ return function() {
47
+ // Cache super variable
48
+ var tmp = this._super;
49
+ // Set variable to super class method
50
+ this._super = _super[nm];
51
+ // Call method
52
+ var ret = fn.apply(this, arguments);
53
+ // Restore super variable
54
+ this._super = tmp;
55
+ // Return value
56
+ return ret;
57
+ };
58
+ };
59
+ // Copy properties to Class
60
+ for ( name in members ) {
61
+ // Add access to super class method to methods
62
+ if ( 'function' === typeof members[name] && 'function' === typeof _super[name] ) {
63
+ proto[name] = make_handler(name, members[name]);
64
+ } else {
65
+ // Transfer properties
66
+ // Objects are copied, not referenced
67
+ proto[name] = ( $.isPlainObject(members[name]) ) ? $.extend({}, members[name]) : members[name];
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Class constructor
73
+ * Supports pre-construction initilization (`Class._init()`)
74
+ * Supports passing constructor for new classes (`Class._c()`)
75
+ */
76
+ function Class() {
77
+ if ( !c_init ) {
78
+ // Private initialization
79
+ if ( 'function' === typeof this._init ) {
80
+ this._init.apply(this, arguments);
81
+ }
82
+ // Main Constructor
83
+ if ( 'function' === typeof this._c ) {
84
+ this._c.apply(this, arguments);
85
+ }
86
+ }
87
+ }
88
+
89
+
90
+ // Populate new prototype
91
+ Class.prototype = proto;
92
+
93
+ // Set constructor
94
+ Class.prototype.constructor = Class;
95
+
96
+ // Set extender
97
+ Class.extend = this.extend;
98
+
99
+ // Return function
100
+ return Class;
101
+ };
102
+
103
+ /**
104
+ * Base Class
105
+ */
106
+ var Base = {
107
+ /* Properties */
108
+
109
+ /**
110
+ * Base object flag
111
+ * @var bool
112
+ */
113
+ base: false,
114
+ /**
115
+ * Instance parent
116
+ * @var object
117
+ */
118
+ _parent: null,
119
+ /**
120
+ * Class prefix
121
+ * @var string
122
+ */
123
+ prefix: 'slb',
124
+
125
+ /* Methods */
126
+
127
+ /**
128
+ * Constructor
129
+ * Sets instance parent
130
+ */
131
+ _init: function() {
132
+ this._set_parent();
133
+ },
134
+
135
+ /**
136
+ * Set instance parent
137
+ * Set utilities parent to current instance
138
+ * @param obj p Parent instance
139
+ */
140
+ _set_parent: function(p) {
141
+ if ( this.util.is_set(p) ) {
142
+ this._parent = p;
143
+ }
144
+ this.util._parent = this;
145
+ },
146
+
147
+ /**
148
+ * Attach new member to instance
149
+ * Member can be property (value) or method
150
+ * @param string name Member name
151
+ * @param object data Member data
152
+ * @param bool simple (optional) Save new member as data object or new class instance (Default: new instance)
153
+ * @return obj Attached object
154
+ */
155
+ attach: function(member, data, simple) {
156
+ var ret = data;
157
+ // Validate
158
+ simple = ( typeof simple === 'undefined' ) ? false : !!simple;
159
+ // Add member to instance
160
+ if ( 'string' === $.type(member) ) {
161
+ // Prepare member value
162
+ if ( $.isPlainObject(data) && !simple ) {
163
+ // Set parent reference for attached instance
164
+ data['_parent'] = this;
165
+ // Define new class
166
+ data = this.Class.extend(data);
167
+ }
168
+ // Save member to current instance
169
+ // Initialize new instance if data is a class
170
+ this[member] = ( 'function' === $.type(data) ) ? new data() : data;
171
+ ret = this[member];
172
+ }
173
+ return ret;
174
+ },
175
+
176
+ /**
177
+ * Check for child object
178
+ * Child object can be multi-level (e.g. Child.Level2child.Level3child)
179
+ *
180
+ * @param string child Name of child object
181
+ */
182
+ has_child: function(child) {
183
+ // Validate
184
+ if ( !this.util.is_string(child) ) {
185
+ return false;
186
+ }
187
+
188
+ var children = child.split('.');
189
+ child = null;
190
+ var o = this;
191
+ var x;
192
+ for ( x = 0; x < children.length; x++ ) {
193
+ child = children[x];
194
+ if ( "" === child ) {
195
+ continue;
196
+ }
197
+ if ( this.util.is_obj(o) && o[child] ) {
198
+ o = o[child];
199
+ } else {
200
+ return false;
201
+ }
202
+ }
203
+ return true;
204
+ },
205
+
206
+ /**
207
+ * Check if instance is set as a base
208
+ * @uses base
209
+ * @return bool TRUE if object is set as a base
210
+ */
211
+ is_base: function() {
212
+ return !!this.base;
213
+ },
214
+
215
+ /**
216
+ * Get parent instance
217
+ * @uses `Base._parent` property
218
+ * @return obj Parent instance
219
+ */
220
+ get_parent: function() {
221
+ var p = this._parent;
222
+ // Validate
223
+ if ( !p ) {
224
+ this._parent = {};
225
+ }
226
+ return this._parent;
227
+ }
228
+ };
229
+
230
+ /**
231
+ * Utility methods
232
+ */
233
+ var Utilities = {
234
+ /* Properties */
235
+
236
+ _base: null,
237
+ _parent: null,
238
+
239
+ /* Methods */
240
+
241
+ /* Connections */
242
+
243
+ /**
244
+ * Get base ancestor
245
+ * @return obj Base ancestor
246
+ */
247
+ get_base: function() {
248
+ if ( !this._base ) {
249
+ var p = this.get_parent();
250
+ var p_prev = null;
251
+ var methods = ['is_base', 'get_parent'];
252
+ // Find base ancestor
253
+ // Either oldest ancestor or object explicitly set as a base
254
+ while ( ( p_prev !== p ) && this.is_method(p, methods) && !p.is_base() ) {
255
+ // Save previous parent
256
+ p_prev = p;
257
+ // Get new parent
258
+ p = p.get_parent();
259
+ }
260
+ // Set base
261
+ this._base = p;
262
+ }
263
+ return this._base;
264
+ },
265
+
266
+ /**
267
+ * Get parent object or parent property value
268
+ * @param string prop (optional) Property to retrieve
269
+ * @return obj Parent object or property value
270
+ */
271
+ get_parent: function(prop) {
272
+ var ret = this._parent;
273
+ // Validate
274
+ if ( !ret ) {
275
+ // Set default parent value
276
+ ret = this._parent = {};
277
+ }
278
+ // Get parent property
279
+ if ( this.is_string(prop) ) {
280
+ ret = ( this.in_obj(ret, prop) ) ? ret[prop] : null;
281
+ }
282
+ return ret;
283
+ },
284
+
285
+ /* Prefix */
286
+
287
+ /**
288
+ * Retrieve valid separator
289
+ * If supplied argument is not a valid separator, use default separator
290
+ * @param string (optional) sep Separator text
291
+ * @return string Separator text
292
+ */
293
+ get_sep: function(sep) {
294
+ var sep_default = '_';
295
+ return ( this.is_string(sep, false) ) ? sep : sep_default;
296
+ },
297
+
298
+ /**
299
+ * Retrieve prefix
300
+ * @return string Prefix
301
+ */
302
+ get_prefix: function() {
303
+ var p = this.get_parent('prefix');
304
+ return ( this.is_string(p, false) ) ? p : '';
305
+ },
306
+
307
+ /**
308
+ * Check if string is prefixed
309
+ */
310
+ has_prefix: function(val, sep) {
311
+ return ( this.is_string(val) && 0 === val.indexOf(this.get_prefix() + this.get_sep(sep)) );
312
+ },
313
+
314
+ /**
315
+ * Add Prefix to a string
316
+ * @param string val Value to add prefix to
317
+ * @param string sep (optional) Separator (Default: `_`)
318
+ * @param bool (optional) once If text should only be prefixed once (Default: TRUE)
319
+ */
320
+ add_prefix: function(val, sep, once) {
321
+ // Validate
322
+ if ( !this.is_string(val) ) {
323
+ // Return prefix if value to add prefix to is empty
324
+ return this.get_prefix();
325
+ }
326
+ sep = this.get_sep(sep);
327
+ if ( !this.is_bool(once) ) {
328
+ once = true;
329
+ }
330
+
331
+ return ( once && this.has_prefix(val, sep) ) ? val : [this.get_prefix(), val].join(sep);
332
+ },
333
+
334
+ /**
335
+ * Remove Prefix from a string
336
+ * @param string val Value to add prefix to
337
+ * @param string sep (optional) Separator (Default: `_`)
338
+ * @param bool (optional) once If text should only be prefixed once (Default: true)
339
+ * @return string Original value with prefix removed
340
+ */
341
+ remove_prefix: function(val, sep, once) {
342
+ // Validate parameters
343
+ if ( !this.is_string(val, true) ) {
344
+ return '';
345
+ }
346
+ // Default values
347
+ sep = this.get_sep(sep);
348
+ if ( !this.is_bool(once) ) {
349
+ once = true;
350
+ }
351
+ // Check if string is prefixed
352
+ if ( this.has_prefix(val, sep) ) {
353
+ // Remove prefix
354
+ var prfx = this.get_prefix() + sep;
355
+ do {
356
+ val = val.substr(prfx.length);
357
+ } while ( !once && this.has_prefix(val, sep) );
358
+ }
359
+ return val;
360
+ },
361
+
362
+ /* Attributes */
363
+
364
+ /*
365
+ * Get attribute name
366
+ * @param string attr_base Attribute's base name
367
+ * @return string Fully-formed attribute name
368
+ */
369
+ get_attribute: function(attr_base) {
370
+ // Setup
371
+ var sep = '-';
372
+ var top = 'data';
373
+ // Validate
374
+ var attr = [top, this.get_prefix()].join(sep);
375
+ // Process
376
+ if ( this.is_string(attr_base) && 0 !== attr_base.indexOf(attr + sep) ) {
377
+ attr = [attr, attr_base].join(sep);
378
+ }
379
+ return attr;
380
+ },
381
+
382
+ /* Request */
383
+
384
+ /**
385
+ * Retrieve valid context
386
+ * @return array Context
387
+ */
388
+ get_context: function() {
389
+ // Validate
390
+ var b = this.get_base();
391
+ if ( !$.isArray(b.context) ) {
392
+ b.context = [];
393
+ }
394
+ // Return context
395
+ return b.context;
396
+ },
397
+
398
+ /**
399
+ * Check if a context exists in current request
400
+ * If multiple contexts are supplied, result will be TRUE if at least ONE context exists
401
+ *
402
+ * @param string|array ctx Context to check for
403
+ * @return bool TRUE if context exists, FALSE otherwise
404
+ */
405
+ is_context: function(ctx) {
406
+ // Validate context
407
+ if ( this.is_string(ctx) ) {
408
+ ctx = [ctx];
409
+ }
410
+ return ( this.is_array(ctx) && this.arr_intersect(this.get_context(), ctx).length > 0 );
411
+ },
412
+
413
+ /* Helpers */
414
+
415
+ /**
416
+ * Check if value is set/defined
417
+ * @param mixed val Value to check
418
+ * @return bool TRUE if value is defined
419
+ */
420
+ is_set: function(val) {
421
+ return ( typeof val !== 'undefined' );
422
+ },
423
+
424
+ /**
425
+ * Validate data type
426
+ * @param mixed val Value to validate
427
+ * @param mixed type Data type to compare with (function gets for instance, string checks data type)
428
+ * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
429
+ * @return bool TRUE if Value matches specified data type
430
+ */
431
+ is_type: function(val, type, nonempty) {
432
+ var ret = false;
433
+ if ( this.is_set(val) && null !== val && this.is_set(type) ) {
434
+ switch ( $.type(type) ) {
435
+ case 'function':
436
+ ret = ( val instanceof type ) ? true : false;
437
+ break;
438
+ case 'string':
439
+ ret = ( $.type(val) === type ) ? true : false;
440
+ break;
441
+ default:
442
+ ret = false;
443
+ break;
444
+ }
445
+ }
446
+
447
+ // Validate empty values
448
+ if ( ret && ( !this.is_set(nonempty) || !!nonempty ) ) {
449
+ ret = !this.is_empty(val);
450
+ }
451
+ return ret;
452
+ },
453
+
454
+ /**
455
+ * Check if value is a string
456
+ * @uses is_type()
457
+ * @param mixed value Value to check
458
+ * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
459
+ * @return bool TRUE if value is a valid string
460
+ */
461
+ is_string: function(value, nonempty) {
462
+ return this.is_type(value, 'string', nonempty);
463
+ },
464
+
465
+ /**
466
+ * Check if value is an array
467
+ * @uses is_type()
468
+ * @param mixed value Value to check
469
+ * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
470
+ * @return bool TRUE if value is a valid array
471
+ */
472
+ is_array: function(value, nonempty) {
473
+ return ( this.is_type(value, 'array', nonempty) );
474
+ },
475
+
476
+ /**
477
+ * Check if value is a boolean
478
+ * @uses is_type()
479
+ * @param mixed value Value to check
480
+ * @return bool TRUE if value is a valid boolean
481
+ */
482
+ is_bool: function(value) {
483
+ return this.is_type(value, 'boolean', false);
484
+ },
485
+
486
+ /**
487
+ * Check if value is an object
488
+ * @uses is_type()
489
+ * @param mixed value Value to check
490
+ * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
491
+ * @return bool TRUE if value is a valid object
492
+ */
493
+ is_obj: function(value, nonempty) {
494
+ return this.is_type(value, 'object', nonempty);
495
+ },
496
+
497
+ /**
498
+ * Check if value is a function
499
+ * @uses is_type()
500
+ * @param mixed value Value to check
501
+ * @return bool TRUE if value is a valid function
502
+ */
503
+ is_func: function(value) {
504
+ return this.is_type(value, 'function', false);
505
+ },
506
+
507
+ /**
508
+ * Checks if an object has a method
509
+ * @param obj obj Object to check
510
+ * @param string|array key Name(s) of methods to check for
511
+ * @return bool TRUE if method(s) exist, FALSE otherwise
512
+ */
513
+ is_method: function(obj, key) {
514
+ var ret = false;
515
+ if ( this.is_string(key) ) {
516
+ key = [key];
517
+ }
518
+ if ( this.in_obj(obj, key) ) {
519
+ ret = true;
520
+ var x = 0;
521
+ while ( ret && x < key.length ) {
522
+ ret = this.is_func(obj[key[x]]);
523
+ x++;
524
+ }
525
+ }
526
+ return ret;
527
+ },
528
+
529
+ /**
530
+ * Check if object is instance of a class
531
+ * @param obj obj Instance object
532
+ * @param obj parent Class to compare with
533
+ * @return bool TRUE if object is instance of class
534
+ */
535
+ is_instance: function(obj, parent) {
536
+ if ( !this.is_func(parent) ) {
537
+ return false;
538
+ }
539
+ return ( this.is_obj(obj) && ( obj instanceof parent ) );
540
+ },
541
+
542
+ /**
543
+ * Check if object is class
544
+ * Optionally check if class is sub-class of another class
545
+ * @param func cls Class to check
546
+ * @param func parent (optional) parent class
547
+ * @return bool TRUE if object is valid class (and sub-class if parent is specified)
548
+ */
549
+ is_class: function(cls, parent) {
550
+ // Validate class
551
+ var ret = ( this.is_func(cls) && ( 'prototype' in cls ) );
552
+ // Check parent class
553
+ if ( ret && this.is_set(parent) ) {
554
+ ret = this.is_instance(cls.prototype, parent);
555
+ }
556
+ return ret;
557
+ },
558
+
559
+ /**
560
+ * Check if value is a number
561
+ * @uses is_type()
562
+ * @param mixed value Value to check
563
+ * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
564
+ * @return bool TRUE if value is a valid number
565
+ */
566
+ is_num: function(value, nonempty) {
567
+ var f = {
568
+ 'nan': ( Number.isNaN ) ? Number.isNaN : isNaN,
569
+ 'finite': ( Number.isFinite ) ? Number.isFinite : isFinite
570
+ };
571
+ return ( this.is_type(value, 'number', nonempty) && !f.nan(value) && f.finite(value) );
572
+ },
573
+
574
+ /**
575
+ * Check if value is a integer
576
+ * @uses is_type()
577
+ * @param mixed value Value to check
578
+ * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
579
+ * @return bool TRUE if value is a valid integer
580
+ */
581
+ is_int: function(value, nonempty) {
582
+ return ( this.is_num(value, nonempty) && Math.floor(value) === value );
583
+ },
584
+
585
+ /**
586
+ * Check if value is scalar (string, number, boolean)
587
+ * @uses is_type()
588
+ * @param mixed value Value to check
589
+ * @param bool nonempty (optional) Check for empty value? (Default: TRUE)
590
+ * @return bool TRUE if value is scalar
591
+ */
592
+ is_scalar: function(value, nonempty) {
593
+ return ( this.is_num(value, nonempty) || this.is_string(value, nonempty) || this.is_bool(value) );
594
+ },
595
+
596
+ /**
597
+ * Checks if value is empty
598
+ * @param mixed value Value to check
599
+ * @param string type (optional) Data type
600
+ * @return bool TRUE if value is empty
601
+ */
602
+ is_empty: function(value, type) {
603
+ var ret = false;
604
+ // Check Undefined
605
+ if ( !this.is_set(value) ) {
606
+ ret = true;
607
+ } else {
608
+ // Check standard values
609
+ var empties = [null, "", false, 0];
610
+ var x = 0;
611
+ while ( !ret && x < empties.length ) {
612
+ ret = ( empties[x] === value );
613
+ x++;
614
+ }
615
+ }
616
+
617
+ // Advanced check
618
+ if ( !ret ) {
619
+ // Validate type
620
+ if ( !this.is_set(type) ) {
621
+ type = $.type(value);
622
+ }
623
+ // Type-based check
624
+ if ( this.is_type(value, type, false) ) {
625
+ switch ( type ) {
626
+ case 'string':
627
+ case 'array':
628
+ ret = ( value.length === 0 );
629
+ break;
630
+ case 'number':
631
+ ret = ( value == 0 ); // jshint ignore:line
632
+ break;
633
+ case 'object':
634
+ if ( !$.isPlainObject(value) ) {
635
+ // Custom object. Unable to evaluate emptiness further
636
+ ret = false;
637
+ } else {
638
+ // Evaluate plain object
639
+ if ( Object.getOwnPropertyNames ) {
640
+ // Modern browser check
641
+ ret = ( Object.getOwnPropertyNames(value).length === 0 );
642
+ } else if ( value.hasOwnProperty ) {
643
+ // Legacy browser check
644
+ ret = true;
645
+ for ( var key in value ) {
646
+ if ( value.hasOwnProperty(key) ) {
647
+ ret = false;
648
+ break;
649
+ }
650
+ }
651
+ }
652
+ }
653
+ break;
654
+ }
655
+ } else {
656
+ ret = true;
657
+ }
658
+ }
659
+ return ret;
660
+ },
661
+
662
+ /**
663
+ * Check if object is a jQuery.Promise instance
664
+ * Will also match (but not guarantee) jQuery.Deferred instances
665
+ * @uses is_method()
666
+ * @param obj obj Object to check
667
+ * @return bool TRUE if object is Promise/Deferred
668
+ */
669
+ is_promise: function(obj) {
670
+ return ( this.is_method(obj, ['then', 'done', 'always', 'fail', 'pipe']) );
671
+ },
672
+
673
+ /**
674
+ * Return formatted string
675
+ * @param string fmt Format template
676
+ * @param string val Replacement value (Multiple parameters may be set)
677
+ * @return string Formatted string
678
+ */
679
+ format: function(fmt, val) {
680
+ // Validate format
681
+ if ( !this.is_string(fmt) ) {
682
+ return '';
683
+ }
684
+ var params = [];
685
+ var ph = '%s';
686
+ /**
687
+ * Clean string (remove placeholders)
688
+ */
689
+ var strip = function(txt) {
690
+ return ( txt.indexOf(ph) !== -1 ) ? txt.replace(ph, '') : txt;
691
+ };
692
+ // Stop processing if no replacement values specified or format string contains no placeholders
693
+ if ( arguments.length < 2 || fmt.indexOf(ph) === -1 ) {
694
+ return strip(fmt);
695
+ }
696
+ // Get replacement values
697
+ params = Array.prototype.slice.call(arguments, 1);
698
+ val = null;
699
+ // Clean parameters
700
+ for ( var x = 0; x < params.length; x++ ) {
701
+ if ( !this.is_scalar(params[x], false) ) {
702
+ params[x] = '';
703
+ }
704
+ }
705
+
706
+ // Replace all placeholders at once if single parameter set
707
+ if ( params.length === 1 ) {
708
+ fmt = fmt.replace(ph, params[0].toString());
709
+ } else {
710
+ var idx = 0; // Current replacement index
711
+ var len = params.length; // Number of replacements
712
+ var rlen = ph.length; // Placeholder length
713
+ var pos = 0; // Current placeholder position (in format template)
714
+ while ( ( pos = fmt.indexOf(ph) ) && pos !== -1 && idx < len ) {
715
+ // Replace current placeholder with respective parameter
716
+ fmt = fmt.substr(0, pos) + params[idx].toString() + fmt.substr(pos + rlen);
717
+ idx++;
718
+ }
719
+ // Remove any remaining placeholders
720
+ fmt = strip(fmt);
721
+ }
722
+ return fmt;
723
+ },
724
+
725
+ /**
726
+ * Checks if key(s) exist in an object
727
+ * @param object obj Object to check
728
+ * @param string|array key Key(s) to check for in object
729
+ * @param bool all (optional) All keys must exist in object? (Default: TRUE)
730
+ * @return bool TRUE if key(s) exist in object
731
+ */
732
+ in_obj: function(obj, key, all) {
733
+ // Validate
734
+ if ( !this.is_bool(all) ) {
735
+ all = true;
736
+ }
737
+ if ( this.is_string(key) ) {
738
+ key = [key];
739
+ }
740
+ // Check for keys
741
+ var ret = false;
742
+ if ( this.is_obj(obj) && this.is_array(key) ) {
743
+ var val;
744
+ for ( var x = 0; x < key.length; x++ ) {
745
+ val = key[x];
746
+ ret = ( this.is_string(val) && ( val in obj ) ) ? true : false;
747
+ // Stop processing if conditions have been met
748
+ if ( ( !all && ret ) || ( all && !ret ) ) {
749
+ break;
750
+ }
751
+ }
752
+ }
753
+ return ret;
754
+ },
755
+
756
+ /**
757
+ * Retrieve an object's keys
758
+ * @param obj Object to parse
759
+ * @return array List of object's keys
760
+ */
761
+ obj_keys: function(obj) {
762
+ var keys = [];
763
+ // Validation
764
+ if ( !this.is_obj(obj) ) {
765
+ return keys;
766
+ }
767
+ if ( Object.keys ) {
768
+ keys = Object.keys(obj);
769
+ } else {
770
+ var prop;
771
+ for ( prop in obj ) {
772
+ if ( obj.hasOwnProperty(prop) ) {
773
+ keys.push(prop);
774
+ }
775
+ }
776
+ }
777
+ return keys;
778
+ },
779
+
780
+ /**
781
+ * Find common elements of 2 or more arrays
782
+ * @param array arr1 First array
783
+ * @param array arr2 Second array (additional arrays can be passed as well)
784
+ * @return array Elements common to all
785
+ */
786
+ arr_intersect: function(arr1, arr2) {
787
+ var ret = [];
788
+ // Get arrays
789
+ var params = Array.prototype.slice.call(arguments);
790
+ // Clean arrays
791
+ var arrs = [];
792
+ var x;
793
+ for ( x = 0; x < params.length; x++ ) {
794
+ if ( this.is_array(params[x], false) ) {
795
+ arrs.push(params[x]);
796
+ }
797
+ }
798
+ // Stop processing if no valid arrays to compare
799
+ if ( arrs.length < 2 ) {
800
+ return ret;
801
+ }
802
+ params = arr1 = arr2 = null;
803
+ // Find common elements in arrays
804
+ var base = arrs.shift();
805
+ var add;
806
+ var sub;
807
+ for ( x = 0; x < base.length; x++ ) {
808
+ add = true;
809
+ // Check other arrays for element match
810
+ for ( sub = 0; sub < arrs.length; sub++ ) {
811
+ if ( arrs[sub].indexOf(base[x]) === -1 ) {
812
+ add = false;
813
+ break;
814
+ }
815
+ }
816
+ if ( add ) {
817
+ ret.push(base[x]);
818
+ }
819
+ }
820
+ // Return intersection results
821
+ return ret;
822
+ },
823
+
824
+ /**
825
+ * Generates a GUID string.
826
+ * @returns string The generated GUID.
827
+ * @example af8a8416-6e18-a307-bd9c-f2c947bbb3aa
828
+ * @author Slavik Meltser (slavik@meltser.info).
829
+ * @link http://slavik.meltser.info/?p=142
830
+ */
831
+ guid: function() {
832
+ function _p8(s) {
833
+ var p = (Math.random().toString(16)+"000000000").substr(2,8);
834
+ return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ;
835
+ }
836
+ return _p8() + _p8(true) + _p8(true) + _p8();
837
+ },
838
+
839
+ /**
840
+ * Parse URI
841
+ * @param string uri URI to parse
842
+ * @return obj URI components (DOM anchor element)
843
+ */
844
+ parse_uri: function(uri) {
845
+ return $('<a href="' + uri + '"/>').get(0);
846
+ },
847
+ /**
848
+ * Parse URI query string
849
+ * @param string uri URI with query string to parse
850
+ * @return obj Query variables and values (empty if no query string)
851
+ */
852
+ parse_query: function(uri) {
853
+ var delim = {
854
+ 'vars': '&',
855
+ 'val': '='
856
+ };
857
+ var query = {
858
+ 'raw': [],
859
+ 'parsed': {},
860
+ 'string': ''
861
+ };
862
+ uri = this.parse_uri(uri);
863
+ if ( 0 === uri.search.indexOf('?') ) {
864
+ // Extract query string
865
+ query.raw = uri.search.substr(1).split(delim.vars);
866
+ var i, temp, key, val;
867
+ // Build query object
868
+ for ( i = 0; i < query.raw.length; i++ ) {
869
+ // Split var and value
870
+ temp = query.raw[i].split(delim.val);
871
+ key = temp.shift();
872
+ val = ( temp.length > 0 ) ? temp.join(delim.val) : null;
873
+ query.parsed[key] = val;
874
+ }
875
+ }
876
+ return query.parsed;
877
+ },
878
+ /**
879
+ * Build query string from object
880
+ * @param obj query Query data
881
+ * @return string Query data formatted as HTTP query string
882
+ */
883
+ build_query: function(query) {
884
+ var q = [];
885
+ var delim = {
886
+ 'vars': '&',
887
+ 'val': '='
888
+ };
889
+ var val;
890
+ for ( var key in query ) {
891
+ val = ( null !== query[key] ) ? delim.val + query[key] : '';
892
+ q.push(key + val);
893
+ }
894
+ return q.join(delim.vars);
895
+ }
896
+ };
897
+
898
+ // Attach Utilities
899
+ Base.attach('util', Utilities, true);
900
+
901
+ /**
902
+ * SLB Base Class
903
+ */
904
+ var SLB_Base = Class.extend(Base);
905
+
906
+ /**
907
+ * Core
908
+ */
909
+ var Core = {
910
+ /* Properties */
911
+
912
+ base: true,
913
+ context: [],
914
+
915
+ /**
916
+ * New object initializer
917
+ * @var obj
918
+ */
919
+ Class: SLB_Base,
920
+
921
+ /* Methods */
922
+
923
+ /**
924
+ * Init
925
+ * Set variables, DOM, etc.
926
+ */
927
+ _init: function() {
928
+ this._super();
929
+ $('html').addClass(this.util.get_prefix());
930
+ }
931
+ };
932
+ var SLB_Core = SLB_Base.extend(Core);
933
+ window.SLB = new SLB_Core();
934
  })(jQuery);}
client/js/dev/lib.view.js CHANGED
@@ -1,4741 +1,4741 @@
1
- /**
2
- * View (Lightbox) functionality
3
- * @package Simple Lightbox
4
- * @subpackage View
5
- * @author Archetyped
6
- */
7
- /* global SLB */
8
- if ( !!window.SLB && !!SLB.attach ) { (function ($) {
9
-
10
- /*-** Controller **-*/
11
-
12
- var View = {
13
-
14
- /* Properties */
15
-
16
- /**
17
- * Media item properties
18
- * > Item key: Link URI
19
- * > Base properties
20
- * > id: WP Attachment ID
21
- * > source: Source URI
22
- * > title: Media title (generally WP attachment title)
23
- * > desc: Media description (generally WP Attachment content)
24
- * > type: Asset type (attachment, image, etc.)
25
- */
26
- assets: {},
27
-
28
- /**
29
- * Component types that can have default instances
30
- * @var array
31
- */
32
- component_defaults: [],
33
-
34
- /**
35
- * Collection of jQuery.Deferred instances added during loading routine
36
- * @var array
37
- */
38
- loading: [],
39
-
40
- /**
41
- * Cache
42
- * @var object
43
- */
44
- cache: {},
45
-
46
- /**
47
- * Temporary component instances
48
- * For use by controller when no component instance is available
49
- * > Key: Component slug
50
- * > Value: Component instance
51
- */
52
- component_temps: {},
53
-
54
- /* Options */
55
- options: {},
56
-
57
- /* Methods */
58
-
59
- /* Init */
60
-
61
- /**
62
- * Instance initialization
63
- */
64
- _init: function() {
65
- this._super();
66
- // Component References
67
- this.init_refs();
68
- // Components
69
- this.init_components();
70
- },
71
-
72
- /**
73
- * Update component references in component definitions
74
- */
75
- init_refs: function() {
76
- var r;
77
- var ref;
78
- var prop;
79
- for ( prop in this ) {
80
- prop = this[prop];
81
- // Process only components
82
- if ( !this.is_component(prop) ) {
83
- continue;
84
- }
85
- // Update component references
86
- if ( !this.util.is_empty(prop.prototype._refs) ) {
87
- for ( r in prop.prototype._refs ) {
88
- ref = prop.prototype._refs[r];
89
- if ( this.util.is_string(ref) && ref in this ) {
90
- ref = prop.prototype._refs[r] = this[ref];
91
- }
92
- if ( !this.util.is_class(ref) ) {
93
- delete prop.prototype_refs[r];
94
- }
95
- }
96
- }
97
- }
98
- },
99
-
100
- /**
101
- * Initialize Components
102
- */
103
- init_components: function() {
104
- this.component_defaults = [
105
- this.Viewer
106
- ];
107
- },
108
-
109
- /**
110
- * Client Initialization
111
- * @param obj options Global options
112
- */
113
- init: function(options) {
114
- var t = this;
115
- // Defer initialization until all components loaded
116
- $.when.apply($, this.loading).always(function() {
117
- // Set options
118
- $.extend(true, t.options, options);
119
-
120
- /* Event handlers */
121
-
122
- // History
123
- $(window).on('popstate', function(e) {
124
- var state = e.originalEvent.state;
125
- if ( t.util.in_obj(state, ['item', 'viewer']) ) {
126
- var v = t.get_viewer(state.viewer);
127
- v.history_handle(e);
128
- return e.preventDefault();
129
- }
130
- });
131
-
132
- /* Set defaults */
133
-
134
- // Items
135
- t.init_items();
136
- });
137
- },
138
-
139
- /* Components */
140
-
141
- /**
142
- * Check if default component instance can be created
143
- * @uses View.component_defaults
144
- * @param func type Component type to check
145
- * @return bool TRUE if default component instance creation is allowed
146
- */
147
- can_make_default_component: function(type) {
148
- return ( -1 !== $.inArray(type, this.component_defaults) );
149
- },
150
-
151
- /**
152
- * Check if object is valid component class
153
- * @param func comp Component to check
154
- * @return bool TRUE if object is valid component class
155
- */
156
- is_component: function(comp) {
157
- return ( this.util.is_class(comp, this.Component) );
158
- },
159
-
160
- /**
161
- * Retrieve collection of components of specified type
162
- * @param func type Component type
163
- * @return object Component collection (Default: Empty object)
164
- */
165
- get_components: function(type) {
166
- var ret = {};
167
- if ( this.is_component(type) ) {
168
- // Determine collection based on component slug
169
- var coll = type.prototype._slug + 's';
170
- // Create default collection
171
- if ( ! ( coll in this.cache ) ) {
172
- this.cache[coll] = {};
173
- }
174
- ret = this.cache[coll];
175
- }
176
- return ret;
177
- },
178
-
179
- /**
180
- * Retrieve component from specific collection
181
- * @param function type Component type
182
- * @param string id Component ID
183
- * @return object|null Component reference (NULL if invalid)
184
- */
185
- get_component: function(type, id) {
186
- var ret = null;
187
- // Validate parameters
188
- if ( !this.util.is_func(type) ) {
189
- return ret;
190
- }
191
- // Sanitize id
192
- if ( !this.util.is_string(id) ) {
193
- id = null;
194
- }
195
-
196
- // Get component from collection
197
- var coll = this.get_components(type);
198
- if ( this.util.is_obj(coll) ) {
199
- var tid = ( this.util.is_string(id) ) ? id : this.util.add_prefix('default');
200
- if ( tid in coll ) {
201
- ret = coll[tid];
202
- }
203
- }
204
-
205
- // Default: Create default component
206
- if ( this.util.is_empty(ret) ) {
207
- if ( this.util.is_string(id) || this.can_make_default_component(type) ) {
208
- ret = this.add_component(type, id);
209
- }
210
- }
211
- // Return component
212
- return ret;
213
- },
214
-
215
- /**
216
- * Create new component instance and save to appropriate collection
217
- * @param function type Component type to create
218
- * @param string id ID of component
219
- * @param object options Component initialization options (Default options used if default component is allowed)
220
- * @return object|null New component (NULL if invalid)
221
- */
222
- add_component: function(type, id, options) {
223
- // Validate type
224
- if ( !this.util.is_func(type) ) {
225
- return false;
226
- }
227
- // Validate request
228
- if ( this.util.is_empty(id) && !this.can_make_default_component(type) ) {
229
- return false;
230
- }
231
- // Defaults
232
- var ret = null;
233
- if ( this.util.is_empty(id) ) {
234
- id = this.util.add_prefix('default');
235
- }
236
- if ( !this.util.is_obj(options) ) {
237
- options = {};
238
- }
239
- // Check if specialized method exists for component type
240
- var m = ( 'component' !== type.prototype._slug ) ? 'add_' + type.prototype._slug : null;
241
- if ( !this.util.is_empty(m) && ( m in this ) && this.util.is_func(this[m]) ) {
242
- ret = this[m](id, options);
243
- }
244
- // Default process
245
- else {
246
- ret = new type(id, options);
247
- }
248
-
249
- // Add new component to collection
250
- if ( this.util.is_type(ret, type) ) {
251
- // Get collection
252
- var coll = this.get_components(type);
253
- // Add to collection
254
- switch ( $.type(coll) ) {
255
- case 'object' :
256
- coll[id] = ret;
257
- break;
258
- case 'array' :
259
- coll.push(ret);
260
- break;
261
- }
262
- } else {
263
- ret = null;
264
- }
265
- // Return new component
266
- return ret;
267
- },
268
-
269
- /**
270
- * Create new temporary component instance
271
- * @param function type Component type
272
- * @return New temporary instance
273
- */
274
- add_component_temp: function(type) {
275
- var ret = null;
276
- if ( this.is_component(type) ) {
277
- // Create new instance
278
- ret = new type('');
279
- // Save to collection
280
- this.component_temps[ret._slug] = ret;
281
- }
282
- return ret;
283
- },
284
-
285
- /**
286
- * Retrieve temporary component instance
287
- * Creates new temp component instance for type if not previously created
288
- * @param function type Component type to retrieve temp instance for
289
- * @return obj Temporary component instance
290
- */
291
- get_component_temp: function(type) {
292
- return ( this.has_component_temp(type) ) ? this.component_temps[type.prototype._slug] : this.add_component_temp(type);
293
- },
294
-
295
- /**
296
- * Check if temporary component instance exists
297
- * @param function type Component type to check for
298
- * @return bool TRUE if temp instance exists, FALSE otherwise
299
- */
300
- has_component_temp: function(type) {
301
- return ( this.is_component(type) && ( type.prototype._slug in this.component_temps ) ) ? true : false;
302
- },
303
-
304
- /* Properties */
305
-
306
- /**
307
- * Retrieve specified options
308
- * @param array opts Array of option names
309
- * @return object Specified options (Default: empty object)
310
- */
311
- get_options: function(opts) {
312
- var ret = {};
313
- // Validate
314
- if ( this.util.is_string(opts) ) {
315
- opts = [opts];
316
- }
317
- if ( !this.util.is_array(opts) ) {
318
- return ret;
319
- }
320
- // Get specified options
321
- for ( var x = 0; x < opts.length; x++ ) {
322
- // Skip if option not set
323
- if ( !( opts[x] in this.options ) ) {
324
- continue;
325
- }
326
- ret[ opts[x] ] = this.options[ opts[x] ];
327
- }
328
- return ret;
329
- },
330
-
331
- /**
332
- * Retrieve option
333
- * @uses View.options
334
- * @param string opt Option to retrieve
335
- * @param mixed def (optional) Default value if option does not exist (Default: NULL)
336
- * @return mixed Option value
337
- */
338
- get_option: function(opt, def) {
339
- var ret = this.get_options(opt);
340
- if ( this.util.is_obj(ret) && ( opt in ret ) ) {
341
- ret = ret[opt];
342
- } else {
343
- ret = ( this.util.is_set(def) ) ? def : null;
344
- }
345
- return ret;
346
- },
347
-
348
- /* Viewers */
349
-
350
- /**
351
- * Add viewer instance to collection
352
- * @param string id Viewer ID
353
- * @param obj options Viewer options
354
- * @return Viewer New viewer instance
355
- */
356
- add_viewer: function(id, options) {
357
- // Create viewer
358
- var v = new this.Viewer(id, options);
359
- // Save viewer
360
- this.get_viewers()[v.get_id()] = v;
361
- // Return viewer
362
- return v;
363
- },
364
-
365
- /**
366
- * Retrieve all viewer instances
367
- * @return obj Viewer instances
368
- */
369
- get_viewers: function() {
370
- return this.get_components(this.Viewer);
371
- },
372
-
373
- /**
374
- * Check if viewer exists
375
- * @param string v Viewer ID
376
- * @return bool TRUE if viewer exists, FALSE otherwise
377
- */
378
- has_viewer: function(v) {
379
- return ( this.util.is_string(v) && v in this.get_viewers() ) ? true : false;
380
- },
381
-
382
- /**
383
- * Retrieve Viewer instance
384
- * Default viewer retrieved if specified viewer does not exist
385
- * > Default viewer created if necessary
386
- * @param string v Viewer ID to retrieve
387
- * @return Viewer Viewer instance
388
- */
389
- get_viewer: function(v) {
390
- // Retrieve default viewer if specified viewer not set
391
- if ( !this.has_viewer(v) ) {
392
- v = this.util.add_prefix('default');
393
- // Create default viewer if necessary
394
- if ( !this.has_viewer(v) ) {
395
- v = this.add_viewer(v);
396
- v = v.get_id();
397
- }
398
- }
399
- return this.get_viewers()[v];
400
- },
401
-
402
- /* Items */
403
-
404
- /**
405
- * Set event handlers
406
- */
407
- init_items: function() {
408
- // Define handler
409
- var t = this;
410
- var handler = function() {
411
- var ret = t.show_item(this);
412
- if ( !t.util.is_bool(ret) ) {
413
- ret = true;
414
- }
415
- return !ret;
416
- };
417
-
418
- // Get activated links
419
- var sel = this.util.format('a[href][%s="%s"]', this.util.get_attribute('active'), 1);
420
- // Add event handler
421
- $(document).on('click', sel, null, handler);
422
- },
423
-
424
- /**
425
- * Retrieve cached items
426
- * @return obj Items collection
427
- */
428
- get_items: function() {
429
- return this.get_components(this.Content_Item);
430
- },
431
-
432
- /**
433
- * Retrieve specific Content_Item instance
434
- * @param mixed Item reference
435
- * > Content_Item: Item instance (returned immediately)
436
- * > DOM element: DOM element to get item for
437
- * > int: Index of cached item
438
- * @return Content_Item Item instance for DOM node
439
- */
440
- get_item: function(ref) {
441
- // Evaluate reference type
442
-
443
- // Content Item instance
444
- if ( this.util.is_type(ref, this.Content_Item) ) {
445
- return ref;
446
- }
447
- // Retrieve item instance
448
- var item = null;
449
-
450
- // DOM element
451
- if ( this.util.in_obj(ref, 'nodeType') ) {
452
- // Check if item instance attached to element
453
- var key = this.get_component_temp(this.Content_Item).get_data_key();
454
- item = $(ref).data(key);
455
- }
456
- // Cached item (index)
457
- else if ( this.util.is_string(ref, false) ) {
458
- var items = this.get_items();
459
- if ( ref in items ) {
460
- item = items[ref];
461
- }
462
- }
463
- // Create default item instance
464
- if ( !this.util.is_instance(item, this.Content_Item) ) {
465
- item = this.add_item(ref);
466
- }
467
- return item;
468
- },
469
-
470
- /**
471
- * Create new item instance
472
- * @param obj el DOM element representing item
473
- * @return Content_Item New item instance
474
- */
475
- add_item: function(el) {
476
- return ( new this.Content_Item(el) );
477
- },
478
-
479
- /**
480
- * Display item in viewer
481
- * @param obj el DOM element representing item
482
- * @return bool Display result (TRUE if item displayed without issues)
483
- */
484
- show_item: function(el) {
485
- return this.get_item(el).show();
486
- },
487
-
488
- /**
489
- * Cache item instance
490
- * @uses View.get_items() to retrieve item cache
491
- * @param Content_Item item Item to cache
492
- * @return Content_item Item instance
493
- */
494
- save_item: function(item) {
495
- if ( !this.util.is_instance(item, this.Content_Item) ) {
496
- return item;
497
- }
498
- // Save item
499
- this.get_items()[item.get_id()] = item;
500
- // Return item instance
501
- return item;
502
- },
503
-
504
- /* Content Handler */
505
-
506
- /**
507
- * Retrieve content handlers
508
- * @return object Content handlers
509
- */
510
- get_content_handlers: function() {
511
- return this.get_components(this.Content_Handler);
512
- },
513
-
514
- /**
515
- * Find matching content handler for item
516
- * @param Content_Item|string item Item to find handler for (or ID of Handler)
517
- * @return Content_Handler|null Matching content handler (NULL if no matching handler found)
518
- */
519
- get_content_handler: function(item) {
520
- // Determine handler to retrieve
521
- var type = ( this.util.is_instance(item, this.Content_Item) ) ? item.get_attribute('type', '') : item.toString();
522
- // Retrieve handler
523
- var types = this.get_content_handlers();
524
- return ( type in types ) ? types[type] : null;
525
- },
526
-
527
- /**
528
- * Add/Update Content Handler
529
- * @param string id Handler ID
530
- * @param obj attr Handler attributes
531
- * @return obj|null Handler instance (NULL on failure)
532
- */
533
- extend_content_handler: function(id, attr) {
534
- var hdl = null;
535
- if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) {
536
- return hdl;
537
- }
538
- hdl = this.get_content_handler(id);
539
- // Add new content handler
540
- if ( null === hdl ) {
541
- var hdls = this.get_content_handlers();
542
- hdls[id] = hdl = new this.Content_Handler(id, attr);
543
- }
544
- // Update existing handler
545
- else {
546
- hdl.set_attributes(attr);
547
- }
548
- // Load styles
549
- if ( this.util.in_obj(attr, 'styles') ) {
550
- this.load_styles(attr.styles);
551
- }
552
- return hdl;
553
- },
554
-
555
- /* Group */
556
-
557
- /**
558
- * Add new group
559
- * @param string g Group ID
560
- * > If group with same ID already set, new group replaces existing one
561
- * @param object attrs (optional) Group attributes
562
- * @return Group New group
563
- */
564
- add_group: function(g, attrs) {
565
- // Create new group
566
- g = new this.Group(g, attrs);
567
- // Cache group
568
- this.get_groups()[g.get_id()] = g;
569
-
570
- return g;
571
- },
572
-
573
- /**
574
- * Retrieve groups
575
- * @uses groups property
576
- * @return object Registered groups
577
- */
578
- get_groups: function() {
579
- return this.get_components(this.Group);
580
- },
581
-
582
- /**
583
- * Retrieve specified group
584
- * New group created if not yet set
585
- * @uses View.has_group()
586
- * @uses View.add_group()
587
- * @uses View.get_groups()
588
- * @param string g Group ID
589
- * @return Group Group instance
590
- */
591
- get_group: function(g) {
592
- return ( !this.has_group(g) ) ? this.add_group(g) : this.get_groups()[g];
593
- },
594
-
595
- /**
596
- * Checks if group is registered
597
- * @uses get_groups() to retrieve registered groups
598
- * @return bool TRUE if group exists, FALSE otherwise
599
- */
600
- has_group: function(g) {
601
- return ( this.util.is_string(g) && ( g in this.get_groups() ) );
602
- },
603
-
604
- /* Theme */
605
-
606
- /**
607
- * Add/Update theme
608
- * @param string name Theme name
609
- * @param obj attr (optional) Theme options
610
- * @return obj|bool Theme model
611
- */
612
- extend_theme: function(id, attr) {
613
- // Validate
614
- if ( !this.util.is_string(id) ) {
615
- return false;
616
- }
617
- var dfr = $.Deferred();
618
- this.loading.push(dfr);
619
-
620
- // Get model if it already exists
621
- var model = this.get_theme_model(id);
622
-
623
- // Create default attributes for new theme
624
- if ( this.util.is_empty(model) ) {
625
- // Save default model
626
- model = this.save_theme_model( {'parent': null, 'id': id} );
627
- }
628
-
629
- // Add custom attributes
630
- if ( this.util.is_obj(attr) ) {
631
- // Sanitize
632
- if ( 'id' in attr ) {
633
- delete(attr['id']);
634
- }
635
- $.extend(model, attr);
636
- }
637
-
638
- // Load styles
639
- if ( this.util.in_obj(attr, 'styles') ) {
640
- this.load_styles(attr.styles);
641
- }
642
-
643
- // Link parent model
644
- if ( !this.util.is_obj(model.parent) ) {
645
- model.parent = this.get_theme_model(model.parent);
646
- }
647
-
648
- // Complete loading when all components loaded
649
- dfr.resolve();
650
- return model;
651
- },
652
-
653
- /**
654
- * Retrieve theme models
655
- * @return obj Theme models
656
- */
657
- get_theme_models: function() {
658
- // Retrieve matching theme model
659
- return this.Theme.prototype._models;
660
- },
661
-
662
- /**
663
- * Retrieve theme model
664
- * @param string id Theme to retrieve
665
- * @return obj Theme model (Default: empty object)
666
- */
667
- get_theme_model: function(id) {
668
- var ms = this.get_theme_models();
669
- return ( this.util.in_obj(ms, id) ) ? ms[id] : {};
670
- },
671
-
672
- /**
673
- * Save theme model
674
- * @uses View.get_theme_models() to retrieve Theme model collection
675
- * @param obj Theme model to save
676
- * @return obj Saved model
677
- */
678
- save_theme_model: function(model) {
679
- if ( this.util.in_obj(model, 'id') && this.util.is_string(model.id) ) {
680
- // Save model
681
- this.get_theme_models()[model.id] = model;
682
- }
683
- return model;
684
- },
685
-
686
- /**
687
- * Add/Update Template Tag Handler
688
- * @param string id Handler ID
689
- * @param obj attr Handler attributes
690
- * @return obj|bool Handler instance (FALSE on failure)
691
- */
692
- extend_template_tag_handler: function(id, attr) {
693
- if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) {
694
- return false;
695
- }
696
- var hdl;
697
- var hdls = this.get_template_tag_handlers();
698
- if ( this.util.in_obj(hdls, id) ) {
699
- // Update existing handler
700
- hdl = hdls[id];
701
- hdl.set_attributes(attr);
702
- } else {
703
- // Add new content handler
704
- hdl = new this.Template_Tag_Handler(id, attr);
705
- hdls[hdl.get_id()] = hdl;
706
- }
707
- // Load styles
708
- if ( this.util.in_obj(attr, 'styles') ) {
709
- this.load_styles(attr.styles);
710
- }
711
- // Set hooks
712
- if ( this.util.in_obj(attr, '_hooks') ) {
713
- attr._hooks.call(hdl);
714
- }
715
- return hdl;
716
- },
717
-
718
- /**
719
- * Retrieve Template Tag Handler collection
720
- * @return obj Template_Tag_Handler objects
721
- */
722
- get_template_tag_handlers: function() {
723
- return this.Template_Tag.prototype.handlers;
724
- },
725
-
726
- /**
727
- * Retrieve template tag handler
728
- * @param string id ID of tag handler to retrieve
729
- * @return Template_Tag_Handler|null Tag Handler instance (NULL for invalid ID)
730
- */
731
- get_template_tag_handler: function(id) {
732
- var handlers = this.get_template_tag_handlers();
733
- // Retrieve existing handler or return new handler
734
- return ( this.util.in_obj(handlers, id) ) ? handlers[id] : null;
735
- },
736
-
737
- /**
738
- * Load styles
739
- * @param array styles Styles to load
740
- */
741
- load_styles: function(styles) {
742
- if ( this.util.is_array(styles) ) {
743
- var out = [];
744
- var style;
745
- for ( var x = 0; x < styles.length; x++ ) {
746
- style = styles[x];
747
- if ( !this.util.in_obj(style, 'uri') || !this.util.is_string(style.uri) ) {
748
- continue;
749
- }
750
- out.push('<link rel="stylesheet" type="text/css" href="' + style.uri + '" />');
751
- }
752
- $('head').append(out.join(''));
753
- }
754
- }
755
- };
756
-
757
- /* Components */
758
- var Component = {
759
- /*-** Properties **-*/
760
-
761
- /* Internal/Configuration */
762
-
763
- /**
764
- * Base name of component type
765
- * @var string
766
- */
767
- _slug: 'component',
768
-
769
- /**
770
- * Component namespace
771
- * @var string
772
- */
773
- _ns: null,
774
-
775
- /**
776
- * Valid component references for current component
777
- * @var object
778
- * > Key (string): Property name that stores reference
779
- * > Value (function): Data type of component
780
- */
781
- _refs: {},
782
-
783
- /**
784
- * Whether DOM element and component are connected in 1:1 relationship
785
- * Some components will be assigned to different DOM elements depending on usage
786
- * @var bool
787
- */
788
- _reciprocal: false,
789
-
790
- /**
791
- * DOM Element tied to component
792
- * @var DOM Element
793
- */
794
- _dom: null,
795
-
796
- /**
797
- * Component attributes
798
- * @var object
799
- * > Key: Attribute ID
800
- * > Value: Attribute value
801
- */
802
- _attributes: false,
803
-
804
- /**
805
- * Default attributes
806
- * @var object
807
- */
808
- _attr_default: {},
809
-
810
- /**
811
- * Flag indicates whether default attributes have previously been parsed
812
- * @var bool
813
- */
814
- _attr_default_parsed: false,
815
-
816
- /**
817
- * Attributes passed to constructor
818
- * @var obj
819
- */
820
- _attr_init: null,
821
-
822
- /**
823
- * Defines how parent properties should be remapped to component properties
824
- * @var object
825
- */
826
- _attr_map: {},
827
-
828
- /**
829
- * Event handlers
830
- * @var object
831
- * > Key: string Event type
832
- * > Value: array Handlers
833
- */
834
- _events: {},
835
-
836
- /**
837
- * Status management
838
- * @var object
839
- * > Key: Status ID
840
- * > Value: Status value
841
- */
842
- _status: null,
843
-
844
- /**
845
- * Component ID
846
- * @var string
847
- */
848
- _id: '',
849
-
850
- /* Init */
851
-
852
- /**
853
- * Constructor
854
- * @param string id (optional) Component ID (Default ID will be generated if no valid ID provided)
855
- * @param object attributes (optional) Component attributes
856
- */
857
- _c: function(id, attributes) {
858
- // Set ID
859
- this._set_id(id);
860
- // Save init attributes
861
- if ( this.util.is_obj(attributes) ) {
862
- this._attr_init = attributes;
863
- }
864
- // Call hooks
865
- this._hooks();
866
- },
867
-
868
- /**
869
- * Set Component parent to View module
870
- * @uses `_super._set_parent()`
871
- */
872
- _set_parent: function() {
873
- this._super(View);
874
- },
875
-
876
- /**
877
- * Register hooks on init
878
- * Placeholder method to be overridden by child classes
879
- */
880
- _hooks: function() {},
881
-
882
- /* Methods */
883
-
884
- /* Properties */
885
-
886
- /**
887
- * Set instance ID
888
- * Instance ID can only be set once (will not change ID if valid ID already set)
889
- * Generates random GUID if no valid ID provided
890
- * @uses Utilities.guid()
891
- * @param string id Unique ID
892
- * @return string Instance ID
893
- */
894
- _set_id: function(id) {
895
- // Set ID only once
896
- if ( this.util.is_empty(this._id) ) {
897
- this._id = ( this.util.is_string(id) ) ? id : this.util.guid();
898
- }
899
- return this._id;
900
- },
901
-
902
- /**
903
- * Retrieve instance ID
904
- * @uses id as ID base
905
- * @uses _slug to add namespace (if necessary)
906
- * @param bool ns (optional) Whether or not to namespace ID (Default: FALSE)
907
- * @return string Instance ID
908
- */
909
- get_id: function(ns) {
910
- // Get raw ID
911
- var id = this._id;
912
- // Namespace ID
913
- if ( this.util.is_bool(ns) && ns ) {
914
- id = this.add_ns(id);
915
- }
916
-
917
- return id;
918
- },
919
-
920
- /**
921
- * Get namespace
922
- * @uses _slug for namespace segment
923
- * @uses Util.add_prefix() to prefix slug
924
- * @return string Component namespace
925
- */
926
- get_ns: function() {
927
- if ( null === this._ns ) {
928
- this._ns = this.util.add_prefix(this._slug);
929
- }
930
- return this._ns;
931
- },
932
-
933
- /**
934
- * Add namespace to value
935
- * @param string val Value to namespace
936
- * @return string Namespaced value (Empty string if invalid value provided)
937
- */
938
- add_ns: function(val) {
939
- return ( this.util.is_string(val) ) ? this.get_ns() + '_' + val : '';
940
- },
941
-
942
- /**
943
- * Retrieve status
944
- * @param string id Status to retrieve
945
- * @param bool raw (optional) Retrieve raw value (Default: FALSE)
946
- * @return mixed Status value (Default: bool)
947
- */
948
- get_status: function(id, raw) {
949
- var ret = false;
950
- if ( this.util.in_obj(this._status, id) ) {
951
- ret = ( !!raw ) ? this._status[id] : !!this._status[id];
952
- }
953
- return ret;
954
- },
955
-
956
- /**
957
- * Set status
958
- * @param string id Status to retrieve
959
- * @param mixed val Status value (Default: TRUE)
960
- * @return mixed Status value
961
- */
962
- set_status: function(id, val) {
963
- // Validate ID
964
- if ( this.util.is_string(id) ) {
965
- // Validate value
966
- if ( !this.util.is_set(val) ) {
967
- val = true;
968
- }
969
- // Initialize status collection
970
- if ( !this.util.is_obj(this._status, false) ) {
971
- this._status = {};
972
- }
973
- // Set status
974
- this._status[id] = val;
975
- } else if ( !this.util.is_set(val) ) {
976
- val = false;
977
- }
978
- return val;
979
- },
980
-
981
- /**
982
- * Get controller object
983
- * @uses get_parent() (alias)
984
- * @return object Controller object (SLB.View)
985
- */
986
- get_controller: function() {
987
- return this.get_parent();
988
- },
989
-
990
- /* Components */
991
-
992
- /**
993
- * Check if reference exists in object
994
- * @param string ref Reference ID
995
- * @return bool TRUE if reference exists, FALSE otherwise
996
- */
997
- has_reference: function(ref) {
998
- return ( this.util.is_string(ref) && ( ref in this ) && ( ref in this.get_references() ) ) ? true : false;
999
- },
1000
-
1001
- /**
1002
- * Retrieve object references
1003
- * @uses _refs
1004
- * @return obj References object
1005
-
1006
- */
1007
- get_references: function() {
1008
- return this._refs;
1009
- },
1010
-
1011
- /**
1012
- * Retrieve reference data type
1013
- * @param string ref Reference ID
1014
- * @return function Reference data type (NULL if invalid)
1015
- */
1016
- get_reference: function(ref) {
1017
- return ( this.has_reference(ref) ) ? this._refs[ref] : null;
1018
- },
1019
-
1020
- /**
1021
- * Retrieve component reference from current object
1022
- * > Procedure:
1023
- * > Check if top-level property already set
1024
- * > Check attributes
1025
- * > Check parent object (controller)
1026
- * @param string cname Component name
1027
- * @param object options (optional) Request options
1028
- * > check_attr bool Whether or not to check instance attributes for component (Default: TRUE)
1029
- * > get_default bool Whether or not to retrieve default object from controller if none exists in current instance (Default: FALSE)
1030
- * @return object|null Component reference (NULL if no component found)
1031
- */
1032
- get_component: function(cname, options) {
1033
- var c = null;
1034
- // Validate request
1035
- if ( !this.has_reference(cname) ) {
1036
- return c;
1037
- }
1038
-
1039
- // Initialize options
1040
- var opt_defaults = {
1041
- check_attr: true,
1042
- get_default: false
1043
- };
1044
- options = $.extend({}, opt_defaults, options);
1045
-
1046
- // Get component type
1047
- var ctype = this.get_reference(cname);
1048
-
1049
- // Phase 1: Check if component reference previously set
1050
- if ( this.util.is_type(this[cname], ctype) ) {
1051
- return this[cname];
1052
- }
1053
- // If reference not set, iterate through component hierarchy until reference is found
1054
- c = this[cname] = null;
1055
-
1056
- // Phase 2: Check attributes
1057
- if ( options.check_attr ) {
1058
- c = this.get_attribute(cname);
1059
- // Save object-specific component reference
1060
- if ( !this.util.is_empty(c) ) {
1061
- c = this.set_component(cname, c);
1062
- }
1063
- }
1064
-
1065
- // Phase 3: From controller (optional)
1066
- if ( this.util.is_empty(c) && options.get_default ) {
1067
- c = this.get_controller().get_component(ctype);
1068
- }
1069
- return c;
1070
- },
1071
-
1072
- /**
1073
- * Sets component reference on current object
1074
- * > Component property reset (set to NULL) if invalid component supplied
1075
- * @param string name Name of property to set component on object
1076
- * @param string|object ref Component or Component ID (to be retrieved from controller)
1077
- * @param function validate (optional) Additional validation to be performed (Must return bool: TRUE (Valid)/FALSE (Invalid))
1078
- * @return object Component (NULL if invalid)
1079
- */
1080
- set_component: function(name, ref, validate) {
1081
- var invalid = null;
1082
- // Make sure component property exists
1083
- if ( !this.has_reference(name) ) {
1084
- return invalid;
1085
- }
1086
-
1087
- // Validate reference
1088
- if ( this.util.is_empty(ref) ) {
1089
- ref = invalid;
1090
- } else {
1091
- var ctype = this.get_reference(name);
1092
-
1093
- // Get component from controller when ID supplied
1094
- if ( this.util.is_string(ref, false) ) {
1095
- ref = this.get_controller().get_component(ctype, ref);
1096
- }
1097
-
1098
- // Validation callback
1099
- if ( !this.util.is_type(ref, ctype) || ( this.util.is_func(validate) && !validate.call(this, ref) ) ) {
1100
- ref = invalid;
1101
- }
1102
- }
1103
-
1104
- // Set (or clear) component reference
1105
- this[name] = ref;
1106
- // Return value for confirmation
1107
- return this[name];
1108
- },
1109
-
1110
-
1111
- /**
1112
- * Clear component reference
1113
- * @uses set_component() to handle component manipulation
1114
- * @param string Component name
1115
- */
1116
- clear_component: function(name) {
1117
- this.set_component(name, null);
1118
- },
1119
-
1120
- /* Attributes */
1121
-
1122
- /**
1123
- * Initialize attributes
1124
- * @param bool force (optional) Force full initialization of attributes (Default: FALSE)
1125
- * @return void
1126
- */
1127
- init_attributes: function(force) {
1128
- if ( !this.util.is_bool(force) ) {
1129
- force = false;
1130
- }
1131
- if ( force || !this.util.is_obj(this._attributes) ) {
1132
- var a = this._attributes = {};
1133
- // Default attributes
1134
- $.extend(a, this.init_default_attributes());
1135
- // Instantiation attributes
1136
- if ( this.util.is_obj(this._attr_init) ) {
1137
- $.extend(a, this._attr_init);
1138
- }
1139
- // DOM attributes
1140
- $.extend(a, this.get_dom_attributes());
1141
- }
1142
- },
1143
-
1144
- /**
1145
- * Generate default attributes for component
1146
- * @uses View.get_options() to get values from controller
1147
- * @uses _attr_map to Remap controller attributes to instance attributes
1148
- * @uses _attr_default to Store default attributes
1149
- * @return obj Default attributes
1150
- */
1151
- init_default_attributes: function() {
1152
- // Get options from controller
1153
- if ( !this._attr_default_parsed && this.util.is_obj(this._attr_map) ) {
1154
- var opts = this.get_controller().get_options(this.util.obj_keys(this._attr_map));
1155
-
1156
- if ( this.util.is_obj(opts) ) {
1157
- // Remap
1158
- for ( var opt in this._attr_map ) {
1159
- if ( opt in opts && null !== this._attr_map[opt]) {
1160
- // Move value to new property
1161
- opts[this._attr_map[opt]] = opts[opt];
1162
- // Delete old property
1163
- delete opts[opt];
1164
- }
1165
- }
1166
- // Merge with default attributes
1167
- $.extend(true, this._attr_default, opts);
1168
- }
1169
- this._attr_default_parsed = true;
1170
- }
1171
- return this._attr_default;
1172
- },
1173
-
1174
- /**
1175
- * Retrieve DOM attributes
1176
- * @return obj DOM Attributes
1177
- */
1178
- get_dom_attributes: function() {
1179
- var attrs = {};
1180
- var el = this.dom_get(null, {'init': false});
1181
- if ( el.length > 0 ) {
1182
- // Get attributes from element
1183
- var attrs_full = $(el).get(0).attributes;
1184
- if ( this.util.is_obj(attrs_full) ) {
1185
- var attr_prefix = this.util.get_attribute();
1186
- var attr_key;
1187
- $.each(attrs_full, function(idx, attr) {
1188
- if ( attr.name.indexOf( attr_prefix ) === -1 ) {
1189
- return true;
1190
- }
1191
- // Process custom attributes (Strip prefix)
1192
- attr_key = attr.name.substr(attr_prefix.length + 1);
1193
- attrs[attr_key] = attr.value;
1194
- });
1195
- }
1196
- }
1197
- return attrs;
1198
- },
1199
-
1200
- /**
1201
- * Retrieve all instance attributes
1202
- * @uses init_attributes() to initialize attributes (if necessary)
1203
- * @uses _attributes object
1204
- * @return obj Component attributes
1205
- */
1206
- get_attributes: function() {
1207
- // Initilize attributes
1208
- this.init_attributes();
1209
- // Return attributes
1210
- return this._attributes;
1211
- },
1212
-
1213
- /**
1214
- * Retrieve value of specified attribute for value
1215
- * @param string key Attribute to retrieve
1216
- * @param mixed def (optional) Default value if attribute is not set
1217
- * @param bool enforce_type (optional) Whether data type should match default value (Default: TRUE)
1218
- * > If possible, attribute value will be converted to match default data type
1219
- * > If attribute value cannot match default data type, default value will be used
1220
- * @return mixed Attribute value (NULL if attribute is not set)
1221
- */
1222
- get_attribute: function(key, def, enforce_type) {
1223
- // Validate
1224
- if ( !this.util.is_set(def) ) {
1225
- def = null;
1226
- }
1227
- if ( !this.util.is_string(key) ) {
1228
- return def;
1229
- }
1230
- if ( !this.util.is_bool(enforce_type) ) {
1231
- enforce_type = true;
1232
- }
1233
-
1234
- // Get attribute value
1235
- var ret = ( this.has_attribute(key) ) ? this.get_attributes()[key] : def;
1236
- // Validate type
1237
- if ( enforce_type && ret !== def && null !== def && !this.util.is_type(ret, $.type(def), false) ) {
1238
- // Convert type
1239
- // Scalar default
1240
- if ( this.util.is_scalar(def, false) ) {
1241
- if ( !this.util.is_scalar(ret, false) ) {
1242
- // Non-scalar attribute
1243
- ret = def;
1244
- } else if ( this.util.is_string(def, false) ) {
1245
- // Convert to string
1246
- ret = ret.toString();
1247
- } else if ( this.util.is_num(def, false) && !this.util.is_num(ret, false) ) {
1248
- // Convert to number
1249
- ret = ( this.util.is_int(def, false) ) ? parseInt(ret) : parseFloat(ret);
1250
- if ( !this.util.is_num(ret, false) ) {
1251
- ret = def;
1252
- }
1253
- } else if ( this.util.is_bool(def, false) ) {
1254
- // Convert to boolean
1255
- ret = ( this.util.is_string(ret) || ( this.util.is_num(ret) ) );
1256
- } else {
1257
- // Fallback: Set to default
1258
- ret = def;
1259
- }
1260
- }
1261
- // Non-scalar default
1262
- else {
1263
- ret = def;
1264
- }
1265
- }
1266
- return ret;
1267
- },
1268
-
1269
- /**
1270
- * Call attribute as method
1271
- * @param string attr Attribute to call
1272
- * @param arguments (optional) Additional arguments to pass to method
1273
- * @return mixed Attribute return value (if attribute is not a function, attribute's value is returned)
1274
- */
1275
- call_attribute: function(attr, args) {
1276
- attr = this.get_attribute(attr);
1277
- if ( this.util.is_func(attr) ) {
1278
- // Get arguments
1279
- args = Array.prototype.slice.call(arguments, 1);
1280
- // Pass arguments to user-defined method
1281
- attr = attr.apply(this, args);
1282
- }
1283
- return attr;
1284
- },
1285
-
1286
- /**
1287
- * Check if attribute exists
1288
- * @param string key Attribute name
1289
- * @return bool TRUE if exists, FALSE otherwise
1290
- */
1291
- has_attribute: function(key) {
1292
- return ( this.util.is_string(key) && ( key in this.get_attributes() ) );
1293
- },
1294
-
1295
- /**
1296
- * Set component attributes
1297
- * @param obj attributes Attributes to set
1298
- * @param bool full (optional) Whether to fully replace or merge component's attributes with new values (Default: Merge)
1299
- */
1300
- set_attributes: function(attributes, full) {
1301
- // Validate
1302
- if ( !this.util.is_bool(full) ) {
1303
- full = false;
1304
- }
1305
-
1306
- // Initialize attributes
1307
- this.init_attributes(full);
1308
-
1309
- // Merge new/existing attributes
1310
- if ( this.util.is_obj(attributes) ) {
1311
- $.extend(this._attributes, attributes);
1312
- }
1313
- },
1314
-
1315
- /**
1316
- * Set value for a component attribute
1317
- * @uses get_attributes() to retrieve attributes
1318
- * @param string key Attribute to set
1319
- * @param mixed val Attribute value
1320
- * @return mixed Attribute value
1321
- */
1322
- set_attribute: function(key, val) {
1323
- if ( this.util.is_string(key) && this.util.is_set(val) ) {
1324
- this.get_attributes()[key] = val;
1325
- }
1326
- return val;
1327
- },
1328
-
1329
- /* DOM */
1330
-
1331
- /**
1332
- * Generate selector for retrieving child element
1333
- * @param string element Class name of child element
1334
- * @return string Element selector
1335
- */
1336
- dom_get_selector: function(element) {
1337
- return ( this.util.is_string(element) ) ? '.' + this.add_ns(element) : '';
1338
- },
1339
-
1340
- dom_get_attribute: function() {
1341
- return this.util.get_attribute(this._slug);
1342
- },
1343
-
1344
- /**
1345
- * Set reference of instance on DOM element
1346
- * @uses _reciprocal to determine if DOM element should also be attached to instance
1347
- * @param string|obj (jQuery) el DOM element to attach instance to
1348
- * @return jQuery DOM element set
1349
- */
1350
- dom_set: function(el) {
1351
- el = $(el);
1352
- // Save instance to DOM object
1353
- el.data(this.get_data_key(), this);
1354
- // Save DOM object to instance
1355
- if ( this._reciprocal ) {
1356
- this._dom = el;
1357
- }
1358
- return el;
1359
- },
1360
-
1361
- /**
1362
- * Retrieve attached DOM element
1363
- * @uses _dom to retrieve attached DOM element
1364
- * @uses dom_put() to insert child element
1365
- * @param string element (optional) ID of child element to retrieve (Default: Main element)
1366
- * @param bool put (optional) Whether to insert element if it does not exist (Default: FALSE)
1367
- * @param obj options (optional) Runtime options
1368
- * @return obj jQuery DOM element
1369
- */
1370
- dom_get: function(element, options) {
1371
- // Build options
1372
- var opts_default = {
1373
- 'init': true,
1374
- 'put': false
1375
- };
1376
- options = ( this.util.is_obj(options) ) ? $.extend({}, opts_default, options) : opts_default;
1377
-
1378
- // Init Component DOM
1379
- if ( options.init && !this.get_status('dom_init') ) {
1380
- this.set_status('dom_init');
1381
- this.dom_init();
1382
- }
1383
- // Check for main DOM element
1384
- var ret = this._dom;
1385
- if ( !!ret && this.util.is_string(element) ) {
1386
- var ch = $(ret).find( this.dom_get_selector(element) );
1387
- // Check for child element
1388
- if ( ch.length ) {
1389
- ret = ch;
1390
- } else if ( true === options.put || this.util.is_obj(options.put) ) {
1391
- // Insert child element
1392
- ret = this.dom_put(element, options.put);
1393
- }
1394
- }
1395
- return $(ret);
1396
- },
1397
-
1398
- /**
1399
- * Initialize DOM element
1400
- * To be overridden by child classes
1401
- */
1402
- dom_init: function() {},
1403
-
1404
- /**
1405
- * Wrap output in DOM element
1406
- * Wrapper element created and added to main DOM element if not yet created
1407
- * @param string element ID for DOM element (Used as class name for wrapper)
1408
- * @param string|jQuery|obj content Content to add to DOM (Object contains element properties)
1409
- * > tag : Element tag name
1410
- * > content : Element content
1411
- * @return jQuery Inserted element(s)
1412
- */
1413
- dom_put: function(element, content) {
1414
- var r = null;
1415
- // Stop processing if main DOM element not set or element is not valid
1416
- if ( !this.dom_has() || !this.util.is_string(element) ) {
1417
- return $(r);
1418
- }
1419
- // Setup options
1420
- var strip = ['tag', 'content', 'success'];
1421
- var options = {
1422
- 'tag': 'div',
1423
- 'content': '',
1424
- 'class': this.add_ns(element)
1425
- };
1426
- // Setup content
1427
- if ( !this.util.is_empty(content) ) {
1428
- if ( this.util.is_type(content, jQuery, false) || this.util.is_string(content, false) ) {
1429
- options.content = content;
1430
- }
1431
- else if ( this.util.is_obj(content, false) ) {
1432
- $.extend(options, content);
1433
- }
1434
- }
1435
- var attrs = $.extend({}, options);
1436
- for ( var x = 0; x < strip.length; x++ ) {
1437
- delete attrs[strip[x]];
1438
- }
1439
- // Retrieve existing element
1440
- var d = this.dom_get();
1441
- r = $(this.dom_get_selector(element), d);
1442
- // Create element (if necessary)
1443
- if ( !r.length ) {
1444
- r = $(this.util.format('<%s />', options.tag), attrs).appendTo(d);
1445
- if ( r.length && this.util.is_method(options, 'success') ) {
1446
- options['success'].call(r, r);
1447
- }
1448
- }
1449
- // Set content
1450
- $(r).append(options.content);
1451
- return $(r);
1452
- },
1453
-
1454
- /**
1455
- * Check if DOM element is set for instance
1456
- * DOM is initialized before evaluation
1457
- * @return bool TRUE if DOM element set, FALSE otherwise
1458
- */
1459
- dom_has: function() {
1460
- return ( !!this.dom_get().length );
1461
- },
1462
-
1463
- /* Data */
1464
-
1465
- /**
1466
- * Retrieve key used to store data in DOM element
1467
- * @return string Data key
1468
- */
1469
- get_data_key: function() {
1470
- return this.get_ns();
1471
- },
1472
-
1473
- /* Events */
1474
-
1475
- /**
1476
- * Register event handler for custom event
1477
- * Structure
1478
- * > Events (obj)
1479
- * > Event-Name (array)
1480
- * > Handlers (functions)
1481
- * @param mixed event Custom event to register handler for
1482
- * > string: Standard event handler
1483
- * > array: Multiple events to register single handler on
1484
- * > object: Map of events/handlers
1485
- * @param function fn Event handler
1486
- * @param obj options Handler registration options
1487
- * > clear (bool) Clear existing event handlers before setting current handler (Default: FALSE)
1488
- * @return obj Component instance (allows chaining)
1489
- */
1490
- on: function(event, fn, options) {
1491
- // Handle request types
1492
- if ( !this.util.is_string(event) || !this.util.is_func(fn) ) {
1493
- var t = this;
1494
- var args = Array.prototype.slice.call(arguments, 1);
1495
- if ( this.util.is_array(event) ) {
1496
- // Events array
1497
- $.each(event, function(idx, val) {
1498
- t.on.apply(t, [val].concat(args));
1499
- });
1500
- } else if ( this.util.is_obj(event) ) {
1501
- // Events map
1502
- $.each(event, function(ev, hdl) {
1503
- t.on.apply(t, [ev, hdl].concat(args));
1504
- });
1505
- }
1506
- return this;
1507
- }
1508
-
1509
- // Options
1510
-
1511
- // Default options
1512
- var options_std = {
1513
- clear: false
1514
- };
1515
- if ( !this.util.is_obj(options, false) ) {
1516
- // Reset options
1517
- options = {};
1518
- }
1519
- // Build options
1520
- options = $.extend({}, options_std, options);
1521
- // Initialize events bucket
1522
- if ( !this.util.is_obj(this._events, false) ) {
1523
- this._events = {};
1524
- }
1525
- // Setup event
1526
- var es = this._events;
1527
- if ( !( event in es ) || !this.util.is_obj(es[event], false) || !!options.clear ) {
1528
- es[event] = [];
1529
- }
1530
- // Add event handler
1531
- es[event].push(fn);
1532
- return this;
1533
- },
1534
-
1535
- /**
1536
- * Trigger custom event
1537
- * Event handlers are executed in the context of the current component instance
1538
- * Event handlers are passed parameters
1539
- * > ev (obj) Event object
1540
- * > type (string) Event name
1541
- * > data (mixed) Data to pass to handlers (if supplied)
1542
- * > component (obj) Current component instance
1543
- * @param string event Custom event to trigger
1544
- * @param mixed data (optional) Data to pass to event handlers
1545
- * @return jQuery.Promise Promise that is resolved once event handlers are resolved
1546
- */
1547
- trigger: function(event, data) {
1548
- var dfr = $.Deferred();
1549
- var dfrs = [];
1550
- var t = this;
1551
- // Handle array of events
1552
- if ( this.util.is_array(event) ) {
1553
- $.each(event, function(idx, val) {
1554
- // Collect promises from triggered events
1555
- dfrs.push( t.trigger(val, data) );
1556
- });
1557
- // Resolve trigger when all events have been resolved
1558
- $.when.apply(t, dfrs).done(function() {
1559
- dfr.resolve();
1560
- });
1561
- return dfr.promise();
1562
- }
1563
- // Validate
1564
- if ( !this.util.is_string(event) || !( event in this._events ) ) {
1565
- dfr.resolve();
1566
- return dfr.promise();
1567
- }
1568
- // Create event object
1569
- var ev = { 'type': event, 'data': null };
1570
- // Add data to event object
1571
- if ( this.util.is_set(data) ) {
1572
- ev.data = data;
1573
- }
1574
- // Fire handlers for event
1575
- $.each(this._events[event], function(idx, fn) {
1576
- // Call handler (`this` set to current instance)
1577
- // Collect promises from event handlers
1578
- dfrs.push( fn.call(t, ev, t) );
1579
- });
1580
- // Resolve trigger when all handlers have been resolved
1581
- $.when.apply(this, dfrs).done(function() {
1582
- dfr.resolve();
1583
- });
1584
- return dfr.promise();
1585
- }
1586
- };
1587
-
1588
- View.Component = Component = SLB.Class.extend(Component);
1589
-
1590
- /**
1591
- * Content viewer
1592
- * @param obj options Init options
1593
- */
1594
- var Viewer = {
1595
-
1596
- /* Configuration */
1597
-
1598
- _slug: 'viewer',
1599
-
1600
- _refs: {
1601
- item: 'Content_Item',
1602
- theme: 'Theme'
1603
- },
1604
-
1605
- _reciprocal: true,
1606
-
1607
- _attr_default: {
1608
- loop: true,
1609
- animate: true,
1610
- autofit: true,
1611
- overlay_enabled: true,
1612
- overlay_opacity: '0.8',
1613
- title_default: false,
1614
- container: null,
1615
- slideshow_enabled: true,
1616
- slideshow_autostart: false,
1617
- slideshow_duration: 2,
1618
- slideshow_active: false,
1619
- slideshow_timer: null,
1620
- labels: {
1621
- close: 'close',
1622
- nav_prev: '&laquo; prev',
1623
- nav_next: 'next &raquo;',
1624
- slideshow_start: 'start slideshow',
1625
- slideshow_stop: 'stop slideshow',
1626
- group_status: 'Image %current% of %total%',
1627
- loading: 'loading'
1628
- }
1629
- },
1630
-
1631
- _attr_map: {
1632
- 'theme': null,
1633
- 'group_loop': 'loop',
1634
- 'ui_autofit': 'autofit',
1635
- 'ui_animate': 'animate',
1636
- 'ui_overlay_opacity': 'overlay_opacity',
1637
- 'ui_labels': 'labels',
1638
- 'ui_title_default': 'title_default',
1639
- 'slideshow_enabled': null,
1640
- 'slideshow_autostart': null,
1641
- 'slideshow_duration': null
1642
- },
1643
-
1644
- /* References */
1645
-
1646
- /**
1647
- * Item currently loaded in viewer
1648
- * @var object Content_Item
1649
- */
1650
- item: null,
1651
-
1652
- /**
1653
- * Queued item to be loaded once viewer is available
1654
- * @var object Content_Item
1655
- */
1656
- item_queued: null,
1657
-
1658
- /**
1659
- * Theme used by viewer
1660
- * @var object Theme
1661
- */
1662
- theme: null,
1663
-
1664
- /* Properties */
1665
-
1666
- item_working: null,
1667
-
1668
- active: false,
1669
- init: false,
1670
- open: false,
1671
- loading: false,
1672
-
1673
- /* Methods */
1674
-
1675
- /* Init */
1676
-
1677
- _hooks: function() {
1678
- var t = this;
1679
- this
1680
- .on(['item-prev', 'item-next'], function() {
1681
- t.trigger('item-change');
1682
- })
1683
- .on(['close', 'item-change'], function() {
1684
- t.unload().done(function() {
1685
- t.unlock();
1686
- });
1687
- });
1688
- },
1689
-
1690
- /* References */
1691
-
1692
- /**
1693
- * Retrieve item instance current attached to viewer
1694
- * @return Content_Item|NULL Current item instance
1695
- */
1696
- get_item: function() {
1697
- return this.get_component('item');
1698
- },
1699
-
1700
- /**
1701
- * Set item reference
1702
- * Validates item before setting
1703
- * @param obj item Content_Item instance
1704
- * @return bool TRUE if valid item set, FALSE otherwise
1705
- */
1706
- set_item: function(item) {
1707
- // Clear existing item
1708
- this.clear_item(false);
1709
- var i = this.set_component('item', item, function(item) {
1710
- return ( item.has_type() );
1711
- });
1712
- return ( !this.util.is_empty(i) );
1713
- },
1714
-
1715
- /**
1716
- * Clear item from viewer
1717
- * Resets item state and removes reference (if necessary)
1718
- * @param bool full (optional) Fully remove item? (Default: TRUE)
1719
- */
1720
- clear_item: function(full) {
1721
- // Validate
1722
- if ( !this.util.is_bool(full) ) {
1723
- full = true;
1724
- }
1725
- var item = this.get_item();
1726
- if ( !!item ) {
1727
- item.reset();
1728
- }
1729
- if ( full ) {
1730
- this.clear_component('item');
1731
- }
1732
- },
1733
-
1734
- /**
1735
- * Retrieve theme reference
1736
- * @return object Theme reference
1737
- */
1738
- get_theme: function() {
1739
- // Get saved theme
1740
- var ret = this.get_component('theme', {check_attr: false});
1741
- if ( this.util.is_empty(ret) ) {
1742
- // Theme needs to be initialized
1743
- ret = this.set_component('theme', new View.Theme(this));
1744
- }
1745
- return ret;
1746
- },
1747
-
1748
- /**
1749
- * Set viewer's theme
1750
- * @param object theme Theme object
1751
- */
1752
- set_theme: function(theme) {
1753
- this.set_component('theme', theme);
1754
- },
1755
-
1756
- /* Properties */
1757
-
1758
- /**
1759
- * Lock the viewer
1760
- * Indicates that item is currently being processed
1761
- * @return jQuery.Deferred Resolved when item processing is complete
1762
- */
1763
- lock: function() {
1764
- return this.set_status('item_working', $.Deferred());
1765
- },
1766
-
1767
- /**
1768
- * Retrieve lock
1769
- * @param bool simple (optional) Whether to return a simple status of the locked status (Default: FALSE)
1770
- * @param bool full (optional) Whether to return Deferred (TRUE) or Promise (FALSE) object (Default: FALSE)
1771
- * @return jQuery.Promise Resolved when item processing is complete
1772
- */
1773
- get_lock: function(simple, full) {
1774
- // Validate
1775
- if ( !this.util.is_bool(simple) ) {
1776
- simple = false;
1777
- }
1778
- if ( !this.util.is_bool(full) ) {
1779
- full = false;
1780
- }
1781
- var s = 'item_working';
1782
- // Simple status
1783
- if ( simple ) {
1784
- return this.get_status(s);
1785
- }
1786
- // Full value
1787
- var r = this.get_status(s, true);
1788
- if ( !this.util.is_promise(r) ) {
1789
- // Create default
1790
- r = this.lock();
1791
- }
1792
- return ( full ) ? r : r.promise();
1793
- },
1794
-
1795
- is_locked: function() {
1796
- return this.get_lock(true);
1797
- },
1798
-
1799
- /**
1800
- * Unlock the viewer
1801
- * Any callbacks registered for this action will be executed
1802
- * @return jQuery.Deferred Resolved instance
1803
- */
1804
- unlock: function() {
1805
- return this.get_lock(false, true).resolve();
1806
- },
1807
-
1808
- /**
1809
- * Set Viewer active status
1810
- * @param bool mode (optional) Activate or deactivate status (Default: TRUE)
1811
- * @return bool Active status
1812
- */
1813
- set_active: function(mode) {
1814
- if ( !this.util.is_bool(mode) ) {
1815
- mode = true;
1816
- }
1817
- return this.set_status('active', mode);
1818
- },
1819
-
1820
- /**
1821
- * Check Viewer active status
1822
- * @return bool Active status
1823
- */
1824
- is_active: function() {
1825
- return this.get_status('active');
1826
- },
1827
-
1828
- /**
1829
- * Set loading mode
1830
- * @param bool mode (optional) Set (TRUE) or unset (FALSE) loading mode (Default: TRUE)
1831
- * @return jQuery.Promise Promise that resolves when loading mode is set
1832
- */
1833
- set_loading: function(mode) {
1834
- var dfr = $.Deferred();
1835
- if ( !this.util.is_bool(mode) ) {
1836
- mode = true;
1837
- }
1838
- this.loading = mode;
1839
- // Pause/Resume slideshow
1840
- if ( this.slideshow_active() ) {
1841
- this.slideshow_pause(mode);
1842
- }
1843
- // Set CSS class on DOM element
1844
- var m = ( mode ) ? 'addClass' : 'removeClass';
1845
- $(this.dom_get())[m]('loading');
1846
- if ( mode ) {
1847
- // Loading transition
1848
- this.get_theme().transition('load').always(function() {
1849
- dfr.resolve();
1850
- });
1851
- } else {
1852
- dfr.resolve();
1853
- }
1854
- return dfr.promise();
1855
- },
1856
-
1857
- /**
1858
- * Unset loading mode
1859
- * @see set_loading()
1860
- * @return jQuery.Promise Promise that resovles when loading mode is set
1861
- */
1862
- unset_loading: function() {
1863
- return this.set_loading(false);
1864
- },
1865
-
1866
- /**
1867
- * Retrieve loading status
1868
- * @return bool Loading status (Default: FALSE)
1869
- */
1870
- get_loading: function() {
1871
- return ( this.util.is_bool(this.loading) ) ? this.loading : false;
1872
- },
1873
-
1874
- /**
1875
- * Check if viewer is currently loading content
1876
- * @return bool Loading status (Default: FALSE)
1877
- */
1878
- is_loading: function() {
1879
- return this.get_loading();
1880
- },
1881
-
1882
- /* Display */
1883
-
1884
- /**
1885
- * Display content in viewer
1886
- * @param Content_Item item Item to show
1887
- * @param obj options (optional) Display options
1888
- */
1889
- show: function(item) {
1890
- this.item_queued = item;
1891
- var fin_set = 'show_deferred';
1892
- // Validate theme
1893
- var vt = 'theme_valid';
1894
- var valid = true;
1895
- if ( this.has_attribute(vt)) {
1896
- valid = this.get_attribute(vt, true);
1897
- } else {
1898
- valid = ( this.get_theme() && this.get_theme().get_template().get_layout(false) !== "" ) ? true : false;
1899
- this.set_attribute(vt, valid);
1900
- }
1901
-
1902
- if ( !valid ) {
1903
- this.close();
1904
- return false;
1905
- }
1906
- var v = this;
1907
- var fin = function() {
1908
- // Lock viewer
1909
- v.lock();
1910
- // Reset callback flag (for new lock)
1911
- v.set_status(fin_set, false);
1912
- // Validate request
1913
- if ( !v.set_item(v.item_queued) ) {
1914
- v.close();
1915
- return false;
1916
- }
1917
- // Add item to history stack
1918
- v.history_add();
1919
- // Activate
1920
- v.set_active();
1921
- // Display
1922
- v.render();
1923
- };
1924
- if ( !this.is_locked() ) {
1925
- fin();
1926
- } else if ( !this.get_status(fin_set) ) {
1927
- // Set flag to avoid duplicate callbacks
1928
- this.set_status(fin_set);
1929
- this.get_lock().always(function() {
1930
- fin();
1931
- });
1932
- }
1933
- },
1934
-
1935
- /* History Management */
1936
-
1937
- history_handle: function(e) {
1938
- var state = e.originalEvent.state;
1939
- // Load item
1940
- if ( this.util.is_string(state.item, false) ) {
1941
- this.get_controller().get_item(state.item).show({'event': e});
1942
- this.trigger('item-change');
1943
- } else {
1944
- var count = this.history_get(true);
1945
- // Reset count
1946
- this.history_set(0);
1947
- // Close viewer
1948
- if ( -1 !== count ) {
1949
- this.close();
1950
- }
1951
- }
1952
- },
1953
-
1954
- history_get: function(full) {
1955
- return this.get_status('history_count', full);
1956
- },
1957
- history_set: function(val) {
1958
- return this.set_status('history_count', val);
1959
- },
1960
- history_add: function() {
1961
- if ( !history.pushState ) {
1962
- return false;
1963
- }
1964
- // Get display options
1965
- var item = this.get_item();
1966
- var opts = item.get_attribute('options_show');
1967
- // Save history state
1968
- var count = ( this.history_get() ) ? this.history_get(true) : 0;
1969
- if ( !this.util.in_obj(opts, 'event') ) {
1970
- // Create state
1971
- var state = {
1972
- 'viewer': this.get_id(),
1973
- 'item': null,
1974
- 'count': count
1975
- };
1976
- // Init: Save viewer state
1977
- if ( !count ) {
1978
- history.replaceState(state, null);
1979
- }
1980
- // Always: Save item state
1981
- state.item = this.get_controller().save_item(item).get_id();
1982
- state.count = ++count;
1983
- history.pushState(state, '');
1984
- } else {
1985
- var e = opts.event.originalEvent;
1986
- if ( this.util.in_obj(e, 'state') && this.util.in_obj(e.state, 'count') ) {
1987
- count = e.state.count;
1988
- }
1989
- }
1990
- // Save history item count
1991
- this.history_set(count);
1992
- },
1993
- history_reset: function() {
1994
- var count = this.history_get(true);
1995
- if ( count ) {
1996
- // Clear history status
1997
- this.history_set(-1);
1998
- // Restore history stack
1999
- history.go( -1 * count );
2000
- }
2001
- },
2002
-
2003
- /**
2004
- * Check if viewer is currently open
2005
- * Checks if node is actually visible in DOM
2006
- * @return bool TRUE if viewer is open, FALSE otherwise
2007
- */
2008
- is_open: function() {
2009
- return ( this.dom_get().css('display') === 'none' ) ? false : true;
2010
- },
2011
-
2012
- /**
2013
- * Load output into DOM
2014
- */
2015
- render: function() {
2016
- // Get theme output
2017
- var v = this;
2018
- var thm = this.get_theme();
2019
- v.dom_prep();
2020
- // Register theme event handlers
2021
- if ( !this.get_status('render-events') ) {
2022
- this.set_status('render-events');
2023
- thm
2024
- // Loading
2025
- .on('render-loading', function(ev, thm) {
2026
- var dfr = $.Deferred();
2027
- if ( !v.is_active() ) {
2028
- dfr.reject();
2029
- return dfr.promise();
2030
- }
2031
- var set_pos = function() {
2032
- // Set position
2033
- v.dom_get().css('top', $(window).scrollTop());
2034
- };
2035
- var always = function() {
2036
- // Set loading flag
2037
- v.set_loading().always(function() {
2038
- dfr.resolve();
2039
- });
2040
- };
2041
- if ( v.is_open() ) {
2042
- thm.transition('unload')
2043
- .fail(function() {
2044
- set_pos();
2045
- thm.dom_get_tag('item', 'content').attr('style', '');
2046
- })
2047
- .always(always);
2048
- } else {
2049
- thm.transition('open')
2050
- .always(function() {
2051
- always();
2052
- v.events_open();
2053
- v.open = true;
2054
- })
2055
- .fail(function() {
2056
- set_pos();
2057
- // Fallback open
2058
- v.get_overlay().show();
2059
- v.dom_get().show();
2060
- });
2061
- }
2062
- return dfr.promise();
2063
- })
2064
- // Complete
2065
- .on('render-complete', function(ev, thm) {
2066
- // Stop if viewer not active
2067
- if ( !v.is_active() ) {
2068
- return false;
2069
- }
2070
- // Set classes
2071
- var d = v.dom_get();
2072
- var classes = ['item_single', 'item_multi'];
2073
- var ms = ['addClass', 'removeClass'];
2074
- if ( !v.get_item().get_group().is_single() ) {
2075
- ms.reverse();
2076
- }
2077
- $.each(ms, function(idx, val) {
2078
- d[val](classes[idx]);
2079
- });
2080
- // Bind events
2081
- v.events_complete();
2082
- // Transition
2083
- thm.transition('complete')
2084
- .fail(function() {
2085
- // Autofit content
2086
- if ( v.get_attribute('autofit', true) ) {
2087
- var dims = $.extend({'display': 'inline-block'}, thm.get_item_dimensions());
2088
- thm.dom_get_tag('item', 'content').css(dims);
2089
- }
2090
- })
2091
- .always(function() {
2092
- // Unset loading flag
2093
- v.unset_loading();
2094
- // Trigger event
2095
- v.trigger('render-complete');
2096
- // Set viewer as initialized
2097
- v.init = true;
2098
- });
2099
- });
2100
- }
2101
- // Render
2102
- thm.render();
2103
- },
2104
-
2105
- /**
2106
- * Retrieve container element
2107
- * Creates default container element if not yet created
2108
- * @return jQuery Container element
2109
- */
2110
- dom_get_container: function() {
2111
- var sel = this.get_attribute('container');
2112
- // Set default container
2113
- if ( this.util.is_empty(sel) ) {
2114
- sel = '#' + this.add_ns('wrap');
2115
- }
2116
- // Add default container to DOM if not yet present
2117
- var c = $(sel);
2118
- if ( !c.length ) {
2119
- // Prepare ID
2120
- var id = ( sel.indexOf('#') === 0 ) ? sel.substr(1) : sel;
2121
- // Add element
2122
- c = $('<div />', {'id': id}).appendTo('body');
2123
- }
2124
- return c;
2125
- },
2126
-
2127
- /**
2128
- * Custom Viewer DOM initialization
2129
- */
2130
- dom_init: function() {
2131
- // Create element & add to DOM
2132
- // Save element to instance
2133
- var d = this.dom_set($('<div/>', {
2134
- 'id': this.get_id(true),
2135
- 'class': this.get_ns()
2136
- })).appendTo(this.dom_get_container()).hide();
2137
- // Add theme classes
2138
- var thm = this.get_theme();
2139
- d.addClass(thm.get_classes(' '));
2140
- // Add theme layout (basic)
2141
- var v = this;
2142
- if ( !this.get_status('render-init') ) {
2143
- this.set_status('render-init');
2144
- thm.on('render-init', function(ev) {
2145
- // Add rendered theme layout to viewer DOM
2146
- v.dom_put('layout', ev.data);
2147
- });
2148
- }
2149
- thm.render(true);
2150
- },
2151
-
2152
- /**
2153
- * Prepare DOM for viewer
2154
- */
2155
- dom_prep: function(mode) {
2156
- var m = ( this.util.is_bool(mode) && !mode ) ? 'removeClass' : 'addClass';
2157
- $('html')[m](this.util.add_prefix('overlay'));
2158
- },
2159
-
2160
- /**
2161
- * Restore DOM
2162
- * Required after viewer is closed
2163
- */
2164
- dom_restore: function() {
2165
- this.dom_prep(false);
2166
- },
2167
-
2168
- /* Layout */
2169
-
2170
- get_layout: function() {
2171
- var ret = this.dom_get('layout', {
2172
- 'put': {
2173
- 'success': function() {
2174
- $(this).hide();
2175
- }
2176
- }
2177
- });
2178
- return ret;
2179
- },
2180
-
2181
- /* Animation */
2182
-
2183
- animation_enabled: function() {
2184
- return this.get_attribute('animate', true);
2185
- },
2186
-
2187
- /* Overlay */
2188
-
2189
- /**
2190
- * Determine if overlay is enabled for viewer
2191
- * @return bool TRUE if overlay is enabled, FALSE otherwise
2192
- */
2193
- overlay_enabled: function() {
2194
- var ov = this.get_attribute('overlay_enabled');
2195
- return ( this.util.is_bool(ov) ) ? ov : false;
2196
- },
2197
-
2198
- /**
2199
- * Retrieve overlay DOM element
2200
- * @return jQuery Overlay element (NULL if no overlay set for viewer)
2201
- */
2202
- get_overlay: function() {
2203
- var o = null;
2204
- var v = this;
2205
- if ( this.overlay_enabled() ) {
2206
- o = this.dom_get('overlay', {
2207
- 'put': {
2208
- 'success': function() {
2209
- $(this).hide().css('opacity', v.get_attribute('overlay_opacity'));
2210
- }
2211
- }
2212
- });
2213
- }
2214
- return $(o);
2215
- },
2216
-
2217
- /**
2218
- * Unload viewer
2219
- */
2220
- unload: function() {
2221
- var dfr = $.Deferred();
2222
- // Unload item data
2223
- this.get_theme().dom_get_tag('item').text('');
2224
- dfr.resolve();
2225
- return dfr.promise();
2226
- },
2227
-
2228
- /**
2229
- * Reset viewer
2230
- */
2231
- reset: function() {
2232
- // Hide viewer
2233
- this.dom_get().hide();
2234
- // Restore DOM
2235
- this.dom_restore();
2236
- // History
2237
- this.history_reset();
2238
- // Item
2239
- this.clear_item();
2240
- // Reset properties
2241
- this.set_active(false);
2242
- this.set_loading(false);
2243
- this.slideshow_stop();
2244
- this.keys_disable();
2245
- // Clear for next item
2246
- this.unlock();
2247
- },
2248
-
2249
- /* Content */
2250
-
2251
- get_labels: function() {
2252
- return this.get_attribute('labels', {});
2253
- },
2254
-
2255
- get_label: function(name) {
2256
- var lbls = this.get_labels();
2257
- return ( name in lbls ) ? lbls[name] : '';
2258
- },
2259
-
2260
- /* Interactivity */
2261
-
2262
- /**
2263
- * Initialize event handlers upon opening lightbox
2264
- */
2265
- events_open: function() {
2266
- // Keyboard bindings
2267
- this.keys_enable();
2268
- if ( this.open ) {
2269
- return false;
2270
- }
2271
-
2272
- // Control event bubbling
2273
- var l = this.get_layout();
2274
- l.children().click(function(ev) {
2275
- ev.stopPropagation();
2276
- });
2277
-
2278
- /* Close */
2279
- var v = this;
2280
- var close = function() {
2281
- v.close();
2282
- };
2283
- // Layout
2284
- l.click(close);
2285
- // Overlay
2286
- this.get_overlay().click(close);
2287
- // Fire event
2288
- this.trigger('events-open');
2289
- },
2290
-
2291
- /**
2292
- * Initialize event handlers upon completing lightbox rendering
2293
- */
2294
- events_complete: function() {
2295
- if ( this.init ) {
2296
- return false;
2297
- }
2298
- // Fire event
2299
- this.trigger('events-complete');
2300
- },
2301
-
2302
- keys_enable: function(mode) {
2303
- if ( !this.util.is_bool(mode) ) {
2304
- mode = true;
2305
- }
2306
- var e = ['keyup', this.util.get_prefix()].join('.');
2307
- var v = this;
2308
- var h = function(ev) {
2309
- return v.keys_control(ev);
2310
- };
2311
- if ( mode ) {
2312
- $(document).on(e, h);
2313
- } else {
2314
- $(document).off(e);
2315
- }
2316
- },
2317
-
2318
- keys_disable: function() {
2319
- this.keys_enable(false);
2320
- },
2321
-
2322
- keys_control: function(ev) {
2323
- var handlers = {
2324
- 27: this.close, /* esc */
2325
- 37: this.item_prev, /* left-arrow */
2326
- 39: this.item_next, /* right-arrow */
2327
- };
2328
- // Swap next/prev keys on RTL pages
2329
- if ('rtl' === document.documentElement.getAttribute('dir')) {
2330
- handlers[37] = this.item_next; /* left-arrow */
2331
- handlers[39] = this.item_prev; /* right-arrow */
2332
- }
2333
- if ( ev.which in handlers ) {
2334
- handlers[ev.which].call(this);
2335
- return false;
2336
- }
2337
- },
2338
-
2339
- /**
2340
- * Check if slideshow functionality is enabled
2341
- * @return bool TRUE if slideshow is enabled, FALSE otherwise
2342
- */
2343
- slideshow_enabled: function() {
2344
- var o = this.get_attribute('slideshow_enabled');
2345
- return ( this.util.is_bool(o) && o && this.get_item() && !this.get_item().get_group().is_single() ) ? true : false;
2346
- },
2347
-
2348
- /**
2349
- * Checks if slideshow is currently active
2350
- * @return bool TRUE if slideshow is active, FALSE otherwise
2351
- */
2352
- slideshow_active: function() {
2353
- return ( this.slideshow_enabled() && ( this.get_attribute('slideshow_active') || ( !this.init && this.get_attribute('slideshow_autostart') ) ) ) ? true : false;
2354
- },
2355
-
2356
- /**
2357
- * Clear slideshow timer
2358
- */
2359
- slideshow_clear_timer: function() {
2360
- clearInterval(this.get_attribute('slideshow_timer'));
2361
- },
2362
-
2363
- /**
2364
- * Start slideshow timer
2365
- * @param function callback Callback function
2366
- */
2367
- slideshow_set_timer: function(callback) {
2368
- this.set_attribute('slideshow_timer', setInterval(callback, this.get_attribute('slideshow_duration') * 1000));
2369
- },
2370
-
2371
- /**
2372
- * Start Slideshow
2373
- */
2374
- slideshow_start: function() {
2375
- if ( !this.slideshow_enabled() ) {
2376
- return false;
2377
- }
2378
- this.set_attribute('slideshow_active', true);
2379
- this.dom_get().addClass('slideshow_active');
2380
- // Clear residual timers
2381
- this.slideshow_clear_timer();
2382
- // Start timer
2383
- var v = this;
2384
- this.slideshow_set_timer(function() {
2385
- // Pause slideshow until next item fully loaded
2386
- v.slideshow_pause();
2387
-
2388
- // Show next item
2389
- v.item_next();
2390
- });
2391
- this.trigger('slideshow-start');
2392
- },
2393
-
2394
- /**
2395
- * Stop Slideshow
2396
- * @param bool full (optional) Full stop (TRUE) or pause (FALSE) (Default: TRUE)
2397
- */
2398
- slideshow_stop: function(full) {
2399
- if ( !this.util.is_bool(full) ) {
2400
- full = true;
2401
- }
2402
- if ( full ) {
2403
- this.set_attribute('slideshow_active', false);
2404
- this.dom_get().removeClass('slideshow_active');
2405
- }
2406
- // Kill timers
2407
- this.slideshow_clear_timer();
2408
- this.trigger('slideshow-stop');
2409
- },
2410
-
2411
- slideshow_toggle: function() {
2412
- if ( !this.slideshow_enabled() ) {
2413
- return false;
2414
- }
2415
- if ( this.slideshow_active() ) {
2416
- this.slideshow_stop();
2417
- } else {
2418
- this.slideshow_start();
2419
- }
2420
- this.trigger('slideshow-toggle');
2421
- },
2422
-
2423
- /**
2424
- * Pause Slideshow
2425
- * @param bool mode (optional) Pause (TRUE) or Resume (FALSE) slideshow (default: TRUE)
2426
- */
2427
- slideshow_pause: function(mode) {
2428
- // Validate
2429
- if ( !this.util.is_bool(mode) ) {
2430
- mode = true;
2431
- }
2432
- // Set viewer slideshow properties
2433
- if ( this.slideshow_active() ) {
2434
- if ( !mode ) {
2435
- // Slideshow resumed
2436
- this.slideshow_start();
2437
- } else {
2438
- // Slideshow paused
2439
- this.slideshow_stop(false);
2440
- }
2441
- }
2442
- this.trigger('slideshow-pause');
2443
- },
2444
-
2445
- /**
2446
- * Resume slideshow
2447
- */
2448
- slideshow_resume: function() {
2449
- this.slideshow_pause(false);
2450
- },
2451
-
2452
- /**
2453
- * Next item
2454
- */
2455
- item_next: function() {
2456
- var g = this.get_item().get_group(true);
2457
- var v = this;
2458
- var ev = 'item-next';
2459
- var st = ['events', 'viewer', ev].join('_');
2460
- // Setup event handler
2461
- if ( !g.get_status(st) ) {
2462
- g.set_status(st);
2463
- g.on(ev, function(e) {
2464
- v.trigger(e.type);
2465
- });
2466
- }
2467
- g.show_next();
2468
- },
2469
-
2470
- /**
2471
- * Previous item
2472
- */
2473
- item_prev: function() {
2474
- var g = this.get_item().get_group(true);
2475
- var v = this;
2476
- var ev = 'item-prev';
2477
- var st = ['events', 'viewer', ev].join('_');
2478
- if ( !g.get_status(st) ) {
2479
- g.set_status(st);
2480
- g.on(ev, function() {
2481
- v.trigger(ev);
2482
- });
2483
- }
2484
- g.show_prev();
2485
- },
2486
-
2487
- /**
2488
- * Close viewer
2489
- */
2490
- close: function() {
2491
- // Deactivate
2492
- this.set_active(false);
2493
- var v = this;
2494
- var thm = this.get_theme();
2495
- thm.transition('unload')
2496
- .always(function() {
2497
- thm.transition('close', true).always(function() {
2498
- // End processes
2499
- v.reset();
2500
- v.trigger('close');
2501
- });
2502
- })
2503
- .fail(function() {
2504
- thm.dom_get_tag('item', 'content').attr('style', '');
2505
- });
2506
- return false;
2507
- }
2508
- };
2509
-
2510
- View.Viewer = Component.extend(Viewer);
2511
-
2512
- /**
2513
- * Content group
2514
- * @param obj options Init options
2515
- */
2516
- var Group = {
2517
- /* Configuration */
2518
-
2519
- _slug: 'group',
2520
- _reciprocal: true,
2521
- _refs: {
2522
- 'current': 'Content_Item'
2523
- },
2524
-
2525
- /* References */
2526
-
2527
- current: null,
2528
-
2529
- /* Properties */
2530
-
2531
- /**
2532
- * Selector for getting group items
2533
- * @var string
2534
- */
2535
- selector: null,
2536
-
2537
- /* Methods */
2538
-
2539
- /* Init */
2540
-
2541
- _hooks: function() {
2542
- var t = this;
2543
- this.on(['item-prev', 'item-next'], function() {
2544
- t.trigger('item-change');
2545
- });
2546
- },
2547
-
2548
- /* Properties */
2549
-
2550
- /**
2551
- * Retrieve selector for group items
2552
- * @return string Group items selector
2553
- */
2554
- get_selector: function() {
2555
- if ( this.util.is_empty(this.selector) ) {
2556
- // Build selector
2557
- this.selector = this.util.format('a[%s="%s"]', this.dom_get_attribute(), this.get_id());
2558
- }
2559
- return this.selector;
2560
- },
2561
-
2562
- /**
2563
- * Retrieve group items
2564
- */
2565
- get_items: function() {
2566
- var items = $(this.get_selector());
2567
- if ( 0 === items.length && this.has_current() ) {
2568
- items = this.get_current().dom_get();
2569
- }
2570
- return items;
2571
- },
2572
-
2573
- /**
2574
- * Retrieve item at specified index
2575
- * If no index specified, first item is returned
2576
- * @param int idx Index of item to return
2577
- * @return Content_Item Item
2578
- */
2579
- get_item: function(idx) {
2580
- // Validation
2581
- if ( !this.util.is_int(idx) ) {
2582
- idx = 0;
2583
- }
2584
- // Retrieve all items
2585
- var items = this.get_items();
2586
- // Validate index
2587
- var max = this.get_size() - 1;
2588
- if ( idx > max ) {
2589
- idx = max;
2590
- }
2591
- // Return specified item
2592
- return items.get(idx);
2593
- },
2594
-
2595
- /**
2596
- * Retrieve (zero-based) position of specified item in group
2597
- * @param Content_Item item Item to locate in group
2598
- * @return int Index position of item in group (-1 if item not in group)
2599
- */
2600
- get_pos: function(item) {
2601
- if ( this.util.is_empty(item) ) {
2602
- // Get current item
2603
- item = this.get_current();
2604
- }
2605
- return ( this.util.is_type(item, View.Content_Item) ) ? this.get_items().index(item.dom_get()) : -1;
2606
- },
2607
-
2608
- /**
2609
- * Check if current item set in group
2610
- * @return bool TRUE if current item is set
2611
- */
2612
- has_current: function() {
2613
- // Sanitize
2614
- return ( !this.util.is_empty( this.get_current() ) );
2615
- },
2616
-
2617
- /**
2618
- * Retrieve current item
2619
- * @uses Group.current
2620
- * @return NULL|Content_Item Current item (NULL if current item not set or invalid)
2621
- */
2622
- get_current: function() {
2623
- // Sanitize
2624
- if ( null !== this.current && !this.util.is_type(this.current, View.Content_Item) ) {
2625
- this.current = null;
2626
- }
2627
- return this.current;
2628
- },
2629
-
2630
- /**
2631
- * Sets current group item
2632
- * @param Content_Item item Item to set as current
2633
- */
2634
- set_current: function(item) {
2635
- // Validate
2636
- if ( this.util.is_type(item, View.Content_Item) ) {
2637
- // Set current item
2638
- this.current = item;
2639
- }
2640
- },
2641
-
2642
- get_next: function(item) {
2643
- // Validate
2644
- if ( !this.util.is_type(item, View.Content_Item) ) {
2645
- item = this.get_current();
2646
- }
2647
- if ( this.get_size() === 1 ) {
2648
- return item;
2649
- }
2650
- var next = null;
2651
- var pos = this.get_pos(item);
2652
- if ( pos !== -1 ) {
2653
- pos = ( pos + 1 < this.get_size() ) ? pos + 1 : 0;
2654
- if ( 0 !== pos || item.get_viewer().get_attribute('loop') ) {
2655
- next = this.get_item(pos);
2656
- }
2657
- }
2658
- return next;
2659
- },
2660
-
2661
- get_prev: function(item) {
2662
- // Validate
2663
- if ( !this.util.is_type(item, View.Content_Item) ) {
2664
- item = this.get_current();
2665
- }
2666
- if ( this.get_size() === 1 ) {
2667
- return item;
2668
- }
2669
- var prev = null;
2670
- var pos = this.get_pos(item);
2671
- if ( pos !== -1 && ( 0 !== pos || item.get_viewer().get_attribute('loop') ) ) {
2672
- if ( pos === 0 ) {
2673
- pos = this.get_size();
2674
- }
2675
- pos -= 1;
2676
- prev = this.get_item(pos);
2677
- }
2678
- return prev;
2679
- },
2680
-
2681
- show_next: function(item) {
2682
- if ( this.get_size() > 1 ) {
2683
- // Retrieve item
2684
- var next = this.get_next(item);
2685
- if ( !next ) {
2686
- if ( !this.util.is_type(item, View.Content_Item) ) {
2687
- item = this.get_current();
2688
- }
2689
- item.get_viewer().close();
2690
- }
2691
- var i = this.get_controller().get_item(next);
2692
- // Update current item
2693
- this.set_current(i);
2694
- // Show item
2695
- i.show();
2696
- // Fire event
2697
- this.trigger('item-next');
2698
- }
2699
- },
2700
-
2701
- show_prev: function(item) {
2702
- if ( this.get_size() > 1 ) {
2703
- // Retrieve item
2704
- var prev = this.get_prev(item);
2705
- if ( !prev ) {
2706
- if ( !this.util.is_type(item, View.Content_Item) ) {
2707
- item = this.get_current();
2708
- }
2709
- item.get_viewer().close();
2710
- }
2711
- var i = this.get_controller().get_item(prev);
2712
- // Update current item
2713
- this.set_current(i);
2714
- // Show item
2715
- i.show();
2716
- // Fire event
2717
- this.trigger('item-prev');
2718
- }
2719
- },
2720
-
2721
- /**
2722
- * Retrieve total number of items in group
2723
- * @return int Number of items in group
2724
- */
2725
- get_size: function() {
2726
- return this.get_items().length;
2727
- },
2728
-
2729
- is_single: function() {
2730
- return ( this.get_size() === 1 );
2731
- }
2732
- };
2733
-
2734
- View.Group = Component.extend(Group);
2735
-
2736
- /**
2737
- * Content Handler
2738
- * @param obj options Init options
2739
- */
2740
- var Content_Handler = {
2741
-
2742
- /* Configuration */
2743
-
2744
- _slug: 'content_handler',
2745
- _refs: {
2746
- 'item': 'Content_Item'
2747
- },
2748
-
2749
- /* References */
2750
-
2751
- item: null,
2752
-
2753
- /* Properties */
2754
-
2755
- /**
2756
- * Raw layout template
2757
- * @var string
2758
- */
2759
- template: '',
2760
-
2761
- /* Methods */
2762
-
2763
- /* Item */
2764
-
2765
- /**
2766
- * Check if item instance set for type
2767
- * @uses get_item()
2768
- * @uses clear_item() to remove invalid item values
2769
- * @return bool TRUE if valid item set, FALSE otherwise
2770
- */
2771
- has_item: function() {
2772
- return ( this.util.is_empty(this.get_item()) ) ? false : true;
2773
- },
2774
-
2775
- /**
2776
- * Retrieve item instance set on type
2777
- * @uses get_component()
2778
- * @return mixed Content_Item if valid item set, NULL otherwise
2779
- */
2780
- get_item: function() {
2781
- return this.get_component('item');
2782
- },
2783
-
2784
- /**
2785
- * Set item instance for type
2786
- * Items are only meant to be set/used while item is being processed
2787
- * @uses set_component()
2788
- * @param Content_Item item Item instance
2789
- * @return obj|null Item instance if item successfully set, NULL otherwise
2790
- */
2791
- set_item: function(item) {
2792
- // Set reference
2793
- var r = this.set_component('item', item);
2794
- return r;
2795
- },
2796
-
2797
- /**
2798
- * Clear item instance from type
2799
- * Sets value to NULL
2800
- */
2801
- clear_item: function() {
2802
- this.clear_component('item');
2803
- },
2804
-
2805
- /* Evaluation */
2806
-
2807
- /**
2808
- * Check if item matches content handler
2809
- * @param object item Content_Item instance to check for type match
2810
- * @return bool TRUE if type matches, FALSE otherwise
2811
- */
2812
- match: function(item) {
2813
- // Validate
2814
- var attr = 'match';
2815
- var m = this.get_attribute(attr);
2816
- // Stop processing types with no matching algorithm
2817
- if ( !this.util.is_empty(m) ) {
2818
- // Process regex patterns
2819
-
2820
- // String-based
2821
- if ( this.util.is_string(m) ) {
2822
- // Create new regexp object
2823
- m = new RegExp(m, "i");
2824
- this.set_attribute(attr, m);
2825
- }
2826
- // RegExp based
2827
- if ( this.util.is_type(m, RegExp) ) {
2828
- return m.test(item.get_uri());
2829
- }
2830
- // Process function
2831
- if ( this.util.is_func(m) ) {
2832
- return ( m.call(this, item) ) ? true : false;
2833
- }
2834
- }
2835
- // Default
2836
- return false;
2837
- },
2838
-
2839
- /* Processing/Output */
2840
-
2841
- /**
2842
- * Loads item data
2843
- * @param obj item Content item to load data for
2844
- * @return obj Promise that is resolved when item data is loaded
2845
- */
2846
- load: function(item) {
2847
- var dfr = $.Deferred();
2848
- var ret = this.call_attribute('load', item, dfr);
2849
- // Handle missing load method
2850
- if ( null === ret ) {
2851
- dfr.resolve();
2852
- }
2853
- return dfr.promise();
2854
- },
2855
-
2856
- /**
2857
- * Render output to display item
2858
- * @param Content_Item item Item to render output for
2859
- * @return obj jQuery.Promise that is resolved when item is rendered
2860
- */
2861
- render: function(item) {
2862
- var dfr = $.Deferred();
2863
- // Validate
2864
- this.call_attribute('render', item, dfr);
2865
- return dfr.promise();
2866
- }
2867
- };
2868
-
2869
- View.Content_Handler = Component.extend(Content_Handler);
2870
-
2871
- /**
2872
- * Content Item
2873
- * @param obj options Init options
2874
- */
2875
- var Content_Item = {
2876
- /* Configuration */
2877
-
2878
- _slug: 'content_item',
2879
- _reciprocal: true,
2880
- _refs: {
2881
- 'viewer': 'Viewer',
2882
- 'group': 'Group',
2883
- 'type': 'Content_Handler'
2884
- },
2885
-
2886
- _attr_default: {
2887
- source: null,
2888
- permalink: null,
2889
- dimensions: null,
2890
- title: '',
2891
- group: null,
2892
- internal: false,
2893
- output: null
2894
- },
2895
-
2896
- /* References */
2897
-
2898
- group: null,
2899
- viewer: null,
2900
- type: null,
2901
-
2902
- /* Properties */
2903
-
2904
- data: null,
2905
- loaded: null,
2906
-
2907
- /* Init */
2908
-
2909
- _c: function(el) {
2910
- // Save element to instance
2911
- this.dom_set(el);
2912
- // Default initialization
2913
- this._super();
2914
- },
2915
-
2916
- /* Methods */
2917
-
2918
- /*-** Attributes **-*/
2919
-
2920
- /**
2921
- * Build default attributes
2922
- * Populates attributes with asset properties (attachments)
2923
- * Overrides super class method
2924
- * @uses Component.init_default_attributes()
2925
- */
2926
- init_default_attributes: function() {
2927
- this._super();
2928
- // Add asset properties
2929
- var d = this.dom_get();
2930
- var key = d.attr(this.util.get_attribute('asset')) || null;
2931
- var assets = this.get_controller().assets || null;
2932
- // Merge asset data with default attributes
2933
- if ( this.util.is_string(key) ) {
2934
- var attrs = [{}, this._attr_default, {'permalink': d.attr('href')}];
2935
- if ( this.util.is_obj(assets) ) {
2936
- var t = this;
2937
- /**
2938
- * Retrieve item assets
2939
- * Handles variant items as well (Retrieves parent item assets)
2940
- * @param string key Item URI
2941
- * @return obj Item assets (Empty if no match)
2942
- */
2943
- var get_assets = function(key) {
2944
- var ret = {};
2945
- if ( key in assets && t.util.is_obj(assets[key]) ) {
2946
- ret = assets[key];
2947
- }
2948
- return ret;
2949
- };
2950
- // Save assets
2951
- attrs.push(get_assets(key));
2952
- }
2953
- this._attr_default = $.extend.apply(this, attrs);
2954
- }
2955
- return this._attr_default;
2956
- },
2957
-
2958
- /*-** Properties **-*/
2959
-
2960
- /**
2961
- * Retrieve item output
2962
- * Output generated based on content handler if not previously generated
2963
- * @uses get_attribute() to retrieve cached output
2964
- * @uses set_attribute() to cache generated output
2965
- * @uses get_type() to retrieve item type
2966
- * @uses Content_Handler.render() to generate item output
2967
- * @return obj jQuery.Promise that is resolved when output is retrieved
2968
- */
2969
- get_output: function() {
2970
- var dfr = $.Deferred();
2971
- // Check for cached output
2972
- var ret = this.get_attribute('output');
2973
- if ( this.util.is_string(ret) ) {
2974
- dfr.resolve(ret);
2975
- } else if ( this.has_type() ) {
2976
- // Render output from scratch (if necessary)
2977
- // Get item type
2978
- var type = this.get_type();
2979
- // Render type-based output
2980
- var item = this;
2981
- type.render(this).done(function(output) {
2982
- // Cache output
2983
- item.set_output(output);
2984
- dfr.resolve(output);
2985
- });
2986
- } else {
2987
- dfr.resolve('');
2988
- }
2989
- return dfr.promise();
2990
- },
2991
-
2992
- /**
2993
- * Cache output for future retrieval
2994
- * @uses set_attribute() to cache output
2995
- */
2996
- set_output: function(out) {
2997
- if ( this.util.is_string(out, false) ) {
2998
- this.set_attribute('output', out);
2999
- }
3000
- },
3001
-
3002
- /**
3003
- * Retrieve item output
3004
- * Alias for `get_output()`
3005
- * @return jQuery.Promise Deferred that is resolved when content is retrieved
3006
- */
3007
- get_content: function() {
3008
- return this.get_output();
3009
- },
3010
-
3011
- /**
3012
- * Retrieve item URI
3013
- * @param string mode (optional) Which URI should be retrieved
3014
- * > source: Media source
3015
- * > permalink: Item permalink
3016
- * @return string Item URI
3017
- */
3018
- get_uri: function(mode) {
3019
- // Validate
3020
- if ( $.inArray(mode ,['source', 'permalink']) === -1 ) {
3021
- mode = 'source';
3022
- }
3023
- // Retrieve URI
3024
- var ret = this.get_attribute(mode);
3025
- if ( !this.util.is_string(ret) ) {
3026
- ret = ( 'source' === mode ) ? this.get_attribute('permalink') : '';
3027
- }
3028
- // Format
3029
- ret = ret.replace(/&(#38|amp);/, '&');
3030
- return ret;
3031
- },
3032
-
3033
- /**
3034
- * Retrieve item title
3035
- */
3036
- get_title: function() {
3037
- var prop = 'title';
3038
- var prop_cached = prop + '_cached';
3039
- // Check for cached value
3040
- if ( this.has_attribute(prop_cached) ) {
3041
- return this.get_attribute(prop_cached, '');
3042
- }
3043
-
3044
- var title = '';
3045
- // Generate title from DOM values
3046
- var dom = this.dom_get();
3047
- var t = this;
3048
- /**
3049
- * Validate title value.
3050
- *
3051
- * Removes default title based on user option.
3052
- *
3053
- * @param string title Title to check.
3054
- * @return string Current title or empty string (if default title set and not permitted).
3055
- */
3056
- var validate = function(title) {
3057
- // Return empty string if empty title set.
3058
- if ( typeof title !== 'string' || '' === title.trim() ) {
3059
- return '';
3060
- }
3061
- // Cleanup title.
3062
- title = title.trim();
3063
- // Stop processing if default title is allowed.
3064
- if ( t.get_viewer().get_attribute('title_default') ) {
3065
- return title;
3066
- }
3067
-
3068
- // Check if default title is used.
3069
- if ( title === t.get_title_default() ) {
3070
- title = '';
3071
- }
3072
- return title;
3073
- };
3074
-
3075
- // DOM-based caption
3076
- if ( dom.length ) {
3077
- // Link title (generally must be manually-entered)
3078
- title = dom.attr(prop);
3079
-
3080
- // Figcaption element
3081
- if ( !title ) {
3082
- title = dom.closest('figure').find('figcaption').first().html();
3083
- }
3084
-
3085
- // Class Name
3086
- if ( !title ) {
3087
- title = dom.closest('figure').find('.wp-caption-text').first().html();
3088
- }
3089
- }
3090
-
3091
- // Saved attributes
3092
- if ( !title ) {
3093
- var props = ['caption', 'title'];
3094
- for ( var x = 0; x < props.length; x++ ) {
3095
- title = validate( this.get_attribute(props[x], '') );
3096
- if ( !this.util.is_empty(title) ) {
3097
- break;
3098
- }
3099
- }
3100
- }
3101
-
3102
- // Fallbacks
3103
- if ( !title && dom.length ) {
3104
- // Image Alt attribute
3105
- title = validate( dom.find('img').first().attr('alt') );
3106
-
3107
- // Element text
3108
- if ( !title ) {
3109
- title = validate( dom.get(0).innerText.trim() );
3110
- }
3111
- }
3112
-
3113
- // Final validation.
3114
- title = validate(title);
3115
-
3116
- // Cache retrieved value
3117
- this.set_attribute(prop_cached, title);
3118
- // Return value
3119
- return title;
3120
- },
3121
-
3122
- /**
3123
- * Retrieve default title.
3124
- *
3125
- * WordPress-generated default title for attachments is base file name (without extension).
3126
- *
3127
- * @return string Default title.
3128
- */
3129
- get_title_default: function() {
3130
- var val = '';
3131
- var prop = 'title_default';
3132
- // Build default title if necessary.
3133
- if ( !this.has_attribute(prop) ) {
3134
- var f = this.get_uri('source');
3135
- var i = f.lastIndexOf('/');
3136
- if ( -1 !== i ) {
3137
- f = f.substr(i + 1);
3138
- i = f.lastIndexOf('.');
3139
- if ( -1 !== i ) {
3140
- f = f.substr(0, i);
3141
- }
3142
- }
3143
- // Save default title
3144
- val = this.set_attribute(prop, f);
3145
- } else {
3146
- val = this.get_attribute(prop);
3147
- }
3148
- return val;
3149
- },
3150
-
3151
- /**
3152
- * Retrieve item dimensions
3153
- * @return obj Item `width` and `height` properties (px)
3154
- */
3155
- get_dimensions: function() {
3156
- return $.extend({'width': 0, 'height': 0}, this.get_attribute('dimensions'), {});
3157
- },
3158
-
3159
- /**
3160
- * Save item data to instance
3161
- * Item data is saved when rendered
3162
- * @param mixed data Item data (property cleared if NULL)
3163
- */
3164
- set_data: function(data) {
3165
- this.data = data;
3166
- },
3167
-
3168
- get_data: function() {
3169
- return this.data;
3170
- },
3171
-
3172
- /**
3173
- * Determine gallery type
3174
- * @return string|null Gallery type ID (NULL if item not in gallery)
3175
- */
3176
- gallery_type: function() {
3177
- var ret = null;
3178
- var types = {
3179
- 'wp': '.gallery-icon',
3180
- 'ngg': '.ngg-gallery-thumbnail'
3181
- };
3182
-
3183
- var dom = this.dom_get();
3184
- for ( var type in types ) {
3185
- if ( dom.parent(types[type]).length > 0 ) {
3186
- ret = type;
3187
- break;
3188
- }
3189
- }
3190
- return ret;
3191
- },
3192
-
3193
- /**
3194
- * Check if current link is part of a gallery
3195
- * @param string gType (optional) Gallery type to check for
3196
- * @return bool TRUE if link is part of (specified) gallery (FALSE otherwise)
3197
- */
3198
- in_gallery: function(gType) {
3199
- var type = this.gallery_type();
3200
- // No gallery
3201
- if ( null === type ) {
3202
- return false;
3203
- }
3204
- // Boolean check
3205
- if ( !this.util.is_string(gType) ) {
3206
- return true;
3207
- }
3208
- // Check for specific gallery type
3209
- return ( gType === type ) ? true : false;
3210
- },
3211
-
3212
- /*-** Component References **-*/
3213
-
3214
- /* Viewer */
3215
-
3216
- get_viewer: function() {
3217
- return this.get_component('viewer', {get_default: true});
3218
- },
3219
-
3220
- /**
3221
- * Sets item's viewer property
3222
- * @uses View.get_viewer() to retrieve global viewer
3223
- * @uses this.viewer to save item's viewer
3224
- * @param string|View.Viewer v Viewer to set for item
3225
- * > Item's viewer is reset if invalid viewer provided
3226
- */
3227
- set_viewer: function(v) {
3228
- return this.set_component('viewer', v);
3229
- },
3230
-
3231
- /* Group */
3232
-
3233
- /**
3234
- * Retrieve item's group
3235
- * @param bool set_current (optional) Sets item as current item in group (Default: FALSE)
3236
- * @return View.Group|bool Group reference item belongs to (FALSE if no group)
3237
- */
3238
- get_group: function(set_current) {
3239
- var prop = 'group';
3240
- // Check if group reference already set
3241
- var g = this.get_component(prop);
3242
- if ( g ) {
3243
- } else {
3244
- // Set empty group if no group exists
3245
- g = this.set_component(prop, new View.Group());
3246
- set_current = true;
3247
- }
3248
- if ( !!set_current ) {
3249
- g.set_current(this);
3250
- }
3251
- return g;
3252
- },
3253
-
3254
- /**
3255
- * Sets item's group property
3256
- * @uses View.get_group() to retrieve global group
3257
- * @uses this.group to set item's group
3258
- * @param string|View.Group g Group to set for item
3259
- * > Item's group is reset if invalid group provided
3260
- */
3261
- set_group: function(g) {
3262
- // If group ID set, get object reference
3263
- if ( this.util.is_string(g) ) {
3264
- g = this.get_controller().get_group(g);
3265
- }
3266
-
3267
- // Set (or clear) group property
3268
- this.group = ( this.util.is_type(g, View.Group) ) ? g : false;
3269
- },
3270
-
3271
- /* Content Handler */
3272
-
3273
- /**
3274
- * Retrieve item type
3275
- * @uses get_component() to retrieve saved reference to Content_Handler instance
3276
- * @uses View.get_content_handler() to determine item content handler (if necessary)
3277
- * @return Content_Handler|null Content Handler of item (NULL no valid type exists)
3278
- */
3279
- get_type: function() {
3280
- var t = this.get_component('type', {check_attr: false});
3281
- if ( !t ) {
3282
- t = this.set_type(this.get_controller().get_content_handler(this));
3283
- }
3284
- return t;
3285
- },
3286
-
3287
- /**
3288
- * Save content handler reference
3289
- * @uses set_component() to save type reference
3290
- * @return Content_Handler|null Saved content handler (NULL if invalid)
3291
- */
3292
- set_type: function(type) {
3293
- return this.set_component('type', type);
3294
- },
3295
-
3296
- /**
3297
- * Check if content handler exists for item
3298
- * @return bool TRUE if content handler exists, FALSE otherwise
3299
- */
3300
- has_type: function() {
3301
- var ret = !this.util.is_empty(this.get_type());
3302
- return ret;
3303
- },
3304
-
3305
- /* Actions */
3306
-
3307
- /**
3308
- * Display item in viewer
3309
- * @uses get_viewer() to retrieve viewer instance for item
3310
- * @uses Viewer.show() to display item in viewer
3311
- * @param obj options (optional) Options
3312
- */
3313
- show: function(options) {
3314
- // Validate content handler
3315
- if ( !this.has_type() ) {
3316
- return false;
3317
- }
3318
- // Set display options
3319
- this.set_attribute('options_show', options);
3320
- // Retrieve viewer
3321
- var v = this.get_viewer();
3322
- // Load item
3323
- this.load();
3324
- var ret = v.show(this);
3325
- return ret;
3326
- },
3327
-
3328
- /**
3329
- * Load item data
3330
- *
3331
- * Retrieves item data from external sources (if necessary)
3332
- * @uses this.loaded to save loaded state
3333
- * @return obj Promise that is resolved when item data is loaded
3334
- */
3335
- load: function() {
3336
- if ( !this.util.is_promise(this.loaded) ) {
3337
- // Load item data (via content handler)
3338
- this.loaded = this.get_type().load(this);
3339
- }
3340
- return this.loaded.promise();
3341
- },
3342
-
3343
- reset: function() {
3344
- this.set_attribute('options_show', null);
3345
- }
3346
- };
3347
-
3348
- View.Content_Item = Component.extend(Content_Item);
3349
-
3350
- /**
3351
- * Modeled Component
3352
- */
3353
- var Modeled_Component = {
3354
-
3355
- _slug: 'modeled_component',
3356
-
3357
- /* Methods */
3358
-
3359
- /* Attributes */
3360
-
3361
- /**
3362
- * Retrieve attribute
3363
- * Gives priority to model values
3364
- * @see Component.get_attribute()
3365
- * @param string key Attribute to retrieve
3366
- * @param mixed def (optional) Default value (Default: NULL)
3367
- * @param bool check_model (optional) Check model for value (Default: TRUE)
3368
- * @param bool enforce_type (optional) Return value data type should match default value data type (Default: TRUE)
3369
- * @return mixed Attribute value
3370
- */
3371
- get_attribute: function(key, def, check_model, enforce_type) {
3372
- // Validate
3373
- if ( !this.util.is_string(key) ) {
3374
- // Invalid requests sent straight to super method
3375
- return this._super(key, def, enforce_type);
3376
- }
3377
- if ( !this.util.is_bool(check_model) ) {
3378
- check_model = true;
3379
- }
3380
- var ret = null;
3381
- // Check model for attribute
3382
- if ( check_model ) {
3383
- var m = this.get_ancestor(key, false);
3384
- if ( this.util.in_obj(m, key) ) {
3385
- ret = m[key];
3386
- }
3387
- }
3388
- // Check standard attributes as fallback
3389
- if ( null === ret ) {
3390
- ret = this._super(key, def, enforce_type);
3391
- }
3392
- return ret;
3393
- },
3394
-
3395
- /**
3396
- * Get attribute recursively
3397
- * Merges objects from ancestors together
3398
- * @see Component.get_attribute() for more information
3399
- */
3400
- get_attribute_recursive: function(key, def, enforce_type) {
3401
- var ret = this.get_attribute(key, def, true, enforce_type);
3402
- if ( this.util.is_obj(ret) ) {
3403
- // Merge ancestor objects
3404
- var models = this.get_ancestors(false);
3405
- ret = [ret];
3406
- var t = this;
3407
- $.each(models, function(idx, model) {
3408
- if ( key in model && t.util.is_obj(model[key]) ) {
3409
- ret.push(model[key]);
3410
- }
3411
- });
3412
- // Merge transition handlers into current theme
3413
- ret.push({});
3414
- ret = $.extend.apply($, ret.reverse());
3415
- }
3416
- return ret;
3417
- },
3418
-
3419
- /**
3420
- * Set attribute value
3421
- * Gives priority to model values
3422
- * @see Component.set_attribute()
3423
- * @param string key Attribute to set
3424
- * @param mixed val Value to set for attribute
3425
- * @param bool|obj use_model (optional) Set the value on the model (Default: TRUE)
3426
- * > bool: Set attribute on current model (TRUE) or as standard attribute (FALSE)
3427
- * > obj: Model object to set attribute on
3428
- * @return mixed Attribute value
3429
- */
3430
- set_attribute: function(key, val, use_model) {
3431
- // Validate
3432
- if ( ( !this.util.is_string(key) ) || !this.util.is_set(val) ) {
3433
- return false;
3434
- }
3435
- if ( !this.util.is_bool(use_model) && !this.util.is_obj(use_model) ) {
3436
- use_model = true;
3437
- }
3438
- // Determine where to set attribute
3439
- if ( !!use_model ) {
3440
- var model = this.util.is_obj(use_model) ? use_model : this.get_model();
3441
-
3442
- // Set attribute in model
3443
- model[key] = val;
3444
- } else {
3445
- // Set as standard attribute
3446
- this._super(key, val);
3447
- }
3448
- return val;
3449
- },
3450
-
3451
-
3452
- /* Model */
3453
-
3454
- /**
3455
- * Retrieve Template model
3456
- * @return obj Model (Default: Empty object)
3457
- */
3458
- get_model: function() {
3459
- var m = this.get_attribute('model', null, false);
3460
- if ( !this.util.is_obj(m) ) {
3461
- // Set default value
3462
- m = {};
3463
- this.set_attribute('model', m, false);
3464
- }
3465
- return m;
3466
- },
3467
-
3468
-
3469
- /**
3470
- * Check if instance has model
3471
- * @return bool TRUE if model is set, FALSE otherwise
3472
- */
3473
- has_model: function() {
3474
- return ( this.util.is_empty( this.get_model() ) ) ? false : true;
3475
- },
3476
-
3477
-
3478
-
3479
- /**
3480
- * Check if specified attribute exists in model
3481
- * @param string key Attribute to check for
3482
- * @return bool TRUE if attribute exists, FALSE otherwise
3483
- */
3484
- in_model: function(key) {
3485
- return ( this.util.in_obj(this.get_model(), key) ) ? true : false;
3486
- },
3487
-
3488
- /**
3489
- * Retrieve all ancestor models
3490
- * @param bool inc_current (optional) Include current model in list (Default: FALSE)
3491
- * @return array Theme ancestor models (Closest parent first)
3492
- */
3493
- get_ancestors: function(inc_current) {
3494
- var ret = [];
3495
- var m = this.get_model();
3496
- while ( this.util.is_obj(m) ) {
3497
- ret.push(m);
3498
- m = ( this.util.in_obj(m, 'parent') && this.util.is_obj(m.parent) ) ? m.parent : null;
3499
- }
3500
- // Remove current model from list
3501
- if ( !inc_current ) {
3502
- ret.shift();
3503
- }
3504
- return ret;
3505
- },
3506
-
3507
- /**
3508
- * Retrieve first ancestor of current theme with specified attribute
3509
- * > Current model is also evaluated
3510
- * @param string attr Attribute to search ancestors for
3511
- * @param bool safe_mode (optional) Return current model if no matching ancestor found (Default: TRUE)
3512
- * @return obj Theme ancestor (Default: Current theme model)
3513
- */
3514
- get_ancestor: function(attr, safe_mode) {
3515
- // Validate
3516
- if ( !this.util.is_string(attr) ) {
3517
- return false;
3518
- }
3519
- if ( !this.util.is_bool(safe_mode) ) {
3520
- safe_mode = true;
3521
- }
3522
- var mcurr = this.get_model();
3523
- var m = mcurr;
3524
- var found = false;
3525
- while ( this.util.is_obj(m) ) {
3526
- // Check if attribute exists in model
3527
- if ( this.util.in_obj(m, attr) && !this.util.is_empty(m[attr]) ) {
3528
- found = true;
3529
- break;
3530
- }
3531
- // Get next model
3532
- m = ( this.util.in_obj(m, 'parent') ) ? m['parent'] : null;
3533
- }
3534
- if ( !found ) {
3535
- if ( safe_mode ) {
3536
- // Use current model as fallback
3537
- if ( this.util.is_empty(m) ) {
3538
- m = mcurr;
3539
- }
3540
- // Add attribute to object
3541
- if ( !this.util.in_obj(m, attr) ) {
3542
- m[attr] = null;
3543
- }
3544
- } else {
3545
- m = null;
3546
- }
3547
- }
3548
- return m;
3549
- }
3550
-
3551
- };
3552
-
3553
- Modeled_Component = Component.extend(Modeled_Component);
3554
-
3555
- /**
3556
- * Theme
3557
- */
3558
- var Theme = {
3559
-
3560
- /* Configuration */
3561
-
3562
- _slug: 'theme',
3563
- _refs: {
3564
- 'viewer': 'Viewer',
3565
- 'template': 'Template'
3566
- },
3567
- _models: {},
3568
-
3569
- _attr_default: {
3570
- template: null,
3571
- model: null
3572
- },
3573
-
3574
- /* References */
3575
-
3576
- viewer: null,
3577
- template: null,
3578
-
3579
- /* Methods */
3580
-
3581
- /**
3582
- * Custom constructor
3583
- * @see Component._c()
3584
- */
3585
- _c: function(id, attributes, viewer) {
3586
- // Validate
3587
- if ( arguments.length === 1 && this.util.is_type(arguments[0], View.Viewer) ) {
3588
- viewer = arguments[0];
3589
- id = null;
3590
- }
3591
- // Pass parameters to parent constructor
3592
- this._super(id, attributes);
3593
-
3594
- // Set viewer instance
3595
- this.set_viewer(viewer);
3596
-
3597
- // Set theme model
3598
- this.set_model(id);
3599
- },
3600
-
3601
- /* Viewer */
3602
-
3603
- get_viewer: function() {
3604
- return this.get_component('viewer', {check_attr: false, get_default: true});
3605
- },
3606
-
3607
- /**
3608
- * Sets theme's viewer property
3609
- * @uses View.get_viewer() to retrieve global viewer
3610
- * @uses this.viewer to save item's viewer
3611
- * @param string|View.Viewer v Viewer to set for item
3612
- * > Theme's viewer is reset if invalid viewer provided
3613
- */
3614
- set_viewer: function(v) {
3615
- return this.set_component('viewer', v);
3616
- },
3617
-
3618
- /* Template */
3619
-
3620
- /**
3621
- * Retrieve template instance
3622
- * @return Template instance
3623
- */
3624
- get_template: function() {
3625
- // Get saved template
3626
- var ret = this.get_component('template');
3627
- // Template needs to be initialized
3628
- if ( this.util.is_empty(ret) ) {
3629
- // Pass model to Template instance
3630
- var attr = { 'theme': this, 'model': this.get_model() };
3631
- ret = this.set_component('template', new View.Template(attr));
3632
- }
3633
- return ret;
3634
- },
3635
-
3636
- /* Tags */
3637
-
3638
- /**
3639
- * Retrieve tags from template
3640
- * All tags will be retrieved by default
3641
- * Specific tag/property instances can be retrieved as well
3642
- * @see Template.get_tags()
3643
- * @param string name (optional) Name of tags to retrieve
3644
- * @param string prop (optional) Specific tag property to retrieve
3645
- * @return array Tags in template
3646
- */
3647
- get_tags: function(name, prop) {
3648
- return this.get_template().get_tags(name, prop);
3649
- },
3650
-
3651
- /**
3652
- * Retrieve tag DOM elements
3653
- * @see Template.dom_get_tag()
3654
- */
3655
- dom_get_tag: function(tag, prop) {
3656
- return $(this.get_template().dom_get_tag(tag, prop));
3657
- },
3658
-
3659
- /**
3660
- * Retrieve template tag CSS selector
3661
- * @uses Template.get_tag_selector()
3662
- * @param name string Tag name
3663
- * @param prop string Tag Property
3664
- * @return string Template tag CSS selector
3665
- */
3666
- get_tag_selector: function(name, prop) {
3667
- return this.get_template().get_tag_selector(name, prop);
3668
- },
3669
-
3670
- /* Model */
3671
-
3672
- /**
3673
- * Retrieve theme models
3674
- * @return obj Theme models
3675
- */
3676
- get_models: function() {
3677
- return this._models;
3678
- },
3679
-
3680
- /**
3681
- * Retrieve specified theme model
3682
- * @param string id (optional) Theme model to retrieve
3683
- * > Default model retrieved if ID is invalid/not set
3684
- * @return obj Specified theme model
3685
- */
3686
- get_model: function(id) {
3687
- var ret = null;
3688
- // Pass request to superclass method
3689
- if ( !this.util.is_set(id) && this.util.is_obj( this.get_attribute('model', null, false) ) ) {
3690
- ret = this._super();
3691
- } else {
3692
- // Retrieve matching theme model
3693
- var models = this.get_models();
3694
- if ( !this.util.is_string(id) ) {
3695
- id = this.get_controller().get_option('theme_default');
3696
- }
3697
- // Select first theme model if specified model is invalid
3698
- if ( !this.util.in_obj(models, id) ) {
3699
- id = $.map(models, function(v, key) { return key; })[0];
3700
- }
3701
- ret = models[id];
3702
- }
3703
- return ret;
3704
- },
3705
-
3706
- /**
3707
- * Set model for current theme instance
3708
- * @param string id (optional) Theme ID (Default theme retrieved if ID invalid)
3709
- */
3710
- set_model: function(id) {
3711
- this.set_attribute('model', this.get_model(id), false);
3712
- /* @deprecated
3713
- // Set ID using model attributes (if necessary)
3714
- if ( !this._check_id(true) ) {
3715
- var m = this.get_model();
3716
- if ( 'id' in m ) {
3717
- this._set_id(m.id);
3718
- }
3719
- }
3720
- */
3721
- },
3722
-
3723
- /* Properties */
3724
-
3725
- /**
3726
- * Generate class names for DOM node
3727
- * @param string rtype (optional) Return data type
3728
- * > Default: array
3729
- * > If string supplied: Joined classes delimited by parameter
3730
- * @uses get_class() to generate class names
3731
- * @uses Array.join() to convert class names array to string
3732
- * @return array Class names
3733
- */
3734
- get_classes: function(rtype) {
3735
- // Build array of class names
3736
- var cls = [];
3737
- var thm = this;
3738
- // Include theme parent's class name
3739
- var models = this.get_ancestors(true);
3740
- $.each(models, function(idx, model) {
3741
- cls.push(thm.add_ns(model.id));
3742
- });
3743
- // Convert class names array to string
3744
- if ( this.util.is_string(rtype) ) {
3745
- cls = cls.join(rtype);
3746
- }
3747
- // Return class names
3748
- return cls;
3749
- },
3750
-
3751
- /**
3752
- * Get custom measurement
3753
- * @param string attr Measurement to retrieve
3754
- * @param obj def (optional) Default value
3755
- * @return obj Attribute measurements
3756
- */
3757
- get_measurement: function(attr, def) {
3758
- var meas = null;
3759
- // Validate
3760
- if ( !this.util.is_string(attr) ) {
3761
- return meas;
3762
- }
3763
- if ( !this.util.is_obj(def, false) ) {
3764
- def = {};
3765
- }
3766
- // Manage cache
3767
- var attr_cache = this.util.format('%s_cache', attr);
3768
- var cache = this.get_attribute(attr_cache, {}, false);
3769
- var status = '_status';
3770
- var item = this.get_viewer().get_item();
3771
- var w = $(window);
3772
- // Check cache freshness
3773
- if ( !( status in cache ) || !this.util.is_obj(cache[status]) || cache[status].width !== w.width() || cache[status].height !== w.height() ) {
3774
- cache = {};
3775
- }
3776
- if ( this.util.is_empty(cache) ) {
3777
- // Set status
3778
- cache[status] = {
3779
- 'width': w.width(),
3780
- 'height': w.height(),
3781
- 'index': []
3782
- };
3783
- }
3784
- // Retrieve cached values
3785
- var pos = $.inArray(item, cache[status].index);
3786
- if ( pos !== -1 && pos in cache ) {
3787
- meas = cache[pos];
3788
- }
3789
- // Generate measurement
3790
- if ( !this.util.is_obj(meas) ) {
3791
- // Get custom theme measurement
3792
- meas = this.call_attribute(attr);
3793
- if ( !this.util.is_obj(meas) ) {
3794
- // Retrieve fallback value
3795
- meas = this.get_measurement_default(attr);
3796
- }
3797
- }
3798
- // Normalize measurement
3799
- meas = ( this.util.is_obj(meas) ) ? $.extend({}, def, meas) : def;
3800
- // Cache measurement
3801
- pos = cache[status].index.push(item) - 1;
3802
- cache[pos] = meas;
3803
- this.set_attribute(attr_cache, cache, false);
3804
- // Return measurement (copy)
3805
- return $.extend({}, meas);
3806
- },
3807
-
3808
- /**
3809
- * Get default measurement using attribute's default handler
3810
- * @param string attr Measurement attribute
3811
- * @return obj Measurement values
3812
- */
3813
- get_measurement_default: function(attr) {
3814
- // Validate
3815
- if ( !this.util.is_string(attr) ) {
3816
- return null;
3817
- }
3818
- // Find default handler
3819
- attr = this.util.format('get_%s_default', attr);
3820
- if ( this.util.in_obj(this, attr) ) {
3821
- attr = this[attr];
3822
- if ( this.util.is_func(attr) ) {
3823
- // Execute default handler
3824
- attr = attr.call(this);
3825
- }
3826
- } else {
3827
- attr = null;
3828
- }
3829
- return attr;
3830
- },
3831
-
3832
- /**
3833
- * Retrieve theme offset
3834
- * @return obj Theme offset with `width` & `height` properties
3835
- */
3836
- get_offset: function() {
3837
- return this.get_measurement('offset', { 'width': 0, 'height': 0});
3838
- },
3839
-
3840
- /**
3841
- * Generate default offset
3842
- * @return obj Theme offsets with `width` & `height` properties
3843
- */
3844
- get_offset_default: function() {
3845
- var offset = { 'width': 0, 'height': 0 };
3846
- var v = this.get_viewer();
3847
- var vn = v.dom_get();
3848
- // Clone viewer
3849
- var vc = vn
3850
- .clone()
3851
- .attr('id', '')
3852
- .css({'visibility': 'hidden', 'position': 'absolute', 'top': ''})
3853
- .removeClass('loading')
3854
- .appendTo(vn.parent());
3855
- // Get offset from layout node
3856
- var l = vc.find(v.dom_get_selector('layout'));
3857
- if ( l.length ) {
3858
- // Clear inline styles
3859
- l.find('*').css({
3860
- 'width': '',
3861
- 'height': '',
3862
- 'display': ''
3863
- });
3864
- // Resize content nodes
3865
- var tags = this.get_tags('item', 'content');
3866
- if ( tags.length ) {
3867
- var offset_item = v.get_item().get_dimensions();
3868
- // Set content dimensions
3869
- tags = $(l.find(tags[0].get_selector('full')).get(0)).css({'width': offset_item.width, 'height': offset_item.height});
3870
- $.each(offset_item, function(key, val) {
3871
- offset[key] = -1 * val;
3872
- });
3873
- }
3874
-
3875
- // Set offset
3876
- offset.width += l.width();
3877
- offset.height += l.height();
3878
- // Normalize
3879
- $.each(offset, function(key, val) {
3880
- if ( val < 0 ) {
3881
- offset[key] = 0;
3882
- }
3883
- });
3884
- }
3885
- vc.empty().remove();
3886
- return offset;
3887
- },
3888
-
3889
- /**
3890
- * Retrieve theme margins
3891
- * @return obj Theme margin with `width` & `height` properties
3892
- */
3893
- get_margin: function() {
3894
- return this.get_measurement('margin', {'width': 0, 'height': 0});
3895
- },
3896
-
3897
- /**
3898
- * Retrieve item dimensions
3899
- * Dimensions are adjusted to fit window (if necessary)
3900
- * @return obj Item dimensions with `width` & `height` properties
3901
- */
3902
- get_item_dimensions: function() {
3903
- var v = this.get_viewer();
3904
- var dims = v.get_item().get_dimensions();
3905
- if ( v.get_attribute('autofit', false) ) {
3906
- // Get maximum dimensions
3907
- var margin = this.get_margin();
3908
- var offset = this.get_offset();
3909
- offset.height += margin.height;
3910
- offset.width += margin.width;
3911
- var max = {'width': $(window).width(), 'height': $(window).height() };
3912
- if ( max.width > offset.width ) {
3913
- max.width -= offset.width;
3914
- }
3915
- if ( max.height > offset.height ) {
3916
- max.height -= offset.height;
3917
- }
3918
- // Get resize factor
3919
- var factor = Math.min(max.width / dims.width, max.height / dims.height);
3920
- // Resize dimensions
3921
- if ( factor < 1 ) {
3922
- $.each(dims, function(key) {
3923
- dims[key] = Math.round(dims[key] * factor);
3924
- });
3925
- }
3926
- }
3927
- return $.extend({}, dims);
3928
- },
3929
-
3930
- /**
3931
- * Retrieve theme dimensions
3932
- * @return obj Theme dimensions with `width` & `height` properties
3933
- */
3934
- get_dimensions: function() {
3935
- var dims = this.get_item_dimensions();
3936
- var offset = this.get_offset();
3937
- $.each(dims, function(key) {
3938
- dims[key] += offset[key];
3939
- });
3940
- return dims;
3941
- },
3942
-
3943
- /**
3944
- * Retrieve all breakpoints
3945
- * @return object Breakpoints
3946
- */
3947
- get_breakpoints: function() {
3948
- return this.get_attribute_recursive('breakpoints');
3949
- },
3950
-
3951
- /**
3952
- * Get breakpoint value
3953
- * @param string target Breakpoint target
3954
- * @return int Breakpoint value (pixels)
3955
- */
3956
- get_breakpoint: function(target) {
3957
- var ret = 0;
3958
- if ( this.util.is_string(target) ) {
3959
- var b = this.get_attribute_recursive('breakpoints');
3960
- if ( this.util.is_obj(b) && target in b ) {
3961
- ret = b[target];
3962
- }
3963
- }
3964
- return ret;
3965
- },
3966
-
3967
- /* Output */
3968
-
3969
- /**
3970
- * Render Theme output
3971
- * @param bool init (optional) Initialize theme (Default: FALSE)
3972
- * @see Template.render()
3973
- */
3974
- render: function(init) {
3975
- var thm = this;
3976
- var tpl = this.get_template();
3977
- var st = 'events_render';
3978
- if ( !this.get_status(st) ) {
3979
- this.set_status(st);
3980
- // Register events
3981
- tpl.on([
3982
- 'render-init',
3983
- 'render-loading',
3984
- 'render-complete'
3985
- ],
3986
- function(ev) {
3987
- return thm.trigger(ev.type, ev.data);
3988
- });
3989
- }
3990
- // Render template
3991
- tpl.render(init);
3992
- },
3993
-
3994
- transition: function(event, clear_queue) {
3995
- var dfr = null;
3996
- var attr = 'transition';
3997
- var v = this.get_viewer();
3998
- var fx_temp = null;
3999
- var anim_on = v.animation_enabled();
4000
- if ( v.get_attribute(attr, true) && this.util.is_string(event) ) {
4001
- var anim_stop = function() {
4002
- var l = v.get_layout();
4003
- l.find('*').each(function() {
4004
- var el = $(this);
4005
- while ( el.queue().length ) {
4006
- el.stop(false, true);
4007
- }
4008
- });
4009
- };
4010
- // Stop queued animations
4011
- if ( !!clear_queue ) {
4012
- anim_stop();
4013
- }
4014
- // Get transition handlers
4015
- var attr_set = [attr, 'set'].join('_');
4016
- var trns;
4017
- if ( !this.get_attribute(attr_set) ) {
4018
- var models = this.get_ancestors(true);
4019
- trns = [];
4020
- this.set_attribute(attr_set, true);
4021
- var thm = this;
4022
- $.each(models, function(idx, model) {
4023
- if ( attr in model && thm.util.is_obj(model[attr]) ) {
4024
- trns.push(model[attr]);
4025
- }
4026
- });
4027
- // Merge transition handlers into current theme
4028
- trns.push({});
4029
- trns = this.set_attribute(attr, $.extend.apply($, trns.reverse()));
4030
- } else {
4031
- trns = this.get_attribute(attr, {});
4032
- }
4033
- if ( this.util.is_method(trns, event) ) {
4034
- // Disable animations if necessary
4035
- if ( !anim_on ) {
4036
- fx_temp = $.fx.off;
4037
- $.fx.off = true;
4038
- }
4039
- // Pass control to transition event
4040
- dfr = trns[event].call(this, v, $.Deferred());
4041
- }
4042
- }
4043
- if ( !this.util.is_promise(dfr) ) {
4044
- dfr = $.Deferred();
4045
- dfr.reject();
4046
- }
4047
- dfr.always(function() {
4048
- // Restore animation state
4049
- if ( null !== fx_temp ) {
4050
- $.fx.off = fx_temp;
4051
- }
4052
- });
4053
- return dfr.promise();
4054
- }
4055
- };
4056
-
4057
- View.Theme = Modeled_Component.extend(Theme);
4058
-
4059
- /**
4060
- * Template handler
4061
- * Parses and Builds layout from raw template
4062
- */
4063
- var Template = {
4064
- /* Configuration */
4065
-
4066
- _slug: 'template',
4067
- _reciprocal: true,
4068
-
4069
- _refs: {
4070
- 'theme': 'Theme'
4071
- },
4072
-
4073
- _attr_default: {
4074
- /**
4075
- * URI to layout (raw) file
4076
- * @var string
4077
- */
4078
- layout_uri: '',
4079
-
4080
- /**
4081
- * Raw layout template
4082
- * @var string
4083
- */
4084
- layout_raw: '',
4085
- /**
4086
- * Parsed layout
4087
- * Placeholders processed
4088
- * @var string
4089
- */
4090
- layout_parsed: '',
4091
- /**
4092
- * Tags in template
4093
- * Populated once template has been parsed
4094
- * @var array
4095
- */
4096
- tags: null,
4097
- /**
4098
- * Model to use for properties
4099
- * Usually reference to an object in other component
4100
- * @var obj
4101
- */
4102
- model: null
4103
- },
4104
-
4105
- /* References */
4106
-
4107
- theme: null,
4108
-
4109
- /* Methods */
4110
-
4111
- _c: function(attributes) {
4112
- this._super('', attributes);
4113
- },
4114
-
4115
- _hooks: function() {
4116
- // TODO: Refactor to event that can save retrieved tags
4117
- // (`dom_init` event called during attribute initialization so tags are not saved)
4118
- this.on('dom_init', function(ev) {
4119
- // Init tag handlers
4120
- var tags = this.get_tags(null, null, true);
4121
- var names = [];
4122
- var t = this;
4123
- $.each(tags, function(idx, tag) {
4124
- var name = tag.get_name();
4125
- if ( -1 === $.inArray(name, names) ) {
4126
- names.push(name);
4127
- tag.get_handler().trigger(ev.type, {template: t});
4128
- }
4129
- });
4130
- });
4131
- },
4132
-
4133
- get_theme: function() {
4134
- var ret = this.get_component('theme');
4135
- return ret;
4136
- },
4137
-
4138
- /* Output */
4139
-
4140
- /**
4141
- * Render output
4142
- * @param bool init (optional) Whether to initialize layout (TRUE) or render item (FALSE) (Default: FALSE)
4143
- * Events
4144
- * > render-init: Initialize template
4145
- * > render-loading: DOM elements created and item content about to be loaded
4146
- * > render-complete: Item content loaded, ready for display
4147
- */
4148
- render: function(init) {
4149
- var v = this.get_theme().get_viewer();
4150
- if ( !this.util.is_bool(init) ) {
4151
- init = false;
4152
- }
4153
- // Populate layout
4154
- if ( !init ) {
4155
- if ( !v.is_active() ) {
4156
- return false;
4157
- }
4158
- var item = v.get_item();
4159
- if ( !this.util.is_type(item, View.Content_Item) ) {
4160
- v.close();
4161
- return false;
4162
- }
4163
- // Iterate through tags and populate layout
4164
- if ( v.is_active() && this.has_tags() ) {
4165
- var loading_promise = this.trigger('render-loading');
4166
- var tpl = this;
4167
- var tags = this.get_tags(),
4168
- tag_promises = [];
4169
- // Render Tag output
4170
- $.when(item.load(), loading_promise).done(function() {
4171
- if ( !v.is_active() ) {
4172
- return false;
4173
- }
4174
- $.each(tags, function(idx, tag) {
4175
- if ( !v.is_active() ) {
4176
- return false;
4177
- }
4178
- tag_promises.push(tag.render(item).done(function(r) {
4179
- if ( !v.is_active() ) {
4180
- return false;
4181
- }
4182
- r.tag.dom_get().html(r.output);
4183
- }));
4184
- });
4185
- // Fire event when all tags rendered
4186
- if ( !v.is_active() ) {
4187
- return false;
4188
- }
4189
- $.when.apply($, tag_promises).done(function() {
4190
- tpl.trigger('render-complete');
4191
- });
4192
- });
4193
- }
4194
- } else {
4195
- // Get Layout (basic)
4196
- this.trigger('render-init', this.dom_get());
4197
- }
4198
- },
4199
-
4200
- /*-** Layout **-*/
4201
-
4202
- /**
4203
- * Retrieve layout
4204
- * @param bool parsed (optional) TRUE retrieves parsed layout, FALSE retrieves raw layout (Default: TRUE)
4205
- * @return string Layout (HTML)
4206
- */
4207
- get_layout: function(parsed) {
4208
- // Validate
4209
- if ( !this.util.is_bool(parsed) ) {
4210
- parsed = true;
4211
- }
4212
- // Determine which layout to retrieve (raw/parsed)
4213
- var l = ( parsed ) ? this.parse_layout() : this.get_attribute('layout_raw', '');
4214
- return l;
4215
- },
4216
-
4217
- /**
4218
- * Parse layout
4219
- * Converts template tags to HTML elements
4220
- * > Template tag properties saved to HTML elements for future initialization
4221
- * Returns saved layout if previously parsed
4222
- * @return string Parsed layout
4223
- */
4224
- parse_layout: function() {
4225
- // Check for previously-parsed layout
4226
- var a = 'layout_parsed';
4227
- var ret = this.get_attribute(a);
4228
- // Return cached layout immediately
4229
- if ( this.util.is_string(ret) ) {
4230
- return ret;
4231
- }
4232
- // Parse raw layout
4233
- ret = this.sanitize_layout( this.get_layout(false) );
4234
- ret = this.parse_tags(ret);
4235
- // Save parsed layout
4236
- this.set_attribute(a, ret);
4237
-
4238
- // Return parsed layout
4239
- return ret;
4240
- },
4241
-
4242
- /**
4243
- * Sanitize layout
4244
- * @param obj|string l Layout string or jQuery object
4245
- * @return obj|string Sanitized layout (Same data type that was passed to method)
4246
- */
4247
- sanitize_layout: function(l) {
4248
- // Stop processing if invalid value
4249
- if ( this.util.is_empty(l) ) {
4250
- return l;
4251
- }
4252
- // Set return type
4253
- var rtype = ( this.util.is_string(l) ) ? 'string' : null;
4254
- /* Quarantine hard-coded tags */
4255
-
4256
- // Create DOM structure from raw template
4257
- var dom = $(l);
4258
- // Find hard-coded tag nodes
4259
- var tag_temp = this.get_tag_temp();
4260
- var cls = tag_temp.get_class();
4261
- var cls_new = ['x', cls].join('_');
4262
- $(tag_temp.get_selector(), dom).each(function() {
4263
- // Replace matching class name with blocking class
4264
- $(this).removeClass(cls).addClass(cls_new);
4265
- });
4266
- // Format return value
4267
- switch ( rtype ) {
4268
- case 'string' :
4269
- dom = dom.wrap('<div />').parent().html();
4270
- l = dom;
4271
- break;
4272
- default :
4273
- l = dom;
4274
- }
4275
- return l;
4276
- },
4277
-
4278
- /*-** Tags **-*/
4279
-
4280
- /**
4281
- * Extract tags from template
4282
- * Tags are replaced with DOM element placeholders
4283
- * Extracted tags are saved as element attribute values (for future use)
4284
- * @param string l Raw layout to parse
4285
- * @return string Parsed layout
4286
- */
4287
- parse_tags: function(l) {
4288
- // Validate
4289
- if ( !this.util.is_string(l) ) {
4290
- return '';
4291
- }
4292
- // Parse tags in layout
4293
- // Tag regex
4294
- var re = /\{{2}\s*(\w.*?)\s*\}{2}/gim;
4295
- // Tag match results
4296
- var match;
4297
- // Iterate through template and find tags
4298
- while ( match = re.exec(l) ) {
4299
- // Replace tag in layout with DOM container
4300
- l = l.substring(0, match.index) + this.get_tag_container(match[1]) + l.substring(match.index + match[0].length);
4301
- }
4302
- return l;
4303
- },
4304
-
4305
- /**
4306
- * Create DOM element container for tag
4307
- * @param string Tag ID (will be prefixed)
4308
- * @return string DOM element
4309
- */
4310
- get_tag_container: function(tag) {
4311
- // Build element
4312
- var attr = this.get_tag_attribute();
4313
- return this.util.format('<span %s="%s"></span>', attr, encodeURI(tag));
4314
- },
4315
-
4316
- get_tag_attribute: function() {
4317
- return this.get_tag_temp().dom_get_attribute();
4318
- },
4319
-
4320
- /**
4321
- * Retrieve Template_Tag instance at specified index
4322
- * @param int idx (optional) Index to retrieve tag from
4323
- * @return Template_Tag Tag instance
4324
- */
4325
- get_tag: function(idx) {
4326
- var ret = null;
4327
- if ( this.has_tags() ) {
4328
- var tags = this.get_tags();
4329
- if ( !this.util.is_int(idx) || 0 > idx || idx >= tags.length ) {
4330
- idx = 0;
4331
- }
4332
- ret = tags[idx];
4333
- }
4334
- return ret;
4335
- },
4336
-
4337
- /**
4338
- * Retrieve tags from template
4339
- * Subset of tags may be retrieved based on parameter values
4340
- * Template is parsed if tags not set
4341
- * @param string name (optional) Tag type to retrieve instances of
4342
- * @param string prop (optional) Tag property to retrieve instances of
4343
- * @param bool isolate (optional) Do not save retrieved tags, only fetch and return (Default: TRUE)
4344
- * @return array Template_Tag instances
4345
- */
4346
- get_tags: function(name, prop, isolate) {
4347
- // Validate
4348
- if ( !this.util.is_bool(isolate) ) {
4349
- isolate = false;
4350
- }
4351
- // Setup
4352
- var a = 'tags';
4353
- var tags = this.get_attribute(a);
4354
- // Initialize tags
4355
- if ( !this.util.is_array(tags) ) {
4356
- tags = [];
4357
- // Retrieve layout DOM tree
4358
- var d = this.dom_get();
4359
- // Select tag nodes
4360
- var attr = this.get_tag_attribute();
4361
- var nodes = $(d).find('[' + attr + ']');
4362
- // Build tag instances from nodes
4363
- $(nodes).each(function() {
4364
- // Get tag placeholder
4365
- var el = $(this);
4366
- var tag = new View.Template_Tag(decodeURI(el.attr(attr)));
4367
- // Populate valid tags
4368
- if ( tag.has_handler() ) {
4369
- // Add tag to array
4370
- tags.push(tag);
4371
- if ( !isolate ) {
4372
- // Connect tag to DOM node
4373
- tag.dom_set(el);
4374
- // Set classes
4375
- el.addClass(tag.get_classes(' '));
4376
- }
4377
- }
4378
- // Clear data attribute
4379
- if ( !isolate ) {
4380
- el.removeAttr(attr);
4381
- }
4382
- });
4383
- if ( !isolate ) {
4384
- // Save tags
4385
- this.set_attribute(a, tags, false);
4386
- }
4387
- }
4388
- // Filter tags by parameters
4389
- if ( !this.util.is_empty(tags) && this.util.is_string(name) ) {
4390
- // Normalize
4391
- if ( !this.util.is_string(prop) ) {
4392
- prop = false;
4393
- }
4394
- var tags_filtered = [];
4395
- var tc = null;
4396
- for ( var x = 0; x < tags.length; x++ ) {
4397
- tc = tags[x];
4398
- if ( name === tc.get_name() ) {
4399
- // Check tag property
4400
- if ( !prop || prop === tc.get_prop() ) {
4401
- tags_filtered.push(tc);
4402
- }
4403
- }
4404
- }
4405
- tags = tags_filtered;
4406
- }
4407
- return ( this.util.is_array(tags, false) ) ? tags : [];
4408
- },
4409
-
4410
- /**
4411
- * Check if template contains tags
4412
- * @return bool TRUE if tags exist, FALSE otherwise
4413
- */
4414
- has_tags: function() {
4415
- return ( this.get_tags().length > 0 ) ? true : false;
4416
- },
4417
-
4418
- /**
4419
- * Retrieve temporary tag instance
4420
- * @return Template_Tag Temporary tag
4421
- */
4422
- get_tag_temp: function() {
4423
- return this.get_controller().get_component_temp(View.Template_Tag);
4424
- },
4425
-
4426
- /**
4427
- * Retrieve Template tag CSS selector
4428
- * @uses Template.get_tag_temp() to retrieve temporary tag instance
4429
- * @uses Template_Tag.get_selector() to retrieve selector
4430
- * @param name string Tag name
4431
- * @param prop string Tag Property
4432
- * @return string Template Tag CSS selector
4433
- */
4434
- get_tag_selector: function(name, prop) {
4435
- if ( !this.util.is_string(name) ) {
4436
- name = '';
4437
- }
4438
- if ( !this.util.is_string(prop) ) {
4439
- prop = '';
4440
- }
4441
- var tag = this.get_tag_temp();
4442
- tag.set_attribute('name', name);
4443
- tag.set_attribute('prop', prop);
4444
- return tag.get_selector('full');
4445
- },
4446
-
4447
- /*-** DOM **-*/
4448
-
4449
- /**
4450
- * Custom DOM initialization
4451
- */
4452
- dom_init: function() {
4453
- // Create DOM object from parsed layout
4454
- this.dom_set(this.get_layout());
4455
- this.trigger('dom_init');
4456
- },
4457
-
4458
- /**
4459
- * Retrieve DOM element(s) for specified tag
4460
- * @param string tag Name of tag to retrieve
4461
- * @param string prop (optional) Specific tag property to retrieve
4462
- * @return array DOM elements for tag
4463
- */
4464
- dom_get_tag: function(tag, prop) {
4465
- var ret = $();
4466
- var tags = this.get_tags(tag, prop);
4467
- if ( tags.length ) {
4468
- // Build selector
4469
- var level = null;
4470
- if ( this.util.is_string(tag) ) {
4471
- level = ( this.util.is_string(prop) ) ? 'full' : 'tag';
4472
- }
4473
- var sel = '.' + tags[0].get_class(level);
4474
- ret = this.dom_get().find(sel);
4475
- }
4476
- return ret;
4477
- }
4478
- };
4479
-
4480
- View.Template = Modeled_Component.extend(Template);
4481
-
4482
- /**
4483
- * Template tag
4484
- */
4485
- var Template_Tag = {
4486
- /* Configuration */
4487
- _slug: 'template_tag',
4488
- _reciprocal: true,
4489
- /* Properties */
4490
- _attr_default: {
4491
- name: null,
4492
- prop: null,
4493
- match: null
4494
- },
4495
- /**
4496
- * Tag Handlers
4497
- * Collection of Template_Tag_Handler instances
4498
- * @var obj
4499
- */
4500
- handlers: {},
4501
- /* Methods */
4502
-
4503
- /**
4504
- * Constructor
4505
- * @param
4506
- */
4507
- _c: function(tag_match) {
4508
- this.parse(tag_match);
4509
- },
4510
-
4511
- /**
4512
- * Set instance attributes using tag extracted from template
4513
- * @param string tag_match Extracted tag match
4514
- */
4515
- parse: function(tag_match) {
4516
- // Return default value for invalid instances
4517
- if ( !this.util.is_string(tag_match) ) {
4518
- return false;
4519
- }
4520
- // Parse instance options
4521
- var parts = tag_match.split('|'),
4522
- part;
4523
- if ( !parts.length ) {
4524
- return null;
4525
- }
4526
- var attrs = {
4527
- name: null,
4528
- prop: null,
4529
- match: tag_match
4530
- };
4531
- // Get tag ID
4532
- attrs.name = parts[0];
4533
- // Get main property
4534
- if ( attrs.name.indexOf('.') !== -1 ) {
4535
- attrs.name = attrs.name.split('.', 2);
4536
- attrs.prop = attrs.name[1];
4537
- attrs.name = attrs.name[0];
4538
- }
4539
- // Get other attributes
4540
- for ( var x = 1; x < parts.length; x++ ) {
4541
- part = parts[x].split(':', 1);
4542
- if ( part.length > 1 && !( part[0] in attrs ) ) {
4543
- // Add key/value pair to attributes
4544
- attrs[part[0]] = part[1];
4545
- }
4546
- }
4547
- // Save to instance
4548
- this.set_attributes(attrs, true);
4549
- },
4550
-
4551
- /**
4552
- * Render tag output
4553
- * @param Content_Item item
4554
- * @return obj jQuery.Promise object that is resolved when tag is rendered
4555
- * Parameters passed to callbacks
4556
- * > tag obj Current tag instance
4557
- * > output string Tag output
4558
- */
4559
- render: function(item) {
4560
- var tag = this;
4561
- return tag.get_handler().render(item, tag).pipe(function(output) {
4562
- return {'tag': tag, 'output': output};
4563
- });
4564
- },
4565
-
4566
- /**
4567
- * Retrieve tag name
4568
- * @return string Tag name (DEFAULT: NULL)
4569
- */
4570
- get_name: function() {
4571
- return this.get_attribute('name');
4572
- },
4573
-
4574
- /**
4575
- * Retrieve tag property
4576
- */
4577
- get_prop: function() {
4578
- return this.get_attribute('prop');
4579
- },
4580
-
4581
- /**
4582
- * Retrieve tag handler
4583
- * @return Template_Tag_Handler Handler instance (Empty instance if handler does not exist)
4584
- */
4585
- get_handler: function() {
4586
- return ( this.has_handler() ) ? this.handlers[this.get_name()] : new View.Template_Tag_Handler('');
4587
- },
4588
-
4589
- /**
4590
- * Check if handler exists for tag
4591
- * @return bool TRUE if handler exists, FALSE otherwise
4592
- */
4593
- has_handler: function() {
4594
- return ( this.get_name() in this.handlers );
4595
- },
4596
-
4597
- /**
4598
- * Generate class names for DOM node
4599
- * @param string rtype (optional) Return data type
4600
- * > Default: array
4601
- * > If string supplied: Joined classes delimited by parameter
4602
- * @uses get_class() to generate class names
4603
- * @uses Array.join() to convert class names array to string
4604
- * @return array Class names
4605
- */
4606
- get_classes: function(rtype) {
4607
- // Build array of class names
4608
- var cls = [
4609
- // General tag class
4610
- this.get_class(),
4611
- // Tag name
4612
- this.get_class('tag'),
4613
- // Tag name + property
4614
- this.get_class('full')
4615
- ];
4616
- // Convert class names array to string
4617
- if ( this.util.is_string(rtype) ) {
4618
- cls = cls.join(rtype);
4619
- }
4620
- // Return class names
4621
- return cls;
4622
- },
4623
-
4624
- /**
4625
- * Generate DOM-compatible class name based with varied levels of specificity
4626
- * @param int level (optional) Class name specificity
4627
- * > Default: General tag class (common to all tag elements)
4628
- * > tag: Tag Name
4629
- * > full: Tag Name + Property
4630
- * @return string Class name
4631
- */
4632
- get_class: function(level) {
4633
- var cls = '';
4634
- // Build base
4635
- switch ( level ) {
4636
- case 'tag' :
4637
- // Tag name
4638
- cls = this.get_name();
4639
- break;
4640
- case 'full' :
4641
- // Tag name + property
4642
- var parts = [this.get_name(), this.get_prop()];
4643
- var a = [];
4644
- var i;
4645
- for ( i = 0; i < parts.length; i++ ) {
4646
- if ( this.util.is_string(parts[i]) ) {
4647
- a.push(parts[i]);
4648
- }
4649
- }
4650
- cls = a.join('_');
4651
- break;
4652
- }
4653
- // Format & return
4654
- return ( !this.util.is_string(cls) ) ? this.get_ns() : this.add_ns(cls);
4655
- },
4656
-
4657
- /**
4658
- * Generate tag selector based on specified class name level
4659
- * @param string level (optional) Class name specificity (@see get_class() for parameter values)
4660
- * @return string Tag selector
4661
- */
4662
- get_selector: function(level) {
4663
- // Get base
4664
- var ret = this.get_class(level);
4665
- // Format
4666
- if ( this.util.is_string(ret) ) {
4667
- ret = '.' + ret;
4668
- } else {
4669
- ret = '';
4670
- }
4671
- return ret;
4672
- }
4673
- };
4674
-
4675
- View.Template_Tag = Component.extend(Template_Tag);
4676
-
4677
- /**
4678
- * Theme tag handler
4679
- */
4680
- var Template_Tag_Handler = {
4681
- /* Configuration */
4682
- _slug: 'template_tag_handler',
4683
- /* Properties */
4684
- _attr_default: {
4685
- supports_modifiers: false,
4686
- dynamic: false,
4687
- props: {}
4688
- },
4689
-
4690
- /* Methods */
4691
-
4692
- /**
4693
- * Render tag output
4694
- * @param Content_Item item Item currently being displayed
4695
- * @param Template_Tag Tag instance (from template)
4696
- * @return obj jQuery.Promise linked to rendering process
4697
- */
4698
- render: function(item, instance) {
4699
- var dfr = $.Deferred();
4700
- // Pass to attribute method
4701
- this.call_attribute('render', item, instance, dfr);
4702
- // Return promise
4703
- return dfr.promise();
4704
- },
4705
-
4706
- add_prop: function(prop, fn) {
4707
- // Get attribute
4708
- var a = 'props';
4709
- var props = this.get_attribute(a);
4710
- // Validate
4711
- if ( !this.util.is_string(prop) || !this.util.is_func(fn) ) {
4712
- return false;
4713
- }
4714
- if ( !this.util.is_obj(props, false) ) {
4715
- props = {};
4716
- }
4717
- // Add property
4718
- props[prop] = fn;
4719
- // Save attribute
4720
- this.set_attribute(a, props);
4721
- },
4722
-
4723
- handle_prop: function(prop, item, instance) {
4724
- // Locate property
4725
- var props = this.get_attribute('props');
4726
- var out = '';
4727
- if ( this.util.is_obj(props) && ( prop in props ) && this.util.is_func(props[prop]) ) {
4728
- out = props[prop].call(this, item, instance);
4729
- } else {
4730
- out = item.get_viewer().get_label(prop);
4731
- }
4732
- return out;
4733
- }
4734
- };
4735
-
4736
- View.Template_Tag_Handler = Component.extend(Template_Tag_Handler);
4737
- /* Update References */
4738
-
4739
- // Attach to global object
4740
- View = SLB.attach('View', View);
4741
  })(jQuery);}
1
+ /**
2
+ * View (Lightbox) functionality
3
+ * @package Simple Lightbox
4
+ * @subpackage View
5
+ * @author Archetyped
6
+ */
7
+ /* global SLB */
8
+ if ( !!window.SLB && !!SLB.attach ) { (function ($) {
9
+
10
+ /*-** Controller **-*/
11
+
12
+ var View = {
13
+
14
+ /* Properties */
15
+
16
+ /**
17
+ * Media item properties
18
+ * > Item key: Link URI
19
+ * > Base properties
20
+ * > id: WP Attachment ID
21
+ * > source: Source URI
22
+ * > title: Media title (generally WP attachment title)
23
+ * > desc: Media description (generally WP Attachment content)
24
+ * > type: Asset type (attachment, image, etc.)
25
+ */
26
+ assets: {},
27
+
28
+ /**
29
+ * Component types that can have default instances
30
+ * @var array
31
+ */
32
+ component_defaults: [],
33
+
34
+ /**
35
+ * Collection of jQuery.Deferred instances added during loading routine
36
+ * @var array
37
+ */
38
+ loading: [],
39
+
40
+ /**
41
+ * Cache
42
+ * @var object
43
+ */
44
+ cache: {},
45
+
46
+ /**
47
+ * Temporary component instances
48
+ * For use by controller when no component instance is available
49
+ * > Key: Component slug
50
+ * > Value: Component instance
51
+ */
52
+ component_temps: {},
53
+
54
+ /* Options */
55
+ options: {},
56
+
57
+ /* Methods */
58
+
59
+ /* Init */
60
+
61
+ /**
62
+ * Instance initialization
63
+ */
64
+ _init: function() {
65
+ this._super();
66
+ // Component References
67
+ this.init_refs();
68
+ // Components
69
+ this.init_components();
70
+ },
71
+
72
+ /**
73
+ * Update component references in component definitions
74
+ */
75
+ init_refs: function() {
76
+ var r;
77
+ var ref;
78
+ var prop;
79
+ for ( prop in this ) {
80
+ prop = this[prop];
81
+ // Process only components
82
+ if ( !this.is_component(prop) ) {
83
+ continue;
84
+ }
85
+ // Update component references
86
+ if ( !this.util.is_empty(prop.prototype._refs) ) {
87
+ for ( r in prop.prototype._refs ) {
88
+ ref = prop.prototype._refs[r];
89
+ if ( this.util.is_string(ref) && ref in this ) {
90
+ ref = prop.prototype._refs[r] = this[ref];
91
+ }
92
+ if ( !this.util.is_class(ref) ) {
93
+ delete prop.prototype_refs[r];
94
+ }
95
+ }
96
+ }
97
+ }
98
+ },
99
+
100
+ /**
101
+ * Initialize Components
102
+ */
103
+ init_components: function() {
104
+ this.component_defaults = [
105
+ this.Viewer
106
+ ];
107
+ },
108
+
109
+ /**
110
+ * Client Initialization
111
+ * @param obj options Global options
112
+ */
113
+ init: function(options) {
114
+ var t = this;
115
+ // Defer initialization until all components loaded
116
+ $.when.apply($, this.loading).always(function() {
117
+ // Set options
118
+ $.extend(true, t.options, options);
119
+
120
+ /* Event handlers */
121
+
122
+ // History
123
+ $(window).on('popstate', function(e) {
124
+ var state = e.originalEvent.state;
125
+ if ( t.util.in_obj(state, ['item', 'viewer']) ) {
126
+ var v = t.get_viewer(state.viewer);
127
+ v.history_handle(e);
128
+ return e.preventDefault();
129
+ }
130
+ });
131
+
132
+ /* Set defaults */
133
+
134
+ // Items
135
+ t.init_items();
136
+ });
137
+ },
138
+
139
+ /* Components */
140
+
141
+ /**
142
+ * Check if default component instance can be created
143
+ * @uses View.component_defaults
144
+ * @param func type Component type to check
145
+ * @return bool TRUE if default component instance creation is allowed
146
+ */
147
+ can_make_default_component: function(type) {
148
+ return ( -1 !== $.inArray(type, this.component_defaults) );
149
+ },
150
+
151
+ /**
152
+ * Check if object is valid component class
153
+ * @param func comp Component to check
154
+ * @return bool TRUE if object is valid component class
155
+ */
156
+ is_component: function(comp) {
157
+ return ( this.util.is_class(comp, this.Component) );
158
+ },
159
+
160
+ /**
161
+ * Retrieve collection of components of specified type
162
+ * @param func type Component type
163
+ * @return object Component collection (Default: Empty object)
164
+ */
165
+ get_components: function(type) {
166
+ var ret = {};
167
+ if ( this.is_component(type) ) {
168
+ // Determine collection based on component slug
169
+ var coll = type.prototype._slug + 's';
170
+ // Create default collection
171
+ if ( ! ( coll in this.cache ) ) {
172
+ this.cache[coll] = {};
173
+ }
174
+ ret = this.cache[coll];
175
+ }
176
+ return ret;
177
+ },
178
+
179
+ /**
180
+ * Retrieve component from specific collection
181
+ * @param function type Component type
182
+ * @param string id Component ID
183
+ * @return object|null Component reference (NULL if invalid)
184
+ */
185
+ get_component: function(type, id) {
186
+ var ret = null;
187
+ // Validate parameters
188
+ if ( !this.util.is_func(type) ) {
189
+ return ret;
190
+ }
191
+ // Sanitize id
192
+ if ( !this.util.is_string(id) ) {
193
+ id = null;
194
+ }
195
+
196
+ // Get component from collection
197
+ var coll = this.get_components(type);
198
+ if ( this.util.is_obj(coll) ) {
199
+ var tid = ( this.util.is_string(id) ) ? id : this.util.add_prefix('default');
200
+ if ( tid in coll ) {
201
+ ret = coll[tid];
202
+ }
203
+ }
204
+
205
+ // Default: Create default component
206
+ if ( this.util.is_empty(ret) ) {
207
+ if ( this.util.is_string(id) || this.can_make_default_component(type) ) {
208
+ ret = this.add_component(type, id);
209
+ }
210
+ }
211
+ // Return component
212
+ return ret;
213
+ },
214
+
215
+ /**
216
+ * Create new component instance and save to appropriate collection
217
+ * @param function type Component type to create
218
+ * @param string id ID of component
219
+ * @param object options Component initialization options (Default options used if default component is allowed)
220
+ * @return object|null New component (NULL if invalid)
221
+ */
222
+ add_component: function(type, id, options) {
223
+ // Validate type
224
+ if ( !this.util.is_func(type) ) {
225
+ return false;
226
+ }
227
+ // Validate request
228
+ if ( this.util.is_empty(id) && !this.can_make_default_component(type) ) {
229
+ return false;
230
+ }
231
+ // Defaults
232
+ var ret = null;
233
+ if ( this.util.is_empty(id) ) {
234
+ id = this.util.add_prefix('default');
235
+ }
236
+ if ( !this.util.is_obj(options) ) {
237
+ options = {};
238
+ }
239
+ // Check if specialized method exists for component type
240
+ var m = ( 'component' !== type.prototype._slug ) ? 'add_' + type.prototype._slug : null;
241
+ if ( !this.util.is_empty(m) && ( m in this ) && this.util.is_func(this[m]) ) {
242
+ ret = this[m](id, options);
243
+ }
244
+ // Default process
245
+ else {
246
+ ret = new type(id, options);
247
+ }
248
+
249
+ // Add new component to collection
250
+ if ( this.util.is_type(ret, type) ) {
251
+ // Get collection
252
+ var coll = this.get_components(type);
253
+ // Add to collection
254
+ switch ( $.type(coll) ) {
255
+ case 'object' :
256
+ coll[id] = ret;
257
+ break;
258
+ case 'array' :
259
+ coll.push(ret);
260
+ break;
261
+ }
262
+ } else {
263
+ ret = null;
264
+ }
265
+ // Return new component
266
+ return ret;
267
+ },
268
+
269
+ /**
270
+ * Create new temporary component instance
271
+ * @param function type Component type
272
+ * @return New temporary instance
273
+ */
274
+ add_component_temp: function(type) {
275
+ var ret = null;
276
+ if ( this.is_component(type) ) {
277
+ // Create new instance
278
+ ret = new type('');
279
+ // Save to collection
280
+ this.component_temps[ret._slug] = ret;
281
+ }
282
+ return ret;
283
+ },
284
+
285
+ /**
286
+ * Retrieve temporary component instance
287
+ * Creates new temp component instance for type if not previously created
288
+ * @param function type Component type to retrieve temp instance for
289
+ * @return obj Temporary component instance
290
+ */
291
+ get_component_temp: function(type) {
292
+ return ( this.has_component_temp(type) ) ? this.component_temps[type.prototype._slug] : this.add_component_temp(type);
293
+ },
294
+
295
+ /**
296
+ * Check if temporary component instance exists
297
+ * @param function type Component type to check for
298
+ * @return bool TRUE if temp instance exists, FALSE otherwise
299
+ */
300
+ has_component_temp: function(type) {
301
+ return ( this.is_component(type) && ( type.prototype._slug in this.component_temps ) ) ? true : false;
302
+ },
303
+
304
+ /* Properties */
305
+
306
+ /**
307
+ * Retrieve specified options
308
+ * @param array opts Array of option names
309
+ * @return object Specified options (Default: empty object)
310
+ */
311
+ get_options: function(opts) {
312
+ var ret = {};
313
+ // Validate
314
+ if ( this.util.is_string(opts) ) {
315
+ opts = [opts];
316
+ }
317
+ if ( !this.util.is_array(opts) ) {
318
+ return ret;
319
+ }
320
+ // Get specified options
321
+ for ( var x = 0; x < opts.length; x++ ) {
322
+ // Skip if option not set
323
+ if ( !( opts[x] in this.options ) ) {
324
+ continue;
325
+ }
326
+ ret[ opts[x] ] = this.options[ opts[x] ];
327
+ }
328
+ return ret;
329
+ },
330
+
331
+ /**
332
+ * Retrieve option
333
+ * @uses View.options
334
+ * @param string opt Option to retrieve
335
+ * @param mixed def (optional) Default value if option does not exist (Default: NULL)
336
+ * @return mixed Option value
337
+ */
338
+ get_option: function(opt, def) {
339
+ var ret = this.get_options(opt);
340
+ if ( this.util.is_obj(ret) && ( opt in ret ) ) {
341
+ ret = ret[opt];
342
+ } else {
343
+ ret = ( this.util.is_set(def) ) ? def : null;
344
+ }
345
+ return ret;
346
+ },
347
+
348
+ /* Viewers */
349
+
350
+ /**
351
+ * Add viewer instance to collection
352
+ * @param string id Viewer ID
353
+ * @param obj options Viewer options
354
+ * @return Viewer New viewer instance
355
+ */
356
+ add_viewer: function(id, options) {
357
+ // Create viewer
358
+ var v = new this.Viewer(id, options);
359
+ // Save viewer
360
+ this.get_viewers()[v.get_id()] = v;
361
+ // Return viewer
362
+ return v;
363
+ },
364
+
365
+ /**
366
+ * Retrieve all viewer instances
367
+ * @return obj Viewer instances
368
+ */
369
+ get_viewers: function() {
370
+ return this.get_components(this.Viewer);
371
+ },
372
+
373
+ /**
374
+ * Check if viewer exists
375
+ * @param string v Viewer ID
376
+ * @return bool TRUE if viewer exists, FALSE otherwise
377
+ */
378
+ has_viewer: function(v) {
379
+ return ( this.util.is_string(v) && v in this.get_viewers() ) ? true : false;
380
+ },
381
+
382
+ /**
383
+ * Retrieve Viewer instance
384
+ * Default viewer retrieved if specified viewer does not exist
385
+ * > Default viewer created if necessary
386
+ * @param string v Viewer ID to retrieve
387
+ * @return Viewer Viewer instance
388
+ */
389
+ get_viewer: function(v) {
390
+ // Retrieve default viewer if specified viewer not set
391
+ if ( !this.has_viewer(v) ) {
392
+ v = this.util.add_prefix('default');
393
+ // Create default viewer if necessary
394
+ if ( !this.has_viewer(v) ) {
395
+ v = this.add_viewer(v);
396
+ v = v.get_id();
397
+ }
398
+ }
399
+ return this.get_viewers()[v];
400
+ },
401
+
402
+ /* Items */
403
+
404
+ /**
405
+ * Set event handlers
406
+ */
407
+ init_items: function() {
408
+ // Define handler
409
+ var t = this;
410
+ var handler = function() {
411
+ var ret = t.show_item(this);
412
+ if ( !t.util.is_bool(ret) ) {
413
+ ret = true;
414
+ }
415
+ return !ret;
416
+ };
417
+
418
+ // Get activated links
419
+ var sel = this.util.format('a[href][%s="%s"]', this.util.get_attribute('active'), 1);
420
+ // Add event handler
421
+ $(document).on('click', sel, null, handler);
422
+ },
423
+
424
+ /**
425
+ * Retrieve cached items
426
+ * @return obj Items collection
427
+ */
428
+ get_items: function() {
429
+ return this.get_components(this.Content_Item);
430
+ },
431
+
432
+ /**
433
+ * Retrieve specific Content_Item instance
434
+ * @param mixed Item reference
435
+ * > Content_Item: Item instance (returned immediately)
436
+ * > DOM element: DOM element to get item for
437
+ * > int: Index of cached item
438
+ * @return Content_Item Item instance for DOM node
439
+ */
440
+ get_item: function(ref) {
441
+ // Evaluate reference type
442
+
443
+ // Content Item instance
444
+ if ( this.util.is_type(ref, this.Content_Item) ) {
445
+ return ref;
446
+ }
447
+ // Retrieve item instance
448
+ var item = null;
449
+
450
+ // DOM element
451
+ if ( this.util.in_obj(ref, 'nodeType') ) {
452
+ // Check if item instance attached to element
453
+ var key = this.get_component_temp(this.Content_Item).get_data_key();
454
+ item = $(ref).data(key);
455
+ }
456
+ // Cached item (index)
457
+ else if ( this.util.is_string(ref, false) ) {
458
+ var items = this.get_items();
459
+ if ( ref in items ) {
460
+ item = items[ref];
461
+ }
462
+ }
463
+ // Create default item instance
464
+ if ( !this.util.is_instance(item, this.Content_Item) ) {
465
+ item = this.add_item(ref);
466
+ }
467
+ return item;
468
+ },
469
+
470
+ /**
471
+ * Create new item instance
472
+ * @param obj el DOM element representing item
473
+ * @return Content_Item New item instance
474
+ */
475
+ add_item: function(el) {
476
+ return ( new this.Content_Item(el) );
477
+ },
478
+
479
+ /**
480
+ * Display item in viewer
481
+ * @param obj el DOM element representing item
482
+ * @return bool Display result (TRUE if item displayed without issues)
483
+ */
484
+ show_item: function(el) {
485
+ return this.get_item(el).show();
486
+ },
487
+
488
+ /**
489
+ * Cache item instance
490
+ * @uses View.get_items() to retrieve item cache
491
+ * @param Content_Item item Item to cache
492
+ * @return Content_item Item instance
493
+ */
494
+ save_item: function(item) {
495
+ if ( !this.util.is_instance(item, this.Content_Item) ) {
496
+ return item;
497
+ }
498
+ // Save item
499
+ this.get_items()[item.get_id()] = item;
500
+ // Return item instance
501
+ return item;
502
+ },
503
+
504
+ /* Content Handler */
505
+
506
+ /**
507
+ * Retrieve content handlers
508
+ * @return object Content handlers
509
+ */
510
+ get_content_handlers: function() {
511
+ return this.get_components(this.Content_Handler);
512
+ },
513
+
514
+ /**
515
+ * Find matching content handler for item
516
+ * @param Content_Item|string item Item to find handler for (or ID of Handler)
517
+ * @return Content_Handler|null Matching content handler (NULL if no matching handler found)
518
+ */
519
+ get_content_handler: function(item) {
520
+ // Determine handler to retrieve
521
+ var type = ( this.util.is_instance(item, this.Content_Item) ) ? item.get_attribute('type', '') : item.toString();
522
+ // Retrieve handler
523
+ var types = this.get_content_handlers();
524
+ return ( type in types ) ? types[type] : null;
525
+ },
526
+
527
+ /**
528
+ * Add/Update Content Handler
529
+ * @param string id Handler ID
530
+ * @param obj attr Handler attributes
531
+ * @return obj|null Handler instance (NULL on failure)
532
+ */
533
+ extend_content_handler: function(id, attr) {
534
+ var hdl = null;
535
+ if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) {
536
+ return hdl;
537
+ }
538
+ hdl = this.get_content_handler(id);
539
+ // Add new content handler
540
+ if ( null === hdl ) {
541
+ var hdls = this.get_content_handlers();
542
+ hdls[id] = hdl = new this.Content_Handler(id, attr);
543
+ }
544
+ // Update existing handler
545
+ else {
546
+ hdl.set_attributes(attr);
547
+ }
548
+ // Load styles
549
+ if ( this.util.in_obj(attr, 'styles') ) {
550
+ this.load_styles(attr.styles);
551
+ }
552
+ return hdl;
553
+ },
554
+
555
+ /* Group */
556
+
557
+ /**
558
+ * Add new group
559
+ * @param string g Group ID
560
+ * > If group with same ID already set, new group replaces existing one
561
+ * @param object attrs (optional) Group attributes
562
+ * @return Group New group
563
+ */
564
+ add_group: function(g, attrs) {
565
+ // Create new group
566
+ g = new this.Group(g, attrs);
567
+ // Cache group
568
+ this.get_groups()[g.get_id()] = g;
569
+
570
+ return g;
571
+ },
572
+
573
+ /**
574
+ * Retrieve groups
575
+ * @uses groups property
576
+ * @return object Registered groups
577
+ */
578
+ get_groups: function() {
579
+ return this.get_components(this.Group);
580
+ },
581
+
582
+ /**
583
+ * Retrieve specified group
584
+ * New group created if not yet set
585
+ * @uses View.has_group()
586
+ * @uses View.add_group()
587
+ * @uses View.get_groups()
588
+ * @param string g Group ID
589
+ * @return Group Group instance
590
+ */
591
+ get_group: function(g) {
592
+ return ( !this.has_group(g) ) ? this.add_group(g) : this.get_groups()[g];
593
+ },
594
+
595
+ /**
596
+ * Checks if group is registered
597
+ * @uses get_groups() to retrieve registered groups
598
+ * @return bool TRUE if group exists, FALSE otherwise
599
+ */
600
+ has_group: function(g) {
601
+ return ( this.util.is_string(g) && ( g in this.get_groups() ) );
602
+ },
603
+
604
+ /* Theme */
605
+
606
+ /**
607
+ * Add/Update theme
608
+ * @param string name Theme name
609
+ * @param obj attr (optional) Theme options
610
+ * @return obj|bool Theme model
611
+ */
612
+ extend_theme: function(id, attr) {
613
+ // Validate
614
+ if ( !this.util.is_string(id) ) {
615
+ return false;
616
+ }
617
+ var dfr = $.Deferred();
618
+ this.loading.push(dfr);
619
+
620
+ // Get model if it already exists
621
+ var model = this.get_theme_model(id);
622
+
623
+ // Create default attributes for new theme
624
+ if ( this.util.is_empty(model) ) {
625
+ // Save default model
626
+ model = this.save_theme_model( {'parent': null, 'id': id} );
627
+ }
628
+
629
+ // Add custom attributes
630
+ if ( this.util.is_obj(attr) ) {
631
+ // Sanitize
632
+ if ( 'id' in attr ) {
633
+ delete(attr['id']);
634
+ }
635
+ $.extend(model, attr);
636
+ }
637
+
638
+ // Load styles
639
+ if ( this.util.in_obj(attr, 'styles') ) {
640
+ this.load_styles(attr.styles);
641
+ }
642
+
643
+ // Link parent model
644
+ if ( !this.util.is_obj(model.parent) ) {
645
+ model.parent = this.get_theme_model(model.parent);
646
+ }
647
+
648
+ // Complete loading when all components loaded
649
+ dfr.resolve();
650
+ return model;
651
+ },
652
+
653
+ /**
654
+ * Retrieve theme models
655
+ * @return obj Theme models
656
+ */
657
+ get_theme_models: function() {
658
+ // Retrieve matching theme model
659
+ return this.Theme.prototype._models;
660
+ },
661
+
662
+ /**
663
+ * Retrieve theme model
664
+ * @param string id Theme to retrieve
665
+ * @return obj Theme model (Default: empty object)
666
+ */
667
+ get_theme_model: function(id) {
668
+ var ms = this.get_theme_models();
669
+ return ( this.util.in_obj(ms, id) ) ? ms[id] : {};
670
+ },
671
+
672
+ /**
673
+ * Save theme model
674
+ * @uses View.get_theme_models() to retrieve Theme model collection
675
+ * @param obj Theme model to save
676
+ * @return obj Saved model
677
+ */
678
+ save_theme_model: function(model) {
679
+ if ( this.util.in_obj(model, 'id') && this.util.is_string(model.id) ) {
680
+ // Save model
681
+ this.get_theme_models()[model.id] = model;
682
+ }
683
+ return model;
684
+ },
685
+
686
+ /**
687
+ * Add/Update Template Tag Handler
688
+ * @param string id Handler ID
689
+ * @param obj attr Handler attributes
690
+ * @return obj|bool Handler instance (FALSE on failure)
691
+ */
692
+ extend_template_tag_handler: function(id, attr) {
693
+ if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) {
694
+ return false;
695
+ }
696
+ var hdl;
697
+ var hdls = this.get_template_tag_handlers();
698
+ if ( this.util.in_obj(hdls, id) ) {
699
+ // Update existing handler
700
+ hdl = hdls[id];
701
+ hdl.set_attributes(attr);
702
+ } else {
703
+ // Add new content handler
704
+ hdl = new this.Template_Tag_Handler(id, attr);
705
+ hdls[hdl.get_id()] = hdl;
706
+ }
707
+ // Load styles
708
+ if ( this.util.in_obj(attr, 'styles') ) {
709
+ this.load_styles(attr.styles);
710
+ }
711
+ // Set hooks
712
+ if ( this.util.in_obj(attr, '_hooks') ) {
713
+ attr._hooks.call(hdl);
714
+ }
715
+ return hdl;
716
+ },
717
+
718
+ /**
719
+ * Retrieve Template Tag Handler collection
720
+ * @return obj Template_Tag_Handler objects
721
+ */
722
+ get_template_tag_handlers: function() {
723
+ return this.Template_Tag.prototype.handlers;
724
+ },
725
+
726
+ /**
727
+ * Retrieve template tag handler
728
+ * @param string id ID of tag handler to retrieve
729
+ * @return Template_Tag_Handler|null Tag Handler instance (NULL for invalid ID)
730
+ */
731
+ get_template_tag_handler: function(id) {
732
+ var handlers = this.get_template_tag_handlers();
733
+ // Retrieve existing handler or return new handler
734
+ return ( this.util.in_obj(handlers, id) ) ? handlers[id] : null;
735
+ },
736
+
737
+ /**
738
+ * Load styles
739
+ * @param array styles Styles to load
740
+ */
741
+ load_styles: function(styles) {
742
+ if ( this.util.is_array(styles) ) {
743
+ var out = [];
744
+ var style;
745
+ for ( var x = 0; x < styles.length; x++ ) {
746
+ style = styles[x];
747
+ if ( !this.util.in_obj(style, 'uri') || !this.util.is_string(style.uri) ) {
748
+ continue;
749
+ }
750
+ out.push('<link rel="stylesheet" type="text/css" href="' + style.uri + '" />');
751
+ }
752
+ $('head').append(out.join(''));
753
+ }
754
+ }
755
+ };
756
+
757
+ /* Components */
758
+ var Component = {
759
+ /*-** Properties **-*/
760
+
761
+ /* Internal/Configuration */
762
+
763
+ /**
764
+ * Base name of component type
765
+ * @var string
766
+ */
767
+ _slug: 'component',
768
+
769
+ /**
770
+ * Component namespace
771
+ * @var string
772
+ */
773
+ _ns: null,
774
+
775
+ /**
776
+ * Valid component references for current component
777
+ * @var object
778
+ * > Key (string): Property name that stores reference
779
+ * > Value (function): Data type of component
780
+ */
781
+ _refs: {},
782
+
783
+ /**
784
+ * Whether DOM element and component are connected in 1:1 relationship
785
+ * Some components will be assigned to different DOM elements depending on usage
786
+ * @var bool
787
+ */
788
+ _reciprocal: false,
789
+
790
+ /**
791
+ * DOM Element tied to component
792
+ * @var DOM Element
793
+ */
794
+ _dom: null,
795
+
796
+ /**
797
+ * Component attributes
798
+ * @var object
799
+ * > Key: Attribute ID
800
+ * > Value: Attribute value
801
+ */
802
+ _attributes: false,
803
+
804
+ /**
805
+ * Default attributes
806
+ * @var object
807
+ */
808
+ _attr_default: {},
809
+
810
+ /**
811
+ * Flag indicates whether default attributes have previously been parsed
812
+ * @var bool
813
+ */
814
+ _attr_default_parsed: false,
815
+
816
+ /**
817
+ * Attributes passed to constructor
818
+ * @var obj
819
+ */
820
+ _attr_init: null,
821
+
822
+ /**
823
+ * Defines how parent properties should be remapped to component properties
824
+ * @var object
825
+ */
826
+ _attr_map: {},
827
+
828
+ /**
829
+ * Event handlers
830
+ * @var object
831
+ * > Key: string Event type
832
+ * > Value: array Handlers
833
+ */
834
+ _events: {},
835
+
836
+ /**
837
+ * Status management
838
+ * @var object
839
+ * > Key: Status ID
840
+ * > Value: Status value
841
+ */
842
+ _status: null,
843
+
844
+ /**
845
+ * Component ID
846
+ * @var string
847
+ */
848
+ _id: '',
849
+
850
+ /* Init */
851
+
852
+ /**
853
+ * Constructor
854
+ * @param string id (optional) Component ID (Default ID will be generated if no valid ID provided)
855
+ * @param object attributes (optional) Component attributes
856
+ */
857
+ _c: function(id, attributes) {
858
+ // Set ID
859
+ this._set_id(id);
860
+ // Save init attributes
861
+ if ( this.util.is_obj(attributes) ) {
862
+ this._attr_init = attributes;
863
+ }
864
+ // Call hooks
865
+ this._hooks();
866
+ },
867
+
868
+ /**
869
+ * Set Component parent to View module
870
+ * @uses `_super._set_parent()`
871
+ */
872
+ _set_parent: function() {
873
+ this._super(View);
874
+ },
875
+
876
+ /**
877
+ * Register hooks on init
878
+ * Placeholder method to be overridden by child classes
879
+ */
880
+ _hooks: function() {},
881
+
882
+ /* Methods */
883
+
884
+ /* Properties */
885
+
886
+ /**
887
+ * Set instance ID
888
+ * Instance ID can only be set once (will not change ID if valid ID already set)
889
+ * Generates random GUID if no valid ID provided
890
+ * @uses Utilities.guid()
891
+ * @param string id Unique ID
892
+ * @return string Instance ID
893
+ */
894
+ _set_id: function(id) {
895
+ // Set ID only once
896
+ if ( this.util.is_empty(this._id) ) {
897
+ this._id = ( this.util.is_string(id) ) ? id : this.util.guid();
898
+ }
899
+ return this._id;
900
+ },
901
+
902
+ /**
903
+ * Retrieve instance ID
904
+ * @uses id as ID base
905
+ * @uses _slug to add namespace (if necessary)
906
+ * @param bool ns (optional) Whether or not to namespace ID (Default: FALSE)
907
+ * @return string Instance ID
908
+ */
909
+ get_id: function(ns) {
910
+ // Get raw ID
911
+ var id = this._id;
912
+ // Namespace ID
913
+ if ( this.util.is_bool(ns) && ns ) {
914
+ id = this.add_ns(id);
915
+ }
916
+
917
+ return id;
918
+ },
919
+
920
+ /**
921
+ * Get namespace
922
+ * @uses _slug for namespace segment
923
+ * @uses Util.add_prefix() to prefix slug
924
+ * @return string Component namespace
925
+ */
926
+ get_ns: function() {
927
+ if ( null === this._ns ) {
928
+ this._ns = this.util.add_prefix(this._slug);
929
+ }
930
+ return this._ns;
931
+ },
932
+
933
+ /**
934
+ * Add namespace to value
935
+ * @param string val Value to namespace
936
+ * @return string Namespaced value (Empty string if invalid value provided)
937
+ */
938
+ add_ns: function(val) {
939
+ return ( this.util.is_string(val) ) ? this.get_ns() + '_' + val : '';
940
+ },
941
+
942
+ /**
943
+ * Retrieve status
944
+ * @param string id Status to retrieve
945
+ * @param bool raw (optional) Retrieve raw value (Default: FALSE)
946
+ * @return mixed Status value (Default: bool)
947
+ */
948
+ get_status: function(id, raw) {
949
+ var ret = false;
950
+ if ( this.util.in_obj(this._status, id) ) {
951
+ ret = ( !!raw ) ? this._status[id] : !!this._status[id];
952
+ }
953
+ return ret;
954
+ },
955
+
956
+ /**
957
+ * Set status
958
+ * @param string id Status to retrieve
959
+ * @param mixed val Status value (Default: TRUE)
960
+ * @return mixed Status value
961
+ */
962
+ set_status: function(id, val) {
963
+ // Validate ID
964
+ if ( this.util.is_string(id) ) {
965
+ // Validate value
966
+ if ( !this.util.is_set(val) ) {
967
+ val = true;
968
+ }
969
+ // Initialize status collection
970
+ if ( !this.util.is_obj(this._status, false) ) {
971
+ this._status = {};
972
+ }
973
+ // Set status
974
+ this._status[id] = val;
975
+ } else if ( !this.util.is_set(val) ) {
976
+ val = false;
977
+ }
978
+ return val;
979
+ },
980
+
981
+ /**
982
+ * Get controller object
983
+ * @uses get_parent() (alias)
984
+ * @return object Controller object (SLB.View)
985
+ */
986
+ get_controller: function() {
987
+ return this.get_parent();
988
+ },
989
+
990
+ /* Components */
991
+
992
+ /**
993
+ * Check if reference exists in object
994
+ * @param string ref Reference ID
995
+ * @return bool TRUE if reference exists, FALSE otherwise
996
+ */
997
+ has_reference: function(ref) {
998
+ return ( this.util.is_string(ref) && ( ref in this ) && ( ref in this.get_references() ) ) ? true : false;
999
+ },
1000
+
1001
+ /**
1002
+ * Retrieve object references
1003
+ * @uses _refs
1004
+ * @return obj References object
1005
+
1006
+ */
1007
+ get_references: function() {
1008
+ return this._refs;
1009
+ },
1010
+
1011
+ /**
1012
+ * Retrieve reference data type
1013
+ * @param string ref Reference ID
1014
+ * @return function Reference data type (NULL if invalid)
1015
+ */
1016
+ get_reference: function(ref) {
1017
+ return ( this.has_reference(ref) ) ? this._refs[ref] : null;
1018
+ },
1019
+
1020
+ /**
1021
+ * Retrieve component reference from current object
1022
+ * > Procedure:
1023
+ * > Check if top-level property already set
1024
+ * > Check attributes
1025
+ * > Check parent object (controller)
1026
+ * @param string cname Component name
1027
+ * @param object options (optional) Request options
1028
+ * > check_attr bool Whether or not to check instance attributes for component (Default: TRUE)
1029
+ * > get_default bool Whether or not to retrieve default object from controller if none exists in current instance (Default: FALSE)
1030
+ * @return object|null Component reference (NULL if no component found)
1031
+ */
1032
+ get_component: function(cname, options) {
1033
+ var c = null;
1034
+ // Validate request
1035
+ if ( !this.has_reference(cname) ) {
1036
+ return c;
1037
+ }
1038
+
1039
+ // Initialize options
1040
+ var opt_defaults = {
1041
+ check_attr: true,
1042
+ get_default: false
1043
+ };
1044
+ options = $.extend({}, opt_defaults, options);
1045
+
1046
+ // Get component type
1047
+ var ctype = this.get_reference(cname);
1048
+
1049
+ // Phase 1: Check if component reference previously set
1050
+ if ( this.util.is_type(this[cname], ctype) ) {
1051
+ return this[cname];
1052
+ }
1053
+ // If reference not set, iterate through component hierarchy until reference is found
1054
+ c = this[cname] = null;
1055
+
1056
+ // Phase 2: Check attributes
1057
+ if ( options.check_attr ) {
1058
+ c = this.get_attribute(cname);
1059
+ // Save object-specific component reference
1060
+ if ( !this.util.is_empty(c) ) {
1061
+ c = this.set_component(cname, c);
1062
+ }
1063
+ }
1064
+
1065
+ // Phase 3: From controller (optional)
1066
+ if ( this.util.is_empty(c) && options.get_default ) {
1067
+ c = this.get_controller().get_component(ctype);
1068
+ }
1069
+ return c;
1070
+ },
1071
+
1072
+ /**
1073
+ * Sets component reference on current object
1074
+ * > Component property reset (set to NULL) if invalid component supplied
1075
+ * @param string name Name of property to set component on object
1076
+ * @param string|object ref Component or Component ID (to be retrieved from controller)
1077
+ * @param function validate (optional) Additional validation to be performed (Must return bool: TRUE (Valid)/FALSE (Invalid))
1078
+ * @return object Component (NULL if invalid)
1079
+ */
1080
+ set_component: function(name, ref, validate) {
1081
+ var invalid = null;
1082
+ // Make sure component property exists
1083
+ if ( !this.has_reference(name) ) {
1084
+ return invalid;
1085
+ }
1086
+
1087
+ // Validate reference
1088
+ if ( this.util.is_empty(ref) ) {
1089
+ ref = invalid;
1090
+ } else {
1091
+ var ctype = this.get_reference(name);
1092
+
1093
+ // Get component from controller when ID supplied
1094
+ if ( this.util.is_string(ref, false) ) {
1095
+ ref = this.get_controller().get_component(ctype, ref);
1096
+ }
1097
+
1098
+ // Validation callback
1099
+ if ( !this.util.is_type(ref, ctype) || ( this.util.is_func(validate) && !validate.call(this, ref) ) ) {
1100
+ ref = invalid;
1101
+ }
1102
+ }
1103
+
1104
+ // Set (or clear) component reference
1105
+ this[name] = ref;
1106
+ // Return value for confirmation
1107
+ return this[name];
1108
+ },
1109
+
1110
+
1111
+ /**
1112
+ * Clear component reference
1113
+ * @uses set_component() to handle component manipulation
1114
+ * @param string Component name
1115
+ */
1116
+ clear_component: function(name) {
1117
+ this.set_component(name, null);
1118
+ },
1119
+
1120
+ /* Attributes */
1121
+
1122
+ /**
1123
+ * Initialize attributes
1124
+ * @param bool force (optional) Force full initialization of attributes (Default: FALSE)
1125
+ * @return void
1126
+ */
1127
+ init_attributes: function(force) {
1128
+ if ( !this.util.is_bool(force) ) {
1129
+ force = false;
1130
+ }
1131
+ if ( force || !this.util.is_obj(this._attributes) ) {
1132
+ var a = this._attributes = {};
1133
+ // Default attributes
1134
+ $.extend(a, this.init_default_attributes());
1135
+ // Instantiation attributes
1136
+ if ( this.util.is_obj(this._attr_init) ) {
1137
+ $.extend(a, this._attr_init);
1138
+ }
1139
+ // DOM attributes
1140
+ $.extend(a, this.get_dom_attributes());
1141
+ }
1142
+ },
1143
+
1144
+ /**
1145
+ * Generate default attributes for component
1146
+ * @uses View.get_options() to get values from controller
1147
+ * @uses _attr_map to Remap controller attributes to instance attributes
1148
+ * @uses _attr_default to Store default attributes
1149
+ * @return obj Default attributes
1150
+ */
1151
+ init_default_attributes: function() {
1152
+ // Get options from controller
1153
+ if ( !this._attr_default_parsed && this.util.is_obj(this._attr_map) ) {
1154
+ var opts = this.get_controller().get_options(this.util.obj_keys(this._attr_map));
1155
+
1156
+ if ( this.util.is_obj(opts) ) {
1157
+ // Remap
1158
+ for ( var opt in this._attr_map ) {
1159
+ if ( opt in opts && null !== this._attr_map[opt]) {
1160
+ // Move value to new property
1161
+ opts[this._attr_map[opt]] = opts[opt];
1162
+ // Delete old property
1163
+ delete opts[opt];
1164
+ }
1165
+ }
1166
+ // Merge with default attributes
1167
+ $.extend(true, this._attr_default, opts);
1168
+ }
1169
+ this._attr_default_parsed = true;
1170
+ }
1171
+ return this._attr_default;
1172
+ },
1173
+
1174
+ /**
1175
+ * Retrieve DOM attributes
1176
+ * @return obj DOM Attributes
1177
+ */
1178
+ get_dom_attributes: function() {
1179
+ var attrs = {};
1180
+ var el = this.dom_get(null, {'init': false});
1181
+ if ( el.length > 0 ) {
1182
+ // Get attributes from element
1183
+ var attrs_full = $(el).get(0).attributes;
1184
+ if ( this.util.is_obj(attrs_full) ) {
1185
+ var attr_prefix = this.util.get_attribute();
1186
+ var attr_key;
1187
+ $.each(attrs_full, function(idx, attr) {
1188
+ if ( attr.name.indexOf( attr_prefix ) === -1 ) {
1189
+ return true;
1190
+ }
1191
+ // Process custom attributes (Strip prefix)
1192
+ attr_key = attr.name.substr(attr_prefix.length + 1);
1193
+ attrs[attr_key] = attr.value;
1194
+ });
1195
+ }
1196
+ }
1197
+ return attrs;
1198
+ },
1199
+
1200
+ /**
1201
+ * Retrieve all instance attributes
1202
+ * @uses init_attributes() to initialize attributes (if necessary)
1203
+ * @uses _attributes object
1204
+ * @return obj Component attributes
1205
+ */
1206
+ get_attributes: function() {
1207
+ // Initilize attributes
1208
+ this.init_attributes();
1209
+ // Return attributes
1210
+ return this._attributes;
1211
+ },
1212
+
1213
+ /**
1214
+ * Retrieve value of specified attribute for value
1215
+ * @param string key Attribute to retrieve
1216
+ * @param mixed def (optional) Default value if attribute is not set
1217
+ * @param bool enforce_type (optional) Whether data type should match default value (Default: TRUE)
1218
+ * > If possible, attribute value will be converted to match default data type
1219
+ * > If attribute value cannot match default data type, default value will be used
1220
+ * @return mixed Attribute value (NULL if attribute is not set)
1221
+ */
1222
+ get_attribute: function(key, def, enforce_type) {
1223
+ // Validate
1224
+ if ( !this.util.is_set(def) ) {
1225
+ def = null;
1226
+ }
1227
+ if ( !this.util.is_string(key) ) {
1228
+ return def;
1229
+ }
1230
+ if ( !this.util.is_bool(enforce_type) ) {
1231
+ enforce_type = true;
1232
+ }
1233
+
1234
+ // Get attribute value
1235
+ var ret = ( this.has_attribute(key) ) ? this.get_attributes()[key] : def;
1236
+ // Validate type
1237
+ if ( enforce_type && ret !== def && null !== def && !this.util.is_type(ret, $.type(def), false) ) {
1238
+ // Convert type
1239
+ // Scalar default
1240
+ if ( this.util.is_scalar(def, false) ) {
1241
+ if ( !this.util.is_scalar(ret, false) ) {
1242
+ // Non-scalar attribute
1243
+ ret = def;
1244
+ } else if ( this.util.is_string(def, false) ) {
1245
+ // Convert to string
1246
+ ret = ret.toString();
1247
+ } else if ( this.util.is_num(def, false) && !this.util.is_num(ret, false) ) {
1248
+ // Convert to number
1249
+ ret = ( this.util.is_int(def, false) ) ? parseInt(ret) : parseFloat(ret);
1250
+ if ( !this.util.is_num(ret, false) ) {
1251
+ ret = def;
1252
+ }
1253
+ } else if ( this.util.is_bool(def, false) ) {
1254
+ // Convert to boolean
1255
+ ret = ( this.util.is_string(ret) || ( this.util.is_num(ret) ) );
1256
+ } else {
1257
+ // Fallback: Set to default
1258
+ ret = def;
1259
+ }
1260
+ }
1261
+ // Non-scalar default
1262
+ else {
1263
+ ret = def;
1264
+ }
1265
+ }
1266
+ return ret;
1267
+ },
1268
+
1269
+ /**
1270
+ * Call attribute as method
1271
+ * @param string attr Attribute to call
1272
+ * @param arguments (optional) Additional arguments to pass to method
1273
+ * @return mixed Attribute return value (if attribute is not a function, attribute's value is returned)
1274
+ */
1275
+ call_attribute: function(attr, args) {
1276
+ attr = this.get_attribute(attr);
1277
+ if ( this.util.is_func(attr) ) {
1278
+ // Get arguments
1279
+ args = Array.prototype.slice.call(arguments, 1);
1280
+ // Pass arguments to user-defined method
1281
+ attr = attr.apply(this, args);
1282
+ }
1283
+ return attr;
1284
+ },
1285
+
1286
+ /**
1287
+ * Check if attribute exists
1288
+ * @param string key Attribute name
1289
+ * @return bool TRUE if exists, FALSE otherwise
1290
+ */
1291
+ has_attribute: function(key) {
1292
+ return ( this.util.is_string(key) && ( key in this.get_attributes() ) );
1293
+ },
1294
+
1295
+ /**
1296
+ * Set component attributes
1297
+ * @param obj attributes Attributes to set
1298
+ * @param bool full (optional) Whether to fully replace or merge component's attributes with new values (Default: Merge)
1299
+ */
1300
+ set_attributes: function(attributes, full) {
1301
+ // Validate
1302
+ if ( !this.util.is_bool(full) ) {
1303
+ full = false;
1304
+ }
1305
+
1306
+ // Initialize attributes
1307
+ this.init_attributes(full);
1308
+
1309
+ // Merge new/existing attributes
1310
+ if ( this.util.is_obj(attributes) ) {
1311
+ $.extend(this._attributes, attributes);
1312
+ }
1313
+ },
1314
+
1315
+ /**
1316
+ * Set value for a component attribute
1317
+ * @uses get_attributes() to retrieve attributes
1318
+ * @param string key Attribute to set
1319
+ * @param mixed val Attribute value
1320
+ * @return mixed Attribute value
1321
+ */
1322
+ set_attribute: function(key, val) {
1323
+ if ( this.util.is_string(key) && this.util.is_set(val) ) {
1324
+ this.get_attributes()[key] = val;
1325
+ }
1326
+ return val;
1327
+ },
1328
+
1329
+ /* DOM */
1330
+
1331
+ /**
1332
+ * Generate selector for retrieving child element
1333
+ * @param string element Class name of child element
1334
+ * @return string Element selector
1335
+ */
1336
+ dom_get_selector: function(element) {
1337
+ return ( this.util.is_string(element) ) ? '.' + this.add_ns(element) : '';
1338
+ },
1339
+
1340
+ dom_get_attribute: function() {
1341
+ return this.util.get_attribute(this._slug);
1342
+ },
1343
+
1344
+ /**
1345
+ * Set reference of instance on DOM element
1346
+ * @uses _reciprocal to determine if DOM element should also be attached to instance
1347
+ * @param string|obj (jQuery) el DOM element to attach instance to
1348
+ * @return jQuery DOM element set
1349
+ */
1350
+ dom_set: function(el) {
1351
+ el = $(el);
1352
+ // Save instance to DOM object
1353
+ el.data(this.get_data_key(), this);
1354
+ // Save DOM object to instance
1355
+ if ( this._reciprocal ) {
1356
+ this._dom = el;
1357
+ }
1358
+ return el;
1359
+ },
1360
+
1361
+ /**
1362
+ * Retrieve attached DOM element
1363
+ * @uses _dom to retrieve attached DOM element
1364
+ * @uses dom_put() to insert child element
1365
+ * @param string element (optional) ID of child element to retrieve (Default: Main element)
1366
+ * @param bool put (optional) Whether to insert element if it does not exist (Default: FALSE)
1367
+ * @param obj options (optional) Runtime options
1368
+ * @return obj jQuery DOM element
1369
+ */
1370
+ dom_get: function(element, options) {
1371
+ // Build options
1372
+ var opts_default = {
1373
+ 'init': true,
1374
+ 'put': false
1375
+ };
1376
+ options = ( this.util.is_obj(options) ) ? $.extend({}, opts_default, options) : opts_default;
1377
+
1378
+ // Init Component DOM
1379
+ if ( options.init && !this.get_status('dom_init') ) {
1380
+ this.set_status('dom_init');
1381
+ this.dom_init();
1382
+ }
1383
+ // Check for main DOM element
1384
+ var ret = this._dom;
1385
+ if ( !!ret && this.util.is_string(element) ) {
1386
+ var ch = $(ret).find( this.dom_get_selector(element) );
1387
+ // Check for child element
1388
+ if ( ch.length ) {
1389
+ ret = ch;
1390
+ } else if ( true === options.put || this.util.is_obj(options.put) ) {
1391
+ // Insert child element
1392
+ ret = this.dom_put(element, options.put);
1393
+ }
1394
+ }
1395
+ return $(ret);
1396
+ },
1397
+
1398
+ /**
1399
+ * Initialize DOM element
1400
+ * To be overridden by child classes
1401
+ */
1402
+ dom_init: function() {},
1403
+
1404
+ /**
1405
+ * Wrap output in DOM element
1406
+ * Wrapper element created and added to main DOM element if not yet created
1407
+ * @param string element ID for DOM element (Used as class name for wrapper)
1408
+ * @param string|jQuery|obj content Content to add to DOM (Object contains element properties)
1409
+ * > tag : Element tag name
1410
+ * > content : Element content
1411
+ * @return jQuery Inserted element(s)
1412
+ */
1413
+ dom_put: function(element, content) {
1414
+ var r = null;
1415
+ // Stop processing if main DOM element not set or element is not valid
1416
+ if ( !this.dom_has() || !this.util.is_string(element) ) {
1417
+ return $(r);
1418
+ }
1419
+ // Setup options
1420
+ var strip = ['tag', 'content', 'success'];
1421
+ var options = {
1422
+ 'tag': 'div',
1423
+ 'content': '',
1424
+ 'class': this.add_ns(element)
1425
+ };
1426
+ // Setup content
1427
+ if ( !this.util.is_empty(content) ) {
1428
+ if ( this.util.is_type(content, jQuery, false) || this.util.is_string(content, false) ) {
1429
+ options.content = content;
1430
+ }
1431
+ else if ( this.util.is_obj(content, false) ) {
1432
+ $.extend(options, content);
1433
+ }
1434
+ }
1435
+ var attrs = $.extend({}, options);
1436
+ for ( var x = 0; x < strip.length; x++ ) {
1437
+ delete attrs[strip[x]];
1438
+ }
1439
+ // Retrieve existing element
1440
+ var d = this.dom_get();
1441
+ r = $(this.dom_get_selector(element), d);
1442
+ // Create element (if necessary)
1443
+ if ( !r.length ) {
1444
+ r = $(this.util.format('<%s />', options.tag), attrs).appendTo(d);
1445
+ if ( r.length && this.util.is_method(options, 'success') ) {
1446
+ options['success'].call(r, r);
1447
+ }
1448
+ }
1449
+ // Set content
1450
+ $(r).append(options.content);
1451
+ return $(r);
1452
+ },
1453
+
1454
+ /**
1455
+ * Check if DOM element is set for instance
1456
+ * DOM is initialized before evaluation
1457
+ * @return bool TRUE if DOM element set, FALSE otherwise
1458
+ */
1459
+ dom_has: function() {
1460
+ return ( !!this.dom_get().length );
1461
+ },
1462
+
1463
+ /* Data */
1464
+
1465
+ /**
1466
+ * Retrieve key used to store data in DOM element
1467
+ * @return string Data key
1468
+ */
1469
+ get_data_key: function() {
1470
+ return this.get_ns();
1471
+ },
1472
+
1473
+ /* Events */
1474
+
1475
+ /**
1476
+ * Register event handler for custom event
1477
+ * Structure
1478
+ * > Events (obj)
1479
+ * > Event-Name (array)
1480
+ * > Handlers (functions)
1481
+ * @param mixed event Custom event to register handler for
1482
+ * > string: Standard event handler
1483
+ * > array: Multiple events to register single handler on
1484
+ * > object: Map of events/handlers
1485
+ * @param function fn Event handler
1486
+ * @param obj options Handler registration options
1487
+ * > clear (bool) Clear existing event handlers before setting current handler (Default: FALSE)
1488
+ * @return obj Component instance (allows chaining)
1489
+ */
1490
+ on: function(event, fn, options) {
1491
+ // Handle request types
1492
+ if ( !this.util.is_string(event) || !this.util.is_func(fn) ) {
1493
+ var t = this;
1494
+ var args = Array.prototype.slice.call(arguments, 1);
1495
+ if ( this.util.is_array(event) ) {
1496
+ // Events array
1497
+ $.each(event, function(idx, val) {
1498
+ t.on.apply(t, [val].concat(args));
1499
+ });
1500
+ } else if ( this.util.is_obj(event) ) {
1501
+ // Events map
1502
+ $.each(event, function(ev, hdl) {
1503
+ t.on.apply(t, [ev, hdl].concat(args));
1504
+ });
1505
+ }
1506
+ return this;
1507
+ }
1508
+
1509
+ // Options
1510
+
1511
+ // Default options
1512
+ var options_std = {
1513
+ clear: false
1514
+ };
1515
+ if ( !this.util.is_obj(options, false) ) {
1516
+ // Reset options
1517
+ options = {};
1518
+ }
1519
+ // Build options
1520
+ options = $.extend({}, options_std, options);
1521
+ // Initialize events bucket
1522
+ if ( !this.util.is_obj(this._events, false) ) {
1523
+ this._events = {};
1524
+ }
1525
+ // Setup event
1526
+ var es = this._events;
1527
+ if ( !( event in es ) || !this.util.is_obj(es[event], false) || !!options.clear ) {
1528
+ es[event] = [];
1529
+ }
1530
+ // Add event handler
1531
+ es[event].push(fn);
1532
+ return this;
1533
+ },
1534
+
1535
+ /**
1536
+ * Trigger custom event
1537
+ * Event handlers are executed in the context of the current component instance
1538
+ * Event handlers are passed parameters
1539
+ * > ev (obj) Event object
1540
+ * > type (string) Event name
1541
+ * > data (mixed) Data to pass to handlers (if supplied)
1542
+ * > component (obj) Current component instance
1543
+ * @param string event Custom event to trigger
1544
+ * @param mixed data (optional) Data to pass to event handlers
1545
+ * @return jQuery.Promise Promise that is resolved once event handlers are resolved
1546
+ */
1547
+ trigger: function(event, data) {
1548
+ var dfr = $.Deferred();
1549
+ var dfrs = [];
1550
+ var t = this;
1551
+ // Handle array of events
1552
+ if ( this.util.is_array(event) ) {
1553
+ $.each(event, function(idx, val) {
1554
+ // Collect promises from triggered events
1555
+ dfrs.push( t.trigger(val, data) );
1556
+ });
1557
+ // Resolve trigger when all events have been resolved
1558
+ $.when.apply(t, dfrs).done(function() {
1559
+ dfr.resolve();
1560
+ });
1561
+ return dfr.promise();
1562
+ }
1563
+ // Validate
1564
+ if ( !this.util.is_string(event) || !( event in this._events ) ) {
1565
+ dfr.resolve();
1566
+ return dfr.promise();
1567
+ }
1568
+ // Create event object
1569
+ var ev = { 'type': event, 'data': null };
1570
+ // Add data to event object
1571
+ if ( this.util.is_set(data) ) {
1572
+ ev.data = data;
1573
+ }
1574
+ // Fire handlers for event
1575
+ $.each(this._events[event], function(idx, fn) {
1576
+ // Call handler (`this` set to current instance)
1577
+ // Collect promises from event handlers
1578
+ dfrs.push( fn.call(t, ev, t) );
1579
+ });
1580
+ // Resolve trigger when all handlers have been resolved
1581
+ $.when.apply(this, dfrs).done(function() {
1582
+ dfr.resolve();
1583
+ });
1584
+ return dfr.promise();
1585
+ }
1586
+ };
1587
+
1588
+ View.Component = Component = SLB.Class.extend(Component);
1589
+
1590
+ /**
1591
+ * Content viewer
1592
+ * @param obj options Init options
1593
+ */
1594
+ var Viewer = {
1595
+
1596
+ /* Configuration */
1597
+
1598
+ _slug: 'viewer',
1599
+
1600
+ _refs: {
1601
+ item: 'Content_Item',
1602
+ theme: 'Theme'
1603
+ },
1604
+
1605
+ _reciprocal: true,
1606
+
1607
+ _attr_default: {
1608
+ loop: true,
1609
+ animate: true,
1610
+ autofit: true,
1611
+ overlay_enabled: true,
1612
+ overlay_opacity: '0.8',
1613
+ title_default: false,
1614
+ container: null,
1615
+ slideshow_enabled: true,
1616
+ slideshow_autostart: false,
1617
+ slideshow_duration: 2,
1618
+ slideshow_active: false,
1619
+ slideshow_timer: null,
1620
+ labels: {
1621
+ close: 'close',
1622
+ nav_prev: '&laquo; prev',
1623
+ nav_next: 'next &raquo;',
1624
+ slideshow_start: 'start slideshow',
1625
+ slideshow_stop: 'stop slideshow',
1626
+ group_status: 'Image %current% of %total%',
1627
+ loading: 'loading'
1628
+ }
1629
+ },
1630
+
1631
+ _attr_map: {
1632
+ 'theme': null,
1633
+ 'group_loop': 'loop',
1634
+ 'ui_autofit': 'autofit',
1635
+ 'ui_animate': 'animate',
1636
+ 'ui_overlay_opacity': 'overlay_opacity',
1637
+ 'ui_labels': 'labels',
1638
+ 'ui_title_default': 'title_default',
1639
+ 'slideshow_enabled': null,
1640
+ 'slideshow_autostart': null,
1641
+ 'slideshow_duration': null
1642
+ },
1643
+
1644
+ /* References */
1645
+
1646
+ /**
1647
+ * Item currently loaded in viewer
1648
+ * @var object Content_Item
1649
+ */
1650
+ item: null,
1651
+
1652
+ /**
1653
+ * Queued item to be loaded once viewer is available
1654
+ * @var object Content_Item
1655
+ */
1656
+ item_queued: null,
1657
+
1658
+ /**
1659
+ * Theme used by viewer
1660
+ * @var object Theme
1661
+ */
1662
+ theme: null,
1663
+
1664
+ /* Properties */
1665
+
1666
+ item_working: null,
1667
+
1668
+ active: false,
1669
+ init: false,
1670
+ open: false,
1671
+ loading: false,
1672
+
1673
+ /* Methods */
1674
+
1675
+ /* Init */
1676
+
1677
+ _hooks: function() {
1678
+ var t = this;
1679
+ this
1680
+ .on(['item-prev', 'item-next'], function() {
1681
+ t.trigger('item-change');
1682
+ })
1683
+ .on(['close', 'item-change'], function() {
1684
+ t.unload().done(function() {
1685
+ t.unlock();
1686
+ });
1687
+ });
1688
+ },
1689
+
1690
+ /* References */
1691
+
1692
+ /**
1693
+ * Retrieve item instance current attached to viewer
1694
+ * @return Content_Item|NULL Current item instance
1695
+ */
1696
+ get_item: function() {
1697
+ return this.get_component('item');
1698
+ },
1699
+
1700
+ /**
1701
+ * Set item reference
1702
+ * Validates item before setting
1703
+ * @param obj item Content_Item instance
1704
+ * @return bool TRUE if valid item set, FALSE otherwise
1705
+ */
1706
+ set_item: function(item) {
1707
+ // Clear existing item
1708
+ this.clear_item(false);
1709
+ var i = this.set_component('item', item, function(item) {
1710
+ return ( item.has_type() );
1711
+ });
1712
+ return ( !this.util.is_empty(i) );
1713
+ },
1714
+
1715
+ /**
1716
+ * Clear item from viewer
1717
+ * Resets item state and removes reference (if necessary)
1718
+ * @param bool full (optional) Fully remove item? (Default: TRUE)
1719
+ */
1720
+ clear_item: function(full) {
1721
+ // Validate
1722
+ if ( !this.util.is_bool(full) ) {
1723
+ full = true;
1724
+ }
1725
+ var item = this.get_item();
1726
+ if ( !!item ) {
1727
+ item.reset();
1728
+ }
1729
+ if ( full ) {
1730
+ this.clear_component('item');
1731
+ }
1732
+ },
1733
+
1734
+ /**
1735
+ * Retrieve theme reference
1736
+ * @return object Theme reference
1737
+ */
1738
+ get_theme: function() {
1739
+ // Get saved theme
1740
+ var ret = this.get_component('theme', {check_attr: false});
1741
+ if ( this.util.is_empty(ret) ) {
1742
+ // Theme needs to be initialized
1743
+ ret = this.set_component('theme', new View.Theme(this));
1744
+ }
1745
+ return ret;
1746
+ },
1747
+
1748
+ /**
1749
+ * Set viewer's theme
1750
+ * @param object theme Theme object
1751
+ */
1752
+ set_theme: function(theme) {
1753
+ this.set_component('theme', theme);
1754
+ },
1755
+
1756
+ /* Properties */
1757
+
1758
+ /**
1759
+ * Lock the viewer
1760
+ * Indicates that item is currently being processed
1761
+ * @return jQuery.Deferred Resolved when item processing is complete
1762
+ */
1763
+ lock: function() {
1764
+ return this.set_status('item_working', $.Deferred());
1765
+ },
1766
+
1767
+ /**
1768
+ * Retrieve lock
1769
+ * @param bool simple (optional) Whether to return a simple status of the locked status (Default: FALSE)
1770
+ * @param bool full (optional) Whether to return Deferred (TRUE) or Promise (FALSE) object (Default: FALSE)
1771
+ * @return jQuery.Promise Resolved when item processing is complete
1772
+ */
1773
+ get_lock: function(simple, full) {
1774
+ // Validate
1775
+ if ( !this.util.is_bool(simple) ) {
1776
+ simple = false;
1777
+ }
1778
+ if ( !this.util.is_bool(full) ) {
1779
+ full = false;
1780
+ }
1781
+ var s = 'item_working';
1782
+ // Simple status
1783
+ if ( simple ) {
1784
+ return this.get_status(s);
1785
+ }
1786
+ // Full value
1787
+ var r = this.get_status(s, true);
1788
+ if ( !this.util.is_promise(r) ) {
1789
+ // Create default
1790
+ r = this.lock();
1791
+ }
1792
+ return ( full ) ? r : r.promise();
1793
+ },
1794
+
1795
+ is_locked: function() {
1796
+ return this.get_lock(true);
1797
+ },
1798
+
1799
+ /**
1800
+ * Unlock the viewer
1801
+ * Any callbacks registered for this action will be executed
1802
+ * @return jQuery.Deferred Resolved instance
1803
+ */
1804
+ unlock: function() {
1805
+ return this.get_lock(false, true).resolve();
1806
+ },
1807
+
1808
+ /**
1809
+ * Set Viewer active status
1810
+ * @param bool mode (optional) Activate or deactivate status (Default: TRUE)
1811
+ * @return bool Active status
1812
+ */
1813
+ set_active: function(mode) {
1814
+ if ( !this.util.is_bool(mode) ) {
1815
+ mode = true;
1816
+ }
1817
+ return this.set_status('active', mode);
1818
+ },
1819
+
1820
+ /**
1821
+ * Check Viewer active status
1822
+ * @return bool Active status
1823
+ */
1824
+ is_active: function() {
1825
+ return this.get_status('active');
1826
+ },
1827
+
1828
+ /**
1829
+ * Set loading mode
1830
+ * @param bool mode (optional) Set (TRUE) or unset (FALSE) loading mode (Default: TRUE)
1831
+ * @return jQuery.Promise Promise that resolves when loading mode is set
1832
+ */
1833
+ set_loading: function(mode) {
1834
+ var dfr = $.Deferred();
1835
+ if ( !this.util.is_bool(mode) ) {
1836
+ mode = true;
1837
+ }
1838
+ this.loading = mode;
1839
+ // Pause/Resume slideshow
1840
+ if ( this.slideshow_active() ) {
1841
+ this.slideshow_pause(mode);
1842
+ }
1843
+ // Set CSS class on DOM element
1844
+ var m = ( mode ) ? 'addClass' : 'removeClass';
1845
+ $(this.dom_get())[m]('loading');
1846
+ if ( mode ) {
1847
+ // Loading transition
1848
+ this.get_theme().transition('load').always(function() {
1849
+ dfr.resolve();
1850
+ });
1851
+ } else {
1852
+ dfr.resolve();
1853
+ }
1854
+ return dfr.promise();
1855
+ },
1856
+
1857
+ /**
1858
+ * Unset loading mode
1859
+ * @see set_loading()
1860
+ * @return jQuery.Promise Promise that resovles when loading mode is set
1861
+ */
1862
+ unset_loading: function() {
1863
+ return this.set_loading(false);
1864
+ },
1865
+
1866
+ /**
1867
+ * Retrieve loading status
1868
+ * @return bool Loading status (Default: FALSE)
1869
+ */
1870
+ get_loading: function() {
1871
+ return ( this.util.is_bool(this.loading) ) ? this.loading : false;
1872
+ },
1873
+
1874
+ /**
1875
+ * Check if viewer is currently loading content
1876
+ * @return bool Loading status (Default: FALSE)
1877
+ */
1878
+ is_loading: function() {
1879
+ return this.get_loading();
1880
+ },
1881
+
1882
+ /* Display */
1883
+
1884
+ /**
1885
+ * Display content in viewer
1886
+ * @param Content_Item item Item to show
1887
+ * @param obj options (optional) Display options
1888
+ */
1889
+ show: function(item) {
1890
+ this.item_queued = item;
1891
+ var fin_set = 'show_deferred';
1892
+ // Validate theme
1893
+ var vt = 'theme_valid';
1894
+ var valid = true;
1895
+ if ( this.has_attribute(vt)) {
1896
+ valid = this.get_attribute(vt, true);
1897
+ } else {
1898
+ valid = ( this.get_theme() && this.get_theme().get_template().get_layout(false) !== "" ) ? true : false;
1899
+ this.set_attribute(vt, valid);
1900
+ }
1901
+
1902
+ if ( !valid ) {
1903
+ this.close();
1904
+ return false;
1905
+ }
1906
+ var v = this;
1907
+ var fin = function() {
1908
+ // Lock viewer
1909
+ v.lock();
1910
+ // Reset callback flag (for new lock)
1911
+ v.set_status(fin_set, false);
1912
+ // Validate request
1913
+ if ( !v.set_item(v.item_queued) ) {
1914
+ v.close();
1915
+ return false;
1916
+ }
1917
+ // Add item to history stack
1918
+ v.history_add();
1919
+ // Activate
1920
+ v.set_active();
1921
+ // Display
1922
+ v.render();
1923
+ };
1924
+ if ( !this.is_locked() ) {
1925
+ fin();
1926
+ } else if ( !this.get_status(fin_set) ) {
1927
+ // Set flag to avoid duplicate callbacks
1928
+ this.set_status(fin_set);
1929
+ this.get_lock().always(function() {
1930
+ fin();
1931
+ });
1932
+ }
1933
+ },
1934
+
1935
+ /* History Management */
1936
+
1937
+ history_handle: function(e) {
1938
+ var state = e.originalEvent.state;
1939
+ // Load item
1940
+ if ( this.util.is_string(state.item, false) ) {
1941
+ this.get_controller().get_item(state.item).show({'event': e});
1942
+ this.trigger('item-change');
1943
+ } else {
1944
+ var count = this.history_get(true);
1945
+ // Reset count
1946
+ this.history_set(0);
1947
+ // Close viewer
1948
+ if ( -1 !== count ) {
1949
+ this.close();
1950
+ }
1951
+ }
1952
+ },
1953
+
1954
+ history_get: function(full) {
1955
+ return this.get_status('history_count', full);
1956
+ },
1957
+ history_set: function(val) {
1958
+ return this.set_status('history_count', val);
1959
+ },
1960
+ history_add: function() {
1961
+ if ( !history.pushState ) {
1962
+ return false;
1963
+ }
1964
+ // Get display options
1965
+ var item = this.get_item();
1966
+ var opts = item.get_attribute('options_show');
1967
+ // Save history state
1968
+ var count = ( this.history_get() ) ? this.history_get(true) : 0;
1969
+ if ( !this.util.in_obj(opts, 'event') ) {
1970
+ // Create state
1971
+ var state = {
1972
+ 'viewer': this.get_id(),
1973
+ 'item': null,
1974
+ 'count': count
1975
+ };
1976
+ // Init: Save viewer state
1977
+ if ( !count ) {
1978
+ history.replaceState(state, null);
1979
+ }
1980
+ // Always: Save item state
1981
+ state.item = this.get_controller().save_item(item).get_id();
1982
+ state.count = ++count;
1983
+ history.pushState(state, '');
1984
+ } else {
1985
+ var e = opts.event.originalEvent;
1986
+ if ( this.util.in_obj(e, 'state') && this.util.in_obj(e.state, 'count') ) {
1987
+ count = e.state.count;
1988
+ }
1989
+ }
1990
+ // Save history item count
1991
+ this.history_set(count);
1992
+ },
1993
+ history_reset: function() {
1994
+ var count = this.history_get(true);
1995
+ if ( count ) {
1996
+ // Clear history status
1997
+ this.history_set(-1);
1998
+ // Restore history stack
1999
+ history.go( -1 * count );
2000
+ }
2001
+ },
2002
+
2003
+ /**
2004
+ * Check if viewer is currently open
2005
+ * Checks if node is actually visible in DOM
2006
+ * @return bool TRUE if viewer is open, FALSE otherwise
2007
+ */
2008
+ is_open: function() {
2009
+ return ( this.dom_get().css('display') === 'none' ) ? false : true;
2010
+ },
2011
+
2012
+ /**
2013
+ * Load output into DOM
2014
+ */
2015
+ render: function() {
2016
+ // Get theme output
2017
+ var v = this;
2018
+ var thm = this.get_theme();
2019
+ v.dom_prep();
2020
+ // Register theme event handlers
2021
+ if ( !this.get_status('render-events') ) {
2022
+ this.set_status('render-events');
2023
+ thm
2024
+ // Loading
2025
+ .on('render-loading', function(ev, thm) {
2026
+ var dfr = $.Deferred();
2027
+ if ( !v.is_active() ) {
2028
+ dfr.reject();
2029
+ return dfr.promise();
2030
+ }
2031
+ var set_pos = function() {
2032
+ // Set position
2033
+ v.dom_get().css('top', $(window).scrollTop());
2034
+ };
2035
+ var always = function() {
2036
+ // Set loading flag
2037
+ v.set_loading().always(function() {
2038
+ dfr.resolve();
2039
+ });
2040
+ };
2041
+ if ( v.is_open() ) {
2042
+ thm.transition('unload')
2043
+ .fail(function() {
2044
+ set_pos();
2045
+ thm.dom_get_tag('item', 'content').attr('style', '');
2046
+ })
2047
+ .always(always);
2048
+ } else {
2049
+ thm.transition('open')
2050
+ .always(function() {
2051
+ always();
2052
+ v.events_open();
2053
+ v.open = true;
2054
+ })
2055
+ .fail(function() {
2056
+ set_pos();
2057
+ // Fallback open
2058
+ v.get_overlay().show();
2059
+ v.dom_get().show();
2060
+ });
2061
+ }
2062
+ return dfr.promise();
2063
+ })
2064
+ // Complete
2065
+ .on('render-complete', function(ev, thm) {
2066
+ // Stop if viewer not active
2067
+ if ( !v.is_active() ) {
2068
+ return false;
2069
+ }
2070
+ // Set classes
2071
+ var d = v.dom_get();
2072
+ var classes = ['item_single', 'item_multi'];
2073
+ var ms = ['addClass', 'removeClass'];
2074
+ if ( !v.get_item().get_group().is_single() ) {
2075
+ ms.reverse();
2076
+ }
2077
+ $.each(ms, function(idx, val) {
2078
+ d[val](classes[idx]);
2079
+ });
2080
+ // Bind events
2081
+ v.events_complete();
2082
+ // Transition
2083
+ thm.transition('complete')
2084
+ .fail(function() {
2085
+ // Autofit content
2086
+ if ( v.get_attribute('autofit', true) ) {
2087
+ var dims = $.extend({'display': 'inline-block'}, thm.get_item_dimensions());
2088
+ thm.dom_get_tag('item', 'content').css(dims);
2089
+ }
2090
+ })
2091
+ .always(function() {
2092
+ // Unset loading flag
2093
+ v.unset_loading();
2094
+ // Trigger event
2095
+ v.trigger('render-complete');
2096
+ // Set viewer as initialized
2097
+ v.init = true;
2098
+ });
2099
+ });
2100
+ }
2101
+ // Render
2102
+ thm.render();
2103
+ },
2104
+
2105
+ /**
2106
+ * Retrieve container element
2107
+ * Creates default container element if not yet created
2108
+ * @return jQuery Container element
2109
+ */
2110
+ dom_get_container: function() {
2111
+ var sel = this.get_attribute('container');
2112
+ // Set default container
2113
+ if ( this.util.is_empty(sel) ) {
2114
+ sel = '#' + this.add_ns('wrap');
2115
+ }
2116
+ // Add default container to DOM if not yet present
2117
+ var c = $(sel);
2118
+ if ( !c.length ) {
2119
+ // Prepare ID
2120
+ var id = ( sel.indexOf('#') === 0 ) ? sel.substr(1) : sel;
2121
+ // Add element
2122
+ c = $('<div />', {'id': id}).appendTo('body');
2123
+ }
2124
+ return c;
2125
+ },
2126
+
2127
+ /**
2128
+ * Custom Viewer DOM initialization
2129
+ */
2130
+ dom_init: function() {
2131
+ // Create element & add to DOM
2132
+ // Save element to instance
2133
+ var d = this.dom_set($('<div/>', {
2134
+ 'id': this.get_id(true),
2135
+ 'class': this.get_ns()
2136
+ })).appendTo(this.dom_get_container()).hide();
2137
+ // Add theme classes
2138
+ var thm = this.get_theme();
2139
+ d.addClass(thm.get_classes(' '));
2140
+ // Add theme layout (basic)
2141
+ var v = this;
2142
+ if ( !this.get_status('render-init') ) {
2143
+ this.set_status('render-init');
2144
+ thm.on('render-init', function(ev) {
2145
+ // Add rendered theme layout to viewer DOM
2146
+ v.dom_put('layout', ev.data);
2147
+ });
2148
+ }
2149
+ thm.render(true);
2150
+ },
2151
+
2152
+ /**
2153
+ * Prepare DOM for viewer
2154
+ */
2155
+ dom_prep: function(mode) {
2156
+ var m = ( this.util.is_bool(mode) && !mode ) ? 'removeClass' : 'addClass';
2157
+ $('html')[m](this.util.add_prefix('overlay'));
2158
+ },
2159
+
2160
+ /**
2161
+ * Restore DOM
2162
+ * Required after viewer is closed
2163
+ */
2164
+ dom_restore: function() {
2165
+ this.dom_prep(false);
2166
+ },
2167
+
2168
+ /* Layout */
2169
+
2170
+ get_layout: function() {
2171
+ var ret = this.dom_get('layout', {
2172
+ 'put': {
2173
+ 'success': function() {
2174
+ $(this).hide();
2175
+ }
2176
+ }
2177
+ });
2178
+ return ret;
2179
+ },
2180
+
2181
+ /* Animation */
2182
+
2183
+ animation_enabled: function() {
2184
+ return this.get_attribute('animate', true);
2185
+ },
2186
+
2187
+ /* Overlay */
2188
+
2189
+ /**
2190
+ * Determine if overlay is enabled for viewer
2191
+ * @return bool TRUE if overlay is enabled, FALSE otherwise
2192
+ */
2193
+ overlay_enabled: function() {
2194
+ var ov = this.get_attribute('overlay_enabled');
2195
+ return ( this.util.is_bool(ov) ) ? ov : false;
2196
+ },
2197
+
2198
+ /**
2199
+ * Retrieve overlay DOM element
2200
+ * @return jQuery Overlay element (NULL if no overlay set for viewer)
2201
+ */
2202
+ get_overlay: function() {
2203
+ var o = null;
2204
+ var v = this;
2205
+ if ( this.overlay_enabled() ) {
2206
+ o = this.dom_get('overlay', {
2207
+ 'put': {
2208
+ 'success': function() {
2209
+ $(this).hide().css('opacity', v.get_attribute('overlay_opacity'));
2210
+ }
2211
+ }
2212
+ });
2213
+ }
2214
+ return $(o);
2215
+ },
2216
+
2217
+ /**
2218
+ * Unload viewer
2219
+ */
2220
+ unload: function() {
2221
+ var dfr = $.Deferred();
2222
+ // Unload item data
2223
+ this.get_theme().dom_get_tag('item').text('');
2224
+ dfr.resolve();
2225
+ return dfr.promise();
2226
+ },
2227
+
2228
+ /**
2229
+ * Reset viewer
2230
+ */
2231
+ reset: function() {
2232
+ // Hide viewer
2233
+ this.dom_get().hide();
2234
+ // Restore DOM
2235
+ this.dom_restore();
2236
+ // History
2237
+ this.history_reset();
2238
+ // Item
2239
+ this.clear_item();
2240
+ // Reset properties
2241
+ this.set_active(false);
2242
+ this.set_loading(false);
2243
+ this.slideshow_stop();
2244
+ this.keys_disable();
2245
+ // Clear for next item
2246
+ this.unlock();
2247
+ },
2248
+
2249
+ /* Content */
2250
+
2251
+ get_labels: function() {
2252
+ return this.get_attribute('labels', {});
2253
+ },
2254
+
2255
+ get_label: function(name) {
2256
+ var lbls = this.get_labels();
2257
+ return ( name in lbls ) ? lbls[name] : '';
2258
+ },
2259
+
2260
+ /* Interactivity */
2261
+
2262
+ /**
2263
+ * Initialize event handlers upon opening lightbox
2264
+ */
2265
+ events_open: function() {
2266
+ // Keyboard bindings
2267
+ this.keys_enable();
2268
+ if ( this.open ) {
2269
+ return false;
2270
+ }
2271
+
2272
+ // Control event bubbling
2273
+ var l = this.get_layout();
2274
+ l.children().click(function(ev) {
2275
+ ev.stopPropagation();
2276
+ });
2277
+
2278
+ /* Close */
2279
+ var v = this;
2280
+ var close = function() {
2281
+ v.close();
2282
+ };
2283
+ // Layout
2284
+ l.click(close);
2285
+ // Overlay
2286
+ this.get_overlay().click(close);
2287
+ // Fire event
2288
+ this.trigger('events-open');
2289
+ },
2290
+
2291
+ /**
2292
+ * Initialize event handlers upon completing lightbox rendering
2293
+ */
2294
+ events_complete: function() {
2295
+ if ( this.init ) {
2296
+ return false;
2297
+ }
2298
+ // Fire event
2299
+ this.trigger('events-complete');
2300
+ },
2301
+
2302
+ keys_enable: function(mode) {
2303
+ if ( !this.util.is_bool(mode) ) {
2304
+ mode = true;
2305
+ }
2306
+ var e = ['keyup', this.util.get_prefix()].join('.');
2307
+ var v = this;
2308
+ var h = function(ev) {
2309
+ return v.keys_control(ev);
2310
+ };
2311
+ if ( mode ) {
2312
+ $(document).on(e, h);
2313
+ } else {
2314
+ $(document).off(e);
2315
+ }
2316
+ },
2317
+
2318
+ keys_disable: function() {
2319
+ this.keys_enable(false);
2320
+ },
2321
+
2322
+ keys_control: function(ev) {
2323
+ var handlers = {
2324
+ 27: this.close, /* esc */
2325
+ 37: this.item_prev, /* left-arrow */
2326
+ 39: this.item_next, /* right-arrow */
2327
+ };
2328
+ // Swap next/prev keys on RTL pages
2329
+ if ('rtl' === document.documentElement.getAttribute('dir')) {
2330
+ handlers[37] = this.item_next; /* left-arrow */
2331
+ handlers[39] = this.item_prev; /* right-arrow */
2332
+ }
2333
+ if ( ev.which in handlers ) {
2334
+ handlers[ev.which].call(this);
2335
+ return false;
2336
+ }
2337
+ },
2338
+
2339
+ /**
2340
+ * Check if slideshow functionality is enabled
2341
+ * @return bool TRUE if slideshow is enabled, FALSE otherwise
2342
+ */
2343
+ slideshow_enabled: function() {
2344
+ var o = this.get_attribute('slideshow_enabled');
2345
+ return ( this.util.is_bool(o) && o && this.get_item() && !this.get_item().get_group().is_single() ) ? true : false;
2346
+ },
2347
+
2348
+ /**
2349
+ * Checks if slideshow is currently active
2350
+ * @return bool TRUE if slideshow is active, FALSE otherwise
2351
+ */
2352
+ slideshow_active: function() {
2353
+ return ( this.slideshow_enabled() && ( this.get_attribute('slideshow_active') || ( !this.init && this.get_attribute('slideshow_autostart') ) ) ) ? true : false;
2354
+ },
2355
+
2356
+ /**
2357
+ * Clear slideshow timer
2358
+ */
2359
+ slideshow_clear_timer: function() {
2360
+ clearInterval(this.get_attribute('slideshow_timer'));
2361
+ },
2362
+
2363
+ /**
2364
+ * Start slideshow timer
2365
+ * @param function callback Callback function
2366
+ */
2367
+ slideshow_set_timer: function(callback) {
2368
+ this.set_attribute('slideshow_timer', setInterval(callback, this.get_attribute('slideshow_duration') * 1000));
2369
+ },
2370
+
2371
+ /**
2372
+ * Start Slideshow
2373
+ */
2374
+ slideshow_start: function() {
2375
+ if ( !this.slideshow_enabled() ) {
2376
+ return false;
2377
+ }
2378
+ this.set_attribute('slideshow_active', true);
2379
+ this.dom_get().addClass('slideshow_active');
2380
+ // Clear residual timers
2381
+ this.slideshow_clear_timer();
2382
+ // Start timer
2383
+ var v = this;
2384
+ this.slideshow_set_timer(function() {
2385
+ // Pause slideshow until next item fully loaded
2386
+ v.slideshow_pause();
2387
+
2388
+ // Show next item
2389
+ v.item_next();
2390
+ });
2391
+ this.trigger('slideshow-start');
2392
+ },
2393
+
2394
+ /**
2395
+ * Stop Slideshow
2396
+ * @param bool full (optional) Full stop (TRUE) or pause (FALSE) (Default: TRUE)
2397
+ */
2398
+ slideshow_stop: function(full) {
2399
+ if ( !this.util.is_bool(full) ) {
2400
+ full = true;
2401
+ }
2402
+ if ( full ) {
2403
+ this.set_attribute('slideshow_active', false);
2404
+ this.dom_get().removeClass('slideshow_active');
2405
+ }
2406
+ // Kill timers
2407
+ this.slideshow_clear_timer();
2408
+ this.trigger('slideshow-stop');
2409
+ },
2410
+
2411
+ slideshow_toggle: function() {
2412
+ if ( !this.slideshow_enabled() ) {
2413
+ return false;
2414
+ }
2415
+ if ( this.slideshow_active() ) {
2416
+ this.slideshow_stop();
2417
+ } else {
2418
+ this.slideshow_start();
2419
+ }
2420
+ this.trigger('slideshow-toggle');
2421
+ },
2422
+
2423
+ /**
2424
+ * Pause Slideshow
2425
+ * @param bool mode (optional) Pause (TRUE) or Resume (FALSE) slideshow (default: TRUE)
2426
+ */
2427
+ slideshow_pause: function(mode) {
2428
+ // Validate
2429
+ if ( !this.util.is_bool(mode) ) {
2430
+ mode = true;
2431
+ }
2432
+ // Set viewer slideshow properties
2433
+ if ( this.slideshow_active() ) {
2434
+ if ( !mode ) {
2435
+ // Slideshow resumed
2436
+ this.slideshow_start();
2437
+ } else {
2438
+ // Slideshow paused
2439
+ this.slideshow_stop(false);
2440
+ }
2441
+ }
2442
+ this.trigger('slideshow-pause');
2443
+ },
2444
+
2445
+ /**
2446
+ * Resume slideshow
2447
+ */
2448
+ slideshow_resume: function() {
2449
+ this.slideshow_pause(false);
2450
+ },
2451
+
2452
+ /**
2453
+ * Next item
2454
+ */
2455
+ item_next: function() {
2456
+ var g = this.get_item().get_group(true);
2457
+ var v = this;
2458
+ var ev = 'item-next';
2459
+ var st = ['events', 'viewer', ev].join('_');
2460
+ // Setup event handler
2461
+ if ( !g.get_status(st) ) {
2462
+ g.set_status(st);
2463
+ g.on(ev, function(e) {
2464
+ v.trigger(e.type);
2465
+ });
2466
+ }
2467
+ g.show_next();
2468
+ },
2469
+
2470
+ /**
2471
+ * Previous item
2472
+ */
2473
+ item_prev: function() {
2474
+ var g = this.get_item().get_group(true);
2475
+ var v = this;
2476
+ var ev = 'item-prev';
2477
+ var st = ['events', 'viewer', ev].join('_');
2478
+ if ( !g.get_status(st) ) {
2479
+ g.set_status(st);
2480
+ g.on(ev, function() {
2481
+ v.trigger(ev);
2482
+ });
2483
+ }
2484
+ g.show_prev();
2485
+ },
2486
+
2487
+ /**
2488
+ * Close viewer
2489
+ */
2490
+ close: function() {
2491
+ // Deactivate
2492
+ this.set_active(false);
2493
+ var v = this;
2494
+ var thm = this.get_theme();
2495
+ thm.transition('unload')
2496
+ .always(function() {
2497
+ thm.transition('close', true).always(function() {
2498
+ // End processes
2499
+ v.reset();
2500
+ v.trigger('close');
2501
+ });
2502
+ })
2503
+ .fail(function() {
2504
+ thm.dom_get_tag('item', 'content').attr('style', '');
2505
+ });
2506
+ return false;
2507
+ }
2508
+ };
2509
+
2510
+ View.Viewer = Component.extend(Viewer);
2511
+
2512
+ /**
2513
+ * Content group
2514
+ * @param obj options Init options
2515
+ */
2516
+ var Group = {
2517
+ /* Configuration */
2518
+
2519
+ _slug: 'group',
2520
+ _reciprocal: true,
2521
+ _refs: {
2522
+ 'current': 'Content_Item'
2523
+ },
2524
+
2525
+ /* References */
2526
+
2527
+ current: null,
2528
+
2529
+ /* Properties */
2530
+
2531
+ /**
2532
+ * Selector for getting group items
2533
+ * @var string
2534
+ */
2535
+ selector: null,
2536
+
2537
+ /* Methods */
2538
+
2539
+ /* Init */
2540
+
2541
+ _hooks: function() {
2542
+ var t = this;
2543
+ this.on(['item-prev', 'item-next'], function() {
2544
+ t.trigger('item-change');
2545
+ });
2546
+ },
2547
+
2548
+ /* Properties */
2549
+
2550
+ /**
2551
+ * Retrieve selector for group items
2552
+ * @return string Group items selector
2553
+ */
2554
+ get_selector: function() {
2555
+ if ( this.util.is_empty(this.selector) ) {
2556
+ // Build selector
2557
+ this.selector = this.util.format('a[%s="%s"]', this.dom_get_attribute(), this.get_id());
2558
+ }
2559
+ return this.selector;
2560
+ },
2561
+
2562
+ /**
2563
+ * Retrieve group items
2564
+ */
2565
+ get_items: function() {
2566
+ var items = $(this.get_selector());
2567
+ if ( 0 === items.length && this.has_current() ) {
2568
+ items = this.get_current().dom_get();
2569
+ }
2570
+ return items;
2571
+ },
2572
+
2573
+ /**
2574
+ * Retrieve item at specified index
2575
+ * If no index specified, first item is returned
2576
+ * @param int idx Index of item to return
2577
+ * @return Content_Item Item
2578
+ */
2579
+ get_item: function(idx) {
2580
+ // Validation
2581
+ if ( !this.util.is_int(idx) ) {
2582
+ idx = 0;
2583
+ }
2584
+ // Retrieve all items
2585
+ var items = this.get_items();
2586
+ // Validate index
2587
+ var max = this.get_size() - 1;
2588
+ if ( idx > max ) {
2589
+ idx = max;
2590
+ }
2591
+ // Return specified item
2592
+ return items.get(idx);
2593
+ },
2594
+
2595
+ /**
2596
+ * Retrieve (zero-based) position of specified item in group
2597
+ * @param Content_Item item Item to locate in group
2598
+ * @return int Index position of item in group (-1 if item not in group)
2599
+ */
2600
+ get_pos: function(item) {
2601
+ if ( this.util.is_empty(item) ) {
2602
+ // Get current item
2603
+ item = this.get_current();
2604
+ }
2605
+ return ( this.util.is_type(item, View.Content_Item) ) ? this.get_items().index(item.dom_get()) : -1;
2606
+ },
2607
+
2608
+ /**
2609
+ * Check if current item set in group
2610
+ * @return bool TRUE if current item is set
2611
+ */
2612
+ has_current: function() {
2613
+ // Sanitize
2614
+ return ( !this.util.is_empty( this.get_current() ) );
2615
+ },
2616
+
2617
+ /**
2618
+ * Retrieve current item
2619
+ * @uses Group.current
2620
+ * @return NULL|Content_Item Current item (NULL if current item not set or invalid)
2621
+ */
2622
+ get_current: function() {
2623
+ // Sanitize
2624
+ if ( null !== this.current && !this.util.is_type(this.current, View.Content_Item) ) {
2625
+ this.current = null;
2626
+ }
2627
+ return this.current;
2628
+ },
2629
+
2630
+ /**
2631
+ * Sets current group item
2632
+ * @param Content_Item item Item to set as current
2633
+ */
2634
+ set_current: function(item) {
2635
+ // Validate
2636
+ if ( this.util.is_type(item, View.Content_Item) ) {
2637
+ // Set current item
2638
+ this.current = item;
2639
+ }
2640
+ },
2641
+
2642
+ get_next: function(item) {
2643
+ // Validate
2644
+ if ( !this.util.is_type(item, View.Content_Item) ) {
2645
+ item = this.get_current();
2646
+ }
2647
+ if ( this.get_size() === 1 ) {
2648
+ return item;
2649
+ }
2650
+ var next = null;
2651
+ var pos = this.get_pos(item);
2652
+ if ( pos !== -1 ) {
2653
+ pos = ( pos + 1 < this.get_size() ) ? pos + 1 : 0;
2654
+ if ( 0 !== pos || item.get_viewer().get_attribute('loop') ) {
2655
+ next = this.get_item(pos);
2656
+ }
2657
+ }
2658
+ return next;
2659
+ },
2660
+
2661
+ get_prev: function(item) {
2662
+ // Validate
2663
+ if ( !this.util.is_type(item, View.Content_Item) ) {
2664
+ item = this.get_current();
2665
+ }
2666
+ if ( this.get_size() === 1 ) {
2667
+ return item;
2668
+ }
2669
+ var prev = null;
2670
+ var pos = this.get_pos(item);
2671
+ if ( pos !== -1 && ( 0 !== pos || item.get_viewer().get_attribute('loop') ) ) {
2672
+ if ( pos === 0 ) {
2673
+ pos = this.get_size();
2674
+ }
2675
+ pos -= 1;
2676
+ prev = this.get_item(pos);
2677
+ }
2678
+ return prev;
2679
+ },
2680
+
2681
+ show_next: function(item) {
2682
+ if ( this.get_size() > 1 ) {
2683
+ // Retrieve item
2684
+ var next = this.get_next(item);
2685
+ if ( !next ) {
2686
+ if ( !this.util.is_type(item, View.Content_Item) ) {
2687
+ item = this.get_current();
2688
+ }
2689
+ item.get_viewer().close();
2690
+ }
2691
+ var i = this.get_controller().get_item(next);
2692
+ // Update current item
2693
+ this.set_current(i);
2694
+ // Show item
2695
+ i.show();
2696
+ // Fire event
2697
+ this.trigger('item-next');
2698
+ }
2699
+ },
2700
+
2701
+ show_prev: function(item) {
2702
+ if ( this.get_size() > 1 ) {
2703
+ // Retrieve item
2704
+ var prev = this.get_prev(item);
2705
+ if ( !prev ) {
2706
+ if ( !this.util.is_type(item, View.Content_Item) ) {
2707
+ item = this.get_current();
2708
+ }
2709
+ item.get_viewer().close();
2710
+ }
2711
+ var i = this.get_controller().get_item(prev);
2712
+ // Update current item
2713
+ this.set_current(i);
2714
+ // Show item
2715
+ i.show();
2716
+ // Fire event
2717
+ this.trigger('item-prev');
2718
+ }
2719
+ },
2720
+
2721
+ /**
2722
+ * Retrieve total number of items in group
2723
+ * @return int Number of items in group
2724
+ */
2725
+ get_size: function() {
2726
+ return this.get_items().length;
2727
+ },
2728
+
2729
+ is_single: function() {
2730
+ return ( this.get_size() === 1 );
2731
+ }
2732
+ };
2733
+
2734
+ View.Group = Component.extend(Group);
2735
+
2736
+ /**
2737
+ * Content Handler
2738
+ * @param obj options Init options
2739
+ */
2740
+ var Content_Handler = {
2741
+
2742
+ /* Configuration */
2743
+
2744
+ _slug: 'content_handler',
2745
+ _refs: {
2746
+ 'item': 'Content_Item'
2747
+ },
2748
+
2749
+ /* References */
2750
+
2751
+ item: null,
2752
+
2753
+ /* Properties */
2754
+
2755
+ /**
2756
+ * Raw layout template
2757
+ * @var string
2758
+ */
2759
+ template: '',
2760
+
2761
+ /* Methods */
2762
+
2763
+ /* Item */
2764
+
2765
+ /**
2766
+ * Check if item instance set for type
2767
+ * @uses get_item()
2768
+ * @uses clear_item() to remove invalid item values
2769
+ * @return bool TRUE if valid item set, FALSE otherwise
2770
+ */
2771
+ has_item: function() {
2772
+ return ( this.util.is_empty(this.get_item()) ) ? false : true;
2773
+ },
2774
+
2775
+ /**
2776
+ * Retrieve item instance set on type
2777
+ * @uses get_component()
2778
+ * @return mixed Content_Item if valid item set, NULL otherwise
2779
+ */
2780
+ get_item: function() {
2781
+ return this.get_component('item');
2782
+ },
2783
+
2784
+ /**
2785
+ * Set item instance for type
2786
+ * Items are only meant to be set/used while item is being processed
2787
+ * @uses set_component()
2788
+ * @param Content_Item item Item instance
2789
+ * @return obj|null Item instance if item successfully set, NULL otherwise
2790
+ */
2791
+ set_item: function(item) {
2792
+ // Set reference
2793
+ var r = this.set_component('item', item);
2794
+ return r;
2795
+ },
2796
+
2797
+ /**
2798
+ * Clear item instance from type
2799
+ * Sets value to NULL
2800
+ */
2801
+ clear_item: function() {
2802
+ this.clear_component('item');
2803
+ },
2804
+
2805
+ /* Evaluation */
2806
+
2807
+ /**
2808
+ * Check if item matches content handler
2809
+ * @param object item Content_Item instance to check for type match
2810
+ * @return bool TRUE if type matches, FALSE otherwise
2811
+ */
2812
+ match: function(item) {
2813
+ // Validate
2814
+ var attr = 'match';
2815
+ var m = this.get_attribute(attr);
2816
+ // Stop processing types with no matching algorithm
2817
+ if ( !this.util.is_empty(m) ) {
2818
+ // Process regex patterns
2819
+
2820
+ // String-based
2821
+ if ( this.util.is_string(m) ) {
2822
+ // Create new regexp object
2823
+ m = new RegExp(m, "i");
2824
+ this.set_attribute(attr, m);
2825
+ }
2826
+ // RegExp based
2827
+ if ( this.util.is_type(m, RegExp) ) {
2828
+ return m.test(item.get_uri());
2829
+ }
2830
+ // Process function
2831
+ if ( this.util.is_func(m) ) {
2832
+ return ( m.call(this, item) ) ? true : false;
2833
+ }
2834
+ }
2835
+ // Default
2836
+ return false;
2837
+ },
2838
+
2839
+ /* Processing/Output */
2840
+
2841
+ /**
2842
+ * Loads item data
2843
+ * @param obj item Content item to load data for
2844
+ * @return obj Promise that is resolved when item data is loaded
2845
+ */
2846
+ load: function(item) {
2847
+ var dfr = $.Deferred();
2848
+ var ret = this.call_attribute('load', item, dfr);
2849
+ // Handle missing load method
2850
+ if ( null === ret ) {
2851
+ dfr.resolve();
2852
+ }
2853
+ return dfr.promise();
2854
+ },
2855
+
2856
+ /**
2857
+ * Render output to display item
2858
+ * @param Content_Item item Item to render output for
2859
+ * @return obj jQuery.Promise that is resolved when item is rendered
2860
+ */
2861
+ render: function(item) {
2862
+ var dfr = $.Deferred();
2863
+ // Validate
2864
+ this.call_attribute('render', item, dfr);
2865
+ return dfr.promise();
2866
+ }
2867
+ };
2868
+
2869
+ View.Content_Handler = Component.extend(Content_Handler);
2870
+
2871
+ /**
2872
+ * Content Item
2873
+ * @param obj options Init options
2874
+ */
2875
+ var Content_Item = {
2876
+ /* Configuration */
2877
+
2878
+ _slug: 'content_item',
2879
+ _reciprocal: true,
2880
+ _refs: {
2881
+ 'viewer': 'Viewer',
2882
+ 'group': 'Group',
2883
+ 'type': 'Content_Handler'
2884
+ },
2885
+
2886
+ _attr_default: {
2887
+ source: null,
2888
+ permalink: null,
2889
+ dimensions: null,
2890
+ title: '',
2891
+ group: null,
2892
+ internal: false,
2893
+ output: null
2894
+ },
2895
+
2896
+ /* References */
2897
+
2898
+ group: null,
2899
+ viewer: null,
2900
+ type: null,
2901
+
2902
+ /* Properties */
2903
+
2904
+ data: null,
2905
+ loaded: null,
2906
+
2907
+ /* Init */
2908
+
2909
+ _c: function(el) {
2910
+ // Save element to instance
2911
+ this.dom_set(el);
2912
+ // Default initialization
2913
+ this._super();
2914
+ },
2915
+
2916
+ /* Methods */
2917
+
2918
+ /*-** Attributes **-*/
2919
+
2920
+ /**
2921
+ * Build default attributes
2922
+ * Populates attributes with asset properties (attachments)
2923
+ * Overrides super class method
2924
+ * @uses Component.init_default_attributes()
2925
+ */
2926
+ init_default_attributes: function() {
2927
+ this._super();
2928
+ // Add asset properties
2929
+ var d = this.dom_get();
2930
+ var key = d.attr(this.util.get_attribute('asset')) || null;
2931
+ var assets = this.get_controller().assets || null;
2932
+ // Merge asset data with default attributes
2933
+ if ( this.util.is_string(key) ) {
2934
+ var attrs = [{}, this._attr_default, {'permalink': d.attr('href')}];
2935
+ if ( this.util.is_obj(assets) ) {
2936
+ var t = this;
2937
+ /**
2938
+ * Retrieve item assets
2939
+ * Handles variant items as well (Retrieves parent item assets)
2940
+ * @param string key Item URI
2941
+ * @return obj Item assets (Empty if no match)
2942
+ */
2943
+ var get_assets = function(key) {
2944
+ var ret = {};
2945
+ if ( key in assets && t.util.is_obj(assets[key]) ) {
2946
+ ret = assets[key];
2947
+ }
2948
+ return ret;
2949
+ };
2950
+ // Save assets
2951
+ attrs.push(get_assets(key));
2952
+ }
2953
+ this._attr_default = $.extend.apply(this, attrs);
2954
+ }
2955
+ return this._attr_default;
2956
+ },
2957
+
2958
+ /*-** Properties **-*/
2959
+
2960
+ /**
2961
+ * Retrieve item output
2962
+ * Output generated based on content handler if not previously generated
2963
+ * @uses get_attribute() to retrieve cached output
2964
+ * @uses set_attribute() to cache generated output
2965
+ * @uses get_type() to retrieve item type
2966
+ * @uses Content_Handler.render() to generate item output
2967
+ * @return obj jQuery.Promise that is resolved when output is retrieved
2968
+ */
2969
+ get_output: function() {
2970
+ var dfr = $.Deferred();
2971
+ // Check for cached output
2972
+ var ret = this.get_attribute('output');
2973
+ if ( this.util.is_string(ret) ) {
2974
+ dfr.resolve(ret);
2975
+ } else if ( this.has_type() ) {
2976
+ // Render output from scratch (if necessary)
2977
+ // Get item type
2978
+ var type = this.get_type();
2979
+ // Render type-based output
2980
+ var item = this;
2981
+ type.render(this).done(function(output) {
2982
+ // Cache output
2983
+ item.set_output(output);
2984
+ dfr.resolve(output);
2985
+ });
2986
+ } else {
2987
+ dfr.resolve('');
2988
+ }
2989
+ return dfr.promise();
2990
+ },
2991
+
2992
+ /**
2993
+ * Cache output for future retrieval
2994
+ * @uses set_attribute() to cache output
2995
+ */
2996
+ set_output: function(out) {
2997
+ if ( this.util.is_string(out, false) ) {
2998
+ this.set_attribute('output', out);
2999
+ }
3000
+ },
3001
+
3002
+ /**
3003
+ * Retrieve item output
3004
+ * Alias for `get_output()`
3005
+ * @return jQuery.Promise Deferred that is resolved when content is retrieved
3006
+ */
3007
+ get_content: function() {
3008
+ return this.get_output();
3009
+ },
3010
+
3011
+ /**
3012
+ * Retrieve item URI
3013
+ * @param string mode (optional) Which URI should be retrieved
3014
+ * > source: Media source
3015
+ * > permalink: Item permalink
3016
+ * @return string Item URI
3017
+ */
3018
+ get_uri: function(mode) {
3019
+ // Validate
3020
+ if ( $.inArray(mode ,['source', 'permalink']) === -1 ) {
3021
+ mode = 'source';
3022
+ }
3023
+ // Retrieve URI
3024
+ var ret = this.get_attribute(mode);
3025
+ if ( !this.util.is_string(ret) ) {
3026
+ ret = ( 'source' === mode ) ? this.get_attribute('permalink') : '';
3027
+ }
3028
+ // Format
3029
+ ret = ret.replace(/&(#38|amp);/, '&');
3030
+ return ret;
3031
+ },
3032
+
3033
+ /**
3034
+ * Retrieve item title
3035
+ */
3036
+ get_title: function() {
3037
+ var prop = 'title';
3038
+ var prop_cached = prop + '_cached';
3039
+ // Check for cached value
3040
+ if ( this.has_attribute(prop_cached) ) {
3041
+ return this.get_attribute(prop_cached, '');
3042
+ }
3043
+
3044
+ var title = '';
3045
+ // Generate title from DOM values
3046
+ var dom = this.dom_get();
3047
+ var t = this;
3048
+ /**
3049
+ * Validate title value.
3050
+ *
3051
+ * Removes default title based on user option.
3052
+ *
3053
+ * @param string title Title to check.
3054
+ * @return string Current title or empty string (if default title set and not permitted).
3055
+ */
3056
+ var validate = function(title) {
3057
+ // Return empty string if empty title set.
3058
+ if ( typeof title !== 'string' || '' === title.trim() ) {
3059
+ return '';
3060
+ }
3061
+ // Cleanup title.
3062
+ title = title.trim();
3063
+ // Stop processing if default title is allowed.
3064
+ if ( t.get_viewer().get_attribute('title_default') ) {
3065
+ return title;
3066
+ }
3067
+
3068
+ // Check if default title is used.
3069
+ if ( title === t.get_title_default() ) {
3070
+ title = '';
3071
+ }
3072
+ return title;
3073
+ };
3074
+
3075
+ // DOM-based caption
3076
+ if ( dom.length ) {
3077
+ // Link title (generally must be manually-entered)
3078
+ title = dom.attr(prop);
3079
+
3080
+ // Figcaption element
3081
+ if ( !title ) {
3082
+ title = dom.closest('figure').find('figcaption').first().html();
3083
+ }
3084
+
3085
+ // Class Name
3086
+ if ( !title ) {
3087
+ title = dom.closest('figure').find('.wp-caption-text').first().html();
3088
+ }
3089
+ }
3090
+
3091
+ // Saved attributes
3092
+ if ( !title ) {
3093
+ var props = ['caption', 'title'];
3094
+ for ( var x = 0; x < props.length; x++ ) {
3095
+ title = validate( this.get_attribute(props[x], '') );
3096
+ if ( !this.util.is_empty(title) ) {
3097
+ break;
3098
+ }
3099
+ }
3100
+ }
3101
+
3102
+ // Fallbacks
3103
+ if ( !title && dom.length ) {
3104
+ // Image Alt attribute
3105
+ title = validate( dom.find('img').first().attr('alt') );
3106
+
3107
+ // Element text
3108
+ if ( !title ) {
3109
+ title = validate( dom.get(0).innerText.trim() );
3110
+ }
3111
+ }
3112
+
3113
+ // Final validation.
3114
+ title = validate(title);
3115
+
3116
+ // Cache retrieved value
3117
+ this.set_attribute(prop_cached, title);
3118
+ // Return value
3119
+ return title;
3120
+ },
3121
+
3122
+ /**
3123
+ * Retrieve default title.
3124
+ *
3125
+ * WordPress-generated default title for attachments is base file name (without extension).
3126
+ *
3127
+ * @return string Default title.
3128
+ */
3129
+ get_title_default: function() {
3130
+ var val = '';
3131
+ var prop = 'title_default';
3132
+ // Build default title if necessary.
3133
+ if ( !this.has_attribute(prop) ) {
3134
+ var f = this.get_uri('source');
3135
+ var i = f.lastIndexOf('/');
3136
+ if ( -1 !== i ) {
3137
+ f = f.substr(i + 1);
3138
+ i = f.lastIndexOf('.');
3139
+ if ( -1 !== i ) {
3140
+ f = f.substr(0, i);
3141
+ }
3142
+ }
3143
+ // Save default title
3144
+ val = this.set_attribute(prop, f);
3145
+ } else {
3146
+ val = this.get_attribute(prop);
3147
+ }
3148
+ return val;
3149
+ },
3150
+
3151
+ /**
3152
+ * Retrieve item dimensions
3153
+ * @return obj Item `width` and `height` properties (px)
3154
+ */
3155
+ get_dimensions: function() {
3156
+ return $.extend({'width': 0, 'height': 0}, this.get_attribute('dimensions'), {});
3157
+ },
3158
+
3159
+ /**
3160
+ * Save item data to instance
3161
+ * Item data is saved when rendered
3162
+ * @param mixed data Item data (property cleared if NULL)
3163
+ */
3164
+ set_data: function(data) {
3165
+ this.data = data;
3166
+ },
3167
+
3168
+ get_data: function() {
3169
+ return this.data;
3170
+ },
3171
+
3172
+ /**
3173
+ * Determine gallery type
3174
+ * @return string|null Gallery type ID (NULL if item not in gallery)
3175
+ */
3176
+ gallery_type: function() {
3177
+ var ret = null;
3178
+ var types = {
3179
+ 'wp': '.gallery-icon',
3180
+ 'ngg': '.ngg-gallery-thumbnail'
3181
+ };
3182
+
3183
+ var dom = this.dom_get();
3184
+ for ( var type in types ) {
3185
+ if ( dom.parent(types[type]).length > 0 ) {
3186
+ ret = type;
3187
+ break;
3188
+ }
3189
+ }
3190
+ return ret;
3191
+ },
3192
+
3193
+ /**
3194
+ * Check if current link is part of a gallery
3195
+ * @param string gType (optional) Gallery type to check for
3196
+ * @return bool TRUE if link is part of (specified) gallery (FALSE otherwise)
3197
+ */
3198
+ in_gallery: function(gType) {
3199
+ var type = this.gallery_type();
3200
+ // No gallery
3201
+ if ( null === type ) {
3202
+ return false;
3203
+ }
3204
+ // Boolean check
3205
+ if ( !this.util.is_string(gType) ) {
3206
+ return true;
3207
+ }
3208
+ // Check for specific gallery type
3209
+ return ( gType === type ) ? true : false;
3210
+ },
3211
+
3212
+ /*-** Component References **-*/
3213
+
3214
+ /* Viewer */
3215
+
3216
+ get_viewer: function() {
3217
+ return this.get_component('viewer', {get_default: true});
3218
+ },
3219
+
3220
+ /**
3221
+ * Sets item's viewer property
3222
+ * @uses View.get_viewer() to retrieve global viewer
3223
+ * @uses this.viewer to save item's viewer
3224
+ * @param string|View.Viewer v Viewer to set for item
3225
+ * > Item's viewer is reset if invalid viewer provided
3226
+ */
3227
+ set_viewer: function(v) {
3228
+ return this.set_component('viewer', v);
3229
+ },
3230
+
3231
+ /* Group */
3232
+
3233
+ /**
3234
+ * Retrieve item's group
3235
+ * @param bool set_current (optional) Sets item as current item in group (Default: FALSE)
3236
+ * @return View.Group|bool Group reference item belongs to (FALSE if no group)
3237
+ */
3238
+ get_group: function(set_current) {
3239
+ var prop = 'group';
3240
+ // Check if group reference already set
3241
+ var g = this.get_component(prop);
3242
+ if ( g ) {
3243
+ } else {
3244
+ // Set empty group if no group exists
3245
+ g = this.set_component(prop, new View.Group());
3246
+ set_current = true;
3247
+ }
3248
+ if ( !!set_current ) {
3249
+ g.set_current(this);
3250
+ }
3251
+ return g;
3252
+ },
3253
+
3254
+ /**
3255
+ * Sets item's group property
3256
+ * @uses View.get_group() to retrieve global group
3257
+ * @uses this.group to set item's group
3258
+ * @param string|View.Group g Group to set for item
3259
+ * > Item's group is reset if invalid group provided
3260
+ */
3261
+ set_group: function(g) {
3262
+ // If group ID set, get object reference
3263
+ if ( this.util.is_string(g) ) {
3264
+ g = this.get_controller().get_group(g);
3265
+ }
3266
+
3267
+ // Set (or clear) group property
3268
+ this.group = ( this.util.is_type(g, View.Group) ) ? g : false;
3269
+ },
3270
+
3271
+ /* Content Handler */
3272
+
3273
+ /**
3274
+ * Retrieve item type
3275
+ * @uses get_component() to retrieve saved reference to Content_Handler instance
3276
+ * @uses View.get_content_handler() to determine item content handler (if necessary)
3277
+ * @return Content_Handler|null Content Handler of item (NULL no valid type exists)
3278
+ */
3279
+ get_type: function() {
3280
+ var t = this.get_component('type', {check_attr: false});
3281
+ if ( !t ) {
3282
+ t = this.set_type(this.get_controller().get_content_handler(this));
3283
+ }
3284
+ return t;
3285
+ },
3286
+
3287
+ /**
3288
+ * Save content handler reference
3289
+ * @uses set_component() to save type reference
3290
+ * @return Content_Handler|null Saved content handler (NULL if invalid)
3291
+ */
3292
+ set_type: function(type) {
3293
+ return this.set_component('type', type);
3294
+ },
3295
+
3296
+ /**
3297
+ * Check if content handler exists for item
3298
+ * @return bool TRUE if content handler exists, FALSE otherwise
3299
+ */
3300
+ has_type: function() {
3301
+ var ret = !this.util.is_empty(this.get_type());
3302
+ return ret;
3303
+ },
3304
+
3305
+ /* Actions */
3306
+
3307
+ /**
3308
+ * Display item in viewer
3309
+ * @uses get_viewer() to retrieve viewer instance for item
3310
+ * @uses Viewer.show() to display item in viewer
3311
+ * @param obj options (optional) Options
3312
+ */
3313
+ show: function(options) {
3314
+ // Validate content handler
3315
+ if ( !this.has_type() ) {
3316
+ return false;
3317
+ }
3318
+ // Set display options
3319
+ this.set_attribute('options_show', options);
3320
+ // Retrieve viewer
3321
+ var v = this.get_viewer();
3322
+ // Load item
3323
+ this.load();
3324
+ var ret = v.show(this);
3325
+ return ret;
3326
+ },
3327
+
3328
+ /**
3329
+ * Load item data
3330
+ *
3331
+ * Retrieves item data from external sources (if necessary)
3332
+ * @uses this.loaded to save loaded state
3333
+ * @return obj Promise that is resolved when item data is loaded
3334
+ */
3335
+ load: function() {
3336
+ if ( !this.util.is_promise(this.loaded) ) {
3337
+ // Load item data (via content handler)
3338
+ this.loaded = this.get_type().load(this);
3339
+ }
3340
+ return this.loaded.promise();
3341
+ },
3342
+
3343
+ reset: function() {
3344
+ this.set_attribute('options_show', null);
3345
+ }
3346
+ };
3347
+
3348
+ View.Content_Item = Component.extend(Content_Item);
3349
+
3350
+ /**
3351
+ * Modeled Component
3352
+ */
3353
+ var Modeled_Component = {
3354
+
3355
+ _slug: 'modeled_component',
3356
+
3357
+ /* Methods */
3358
+
3359
+ /* Attributes */
3360
+
3361
+ /**
3362
+ * Retrieve attribute
3363
+ * Gives priority to model values
3364
+ * @see Component.get_attribute()
3365
+ * @param string key Attribute to retrieve
3366
+ * @param mixed def (optional) Default value (Default: NULL)
3367
+ * @param bool check_model (optional) Check model for value (Default: TRUE)
3368
+ * @param bool enforce_type (optional) Return value data type should match default value data type (Default: TRUE)
3369
+ * @return mixed Attribute value
3370
+ */
3371
+ get_attribute: function(key, def, check_model, enforce_type) {
3372
+ // Validate
3373
+ if ( !this.util.is_string(key) ) {
3374
+ // Invalid requests sent straight to super method
3375
+ return this._super(key, def, enforce_type);
3376
+ }
3377
+ if ( !this.util.is_bool(check_model) ) {
3378
+ check_model = true;
3379
+ }
3380
+ var ret = null;
3381
+ // Check model for attribute
3382
+ if ( check_model ) {
3383
+ var m = this.get_ancestor(key, false);
3384
+ if ( this.util.in_obj(m, key) ) {
3385
+ ret = m[key];
3386
+ }
3387
+ }
3388
+ // Check standard attributes as fallback
3389
+ if ( null === ret ) {
3390
+ ret = this._super(key, def, enforce_type);
3391
+ }
3392
+ return ret;
3393
+ },
3394
+
3395
+ /**
3396
+ * Get attribute recursively
3397
+ * Merges objects from ancestors together
3398
+ * @see Component.get_attribute() for more information
3399
+ */
3400
+ get_attribute_recursive: function(key, def, enforce_type) {
3401
+ var ret = this.get_attribute(key, def, true, enforce_type);
3402
+ if ( this.util.is_obj(ret) ) {
3403
+ // Merge ancestor objects
3404
+ var models = this.get_ancestors(false);
3405
+ ret = [ret];
3406
+ var t = this;
3407
+ $.each(models, function(idx, model) {
3408
+ if ( key in model && t.util.is_obj(model[key]) ) {
3409
+ ret.push(model[key]);
3410
+ }
3411
+ });
3412
+ // Merge transition handlers into current theme
3413
+ ret.push({});
3414
+ ret = $.extend.apply($, ret.reverse());
3415
+ }
3416
+ return ret;
3417
+ },
3418
+
3419
+ /**
3420
+ * Set attribute value
3421
+ * Gives priority to model values
3422
+ * @see Component.set_attribute()
3423
+ * @param string key Attribute to set
3424
+ * @param mixed val Value to set for attribute
3425
+ * @param bool|obj use_model (optional) Set the value on the model (Default: TRUE)
3426
+ * > bool: Set attribute on current model (TRUE) or as standard attribute (FALSE)
3427
+ * > obj: Model object to set attribute on
3428
+ * @return mixed Attribute value
3429
+ */
3430
+ set_attribute: function(key, val, use_model) {
3431
+ // Validate
3432
+ if ( ( !this.util.is_string(key) ) || !this.util.is_set(val) ) {
3433
+ return false;
3434
+ }
3435
+ if ( !this.util.is_bool(use_model) && !this.util.is_obj(use_model) ) {
3436
+ use_model = true;
3437
+ }
3438
+ // Determine where to set attribute
3439
+ if ( !!use_model ) {
3440
+ var model = this.util.is_obj(use_model) ? use_model : this.get_model();
3441
+
3442
+ // Set attribute in model
3443
+ model[key] = val;
3444
+ } else {
3445
+ // Set as standard attribute
3446
+ this._super(key, val);
3447
+ }
3448
+ return val;
3449
+ },
3450
+
3451
+
3452
+ /* Model */
3453
+
3454
+ /**
3455
+ * Retrieve Template model
3456
+ * @return obj Model (Default: Empty object)
3457
+ */
3458
+ get_model: function() {
3459
+ var m = this.get_attribute('model', null, false);
3460
+ if ( !this.util.is_obj(m) ) {
3461
+ // Set default value
3462
+ m = {};
3463
+ this.set_attribute('model', m, false);
3464
+ }
3465
+ return m;
3466
+ },
3467
+
3468
+
3469
+ /**
3470
+ * Check if instance has model
3471
+ * @return bool TRUE if model is set, FALSE otherwise
3472
+ */
3473
+ has_model: function() {
3474
+ return ( this.util.is_empty( this.get_model() ) ) ? false : true;
3475
+ },
3476
+
3477
+
3478
+
3479
+ /**
3480
+ * Check if specified attribute exists in model
3481
+ * @param string key Attribute to check for
3482
+ * @return bool TRUE if attribute exists, FALSE otherwise
3483
+ */
3484
+ in_model: function(key) {
3485
+ return ( this.util.in_obj(this.get_model(), key) ) ? true : false;
3486
+ },
3487
+
3488
+ /**
3489
+ * Retrieve all ancestor models
3490
+ * @param bool inc_current (optional) Include current model in list (Default: FALSE)
3491
+ * @return array Theme ancestor models (Closest parent first)
3492
+ */
3493
+ get_ancestors: function(inc_current) {
3494
+ var ret = [];
3495
+ var m = this.get_model();
3496
+ while ( this.util.is_obj(m) ) {
3497
+ ret.push(m);
3498
+ m = ( this.util.in_obj(m, 'parent') && this.util.is_obj(m.parent) ) ? m.parent : null;
3499
+ }
3500
+ // Remove current model from list
3501
+ if ( !inc_current ) {
3502
+ ret.shift();
3503
+ }
3504
+ return ret;
3505
+ },
3506
+
3507
+ /**
3508
+ * Retrieve first ancestor of current theme with specified attribute
3509
+ * > Current model is also evaluated
3510
+ * @param string attr Attribute to search ancestors for
3511
+ * @param bool safe_mode (optional) Return current model if no matching ancestor found (Default: TRUE)
3512
+ * @return obj Theme ancestor (Default: Current theme model)
3513
+ */
3514
+ get_ancestor: function(attr, safe_mode) {
3515
+ // Validate
3516
+ if ( !this.util.is_string(attr) ) {
3517
+ return false;
3518
+ }
3519
+ if ( !this.util.is_bool(safe_mode) ) {
3520
+ safe_mode = true;
3521
+ }
3522
+ var mcurr = this.get_model();
3523
+ var m = mcurr;
3524
+ var found = false;
3525
+ while ( this.util.is_obj(m) ) {
3526
+ // Check if attribute exists in model
3527
+ if ( this.util.in_obj(m, attr) && !this.util.is_empty(m[attr]) ) {
3528
+ found = true;
3529
+ break;
3530
+ }
3531
+ // Get next model
3532
+ m = ( this.util.in_obj(m, 'parent') ) ? m['parent'] : null;
3533
+ }
3534
+ if ( !found ) {
3535
+ if ( safe_mode ) {
3536
+ // Use current model as fallback
3537
+ if ( this.util.is_empty(m) ) {
3538
+ m = mcurr;
3539
+ }
3540
+ // Add attribute to object
3541
+ if ( !this.util.in_obj(m, attr) ) {
3542
+ m[attr] = null;
3543
+ }
3544
+ } else {
3545
+ m = null;
3546
+ }
3547
+ }
3548
+ return m;
3549
+ }
3550
+
3551
+ };
3552
+
3553
+ Modeled_Component = Component.extend(Modeled_Component);
3554
+
3555
+ /**
3556
+ * Theme
3557
+ */
3558
+ var Theme = {
3559
+
3560
+ /* Configuration */
3561
+
3562
+ _slug: 'theme',
3563
+ _refs: {
3564
+ 'viewer': 'Viewer',
3565
+ 'template': 'Template'
3566
+ },
3567
+ _models: {},
3568
+
3569
+ _attr_default: {
3570
+ template: null,
3571
+ model: null
3572
+ },
3573
+
3574
+ /* References */
3575
+
3576
+ viewer: null,
3577
+ template: null,
3578
+
3579
+ /* Methods */
3580
+
3581
+ /**
3582
+ * Custom constructor
3583
+ * @see Component._c()
3584
+ */
3585
+ _c: function(id, attributes, viewer) {
3586
+ // Validate
3587
+ if ( arguments.length === 1 && this.util.is_type(arguments[0], View.Viewer) ) {
3588
+ viewer = arguments[0];
3589
+ id = null;
3590
+ }
3591
+ // Pass parameters to parent constructor
3592
+ this._super(id, attributes);
3593
+
3594
+ // Set viewer instance
3595
+ this.set_viewer(viewer);
3596
+
3597
+ // Set theme model
3598
+ this.set_model(id);
3599
+ },
3600
+
3601
+ /* Viewer */
3602
+
3603
+ get_viewer: function() {
3604
+ return this.get_component('viewer', {check_attr: false, get_default: true});
3605
+ },
3606
+
3607
+ /**
3608
+ * Sets theme's viewer property
3609
+ * @uses View.get_viewer() to retrieve global viewer
3610
+ * @uses this.viewer to save item's viewer
3611
+ * @param string|View.Viewer v Viewer to set for item
3612
+ * > Theme's viewer is reset if invalid viewer provided
3613
+ */
3614
+ set_viewer: function(v) {
3615
+ return this.set_component('viewer', v);
3616
+ },
3617
+
3618
+ /* Template */
3619
+
3620
+ /**
3621
+ * Retrieve template instance
3622
+ * @return Template instance
3623
+ */
3624
+ get_template: function() {
3625
+ // Get saved template
3626
+ var ret = this.get_component('template');
3627
+ // Template needs to be initialized
3628
+ if ( this.util.is_empty(ret) ) {
3629
+ // Pass model to Template instance
3630
+ var attr = { 'theme': this, 'model': this.get_model() };
3631
+ ret = this.set_component('template', new View.Template(attr));
3632
+ }
3633
+ return ret;
3634
+ },
3635
+
3636
+ /* Tags */
3637
+
3638
+ /**
3639
+ * Retrieve tags from template
3640
+ * All tags will be retrieved by default
3641
+ * Specific tag/property instances can be retrieved as well
3642
+ * @see Template.get_tags()
3643
+ * @param string name (optional) Name of tags to retrieve
3644
+ * @param string prop (optional) Specific tag property to retrieve
3645
+ * @return array Tags in template
3646
+ */
3647
+ get_tags: function(name, prop) {
3648
+ return this.get_template().get_tags(name, prop);
3649
+ },
3650
+
3651
+ /**
3652
+ * Retrieve tag DOM elements
3653
+ * @see Template.dom_get_tag()
3654
+ */
3655
+ dom_get_tag: function(tag, prop) {
3656
+ return $(this.get_template().dom_get_tag(tag, prop));
3657
+ },
3658
+
3659
+ /**
3660
+ * Retrieve template tag CSS selector
3661
+ * @uses Template.get_tag_selector()
3662
+ * @param name string Tag name
3663
+ * @param prop string Tag Property
3664
+ * @return string Template tag CSS selector
3665
+ */
3666
+ get_tag_selector: function(name, prop) {
3667
+ return this.get_template().get_tag_selector(name, prop);
3668
+ },
3669
+
3670
+ /* Model */
3671
+
3672
+ /**
3673
+ * Retrieve theme models
3674
+ * @return obj Theme models
3675
+ */
3676
+ get_models: function() {
3677
+ return this._models;
3678
+ },
3679
+
3680
+ /**
3681
+ * Retrieve specified theme model
3682
+ * @param string id (optional) Theme model to retrieve
3683
+ * > Default model retrieved if ID is invalid/not set
3684
+ * @return obj Specified theme model
3685
+ */
3686
+ get_model: function(id) {
3687
+ var ret = null;
3688
+ // Pass request to superclass method
3689
+ if ( !this.util.is_set(id) && this.util.is_obj( this.get_attribute('model', null, false) ) ) {
3690
+ ret = this._super();
3691
+ } else {
3692
+ // Retrieve matching theme model
3693
+ var models = this.get_models();
3694
+ if ( !this.util.is_string(id) ) {
3695
+ id = this.get_controller().get_option('theme_default');
3696
+ }
3697
+ // Select first theme model if specified model is invalid
3698
+ if ( !this.util.in_obj(models, id) ) {
3699
+ id = $.map(models, function(v, key) { return key; })[0];
3700
+ }
3701
+ ret = models[id];
3702
+ }
3703
+ return ret;
3704
+ },
3705
+
3706
+ /**
3707
+ * Set model for current theme instance
3708
+ * @param string id (optional) Theme ID (Default theme retrieved if ID invalid)
3709
+ */
3710
+ set_model: function(id) {
3711
+ this.set_attribute('model', this.get_model(id), false);
3712
+ /* @deprecated
3713
+ // Set ID using model attributes (if necessary)
3714
+ if ( !this._check_id(true) ) {
3715
+ var m = this.get_model();
3716
+ if ( 'id' in m ) {
3717
+ this._set_id(m.id);
3718
+ }
3719
+ }
3720
+ */
3721
+ },
3722
+
3723
+ /* Properties */
3724
+
3725
+ /**
3726
+ * Generate class names for DOM node
3727
+ * @param string rtype (optional) Return data type
3728
+ * > Default: array
3729
+ * > If string supplied: Joined classes delimited by parameter
3730
+ * @uses get_class() to generate class names
3731
+ * @uses Array.join() to convert class names array to string
3732
+ * @return array Class names
3733
+ */
3734
+ get_classes: function(rtype) {
3735
+ // Build array of class names
3736
+ var cls = [];
3737
+ var thm = this;
3738
+ // Include theme parent's class name
3739
+ var models = this.get_ancestors(true);
3740
+ $.each(models, function(idx, model) {
3741
+ cls.push(thm.add_ns(model.id));
3742
+ });
3743
+ // Convert class names array to string
3744
+ if ( this.util.is_string(rtype) ) {
3745
+ cls = cls.join(rtype);
3746
+ }
3747
+ // Return class names
3748
+ return cls;
3749
+ },
3750
+
3751
+ /**
3752
+ * Get custom measurement
3753
+ * @param string attr Measurement to retrieve
3754
+ * @param obj def (optional) Default value
3755
+ * @return obj Attribute measurements
3756
+ */
3757
+ get_measurement: function(attr, def) {
3758
+ var meas = null;
3759
+ // Validate
3760
+ if ( !this.util.is_string(attr) ) {
3761
+ return meas;
3762
+ }
3763
+ if ( !this.util.is_obj(def, false) ) {
3764
+ def = {};
3765
+ }
3766
+ // Manage cache
3767
+ var attr_cache = this.util.format('%s_cache', attr);
3768
+ var cache = this.get_attribute(attr_cache, {}, false);
3769
+ var status = '_status';
3770
+ var item = this.get_viewer().get_item();
3771
+ var w = $(window);
3772
+ // Check cache freshness
3773
+ if ( !( status in cache ) || !this.util.is_obj(cache[status]) || cache[status].width !== w.width() || cache[status].height !== w.height() ) {
3774
+ cache = {};
3775
+ }
3776
+ if ( this.util.is_empty(cache) ) {
3777
+ // Set status
3778
+ cache[status] = {
3779
+ 'width': w.width(),
3780
+ 'height': w.height(),
3781
+ 'index': []
3782
+ };
3783
+ }
3784
+ // Retrieve cached values
3785
+ var pos = $.inArray(item, cache[status].index);
3786
+ if ( pos !== -1 && pos in cache ) {
3787
+ meas = cache[pos];
3788
+ }
3789
+ // Generate measurement
3790
+ if ( !this.util.is_obj(meas) ) {
3791
+ // Get custom theme measurement
3792
+ meas = this.call_attribute(attr);
3793
+ if ( !this.util.is_obj(meas) ) {
3794
+ // Retrieve fallback value
3795
+ meas = this.get_measurement_default(attr);
3796
+ }
3797
+ }
3798
+ // Normalize measurement
3799
+ meas = ( this.util.is_obj(meas) ) ? $.extend({}, def, meas) : def;
3800
+ // Cache measurement
3801
+ pos = cache[status].index.push(item) - 1;
3802
+ cache[pos] = meas;
3803
+ this.set_attribute(attr_cache, cache, false);
3804
+ // Return measurement (copy)
3805
+ return $.extend({}, meas);
3806
+ },
3807
+
3808
+ /**
3809
+ * Get default measurement using attribute's default handler
3810
+ * @param string attr Measurement attribute
3811
+ * @return obj Measurement values
3812
+ */
3813
+ get_measurement_default: function(attr) {
3814
+ // Validate
3815
+ if ( !this.util.is_string(attr) ) {
3816
+ return null;
3817
+ }
3818
+ // Find default handler
3819
+ attr = this.util.format('get_%s_default', attr);
3820
+ if ( this.util.in_obj(this, attr) ) {
3821
+ attr = this[attr];
3822
+ if ( this.util.is_func(attr) ) {
3823
+ // Execute default handler
3824
+ attr = attr.call(this);
3825
+ }
3826
+ } else {
3827
+ attr = null;
3828
+ }
3829
+ return attr;
3830
+ },
3831
+
3832
+ /**
3833
+ * Retrieve theme offset
3834
+ * @return obj Theme offset with `width` & `height` properties
3835
+ */
3836
+ get_offset: function() {
3837
+ return this.get_measurement('offset', { 'width': 0, 'height': 0});
3838
+ },
3839
+
3840
+ /**
3841
+ * Generate default offset
3842
+ * @return obj Theme offsets with `width` & `height` properties
3843
+ */
3844
+ get_offset_default: function() {
3845
+ var offset = { 'width': 0, 'height': 0 };
3846
+ var v = this.get_viewer();
3847
+ var vn = v.dom_get();
3848
+ // Clone viewer
3849
+ var vc = vn
3850
+ .clone()
3851
+ .attr('id', '')
3852
+ .css({'visibility': 'hidden', 'position': 'absolute', 'top': ''})
3853
+ .removeClass('loading')
3854
+ .appendTo(vn.parent());
3855
+ // Get offset from layout node
3856
+ var l = vc.find(v.dom_get_selector('layout'));
3857
+ if ( l.length ) {
3858
+ // Clear inline styles
3859
+ l.find('*').css({
3860
+ 'width': '',
3861
+ 'height': '',
3862
+ 'display': ''
3863
+ });
3864
+ // Resize content nodes
3865
+ var tags = this.get_tags('item', 'content');
3866
+ if ( tags.length ) {
3867
+ var offset_item = v.get_item().get_dimensions();
3868
+ // Set content dimensions
3869
+ tags = $(l.find(tags[0].get_selector('full')).get(0)).css({'width': offset_item.width, 'height': offset_item.height});
3870
+ $.each(offset_item, function(key, val) {
3871
+ offset[key] = -1 * val;
3872
+ });
3873
+ }
3874
+
3875
+ // Set offset
3876
+ offset.width += l.width();
3877
+ offset.height += l.height();
3878
+ // Normalize
3879
+ $.each(offset, function(key, val) {
3880
+ if ( val < 0 ) {
3881
+ offset[key] = 0;
3882
+ }
3883
+ });
3884
+ }
3885
+ vc.empty().remove();
3886
+ return offset;
3887
+ },
3888
+
3889
+ /**
3890
+ * Retrieve theme margins
3891
+ * @return obj Theme margin with `width` & `height` properties
3892
+ */
3893
+ get_margin: function() {
3894
+ return this.get_measurement('margin', {'width': 0, 'height': 0});
3895
+ },
3896
+
3897
+ /**
3898
+ * Retrieve item dimensions
3899
+ * Dimensions are adjusted to fit window (if necessary)
3900
+ * @return obj Item dimensions with `width` & `height` properties
3901
+ */
3902
+ get_item_dimensions: function() {
3903
+ var v = this.get_viewer();
3904
+ var dims = v.get_item().get_dimensions();
3905
+ if ( v.get_attribute('autofit', false) ) {
3906
+ // Get maximum dimensions
3907
+ var margin = this.get_margin();
3908
+ var offset = this.get_offset();
3909
+ offset.height += margin.height;
3910
+ offset.width += margin.width;
3911
+ var max = {'width': $(window).width(), 'height': $(window).height() };
3912
+ if ( max.width > offset.width ) {
3913
+ max.width -= offset.width;
3914
+ }
3915
+ if ( max.height > offset.height ) {
3916
+ max.height -= offset.height;
3917
+ }
3918
+ // Get resize factor
3919
+ var factor = Math.min(max.width / dims.width, max.height / dims.height);
3920
+ // Resize dimensions
3921
+ if ( factor < 1 ) {
3922
+ $.each(dims, function(key) {
3923
+ dims[key] = Math.round(dims[key] * factor);
3924
+ });
3925
+ }
3926
+ }
3927
+ return $.extend({}, dims);
3928
+ },
3929
+
3930
+ /**
3931
+ * Retrieve theme dimensions
3932
+ * @return obj Theme dimensions with `width` & `height` properties
3933
+ */
3934
+ get_dimensions: function() {
3935
+ var dims = this.get_item_dimensions();
3936
+ var offset = this.get_offset();
3937
+ $.each(dims, function(key) {
3938
+ dims[key] += offset[key];
3939
+ });
3940
+ return dims;
3941
+ },
3942
+
3943
+ /**
3944
+ * Retrieve all breakpoints
3945
+ * @return object Breakpoints
3946
+ */
3947
+ get_breakpoints: function() {
3948
+ return this.get_attribute_recursive('breakpoints');
3949
+ },
3950
+
3951
+ /**
3952
+ * Get breakpoint value
3953
+ * @param string target Breakpoint target
3954
+ * @return int Breakpoint value (pixels)
3955
+ */
3956
+ get_breakpoint: function(target) {
3957
+ var ret = 0;
3958
+ if ( this.util.is_string(target) ) {
3959
+ var b = this.get_attribute_recursive('breakpoints');
3960
+ if ( this.util.is_obj(b) && target in b ) {
3961
+ ret = b[target];
3962
+ }
3963
+ }
3964
+ return ret;
3965
+ },
3966
+
3967
+ /* Output */
3968
+
3969
+ /**
3970
+ * Render Theme output
3971
+ * @param bool init (optional) Initialize theme (Default: FALSE)
3972
+ * @see Template.render()
3973
+ */
3974
+ render: function(init) {
3975
+ var thm = this;
3976
+ var tpl = this.get_template();
3977
+ var st = 'events_render';
3978
+ if ( !this.get_status(st) ) {
3979
+ this.set_status(st);
3980
+ // Register events
3981
+ tpl.on([
3982
+ 'render-init',
3983
+ 'render-loading',
3984
+ 'render-complete'
3985
+ ],
3986
+ function(ev) {
3987
+ return thm.trigger(ev.type, ev.data);
3988
+ });
3989
+ }
3990
+ // Render template
3991
+ tpl.render(init);
3992
+ },
3993
+
3994
+ transition: function(event, clear_queue) {
3995
+ var dfr = null;
3996
+ var attr = 'transition';
3997
+ var v = this.get_viewer();
3998
+ var fx_temp = null;
3999
+ var anim_on = v.animation_enabled();
4000
+ if ( v.get_attribute(attr, true) && this.util.is_string(event) ) {
4001
+ var anim_stop = function() {
4002
+ var l = v.get_layout();
4003
+ l.find('*').each(function() {
4004
+ var el = $(this);
4005
+ while ( el.queue().length ) {
4006
+ el.stop(false, true);
4007
+ }
4008
+ });
4009
+ };
4010
+ // Stop queued animations
4011
+ if ( !!clear_queue ) {
4012
+ anim_stop();
4013
+ }
4014
+ // Get transition handlers
4015
+ var attr_set = [attr, 'set'].join('_');
4016
+ var trns;
4017
+ if ( !this.get_attribute(attr_set) ) {
4018
+ var models = this.get_ancestors(true);
4019
+ trns = [];
4020
+ this.set_attribute(attr_set, true);
4021
+ var thm = this;
4022
+ $.each(models, function(idx, model) {
4023
+ if ( attr in model && thm.util.is_obj(model[attr]) ) {
4024
+ trns.push(model[attr]);
4025
+ }
4026
+ });
4027
+ // Merge transition handlers into current theme
4028
+ trns.push({});
4029
+ trns = this.set_attribute(attr, $.extend.apply($, trns.reverse()));
4030
+ } else {
4031
+ trns = this.get_attribute(attr, {});
4032
+ }
4033
+ if ( this.util.is_method(trns, event) ) {
4034
+ // Disable animations if necessary
4035
+ if ( !anim_on ) {
4036
+ fx_temp = $.fx.off;
4037
+ $.fx.off = true;
4038
+ }
4039
+ // Pass control to transition event
4040
+ dfr = trns[event].call(this, v, $.Deferred());
4041
+ }
4042
+ }
4043
+ if ( !this.util.is_promise(dfr) ) {
4044
+ dfr = $.Deferred();
4045
+ dfr.reject();
4046
+ }
4047
+ dfr.always(function() {
4048
+ // Restore animation state
4049
+ if ( null !== fx_temp ) {
4050
+ $.fx.off = fx_temp;
4051
+ }
4052
+ });
4053
+ return dfr.promise();
4054
+ }
4055
+ };
4056
+
4057
+ View.Theme = Modeled_Component.extend(Theme);
4058
+
4059
+ /**
4060
+ * Template handler
4061
+ * Parses and Builds layout from raw template
4062
+ */
4063
+ var Template = {
4064
+ /* Configuration */
4065
+
4066
+ _slug: 'template',
4067
+ _reciprocal: true,
4068
+
4069
+ _refs: {
4070
+ 'theme': 'Theme'
4071
+ },
4072
+
4073
+ _attr_default: {
4074
+ /**
4075
+ * URI to layout (raw) file
4076
+ * @var string
4077
+ */
4078
+ layout_uri: '',
4079
+
4080
+ /**
4081
+ * Raw layout template
4082
+ * @var string
4083
+ */
4084
+ layout_raw: '',
4085
+ /**
4086
+ * Parsed layout
4087
+ * Placeholders processed
4088
+ * @var string
4089
+ */
4090
+ layout_parsed: '',
4091
+ /**
4092
+ * Tags in template
4093
+ * Populated once template has been parsed
4094
+ * @var array
4095
+ */
4096
+ tags: null,
4097
+ /**
4098
+ * Model to use for properties
4099
+ * Usually reference to an object in other component
4100
+ * @var obj
4101
+ */
4102
+ model: null
4103
+ },
4104
+
4105
+ /* References */
4106
+
4107
+ theme: null,
4108
+
4109
+ /* Methods */
4110
+
4111
+ _c: function(attributes) {
4112
+ this._super('', attributes);
4113
+ },
4114
+
4115
+ _hooks: function() {
4116
+ // TODO: Refactor to event that can save retrieved tags
4117
+ // (`dom_init` event called during attribute initialization so tags are not saved)
4118
+ this.on('dom_init', function(ev) {
4119
+ // Init tag handlers
4120
+ var tags = this.get_tags(null, null, true);
4121
+ var names = [];
4122
+ var t = this;
4123
+ $.each(tags, function(idx, tag) {
4124
+ var name = tag.get_name();
4125
+ if ( -1 === $.inArray(name, names) ) {
4126
+ names.push(name);
4127
+ tag.get_handler().trigger(ev.type, {template: t});
4128
+ }
4129
+ });
4130
+ });
4131
+ },
4132
+
4133
+ get_theme: function() {
4134
+ var ret = this.get_component('theme');
4135
+ return ret;
4136
+ },
4137
+
4138
+ /* Output */
4139
+
4140
+ /**
4141
+ * Render output
4142
+ * @param bool init (optional) Whether to initialize layout (TRUE) or render item (FALSE) (Default: FALSE)
4143
+ * Events
4144
+ * > render-init: Initialize template
4145
+ * > render-loading: DOM elements created and item content about to be loaded
4146
+ * > render-complete: Item content loaded, ready for display
4147
+ */
4148
+ render: function(init) {
4149
+ var v = this.get_theme().get_viewer();
4150
+ if ( !this.util.is_bool(init) ) {
4151
+ init = false;
4152
+ }
4153
+ // Populate layout
4154
+ if ( !init ) {
4155
+ if ( !v.is_active() ) {
4156
+ return false;
4157
+ }
4158
+ var item = v.get_item();
4159
+ if ( !this.util.is_type(item, View.Content_Item) ) {
4160
+ v.close();
4161
+ return false;
4162
+ }
4163
+ // Iterate through tags and populate layout
4164
+ if ( v.is_active() && this.has_tags() ) {
4165
+ var loading_promise = this.trigger('render-loading');
4166
+ var tpl = this;
4167
+ var tags = this.get_tags(),
4168
+ tag_promises = [];
4169
+ // Render Tag output
4170
+ $.when(item.load(), loading_promise).done(function() {
4171
+ if ( !v.is_active() ) {
4172
+ return false;
4173
+ }
4174
+ $.each(tags, function(idx, tag) {
4175
+ if ( !v.is_active() ) {
4176
+ return false;
4177
+ }
4178
+ tag_promises.push(tag.render(item).done(function(r) {
4179
+ if ( !v.is_active() ) {
4180
+ return false;
4181
+ }
4182
+ r.tag.dom_get().html(r.output);
4183
+ }));
4184
+ });
4185
+ // Fire event when all tags rendered
4186
+ if ( !v.is_active() ) {
4187
+ return false;
4188
+ }
4189
+ $.when.apply($, tag_promises).done(function() {
4190
+ tpl.trigger('render-complete');
4191
+ });
4192
+ });
4193
+ }
4194
+ } else {
4195
+ // Get Layout (basic)
4196
+ this.trigger('render-init', this.dom_get());
4197
+ }
4198
+ },
4199
+
4200
+ /*-** Layout **-*/
4201
+
4202
+ /**
4203
+ * Retrieve layout
4204
+ * @param bool parsed (optional) TRUE retrieves parsed layout, FALSE retrieves raw layout (Default: TRUE)
4205
+ * @return string Layout (HTML)
4206
+ */
4207
+ get_layout: function(parsed) {
4208
+ // Validate
4209
+ if ( !this.util.is_bool(parsed) ) {
4210
+ parsed = true;
4211
+ }
4212
+ // Determine which layout to retrieve (raw/parsed)
4213
+ var l = ( parsed ) ? this.parse_layout() : this.get_attribute('layout_raw', '');
4214
+ return l;
4215
+ },
4216
+
4217
+ /**
4218
+ * Parse layout
4219
+ * Converts template tags to HTML elements
4220
+ * > Template tag properties saved to HTML elements for future initialization
4221
+ * Returns saved layout if previously parsed
4222
+ * @return string Parsed layout
4223
+ */
4224
+ parse_layout: function() {
4225
+ // Check for previously-parsed layout
4226
+ var a = 'layout_parsed';
4227
+ var ret = this.get_attribute(a);
4228
+ // Return cached layout immediately
4229
+ if ( this.util.is_string(ret) ) {
4230
+ return ret;
4231
+ }
4232
+ // Parse raw layout
4233
+ ret = this.sanitize_layout( this.get_layout(false) );
4234
+ ret = this.parse_tags(ret);
4235
+ // Save parsed layout
4236
+ this.set_attribute(a, ret);
4237
+
4238
+ // Return parsed layout
4239
+ return ret;
4240
+ },
4241
+
4242
+ /**
4243
+ * Sanitize layout
4244
+ * @param obj|string l Layout string or jQuery object
4245
+ * @return obj|string Sanitized layout (Same data type that was passed to method)
4246
+ */
4247
+ sanitize_layout: function(l) {
4248
+ // Stop processing if invalid value
4249
+ if ( this.util.is_empty(l) ) {
4250
+ return l;
4251
+ }
4252
+ // Set return type
4253
+ var rtype = ( this.util.is_string(l) ) ? 'string' : null;
4254
+ /* Quarantine hard-coded tags */
4255
+
4256
+ // Create DOM structure from raw template
4257
+ var dom = $(l);
4258
+ // Find hard-coded tag nodes
4259
+ var tag_temp = this.get_tag_temp();
4260
+ var cls = tag_temp.get_class();
4261
+ var cls_new = ['x', cls].join('_');
4262
+ $(tag_temp.get_selector(), dom).each(function() {
4263
+ // Replace matching class name with blocking class
4264
+ $(this).removeClass(cls).addClass(cls_new);
4265
+ });
4266
+ // Format return value
4267
+ switch ( rtype ) {
4268
+ case 'string' :
4269
+ dom = dom.wrap('<div />').parent().html();
4270
+ l = dom;
4271
+ break;
4272
+ default :
4273
+ l = dom;
4274
+ }
4275
+ return l;
4276
+ },
4277
+
4278
+ /*-** Tags **-*/
4279
+
4280
+ /**
4281
+ * Extract tags from template
4282
+ * Tags are replaced with DOM element placeholders
4283
+ * Extracted tags are saved as element attribute values (for future use)
4284
+ * @param string l Raw layout to parse
4285
+ * @return string Parsed layout
4286
+ */
4287
+ parse_tags: function(l) {
4288
+ // Validate
4289
+ if ( !this.util.is_string(l) ) {
4290
+ return '';
4291
+ }
4292
+ // Parse tags in layout
4293
+ // Tag regex
4294
+ var re = /\{{2}\s*(\w.*?)\s*\}{2}/gim;
4295
+ // Tag match results
4296
+ var match;
4297
+ // Iterate through template and find tags
4298
+ while ( match = re.exec(l) ) {
4299
+ // Replace tag in layout with DOM container
4300
+ l = l.substring(0, match.index) + this.get_tag_container(match[1]) + l.substring(match.index + match[0].length);
4301
+ }
4302
+ return l;
4303
+ },
4304
+
4305
+ /**
4306
+ * Create DOM element container for tag
4307
+ * @param string Tag ID (will be prefixed)
4308
+ * @return string DOM element
4309
+ */
4310
+ get_tag_container: function(tag) {
4311
+ // Build element
4312
+ var attr = this.get_tag_attribute();
4313
+ return this.util.format('<span %s="%s"></span>', attr, encodeURI(tag));
4314
+ },
4315
+
4316
+ get_tag_attribute: function() {
4317
+ return this.get_tag_temp().dom_get_attribute();
4318
+ },
4319
+
4320
+ /**
4321
+ * Retrieve Template_Tag instance at specified index
4322
+ * @param int idx (optional) Index to retrieve tag from
4323
+ * @return Template_Tag Tag instance
4324
+ */
4325
+ get_tag: function(idx) {
4326
+ var ret = null;
4327
+ if ( this.has_tags() ) {
4328
+ var tags = this.get_tags();
4329
+ if ( !this.util.is_int(idx) || 0 > idx || idx >= tags.length ) {
4330
+ idx = 0;
4331
+ }
4332
+ ret = tags[idx];
4333
+ }
4334
+ return ret;
4335
+ },
4336
+
4337
+ /**
4338
+ * Retrieve tags from template
4339
+ * Subset of tags may be retrieved based on parameter values
4340
+ * Template is parsed if tags not set
4341
+ * @param string name (optional) Tag type to retrieve instances of
4342
+ * @param string prop (optional) Tag property to retrieve instances of
4343
+ * @param bool isolate (optional) Do not save retrieved tags, only fetch and return (Default: TRUE)
4344
+ * @return array Template_Tag instances
4345
+ */
4346
+ get_tags: function(name, prop, isolate) {
4347
+ // Validate
4348
+ if ( !this.util.is_bool(isolate) ) {
4349
+ isolate = false;
4350
+ }
4351
+ // Setup
4352
+ var a = 'tags';
4353
+ var tags = this.get_attribute(a);
4354
+ // Initialize tags
4355
+ if ( !this.util.is_array(tags) ) {
4356
+ tags = [];
4357
+ // Retrieve layout DOM tree
4358
+ var d = this.dom_get();
4359
+ // Select tag nodes
4360
+ var attr = this.get_tag_attribute();
4361
+ var nodes = $(d).find('[' + attr + ']');
4362
+ // Build tag instances from nodes
4363
+ $(nodes).each(function() {
4364
+ // Get tag placeholder
4365
+ var el = $(this);
4366
+ var tag = new View.Template_Tag(decodeURI(el.attr(attr)));
4367
+ // Populate valid tags
4368
+ if ( tag.has_handler() ) {
4369
+ // Add tag to array
4370
+ tags.push(tag);
4371
+ if ( !isolate ) {
4372
+ // Connect tag to DOM node
4373
+ tag.dom_set(el);
4374
+ // Set classes
4375
+ el.addClass(tag.get_classes(' '));
4376
+ }
4377
+ }
4378
+ // Clear data attribute
4379
+ if ( !isolate ) {
4380
+ el.removeAttr(attr);
4381
+ }
4382
+ });
4383
+ if ( !isolate ) {
4384
+ // Save tags
4385
+ this.set_attribute(a, tags, false);
4386
+ }
4387
+ }
4388
+ // Filter tags by parameters
4389
+ if ( !this.util.is_empty(tags) && this.util.is_string(name) ) {
4390
+ // Normalize
4391
+ if ( !this.util.is_string(prop) ) {
4392
+ prop = false;
4393
+ }
4394
+ var tags_filtered = [];
4395
+ var tc = null;
4396
+ for ( var x = 0; x < tags.length; x++ ) {
4397
+ tc = tags[x];
4398
+ if ( name === tc.get_name() ) {
4399
+ // Check tag property
4400
+ if ( !prop || prop === tc.get_prop() ) {
4401
+ tags_filtered.push(tc);
4402
+ }
4403
+ }
4404
+ }
4405
+ tags = tags_filtered;
4406
+ }
4407
+ return ( this.util.is_array(tags, false) ) ? tags : [];
4408
+ },
4409
+
4410
+ /**
4411
+ * Check if template contains tags
4412
+ * @return bool TRUE if tags exist, FALSE otherwise
4413
+ */
4414
+ has_tags: function() {
4415
+ return ( this.get_tags().length > 0 ) ? true : false;
4416
+ },
4417
+
4418
+ /**
4419
+ * Retrieve temporary tag instance
4420
+ * @return Template_Tag Temporary tag
4421
+ */
4422
+ get_tag_temp: function() {
4423
+ return this.get_controller().get_component_temp(View.Template_Tag);
4424
+ },
4425
+
4426
+ /**
4427
+ * Retrieve Template tag CSS selector
4428
+ * @uses Template.get_tag_temp() to retrieve temporary tag instance
4429
+ * @uses Template_Tag.get_selector() to retrieve selector
4430
+ * @param name string Tag name
4431
+ * @param prop string Tag Property
4432
+ * @return string Template Tag CSS selector
4433
+ */
4434
+ get_tag_selector: function(name, prop) {
4435
+ if ( !this.util.is_string(name) ) {
4436
+ name = '';
4437
+ }
4438
+ if ( !this.util.is_string(prop) ) {
4439
+ prop = '';
4440
+ }
4441
+ var tag = this.get_tag_temp();
4442
+ tag.set_attribute('name', name);
4443
+ tag.set_attribute('prop', prop);
4444
+ return tag.get_selector('full');
4445
+ },
4446
+
4447
+ /*-** DOM **-*/
4448
+
4449
+ /**
4450
+ * Custom DOM initialization
4451
+ */
4452
+ dom_init: function() {
4453
+ // Create DOM object from parsed layout
4454
+ this.dom_set(this.get_layout());
4455
+ this.trigger('dom_init');
4456
+ },
4457
+
4458
+ /**
4459
+ * Retrieve DOM element(s) for specified tag
4460
+ * @param string tag Name of tag to retrieve
4461
+ * @param string prop (optional) Specific tag property to retrieve
4462
+ * @return array DOM elements for tag
4463
+ */
4464
+ dom_get_tag: function(tag, prop) {
4465
+ var ret = $();
4466
+ var tags = this.get_tags(tag, prop);
4467
+ if ( tags.length ) {
4468
+ // Build selector
4469
+ var level = null;
4470
+ if ( this.util.is_string(tag) ) {
4471
+ level = ( this.util.is_string(prop) ) ? 'full' : 'tag';
4472
+ }
4473
+ var sel = '.' + tags[0].get_class(level);
4474
+ ret = this.dom_get().find(sel);
4475
+ }
4476
+ return ret;
4477
+ }
4478
+ };
4479
+
4480
+ View.Template = Modeled_Component.extend(Template);
4481
+
4482
+ /**
4483
+ * Template tag
4484
+ */
4485
+ var Template_Tag = {
4486
+ /* Configuration */
4487
+ _slug: 'template_tag',
4488
+ _reciprocal: true,
4489
+ /* Properties */
4490
+ _attr_default: {
4491
+ name: null,
4492
+ prop: null,
4493
+ match: null
4494
+ },
4495
+ /**
4496
+ * Tag Handlers
4497
+ * Collection of Template_Tag_Handler instances
4498
+ * @var obj
4499
+ */
4500
+ handlers: {},
4501
+ /* Methods */
4502
+
4503
+ /**
4504
+ * Constructor
4505
+ * @param
4506
+ */
4507
+ _c: function(tag_match) {
4508
+ this.parse(tag_match);
4509
+ },
4510
+
4511
+ /**
4512
+ * Set instance attributes using tag extracted from template
4513
+ * @param string tag_match Extracted tag match
4514
+ */
4515
+ parse: function(tag_match) {
4516
+ // Return default value for invalid instances
4517
+ if ( !this.util.is_string(tag_match) ) {
4518
+ return false;
4519
+ }
4520
+ // Parse instance options
4521
+ var parts = tag_match.split('|'),
4522
+ part;
4523
+ if ( !parts.length ) {
4524
+ return null;
4525
+ }
4526
+ var attrs = {
4527
+ name: null,
4528
+ prop: null,
4529
+ match: tag_match
4530
+ };
4531
+ // Get tag ID
4532
+ attrs.name = parts[0];
4533
+ // Get main property
4534
+ if ( attrs.name.indexOf('.') !== -1 ) {
4535
+ attrs.name = attrs.name.split('.', 2);
4536
+ attrs.prop = attrs.name[1];
4537
+ attrs.name = attrs.name[0];
4538
+ }
4539
+ // Get other attributes
4540
+ for ( var x = 1; x < parts.length; x++ ) {
4541
+ part = parts[x].split(':', 1);
4542
+ if ( part.length > 1 && !( part[0] in attrs ) ) {
4543
+ // Add key/value pair to attributes
4544
+ attrs[part[0]] = part[1];
4545
+ }
4546
+ }
4547
+ // Save to instance
4548
+ this.set_attributes(attrs, true);
4549
+ },
4550
+
4551
+ /**
4552
+ * Render tag output
4553
+ * @param Content_Item item
4554
+ * @return obj jQuery.Promise object that is resolved when tag is rendered
4555
+ * Parameters passed to callbacks
4556
+ * > tag obj Current tag instance
4557
+ * > output string Tag output
4558
+ */
4559
+ render: function(item) {
4560
+ var tag = this;
4561
+ return tag.get_handler().render(item, tag).pipe(function(output) {
4562
+ return {'tag': tag, 'output': output};
4563
+ });
4564
+ },
4565
+
4566
+ /**
4567
+ * Retrieve tag name
4568
+ * @return string Tag name (DEFAULT: NULL)
4569
+ */
4570
+ get_name: function() {
4571
+ return this.get_attribute('name');
4572
+ },
4573
+
4574
+ /**
4575
+ * Retrieve tag property
4576
+ */
4577
+ get_prop: function() {
4578
+ return this.get_attribute('prop');
4579
+ },
4580
+
4581
+ /**
4582
+ * Retrieve tag handler
4583
+ * @return Template_Tag_Handler Handler instance (Empty instance if handler does not exist)
4584
+ */
4585
+ get_handler: function() {
4586
+ return ( this.has_handler() ) ? this.handlers[this.get_name()] : new View.Template_Tag_Handler('');
4587
+ },
4588
+
4589
+ /**
4590
+ * Check if handler exists for tag
4591
+ * @return bool TRUE if handler exists, FALSE otherwise
4592
+ */
4593
+ has_handler: function() {
4594
+ return ( this.get_name() in this.handlers );
4595
+ },
4596
+
4597
+ /**
4598
+ * Generate class names for DOM node
4599
+ * @param string rtype (optional) Return data type
4600
+ * > Default: array
4601
+ * > If string supplied: Joined classes delimited by parameter
4602
+ * @uses get_class() to generate class names
4603
+ * @uses Array.join() to convert class names array to string
4604
+ * @return array Class names
4605
+ */
4606
+ get_classes: function(rtype) {
4607
+ // Build array of class names
4608
+ var cls = [
4609
+ // General tag class
4610
+ this.get_class(),
4611
+ // Tag name
4612
+ this.get_class('tag'),
4613
+ // Tag name + property
4614
+ this.get_class('full')
4615
+ ];
4616
+ // Convert class names array to string
4617
+ if ( this.util.is_string(rtype) ) {
4618
+ cls = cls.join(rtype);
4619
+ }
4620
+ // Return class names
4621
+ return cls;
4622
+ },
4623
+
4624
+ /**
4625
+ * Generate DOM-compatible class name based with varied levels of specificity
4626
+ * @param int level (optional) Class name specificity
4627
+ * > Default: General tag class (common to all tag elements)
4628
+ * > tag: Tag Name
4629
+ * > full: Tag Name + Property
4630
+ * @return string Class name
4631
+ */
4632
+ get_class: function(level) {
4633
+ var cls = '';
4634
+ // Build base
4635
+ switch ( level ) {
4636
+ case 'tag' :
4637
+ // Tag name
4638
+ cls = this.get_name();
4639
+ break;
4640
+ case 'full' :
4641
+ // Tag name + property
4642
+ var parts = [this.get_name(), this.get_prop()];
4643
+ var a = [];
4644
+ var i;
4645
+ for ( i = 0; i < parts.length; i++ ) {
4646
+ if ( this.util.is_string(parts[i]) ) {
4647
+ a.push(parts[i]);
4648
+ }
4649
+ }
4650
+ cls = a.join('_');
4651
+ break;
4652
+ }
4653
+ // Format & return
4654
+ return ( !this.util.is_string(cls) ) ? this.get_ns() : this.add_ns(cls);
4655
+ },
4656
+
4657
+ /**
4658
+ * Generate tag selector based on specified class name level
4659
+ * @param string level (optional) Class name specificity (@see get_class() for parameter values)
4660
+ * @return string Tag selector
4661
+ */
4662
+ get_selector: function(level) {
4663
+ // Get base
4664
+ var ret = this.get_class(level);
4665
+ // Format
4666
+ if ( this.util.is_string(ret) ) {
4667
+ ret = '.' + ret;
4668
+ } else {
4669
+ ret = '';
4670
+ }
4671
+ return ret;
4672
+ }
4673
+ };
4674
+
4675
+ View.Template_Tag = Component.extend(Template_Tag);
4676
+
4677
+ /**
4678
+ * Theme tag handler
4679
+ */
4680
+ var Template_Tag_Handler = {
4681
+ /* Configuration */
4682
+ _slug: 'template_tag_handler',
4683
+ /* Properties */
4684
+ _attr_default: {
4685
+ supports_modifiers: false,
4686
+ dynamic: false,
4687
+ props: {}
4688
+ },
4689
+
4690
+ /* Methods */
4691
+
4692
+ /**
4693
+ * Render tag output
4694
+ * @param Content_Item item Item currently being displayed
4695
+ * @param Template_Tag Tag instance (from template)
4696
+ * @return obj jQuery.Promise linked to rendering process
4697
+ */
4698
+ render: function(item, instance) {
4699
+ var dfr = $.Deferred();
4700
+ // Pass to attribute method
4701
+ this.call_attribute('render', item, instance, dfr);
4702
+ // Return promise
4703
+ return dfr.promise();
4704
+ },
4705
+
4706
+ add_prop: function(prop, fn) {
4707
+ // Get attribute
4708
+ var a = 'props';
4709
+ var props = this.get_attribute(a);
4710
+ // Validate
4711
+ if ( !this.util.is_string(prop) || !this.util.is_func(fn) ) {
4712
+ return false;
4713
+ }
4714
+ if ( !this.util.is_obj(props, false) ) {
4715
+ props = {};
4716
+ }
4717
+ // Add property
4718
+ props[prop] = fn;
4719
+ // Save attribute
4720
+ this.set_attribute(a, props);
4721
+ },
4722
+
4723
+ handle_prop: function(prop, item, instance) {
4724
+ // Locate property
4725
+ var props = this.get_attribute('props');
4726
+ var out = '';
4727
+ if ( this.util.is_obj(props) && ( prop in props ) && this.util.is_func(props[prop]) ) {
4728
+ out = props[prop].call(this, item, instance);
4729
+ } else {
4730
+ out = item.get_viewer().get_label(prop);
4731
+ }
4732
+ return out;
4733
+ }
4734
+ };
4735
+
4736
+ View.Template_Tag_Handler = Component.extend(Template_Tag_Handler);
4737
+ /* Update References */
4738
+
4739
+ // Attach to global object
4740
+ View = SLB.attach('View', View);
4741
  })(jQuery);}
client/js/prod/lib.admin.js CHANGED
@@ -1 +1 @@
1
- window.SLB&&SLB.attach&&function($){SLB.attach("Admin",{init:function(){postboxes&&postboxes.add_postbox_toggles(pagenow)}}),$(document).ready(function(){SLB.Admin.init()})}(jQuery);
1
+ window.SLB&&SLB.attach&&!function($){SLB.attach("Admin",{init:function(){postboxes&&postboxes.add_postbox_toggles(pagenow)}}),$(document).ready(function(){SLB.Admin.init()})}(jQuery);
client/js/prod/lib.core.js CHANGED
@@ -1 +1 @@
1
- window.jQuery&&function($){"use strict";function Class(){}var c_init=!1,Base={base:!(Class.extend=function(members){var _super=this.prototype;c_init=!0;var val,name,proto=new this;for(name in c_init=!1,proto)$.isPlainObject(proto[name])&&(val=$.extend({},proto[name]),proto[name]=val);function make_handler(nm,fn){return function(){var tmp=this._super;this._super=_super[nm];var ret=fn.apply(this,arguments);return this._super=tmp,ret}}for(name in members)"function"==typeof members[name]&&"function"==typeof _super[name]?proto[name]=make_handler(name,members[name]):proto[name]=$.isPlainObject(members[name])?$.extend({},members[name]):members[name];function Class(){c_init||("function"==typeof this._init&&this._init.apply(this,arguments),"function"==typeof this._c&&this._c.apply(this,arguments))}return((Class.prototype=proto).constructor=Class).extend=this.extend,Class}),_parent:null,prefix:"slb",_init:function(){this._set_parent()},_set_parent:function(p){this.util.is_set(p)&&(this._parent=p),this.util._parent=this},attach:function(member,data,simple){var ret=data;return simple=void 0!==simple&&!!simple,"string"===$.type(member)&&($.isPlainObject(data)&&!simple&&(data._parent=this,data=this.Class.extend(data)),this[member]="function"===$.type(data)?new data:data,ret=this[member]),ret},has_child:function(child){if(!this.util.is_string(child))return!1;var children=child.split(".");child=null;var x,o=this;for(x=0;x<children.length;x++)if(""!==(child=children[x])){if(!this.util.is_obj(o)||!o[child])return!1;o=o[child]}return!0},is_base:function(){return!!this.base},get_parent:function(){return this._parent||(this._parent={}),this._parent}},Utilities={_base:null,_parent:null,get_base:function(){if(!this._base){for(var p=this.get_parent(),p_prev=null,methods=["is_base","get_parent"];p_prev!==p&&this.is_method(p,methods)&&!p.is_base();)p=(p_prev=p).get_parent();this._base=p}return this._base},get_parent:function(prop){var ret=this._parent;return ret=ret||(this._parent={}),this.is_string(prop)&&(ret=this.in_obj(ret,prop)?ret[prop]:null),ret},get_sep:function(sep){return this.is_string(sep,!1)?sep:"_"},get_prefix:function(){var p=this.get_parent("prefix");return this.is_string(p,!1)?p:""},has_prefix:function(val,sep){return this.is_string(val)&&0===val.indexOf(this.get_prefix()+this.get_sep(sep))},add_prefix:function(val,sep,once){return this.is_string(val)?(sep=this.get_sep(sep),this.is_bool(once)||(once=!0),once&&this.has_prefix(val,sep)?val:[this.get_prefix(),val].join(sep)):this.get_prefix()},remove_prefix:function(val,sep,once){if(!this.is_string(val,!0))return"";if(sep=this.get_sep(sep),this.is_bool(once)||(once=!0),this.has_prefix(val,sep))for(var prfx=this.get_prefix()+sep;val=val.substr(prfx.length),!once&&this.has_prefix(val,sep););return val},get_attribute:function(attr_base){var attr=["data",this.get_prefix()].join("-");return this.is_string(attr_base)&&0!==attr_base.indexOf(attr+"-")&&(attr=[attr,attr_base].join("-")),attr},get_context:function(){var b=this.get_base();return $.isArray(b.context)||(b.context=[]),b.context},is_context:function(ctx){return this.is_string(ctx)&&(ctx=[ctx]),this.is_array(ctx)&&0<this.arr_intersect(this.get_context(),ctx).length},is_set:function(val){return void 0!==val},is_type:function(val,type,nonempty){var ret=!1;if(this.is_set(val)&&null!==val&&this.is_set(type))switch($.type(type)){case"function":ret=val instanceof type;break;case"string":ret=$.type(val)===type;break;default:ret=!1}return!ret||this.is_set(nonempty)&&!nonempty||(ret=!this.is_empty(val)),ret},is_string:function(value,nonempty){return this.is_type(value,"string",nonempty)},is_array:function(value,nonempty){return this.is_type(value,"array",nonempty)},is_bool:function(value){return this.is_type(value,"boolean",!1)},is_obj:function(value,nonempty){return this.is_type(value,"object",nonempty)},is_func:function(value){return this.is_type(value,"function",!1)},is_method:function(obj,key){var ret=!1;if(this.is_string(key)&&(key=[key]),this.in_obj(obj,key)){ret=!0;for(var x=0;ret&&x<key.length;)ret=this.is_func(obj[key[x]]),x++}return ret},is_instance:function(obj,parent){return!!this.is_func(parent)&&(this.is_obj(obj)&&obj instanceof parent)},is_class:function(cls,parent){var ret=this.is_func(cls)&&"prototype"in cls;return ret&&this.is_set(parent)&&(ret=this.is_instance(cls.prototype,parent)),ret},is_num:function(value,nonempty){var f={nan:Number.isNaN?Number.isNaN:isNaN,finite:Number.isFinite?Number.isFinite:isFinite};return this.is_type(value,"number",nonempty)&&!f.nan(value)&&f.finite(value)},is_int:function(value,nonempty){return this.is_num(value,nonempty)&&Math.floor(value)===value},is_scalar:function(value,nonempty){return this.is_num(value,nonempty)||this.is_string(value,nonempty)||this.is_bool(value)},is_empty:function(value,type){var ret=!1;if(this.is_set(value))for(var empties=[null,"",!1,0],x=0;!ret&&x<empties.length;)ret=empties[x]===value,x++;else ret=!0;if(!ret)if(this.is_set(type)||(type=$.type(value)),this.is_type(value,type,!1))switch(type){case"string":case"array":ret=0===value.length;break;case"number":ret=0==value;break;case"object":if($.isPlainObject(value)){if(Object.getOwnPropertyNames)ret=0===Object.getOwnPropertyNames(value).length;else if(value.hasOwnProperty)for(var key in ret=!0,value)if(value.hasOwnProperty(key)){ret=!1;break}}else ret=!1}else ret=!0;return ret},is_promise:function(obj){return this.is_method(obj,["then","done","always","fail","pipe"])},format:function(fmt,val){if(!this.is_string(fmt))return"";function strip(txt){return-1!==txt.indexOf(ph)?txt.replace(ph,""):txt}var params=[],ph="%s";if(arguments.length<2||-1===fmt.indexOf(ph))return strip(fmt);params=Array.prototype.slice.call(arguments,1),val=null;for(var x=0;x<params.length;x++)this.is_scalar(params[x],!1)||(params[x]="");if(1===params.length)fmt=fmt.replace(ph,params[0].toString());else{for(var idx=0,len=params.length,rlen=ph.length,pos=0;(pos=fmt.indexOf(ph))&&-1!==pos&&idx<len;)fmt=fmt.substr(0,pos)+params[idx].toString()+fmt.substr(pos+rlen),idx++;fmt=strip(fmt)}return fmt},in_obj:function(obj,key,all){this.is_bool(all)||(all=!0),this.is_string(key)&&(key=[key]);var ret=!1;if(this.is_obj(obj)&&this.is_array(key))for(var val,x=0;x<key.length&&(val=key[x],ret=!!(this.is_string(val)&&val in obj),!(!all&&ret||all&&!ret));x++);return ret},obj_keys:function(obj){var prop,keys=[];if(!this.is_obj(obj))return keys;if(Object.keys)keys=Object.keys(obj);else for(prop in obj)obj.hasOwnProperty(prop)&&keys.push(prop);return keys},arr_intersect:function(arr1,arr2){var x,ret=[],params=Array.prototype.slice.call(arguments),arrs=[];for(x=0;x<params.length;x++)this.is_array(params[x],!1)&&arrs.push(params[x]);if(arrs.length<2)return ret;params=arr1=arr2=null;var add,sub,base=arrs.shift();for(x=0;x<base.length;x++){for(add=!0,sub=0;sub<arrs.length;sub++)if(-1===arrs[sub].indexOf(base[x])){add=!1;break}add&&ret.push(base[x])}return ret},guid:function(){function _p8(s){var p=(Math.random().toString(16)+"000000000").substr(2,8);return s?"-"+p.substr(0,4)+"-"+p.substr(4,4):p}return _p8()+_p8(!0)+_p8(!0)+_p8()},parse_uri:function(uri){return $('<a href="'+uri+'"/>').get(0)},parse_query:function(uri){var i,temp,key,val,delim_vars="&",delim_val="=",query={raw:[],parsed:{},string:""};if(0===(uri=this.parse_uri(uri)).search.indexOf("?"))for(query.raw=uri.search.substr(1).split(delim_vars),i=0;i<query.raw.length;i++)key=(temp=query.raw[i].split(delim_val)).shift(),val=0<temp.length?temp.join(delim_val):null,query.parsed[key]=val;return query.parsed},build_query:function(query){var val,q=[],delim_vars="&",delim_val="=";for(var key in query)val=null!==query[key]?delim_val+query[key]:"",q.push(key+val);return q.join(delim_vars)}};Base.attach("util",Utilities,!0);var SLB_Base=Class.extend(Base),Core={base:!0,context:[],Class:SLB_Base,_init:function(){this._super(),$("html").addClass(this.util.get_prefix())}},SLB_Core=SLB_Base.extend(Core);window.SLB=new SLB_Core}(jQuery);
1
+ window.jQuery&&!function($){"use strict";function Class(){}var c_init=!1,Base={base:!(Class.extend=function(members){var val,name,_super=this.prototype,proto=(c_init=!0,new this);for(name in c_init=!1,proto)$.isPlainObject(proto[name])&&(val=$.extend({},proto[name]),proto[name]=val);for(name in members)"function"==typeof members[name]&&"function"==typeof _super[name]?proto[name]=function(nm,fn){return function(){var tmp=this._super,ret=(this._super=_super[nm],fn.apply(this,arguments));return this._super=tmp,ret}}(name,members[name]):proto[name]=$.isPlainObject(members[name])?$.extend({},members[name]):members[name];function Class(){c_init||("function"==typeof this._init&&this._init.apply(this,arguments),"function"==typeof this._c&&this._c.apply(this,arguments))}return((Class.prototype=proto).constructor=Class).extend=this.extend,Class}),_parent:null,prefix:"slb",_init:function(){this._set_parent()},_set_parent:function(p){this.util.is_set(p)&&(this._parent=p),this.util._parent=this},attach:function(member,data,simple){var ret=data;return simple=void 0!==simple&&!!simple,"string"===$.type(member)&&($.isPlainObject(data)&&!simple&&(data._parent=this,data=this.Class.extend(data)),this[member]="function"===$.type(data)?new data:data,ret=this[member]),ret},has_child:function(child){if(!this.util.is_string(child))return!1;for(var children=child.split("."),o=(child=null,this),x=0;x<children.length;x++)if(""!==(child=children[x])){if(!this.util.is_obj(o)||!o[child])return!1;o=o[child]}return!0},is_base:function(){return!!this.base},get_parent:function(){return this._parent||(this._parent={}),this._parent}},Base=(Base.attach("util",{_base:null,_parent:null,get_base:function(){if(!this._base){for(var p=this.get_parent(),p_prev=null,methods=["is_base","get_parent"];p_prev!==p&&this.is_method(p,methods)&&!p.is_base();)p=(p_prev=p).get_parent();this._base=p}return this._base},get_parent:function(prop){var ret=(ret=this._parent)||(this._parent={});return ret=this.is_string(prop)?this.in_obj(ret,prop)?ret[prop]:null:ret},get_sep:function(sep){return this.is_string(sep,!1)?sep:"_"},get_prefix:function(){var p=this.get_parent("prefix");return this.is_string(p,!1)?p:""},has_prefix:function(val,sep){return this.is_string(val)&&0===val.indexOf(this.get_prefix()+this.get_sep(sep))},add_prefix:function(val,sep,once){return this.is_string(val)?(sep=this.get_sep(sep),(once=this.is_bool(once)?once:!0)&&this.has_prefix(val,sep)?val:[this.get_prefix(),val].join(sep)):this.get_prefix()},remove_prefix:function(val,sep,once){if(!this.is_string(val,!0))return"";if(sep=this.get_sep(sep),this.is_bool(once)||(once=!0),this.has_prefix(val,sep))for(var prfx=this.get_prefix()+sep;val=val.substr(prfx.length),!once&&this.has_prefix(val,sep););return val},get_attribute:function(attr_base){var attr=["data",this.get_prefix()].join("-");return attr=this.is_string(attr_base)&&0!==attr_base.indexOf(attr+"-")?[attr,attr_base].join("-"):attr},get_context:function(){var b=this.get_base();return $.isArray(b.context)||(b.context=[]),b.context},is_context:function(ctx){return this.is_string(ctx)&&(ctx=[ctx]),this.is_array(ctx)&&0<this.arr_intersect(this.get_context(),ctx).length},is_set:function(val){return void 0!==val},is_type:function(val,type,nonempty){var ret=!1;if(this.is_set(val)&&null!==val&&this.is_set(type))switch($.type(type)){case"function":ret=val instanceof type;break;case"string":ret=$.type(val)===type;break;default:ret=!1}return ret=!ret||this.is_set(nonempty)&&!nonempty?ret:!this.is_empty(val)},is_string:function(value,nonempty){return this.is_type(value,"string",nonempty)},is_array:function(value,nonempty){return this.is_type(value,"array",nonempty)},is_bool:function(value){return this.is_type(value,"boolean",!1)},is_obj:function(value,nonempty){return this.is_type(value,"object",nonempty)},is_func:function(value){return this.is_type(value,"function",!1)},is_method:function(obj,key){var ret=!1;if(this.is_string(key)&&(key=[key]),this.in_obj(obj,key))for(var ret=!0,x=0;ret&&x<key.length;)ret=this.is_func(obj[key[x]]),x++;return ret},is_instance:function(obj,parent){return!!this.is_func(parent)&&(this.is_obj(obj)&&obj instanceof parent)},is_class:function(cls,parent){var ret=this.is_func(cls)&&"prototype"in cls;return ret=ret&&this.is_set(parent)?this.is_instance(cls.prototype,parent):ret},is_num:function(value,nonempty){var f={nan:Number.isNaN||isNaN,finite:Number.isFinite||isFinite};return this.is_type(value,"number",nonempty)&&!f.nan(value)&&f.finite(value)},is_int:function(value,nonempty){return this.is_num(value,nonempty)&&Math.floor(value)===value},is_scalar:function(value,nonempty){return this.is_num(value,nonempty)||this.is_string(value,nonempty)||this.is_bool(value)},is_empty:function(value,type){var ret=!1;if(this.is_set(value))for(var empties=[null,"",!1,0],x=0;!ret&&x<empties.length;)ret=empties[x]===value,x++;else ret=!0;if(!ret)if(this.is_set(type)||(type=$.type(value)),this.is_type(value,type,!1))switch(type){case"string":case"array":ret=0===value.length;break;case"number":ret=0==value;break;case"object":if($.isPlainObject(value)){if(Object.getOwnPropertyNames)ret=0===Object.getOwnPropertyNames(value).length;else if(value.hasOwnProperty)for(var key in ret=!0,value)if(value.hasOwnProperty(key)){ret=!1;break}}else ret=!1}else ret=!0;return ret},is_promise:function(obj){return this.is_method(obj,["then","done","always","fail","pipe"])},format:function(fmt,val){if(!this.is_string(fmt))return"";function strip(txt){return-1!==txt.indexOf(ph)?txt.replace(ph,""):txt}var params=[],ph="%s";if(arguments.length<2||-1===fmt.indexOf(ph))return strip(fmt);params=Array.prototype.slice.call(arguments,1);for(var x=0;x<params.length;x++)this.is_scalar(params[x],!1)||(params[x]="");if(1===params.length)fmt=fmt.replace(ph,params[0].toString());else{for(var pos,idx=0,len=params.length,rlen=ph.length;(pos=fmt.indexOf(ph))&&-1!==pos&&idx<len;)fmt=fmt.substr(0,pos)+params[idx].toString()+fmt.substr(pos+rlen),idx++;fmt=strip(fmt)}return fmt},in_obj:function(obj,key,all){this.is_bool(all)||(all=!0),this.is_string(key)&&(key=[key]);var ret=!1;if(this.is_obj(obj)&&this.is_array(key))for(var val,x=0;x<key.length&&(val=key[x],ret=!!(this.is_string(val)&&val in obj),!(!all&&ret||all&&!ret));x++);return ret},obj_keys:function(obj){var prop,keys=[];if(this.is_obj(obj))if(Object.keys)keys=Object.keys(obj);else for(prop in obj)obj.hasOwnProperty(prop)&&keys.push(prop);return keys},arr_intersect:function(arr1,arr2){var ret=[],params=Array.prototype.slice.call(arguments),arrs=[];for(x=0;x<params.length;x++)this.is_array(params[x],!1)&&arrs.push(params[x]);if(!(arrs.length<2))for(var add,sub,params=null,base=arrs.shift(),x=0;x<base.length;x++){for(add=!0,sub=0;sub<arrs.length;sub++)if(-1===arrs[sub].indexOf(base[x])){add=!1;break}add&&ret.push(base[x])}return ret},guid:function(){function _p8(s){var p=(Math.random().toString(16)+"000000000").substr(2,8);return s?"-"+p.substr(0,4)+"-"+p.substr(4,4):p}return _p8()+_p8(!0)+_p8(!0)+_p8()},parse_uri:function(uri){return $('<a href="'+uri+'"/>').get(0)},parse_query:function(uri){var i,key,temp,delim_vars="&",delim_val="=",query={raw:[],parsed:{},string:""};if(0===(uri=this.parse_uri(uri)).search.indexOf("?"))for(query.raw=uri.search.substr(1).split(delim_vars),i=0;i<query.raw.length;i++)key=(temp=query.raw[i].split(delim_val)).shift(),temp=0<temp.length?temp.join(delim_val):null,query.parsed[key]=temp;return query.parsed},build_query:function(query){var val,key,q=[],delim_vars="&",delim_val="=";for(key in query)val=null!==query[key]?delim_val+query[key]:"",q.push(key+val);return q.join(delim_vars)}},!0),Class.extend(Base)),Base=Base.extend({base:!0,context:[],Class:Base,_init:function(){this._super(),$("html").addClass(this.util.get_prefix())}});window.SLB=new Base}(jQuery);
client/js/prod/lib.view.js CHANGED
@@ -1 +1 @@
1
- window.SLB&&SLB.attach&&function($){var View={assets:{},component_defaults:[],loading:[],cache:{},component_temps:{},options:{},_init:function(){this._super(),this.init_refs(),this.init_components()},init_refs:function(){var r,ref,prop;for(prop in this)if(prop=this[prop],this.is_component(prop)&&!this.util.is_empty(prop.prototype._refs))for(r in prop.prototype._refs)ref=prop.prototype._refs[r],this.util.is_string(ref)&&ref in this&&(ref=prop.prototype._refs[r]=this[ref]),this.util.is_class(ref)||delete prop.prototype_refs[r]},init_components:function(){this.component_defaults=[this.Viewer]},init:function(options){var t=this;$.when.apply($,this.loading).always(function(){$.extend(!0,t.options,options),$(window).on("popstate",function(e){var state=e.originalEvent.state;if(t.util.in_obj(state,["item","viewer"]))return t.get_viewer(state.viewer).history_handle(e),e.preventDefault()}),t.init_items()})},can_make_default_component:function(type){return-1!==$.inArray(type,this.component_defaults)},is_component:function(comp){return this.util.is_class(comp,this.Component)},get_components:function(type){var ret={};if(this.is_component(type)){var coll=type.prototype._slug+"s";coll in this.cache||(this.cache[coll]={}),ret=this.cache[coll]}return ret},get_component:function(type,id){var ret=null;if(!this.util.is_func(type))return ret;this.util.is_string(id)||(id=null);var coll=this.get_components(type);if(this.util.is_obj(coll)){var tid=this.util.is_string(id)?id:this.util.add_prefix("default");tid in coll&&(ret=coll[tid])}return this.util.is_empty(ret)&&(this.util.is_string(id)||this.can_make_default_component(type))&&(ret=this.add_component(type,id)),ret},add_component:function(type,id,options){if(!this.util.is_func(type))return!1;if(this.util.is_empty(id)&&!this.can_make_default_component(type))return!1;var ret=null;this.util.is_empty(id)&&(id=this.util.add_prefix("default")),this.util.is_obj(options)||(options={});var m="component"!==type.prototype._slug?"add_"+type.prototype._slug:null;if(ret=!this.util.is_empty(m)&&m in this&&this.util.is_func(this[m])?this[m](id,options):new type(id,options),this.util.is_type(ret,type)){var coll=this.get_components(type);switch($.type(coll)){case"object":coll[id]=ret;break;case"array":coll.push(ret)}}else ret=null;return ret},add_component_temp:function(type){var ret=null;return this.is_component(type)&&(ret=new type(""),this.component_temps[ret._slug]=ret),ret},get_component_temp:function(type){return this.has_component_temp(type)?this.component_temps[type.prototype._slug]:this.add_component_temp(type)},has_component_temp:function(type){return!!(this.is_component(type)&&type.prototype._slug in this.component_temps)},get_options:function(opts){var ret={};if(this.util.is_string(opts)&&(opts=[opts]),!this.util.is_array(opts))return ret;for(var x=0;x<opts.length;x++)opts[x]in this.options&&(ret[opts[x]]=this.options[opts[x]]);return ret},get_option:function(opt,def){var ret=this.get_options(opt);return ret=this.util.is_obj(ret)&&opt in ret?ret[opt]:this.util.is_set(def)?def:null},add_viewer:function(id,options){var v=new this.Viewer(id,options);return this.get_viewers()[v.get_id()]=v},get_viewers:function(){return this.get_components(this.Viewer)},has_viewer:function(v){return!!(this.util.is_string(v)&&v in this.get_viewers())},get_viewer:function(v){return this.has_viewer(v)||(v=this.util.add_prefix("default"),this.has_viewer(v)||(v=(v=this.add_viewer(v)).get_id())),this.get_viewers()[v]},init_items:function(){var t=this,sel=this.util.format('a[href][%s="%s"]',this.util.get_attribute("active"),1);$(document).on("click",sel,null,function(){var ret=t.show_item(this);return t.util.is_bool(ret)||(ret=!0),!ret})},get_items:function(){return this.get_components(this.Content_Item)},get_item:function(ref){if(this.util.is_type(ref,this.Content_Item))return ref;var item=null;if(this.util.in_obj(ref,"nodeType")){var key=this.get_component_temp(this.Content_Item).get_data_key();item=$(ref).data(key)}else if(this.util.is_string(ref,!1)){var items=this.get_items();ref in items&&(item=items[ref])}return this.util.is_instance(item,this.Content_Item)||(item=this.add_item(ref)),item},add_item:function(el){return new this.Content_Item(el)},show_item:function(el){return this.get_item(el).show()},save_item:function(item){return this.util.is_instance(item,this.Content_Item)?this.get_items()[item.get_id()]=item:item},get_content_handlers:function(){return this.get_components(this.Content_Handler)},get_content_handler:function(item){var type=this.util.is_instance(item,this.Content_Item)?item.get_attribute("type",""):item.toString(),types=this.get_content_handlers();return type in types?types[type]:null},extend_content_handler:function(id,attr){var hdl=null;if(!this.util.is_string(id)||!this.util.is_obj(attr))return hdl;null===(hdl=this.get_content_handler(id))?this.get_content_handlers()[id]=hdl=new this.Content_Handler(id,attr):hdl.set_attributes(attr);return this.util.in_obj(attr,"styles")&&this.load_styles(attr.styles),hdl},add_group:function(g,attrs){return g=new this.Group(g,attrs),this.get_groups()[g.get_id()]=g},get_groups:function(){return this.get_components(this.Group)},get_group:function(g){return this.has_group(g)?this.get_groups()[g]:this.add_group(g)},has_group:function(g){return this.util.is_string(g)&&g in this.get_groups()},extend_theme:function(id,attr){if(!this.util.is_string(id))return!1;var dfr=$.Deferred();this.loading.push(dfr);var model=this.get_theme_model(id);return this.util.is_empty(model)&&(model=this.save_theme_model({parent:null,id:id})),this.util.is_obj(attr)&&("id"in attr&&delete attr.id,$.extend(model,attr)),this.util.in_obj(attr,"styles")&&this.load_styles(attr.styles),this.util.is_obj(model.parent)||(model.parent=this.get_theme_model(model.parent)),dfr.resolve(),model},get_theme_models:function(){return this.Theme.prototype._models},get_theme_model:function(id){var ms=this.get_theme_models();return this.util.in_obj(ms,id)?ms[id]:{}},save_theme_model:function(model){return this.util.in_obj(model,"id")&&this.util.is_string(model.id)&&(this.get_theme_models()[model.id]=model),model},extend_template_tag_handler:function(id,attr){if(!this.util.is_string(id)||!this.util.is_obj(attr))return!1;var hdl,hdls=this.get_template_tag_handlers();return this.util.in_obj(hdls,id)?(hdl=hdls[id]).set_attributes(attr):hdls[(hdl=new this.Template_Tag_Handler(id,attr)).get_id()]=hdl,this.util.in_obj(attr,"styles")&&this.load_styles(attr.styles),this.util.in_obj(attr,"_hooks")&&attr._hooks.call(hdl),hdl},get_template_tag_handlers:function(){return this.Template_Tag.prototype.handlers},get_template_tag_handler:function(id){var handlers=this.get_template_tag_handlers();return this.util.in_obj(handlers,id)?handlers[id]:null},load_styles:function(styles){if(this.util.is_array(styles)){for(var style,out=[],x=0;x<styles.length;x++)style=styles[x],this.util.in_obj(style,"uri")&&this.util.is_string(style.uri)&&out.push('<link rel="stylesheet" type="text/css" href="'+style.uri+'" />');$("head").append(out.join(""))}}},Component={_slug:"component",_ns:null,_refs:{},_reciprocal:!1,_dom:null,_attributes:!1,_attr_default:{},_attr_default_parsed:!1,_attr_init:null,_attr_map:{},_events:{},_status:null,_id:"",_c:function(id,attributes){this._set_id(id),this.util.is_obj(attributes)&&(this._attr_init=attributes),this._hooks()},_set_parent:function(){this._super(View)},_hooks:function(){},_set_id:function(id){return this.util.is_empty(this._id)&&(this._id=this.util.is_string(id)?id:this.util.guid()),this._id},get_id:function(ns){var id=this._id;return this.util.is_bool(ns)&&ns&&(id=this.add_ns(id)),id},get_ns:function(){return null===this._ns&&(this._ns=this.util.add_prefix(this._slug)),this._ns},add_ns:function(val){return this.util.is_string(val)?this.get_ns()+"_"+val:""},get_status:function(id,raw){var ret=!1;return this.util.in_obj(this._status,id)&&(ret=raw?this._status[id]:!!this._status[id]),ret},set_status:function(id,val){return this.util.is_string(id)?(this.util.is_set(val)||(val=!0),this.util.is_obj(this._status,!1)||(this._status={}),this._status[id]=val):this.util.is_set(val)||(val=!1),val},get_controller:function(){return this.get_parent()},has_reference:function(ref){return!!(this.util.is_string(ref)&&ref in this&&ref in this.get_references())},get_references:function(){return this._refs},get_reference:function(ref){return this.has_reference(ref)?this._refs[ref]:null},get_component:function(cname,options){var c=null;if(!this.has_reference(cname))return c;options=$.extend({},{check_attr:!0,get_default:!1},options);var ctype=this.get_reference(cname);return this.util.is_type(this[cname],ctype)?this[cname]:(c=this[cname]=null,options.check_attr&&(c=this.get_attribute(cname),this.util.is_empty(c)||(c=this.set_component(cname,c))),this.util.is_empty(c)&&options.get_default&&(c=this.get_controller().get_component(ctype)),c)},set_component:function(name,ref,validate){if(!this.has_reference(name))return null;if(this.util.is_empty(ref))ref=null;else{var ctype=this.get_reference(name);this.util.is_string(ref,!1)&&(ref=this.get_controller().get_component(ctype,ref)),this.util.is_type(ref,ctype)&&(!this.util.is_func(validate)||validate.call(this,ref))||(ref=null)}return this[name]=ref,this[name]},clear_component:function(name){this.set_component(name,null)},init_attributes:function(force){if(this.util.is_bool(force)||(force=!1),force||!this.util.is_obj(this._attributes)){var a=this._attributes={};$.extend(a,this.init_default_attributes()),this.util.is_obj(this._attr_init)&&$.extend(a,this._attr_init),$.extend(a,this.get_dom_attributes())}},init_default_attributes:function(){if(!this._attr_default_parsed&&this.util.is_obj(this._attr_map)){var opts=this.get_controller().get_options(this.util.obj_keys(this._attr_map));if(this.util.is_obj(opts)){for(var opt in this._attr_map)opt in opts&&null!==this._attr_map[opt]&&(opts[this._attr_map[opt]]=opts[opt],delete opts[opt]);$.extend(!0,this._attr_default,opts)}this._attr_default_parsed=!0}return this._attr_default},get_dom_attributes:function(){var attrs={},el=this.dom_get(null,{init:!1});if(0<el.length){var attrs_full=$(el).get(0).attributes;if(this.util.is_obj(attrs_full)){var attr_key,attr_prefix=this.util.get_attribute();$.each(attrs_full,function(idx,attr){if(-1===attr.name.indexOf(attr_prefix))return!0;attr_key=attr.name.substr(attr_prefix.length+1),attrs[attr_key]=attr.value})}}return attrs},get_attributes:function(){return this.init_attributes(),this._attributes},get_attribute:function(key,def,enforce_type){if(this.util.is_set(def)||(def=null),!this.util.is_string(key))return def;this.util.is_bool(enforce_type)||(enforce_type=!0);var ret=this.has_attribute(key)?this.get_attributes()[key]:def;return enforce_type&&ret!==def&&null!==def&&!this.util.is_type(ret,$.type(def),!1)&&(this.util.is_scalar(def,!1)&&this.util.is_scalar(ret,!1)?this.util.is_string(def,!1)?ret=ret.toString():this.util.is_num(def,!1)&&!this.util.is_num(ret,!1)?(ret=this.util.is_int(def,!1)?parseInt(ret):parseFloat(ret),this.util.is_num(ret,!1)||(ret=def)):ret=this.util.is_bool(def,!1)?this.util.is_string(ret)||this.util.is_num(ret):def:ret=def),ret},call_attribute:function(attr,args){return attr=this.get_attribute(attr),this.util.is_func(attr)&&(args=Array.prototype.slice.call(arguments,1),attr=attr.apply(this,args)),attr},has_attribute:function(key){return this.util.is_string(key)&&key in this.get_attributes()},set_attributes:function(attributes,full){this.util.is_bool(full)||(full=!1),this.init_attributes(full),this.util.is_obj(attributes)&&$.extend(this._attributes,attributes)},set_attribute:function(key,val){return this.util.is_string(key)&&this.util.is_set(val)&&(this.get_attributes()[key]=val),val},dom_get_selector:function(element){return this.util.is_string(element)?"."+this.add_ns(element):""},dom_get_attribute:function(){return this.util.get_attribute(this._slug)},dom_set:function(el){return(el=$(el)).data(this.get_data_key(),this),this._reciprocal&&(this._dom=el),el},dom_get:function(element,options){var opts_default={init:!0,put:!1};(options=this.util.is_obj(options)?$.extend({},opts_default,options):opts_default).init&&!this.get_status("dom_init")&&(this.set_status("dom_init"),this.dom_init());var ret=this._dom;if(ret&&this.util.is_string(element)){var ch=$(ret).find(this.dom_get_selector(element));ch.length?ret=ch:!0!==options.put&&!this.util.is_obj(options.put)||(ret=this.dom_put(element,options.put))}return $(ret)},dom_init:function(){},dom_put:function(element,content){var r=null;if(!this.dom_has()||!this.util.is_string(element))return $(r);var strip=["tag","content","success"],options={tag:"div",content:"",class:this.add_ns(element)};this.util.is_empty(content)||(this.util.is_type(content,jQuery,!1)||this.util.is_string(content,!1)?options.content=content:this.util.is_obj(content,!1)&&$.extend(options,content));for(var attrs=$.extend({},options),x=0;x<strip.length;x++)delete attrs[strip[x]];var d=this.dom_get();return(r=$(this.dom_get_selector(element),d)).length||(r=$(this.util.format("<%s />",options.tag),attrs).appendTo(d)).length&&this.util.is_method(options,"success")&&options.success.call(r,r),$(r).append(options.content),$(r)},dom_has:function(){return!!this.dom_get().length},get_data_key:function(){return this.get_ns()},on:function(event,fn,options){if(!this.util.is_string(event)||!this.util.is_func(fn)){var t=this,args=Array.prototype.slice.call(arguments,1);return this.util.is_array(event)?$.each(event,function(idx,val){t.on.apply(t,[val].concat(args))}):this.util.is_obj(event)&&$.each(event,function(ev,hdl){t.on.apply(t,[ev,hdl].concat(args))}),this}this.util.is_obj(options,!1)||(options={}),options=$.extend({},{clear:!1},options),this.util.is_obj(this._events,!1)||(this._events={});var es=this._events;return event in es&&this.util.is_obj(es[event],!1)&&!options.clear||(es[event]=[]),es[event].push(fn),this},trigger:function(event,data){var dfr=$.Deferred(),dfrs=[],t=this;if(this.util.is_array(event))return $.each(event,function(idx,val){dfrs.push(t.trigger(val,data))}),$.when.apply(t,dfrs).done(function(){dfr.resolve()}),dfr.promise();if(!(this.util.is_string(event)&&event in this._events))return dfr.resolve(),dfr.promise();var ev={type:event,data:null};return this.util.is_set(data)&&(ev.data=data),$.each(this._events[event],function(idx,fn){dfrs.push(fn.call(t,ev,t))}),$.when.apply(this,dfrs).done(function(){dfr.resolve()}),dfr.promise()}};View.Component=Component=SLB.Class.extend(Component);var Viewer={_slug:"viewer",_refs:{item:"Content_Item",theme:"Theme"},_reciprocal:!0,_attr_default:{loop:!0,animate:!0,autofit:!0,overlay_enabled:!0,overlay_opacity:"0.8",title_default:!1,container:null,slideshow_enabled:!0,slideshow_autostart:!1,slideshow_duration:2,slideshow_active:!1,slideshow_timer:null,labels:{close:"close",nav_prev:"&laquo; prev",nav_next:"next &raquo;",slideshow_start:"start slideshow",slideshow_stop:"stop slideshow",group_status:"Image %current% of %total%",loading:"loading"}},_attr_map:{theme:null,group_loop:"loop",ui_autofit:"autofit",ui_animate:"animate",ui_overlay_opacity:"overlay_opacity",ui_labels:"labels",ui_title_default:"title_default",slideshow_enabled:null,slideshow_autostart:null,slideshow_duration:null},item:null,item_queued:null,theme:null,item_working:null,active:!1,init:!1,open:!1,loading:!1,_hooks:function(){var t=this;this.on(["item-prev","item-next"],function(){t.trigger("item-change")}).on(["close","item-change"],function(){t.unload().done(function(){t.unlock()})})},get_item:function(){return this.get_component("item")},set_item:function(item){this.clear_item(!1);var i=this.set_component("item",item,function(item){return item.has_type()});return!this.util.is_empty(i)},clear_item:function(full){this.util.is_bool(full)||(full=!0);var item=this.get_item();item&&item.reset(),full&&this.clear_component("item")},get_theme:function(){var ret=this.get_component("theme",{check_attr:!1});return this.util.is_empty(ret)&&(ret=this.set_component("theme",new View.Theme(this))),ret},set_theme:function(theme){this.set_component("theme",theme)},lock:function(){return this.set_status("item_working",$.Deferred())},get_lock:function(simple,full){this.util.is_bool(simple)||(simple=!1),this.util.is_bool(full)||(full=!1);var s="item_working";if(simple)return this.get_status(s);var r=this.get_status(s,!0);return this.util.is_promise(r)||(r=this.lock()),full?r:r.promise()},is_locked:function(){return this.get_lock(!0)},unlock:function(){return this.get_lock(!1,!0).resolve()},set_active:function(mode){return this.util.is_bool(mode)||(mode=!0),this.set_status("active",mode)},is_active:function(){return this.get_status("active")},set_loading:function(mode){var dfr=$.Deferred();this.util.is_bool(mode)||(mode=!0),this.loading=mode,this.slideshow_active()&&this.slideshow_pause(mode);var m=mode?"addClass":"removeClass";return $(this.dom_get())[m]("loading"),mode?this.get_theme().transition("load").always(function(){dfr.resolve()}):dfr.resolve(),dfr.promise()},unset_loading:function(){return this.set_loading(!1)},get_loading:function(){return!!this.util.is_bool(this.loading)&&this.loading},is_loading:function(){return this.get_loading()},show:function(item){this.item_queued=item;var vt="theme_valid",valid=!0;if(this.has_attribute(vt)?valid=this.get_attribute(vt,!0):(valid=!(!this.get_theme()||""===this.get_theme().get_template().get_layout(!1)),this.set_attribute(vt,valid)),!valid)return this.close(),!1;function fin(){if(v.lock(),v.set_status("show_deferred",!1),!v.set_item(v.item_queued))return v.close(),!1;v.history_add(),v.set_active(),v.render()}var v=this;this.is_locked()?this.get_status("show_deferred")||(this.set_status("show_deferred"),this.get_lock().always(function(){fin()})):fin()},history_handle:function(e){var state=e.originalEvent.state;if(this.util.is_string(state.item,!1))this.get_controller().get_item(state.item).show({event:e}),this.trigger("item-change");else{var count=this.history_get(!0);this.history_set(0),-1!==count&&this.close()}},history_get:function(full){return this.get_status("history_count",full)},history_set:function(val){return this.set_status("history_count",val)},history_add:function(){if(!history.pushState)return!1;var item=this.get_item(),opts=item.get_attribute("options_show"),count=this.history_get()?this.history_get(!0):0;if(this.util.in_obj(opts,"event")){var e=opts.event.originalEvent;this.util.in_obj(e,"state")&&this.util.in_obj(e.state,"count")&&(count=e.state.count)}else{var state={viewer:this.get_id(),item:null,count:count};count||history.replaceState(state,null),state.item=this.get_controller().save_item(item).get_id(),state.count=++count,history.pushState(state,"")}this.history_set(count)},history_reset:function(){var count=this.history_get(!0);count&&(this.history_set(-1),history.go(-1*count))},is_open:function(){return"none"!==this.dom_get().css("display")},render:function(){var v=this,thm=this.get_theme();v.dom_prep(),this.get_status("render-events")||(this.set_status("render-events"),thm.on("render-loading",function(ev,thm){var dfr=$.Deferred();if(!v.is_active())return dfr.reject(),dfr.promise();function set_pos(){v.dom_get().css("top",$(window).scrollTop())}function always(){v.set_loading().always(function(){dfr.resolve()})}return v.is_open()?thm.transition("unload").fail(function(){set_pos(),thm.dom_get_tag("item","content").attr("style","")}).always(always):thm.transition("open").always(function(){always(),v.events_open(),v.open=!0}).fail(function(){set_pos(),v.get_overlay().show(),v.dom_get().show()}),dfr.promise()}).on("render-complete",function(ev,thm){if(!v.is_active())return!1;var d=v.dom_get(),classes=["item_single","item_multi"],ms=["addClass","removeClass"];v.get_item().get_group().is_single()||ms.reverse(),$.each(ms,function(idx,val){d[val](classes[idx])}),v.events_complete(),thm.transition("complete").fail(function(){if(v.get_attribute("autofit",!0)){var dims=$.extend({display:"inline-block"},thm.get_item_dimensions());thm.dom_get_tag("item","content").css(dims)}}).always(function(){v.unset_loading(),v.trigger("render-complete"),v.init=!0})})),thm.render()},dom_get_container:function(){var sel=this.get_attribute("container");this.util.is_empty(sel)&&(sel="#"+this.add_ns("wrap"));var c=$(sel);if(!c.length){var id=0===sel.indexOf("#")?sel.substr(1):sel;c=$("<div />",{id:id}).appendTo("body")}return c},dom_init:function(){var d=this.dom_set($("<div/>",{id:this.get_id(!0),class:this.get_ns()})).appendTo(this.dom_get_container()).hide(),thm=this.get_theme();d.addClass(thm.get_classes(" "));var v=this;this.get_status("render-init")||(this.set_status("render-init"),thm.on("render-init",function(ev){v.dom_put("layout",ev.data)})),thm.render(!0)},dom_prep:function(mode){var m=this.util.is_bool(mode)&&!mode?"removeClass":"addClass";$("html")[m](this.util.add_prefix("overlay"))},dom_restore:function(){this.dom_prep(!1)},get_layout:function(){return this.dom_get("layout",{put:{success:function(){$(this).hide()}}})},animation_enabled:function(){return this.get_attribute("animate",!0)},overlay_enabled:function(){var ov=this.get_attribute("overlay_enabled");return!!this.util.is_bool(ov)&&ov},get_overlay:function(){var o=null,v=this;return this.overlay_enabled()&&(o=this.dom_get("overlay",{put:{success:function(){$(this).hide().css("opacity",v.get_attribute("overlay_opacity"))}}})),$(o)},unload:function(){var dfr=$.Deferred();return this.get_theme().dom_get_tag("item").text(""),dfr.resolve(),dfr.promise()},reset:function(){this.dom_get().hide(),this.dom_restore(),this.history_reset(),this.clear_item(),this.set_active(!1),this.set_loading(!1),this.slideshow_stop(),this.keys_disable(),this.unlock()},get_labels:function(){return this.get_attribute("labels",{})},get_label:function(name){var lbls=this.get_labels();return name in lbls?lbls[name]:""},events_open:function(){if(this.keys_enable(),this.open)return!1;var l=this.get_layout();l.children().click(function(ev){ev.stopPropagation()});function close(){v.close()}var v=this;l.click(close),this.get_overlay().click(close),this.trigger("events-open")},events_complete:function(){if(this.init)return!1;this.trigger("events-complete")},keys_enable:function(mode){this.util.is_bool(mode)||(mode=!0);var e=["keyup",this.util.get_prefix()].join("."),v=this;mode?$(document).on(e,function(ev){return v.keys_control(ev)}):$(document).off(e)},keys_disable:function(){this.keys_enable(!1)},keys_control:function(ev){var handlers={27:this.close,37:this.item_prev,39:this.item_next};if("rtl"===document.documentElement.getAttribute("dir")&&(handlers[37]=this.item_next,handlers[39]=this.item_prev),ev.which in handlers)return handlers[ev.which].call(this),!1},slideshow_enabled:function(){var o=this.get_attribute("slideshow_enabled");return!(!(this.util.is_bool(o)&&o&&this.get_item())||this.get_item().get_group().is_single())},slideshow_active:function(){return!(!this.slideshow_enabled()||!(this.get_attribute("slideshow_active")||!this.init&&this.get_attribute("slideshow_autostart")))},slideshow_clear_timer:function(){clearInterval(this.get_attribute("slideshow_timer"))},slideshow_set_timer:function(callback){this.set_attribute("slideshow_timer",setInterval(callback,1e3*this.get_attribute("slideshow_duration")))},slideshow_start:function(){if(!this.slideshow_enabled())return!1;this.set_attribute("slideshow_active",!0),this.dom_get().addClass("slideshow_active"),this.slideshow_clear_timer();var v=this;this.slideshow_set_timer(function(){v.slideshow_pause(),v.item_next()}),this.trigger("slideshow-start")},slideshow_stop:function(full){this.util.is_bool(full)||(full=!0),full&&(this.set_attribute("slideshow_active",!1),this.dom_get().removeClass("slideshow_active")),this.slideshow_clear_timer(),this.trigger("slideshow-stop")},slideshow_toggle:function(){if(!this.slideshow_enabled())return!1;this.slideshow_active()?this.slideshow_stop():this.slideshow_start(),this.trigger("slideshow-toggle")},slideshow_pause:function(mode){this.util.is_bool(mode)||(mode=!0),this.slideshow_active()&&(mode?this.slideshow_stop(!1):this.slideshow_start()),this.trigger("slideshow-pause")},slideshow_resume:function(){this.slideshow_pause(!1)},item_next:function(){var g=this.get_item().get_group(!0),v=this,ev="item-next",st=["events","viewer",ev].join("_");g.get_status(st)||(g.set_status(st),g.on(ev,function(e){v.trigger(e.type)})),g.show_next()},item_prev:function(){var g=this.get_item().get_group(!0),v=this,ev="item-prev",st=["events","viewer",ev].join("_");g.get_status(st)||(g.set_status(st),g.on(ev,function(){v.trigger(ev)})),g.show_prev()},close:function(){this.set_active(!1);var v=this,thm=this.get_theme();return thm.transition("unload").always(function(){thm.transition("close",!0).always(function(){v.reset(),v.trigger("close")})}).fail(function(){thm.dom_get_tag("item","content").attr("style","")}),!1}};View.Viewer=Component.extend(Viewer);var Group={_slug:"group",_reciprocal:!0,_refs:{current:"Content_Item"},current:null,selector:null,_hooks:function(){var t=this;this.on(["item-prev","item-next"],function(){t.trigger("item-change")})},get_selector:function(){return this.util.is_empty(this.selector)&&(this.selector=this.util.format('a[%s="%s"]',this.dom_get_attribute(),this.get_id())),this.selector},get_items:function(){var items=$(this.get_selector());return 0===items.length&&this.has_current()&&(items=this.get_current().dom_get()),items},get_item:function(idx){this.util.is_int(idx)||(idx=0);var items=this.get_items(),max=this.get_size()-1;return max<idx&&(idx=max),items.get(idx)},get_pos:function(item){return this.util.is_empty(item)&&(item=this.get_current()),this.util.is_type(item,View.Content_Item)?this.get_items().index(item.dom_get()):-1},has_current:function(){return!this.util.is_empty(this.get_current())},get_current:function(){return null===this.current||this.util.is_type(this.current,View.Content_Item)||(this.current=null),this.current},set_current:function(item){this.util.is_type(item,View.Content_Item)&&(this.current=item)},get_next:function(item){if(this.util.is_type(item,View.Content_Item)||(item=this.get_current()),1===this.get_size())return item;var next=null,pos=this.get_pos(item);return-1!==pos&&(0===(pos=pos+1<this.get_size()?pos+1:0)&&!item.get_viewer().get_attribute("loop")||(next=this.get_item(pos))),next},get_prev:function(item){if(this.util.is_type(item,View.Content_Item)||(item=this.get_current()),1===this.get_size())return item;var prev=null,pos=this.get_pos(item);return-1===pos||0===pos&&!item.get_viewer().get_attribute("loop")||(0===pos&&(pos=this.get_size()),pos-=1,prev=this.get_item(pos)),prev},show_next:function(item){if(1<this.get_size()){var next=this.get_next(item);next||(this.util.is_type(item,View.Content_Item)||(item=this.get_current()),item.get_viewer().close());var i=this.get_controller().get_item(next);this.set_current(i),i.show(),this.trigger("item-next")}},show_prev:function(item){if(1<this.get_size()){var prev=this.get_prev(item);prev||(this.util.is_type(item,View.Content_Item)||(item=this.get_current()),item.get_viewer().close());var i=this.get_controller().get_item(prev);this.set_current(i),i.show(),this.trigger("item-prev")}},get_size:function(){return this.get_items().length},is_single:function(){return 1===this.get_size()}};View.Group=Component.extend(Group);var Content_Handler={_slug:"content_handler",_refs:{item:"Content_Item"},item:null,template:"",has_item:function(){return!this.util.is_empty(this.get_item())},get_item:function(){return this.get_component("item")},set_item:function(item){return this.set_component("item",item)},clear_item:function(){this.clear_component("item")},match:function(item){var m=this.get_attribute("match");if(!this.util.is_empty(m)){if(this.util.is_string(m)&&(m=new RegExp(m,"i"),this.set_attribute("match",m)),this.util.is_type(m,RegExp))return m.test(item.get_uri());if(this.util.is_func(m))return!!m.call(this,item)}return!1},load:function(item){var dfr=$.Deferred();return null===this.call_attribute("load",item,dfr)&&dfr.resolve(),dfr.promise()},render:function(item){var dfr=$.Deferred();return this.call_attribute("render",item,dfr),dfr.promise()}};View.Content_Handler=Component.extend(Content_Handler);var Content_Item={_slug:"content_item",_reciprocal:!0,_refs:{viewer:"Viewer",group:"Group",type:"Content_Handler"},_attr_default:{source:null,permalink:null,dimensions:null,title:"",group:null,internal:!1,output:null},group:null,viewer:null,type:null,data:null,loaded:null,_c:function(el){this.dom_set(el),this._super()},init_default_attributes:function(){this._super();var d=this.dom_get(),key=d.attr(this.util.get_attribute("asset"))||null,assets=this.get_controller().assets||null;if(this.util.is_string(key)){var attrs=[{},this._attr_default,{permalink:d.attr("href")}];if(this.util.is_obj(assets)){var t=this;attrs.push(function(key){var ret={};return key in assets&&t.util.is_obj(assets[key])&&(ret=assets[key]),ret}(key))}this._attr_default=$.extend.apply(this,attrs)}return this._attr_default},get_output:function(){var dfr=$.Deferred(),ret=this.get_attribute("output");if(this.util.is_string(ret))dfr.resolve(ret);else if(this.has_type()){var type=this.get_type(),item=this;type.render(this).done(function(output){item.set_output(output),dfr.resolve(output)})}else dfr.resolve("");return dfr.promise()},set_output:function(out){this.util.is_string(out,!1)&&this.set_attribute("output",out)},get_content:function(){return this.get_output()},get_uri:function(mode){-1===$.inArray(mode,["source","permalink"])&&(mode="source");var ret=this.get_attribute(mode);return this.util.is_string(ret)||(ret="source"===mode?this.get_attribute("permalink"):""),ret=ret.replace(/&(#38|amp);/,"&")},get_title:function(){if(this.has_attribute("title_cached"))return this.get_attribute("title_cached","");function validate(title){return"string"!=typeof title||""===title.trim()?"":(title=title.trim(),t.get_viewer().get_attribute("title_default")||title===t.get_title_default()&&(title=""),title)}var title="",dom=this.dom_get(),t=this;if(dom.length&&(title=(title=(title=dom.attr("title"))||dom.closest("figure").find("figcaption").first().html())||dom.closest("figure").find(".wp-caption-text").first().html()),!title)for(var props=["caption","title"],x=0;x<props.length&&(title=validate(this.get_attribute(props[x],"")),this.util.is_empty(title));x++);return!title&&dom.length&&(title=(title=validate(dom.find("img").first().attr("alt")))||validate(dom.get(0).innerText.trim())),title=validate(title),this.set_attribute("title_cached",title),title},get_title_default:function(){var val="",prop="title_default";if(this.has_attribute(prop))val=this.get_attribute(prop);else{var f=this.get_uri("source"),i=f.lastIndexOf("/");-1!==i&&-1!==(i=(f=f.substr(i+1)).lastIndexOf("."))&&(f=f.substr(0,i)),val=this.set_attribute(prop,f)}return val},get_dimensions:function(){return $.extend({width:0,height:0},this.get_attribute("dimensions"),{})},set_data:function(data){this.data=data},get_data:function(){return this.data},gallery_type:function(){var ret=null,types={wp:".gallery-icon",ngg:".ngg-gallery-thumbnail"},dom=this.dom_get();for(var type in types)if(0<dom.parent(types[type]).length){ret=type;break}return ret},in_gallery:function(gType){var type=this.gallery_type();return null!==type&&(!this.util.is_string(gType)||gType===type)},get_viewer:function(){return this.get_component("viewer",{get_default:!0})},set_viewer:function(v){return this.set_component("viewer",v)},get_group:function(set_current){var g=this.get_component("group");return g||(g=this.set_component("group",new View.Group),set_current=!0),set_current&&g.set_current(this),g},set_group:function(g){this.util.is_string(g)&&(g=this.get_controller().get_group(g)),this.group=!!this.util.is_type(g,View.Group)&&g},get_type:function(){var t=this.get_component("type",{check_attr:!1});return t=t||this.set_type(this.get_controller().get_content_handler(this))},set_type:function(type){return this.set_component("type",type)},has_type:function(){return!this.util.is_empty(this.get_type())},show:function(options){if(!this.has_type())return!1;this.set_attribute("options_show",options);var v=this.get_viewer();return this.load(),v.show(this)},load:function(){return this.util.is_promise(this.loaded)||(this.loaded=this.get_type().load(this)),this.loaded.promise()},reset:function(){this.set_attribute("options_show",null)}};View.Content_Item=Component.extend(Content_Item);var Modeled_Component={_slug:"modeled_component",get_attribute:function(key,def,check_model,enforce_type){if(!this.util.is_string(key))return this._super(key,def,enforce_type);this.util.is_bool(check_model)||(check_model=!0);var ret=null;if(check_model){var m=this.get_ancestor(key,!1);this.util.in_obj(m,key)&&(ret=m[key])}return null===ret&&(ret=this._super(key,def,enforce_type)),ret},get_attribute_recursive:function(key,def,enforce_type){var ret=this.get_attribute(key,def,!0,enforce_type);if(this.util.is_obj(ret)){var models=this.get_ancestors(!1);ret=[ret];var t=this;$.each(models,function(idx,model){key in model&&t.util.is_obj(model[key])&&ret.push(model[key])}),ret.push({}),ret=$.extend.apply($,ret.reverse())}return ret},set_attribute:function(key,val,use_model){if(!this.util.is_string(key)||!this.util.is_set(val))return!1;(this.util.is_bool(use_model)||this.util.is_obj(use_model)||(use_model=!0),use_model)?(this.util.is_obj(use_model)?use_model:this.get_model())[key]=val:this._super(key,val);return val},get_model:function(){var m=this.get_attribute("model",null,!1);return this.util.is_obj(m)||(m={},this.set_attribute("model",m,!1)),m},has_model:function(){return!this.util.is_empty(this.get_model())},in_model:function(key){return!!this.util.in_obj(this.get_model(),key)},get_ancestors:function(inc_current){for(var ret=[],m=this.get_model();this.util.is_obj(m);)ret.push(m),m=this.util.in_obj(m,"parent")&&this.util.is_obj(m.parent)?m.parent:null;return inc_current||ret.shift(),ret},get_ancestor:function(attr,safe_mode){if(!this.util.is_string(attr))return!1;this.util.is_bool(safe_mode)||(safe_mode=!0);for(var mcurr=this.get_model(),m=mcurr,found=!1;this.util.is_obj(m);){if(this.util.in_obj(m,attr)&&!this.util.is_empty(m[attr])){found=!0;break}m=this.util.in_obj(m,"parent")?m.parent:null}return found||(safe_mode?(this.util.is_empty(m)&&(m=mcurr),this.util.in_obj(m,attr)||(m[attr]=null)):m=null),m}};Modeled_Component=Component.extend(Modeled_Component);var Theme={_slug:"theme",_refs:{viewer:"Viewer",template:"Template"},_models:{},_attr_default:{template:null,model:null},viewer:null,template:null,_c:function(id,attributes,viewer){1===arguments.length&&this.util.is_type(id,View.Viewer)&&(viewer=id,id=null),this._super(id,attributes),this.set_viewer(viewer),this.set_model(id)},get_viewer:function(){return this.get_component("viewer",{check_attr:!1,get_default:!0})},set_viewer:function(v){return this.set_component("viewer",v)},get_template:function(){var ret=this.get_component("template");if(this.util.is_empty(ret)){var attr={theme:this,model:this.get_model()};ret=this.set_component("template",new View.Template(attr))}return ret},get_tags:function(name,prop){return this.get_template().get_tags(name,prop)},dom_get_tag:function(tag,prop){return $(this.get_template().dom_get_tag(tag,prop))},get_tag_selector:function(name,prop){return this.get_template().get_tag_selector(name,prop)},get_models:function(){return this._models},get_model:function(id){var ret=null;if(!this.util.is_set(id)&&this.util.is_obj(this.get_attribute("model",null,!1)))ret=this._super();else{var models=this.get_models();this.util.is_string(id)||(id=this.get_controller().get_option("theme_default")),this.util.in_obj(models,id)||(id=$.map(models,function(v,key){return key})[0]),ret=models[id]}return ret},set_model:function(id){this.set_attribute("model",this.get_model(id),!1)},get_classes:function(rtype){var cls=[],thm=this,models=this.get_ancestors(!0);return $.each(models,function(idx,model){cls.push(thm.add_ns(model.id))}),this.util.is_string(rtype)&&(cls=cls.join(rtype)),cls},get_measurement:function(attr,def){var meas=null;if(!this.util.is_string(attr))return meas;this.util.is_obj(def,!1)||(def={});var attr_cache=this.util.format("%s_cache",attr),cache=this.get_attribute(attr_cache,{},!1),status="_status",item=this.get_viewer().get_item(),w=$(window);status in cache&&this.util.is_obj(cache._status)&&cache._status.width===w.width()&&cache._status.height===w.height()||(cache={}),this.util.is_empty(cache)&&(cache._status={width:w.width(),height:w.height(),index:[]});var pos=$.inArray(item,cache._status.index);return-1!==pos&&pos in cache&&(meas=cache[pos]),this.util.is_obj(meas)||(meas=this.call_attribute(attr),this.util.is_obj(meas)||(meas=this.get_measurement_default(attr))),meas=this.util.is_obj(meas)?$.extend({},def,meas):def,pos=cache._status.index.push(item)-1,cache[pos]=meas,this.set_attribute(attr_cache,cache,!1),$.extend({},meas)},get_measurement_default:function(attr){return this.util.is_string(attr)?(attr=this.util.format("get_%s_default",attr),this.util.in_obj(this,attr)?(attr=this[attr],this.util.is_func(attr)&&(attr=attr.call(this))):attr=null,attr):null},get_offset:function(){return this.get_measurement("offset",{width:0,height:0})},get_offset_default:function(){var offset={width:0,height:0},v=this.get_viewer(),vn=v.dom_get(),vc=vn.clone().attr("id","").css({visibility:"hidden",position:"absolute",top:""}).removeClass("loading").appendTo(vn.parent()),l=vc.find(v.dom_get_selector("layout"));if(l.length){l.find("*").css({width:"",height:"",display:""});var tags=this.get_tags("item","content");if(tags.length){var offset_item=v.get_item().get_dimensions();tags=$(l.find(tags[0].get_selector("full")).get(0)).css({width:offset_item.width,height:offset_item.height}),$.each(offset_item,function(key,val){offset[key]=-1*val})}offset.width+=l.width(),offset.height+=l.height(),$.each(offset,function(key,val){val<0&&(offset[key]=0)})}return vc.empty().remove(),offset},get_margin:function(){return this.get_measurement("margin",{width:0,height:0})},get_item_dimensions:function(){var v=this.get_viewer(),dims=v.get_item().get_dimensions();if(v.get_attribute("autofit",!1)){var margin=this.get_margin(),offset=this.get_offset();offset.height+=margin.height,offset.width+=margin.width;var max={width:$(window).width(),height:$(window).height()};max.width>offset.width&&(max.width-=offset.width),max.height>offset.height&&(max.height-=offset.height);var factor=Math.min(max.width/dims.width,max.height/dims.height);factor<1&&$.each(dims,function(key){dims[key]=Math.round(dims[key]*factor)})}return $.extend({},dims)},get_dimensions:function(){var dims=this.get_item_dimensions(),offset=this.get_offset();return $.each(dims,function(key){dims[key]+=offset[key]}),dims},get_breakpoints:function(){return this.get_attribute_recursive("breakpoints")},get_breakpoint:function(target){var ret=0;if(this.util.is_string(target)){var b=this.get_attribute_recursive("breakpoints");this.util.is_obj(b)&&target in b&&(ret=b[target])}return ret},render:function(init){var thm=this,tpl=this.get_template(),st="events_render";this.get_status(st)||(this.set_status(st),tpl.on(["render-init","render-loading","render-complete"],function(ev){return thm.trigger(ev.type,ev.data)})),tpl.render(init)},transition:function(event,clear_queue){var dfr=null,attr="transition",v=this.get_viewer(),fx_temp=null,anim_on=v.animation_enabled();if(v.get_attribute(attr,!0)&&this.util.is_string(event)){clear_queue&&v.get_layout().find("*").each(function(){for(var el=$(this);el.queue().length;)el.stop(!1,!0)});var trns,attr_set=[attr,"set"].join("_");if(this.get_attribute(attr_set))trns=this.get_attribute(attr,{});else{var models=this.get_ancestors(!0);trns=[],this.set_attribute(attr_set,!0);var thm=this;$.each(models,function(idx,model){attr in model&&thm.util.is_obj(model[attr])&&trns.push(model[attr])}),trns.push({}),trns=this.set_attribute(attr,$.extend.apply($,trns.reverse()))}this.util.is_method(trns,event)&&(anim_on||(fx_temp=$.fx.off,$.fx.off=!0),dfr=trns[event].call(this,v,$.Deferred()))}return this.util.is_promise(dfr)||(dfr=$.Deferred()).reject(),dfr.always(function(){null!==fx_temp&&($.fx.off=fx_temp)}),dfr.promise()}};View.Theme=Modeled_Component.extend(Theme);var Template={_slug:"template",_reciprocal:!0,_refs:{theme:"Theme"},_attr_default:{layout_uri:"",layout_raw:"",layout_parsed:"",tags:null,model:null},theme:null,_c:function(attributes){this._super("",attributes)},_hooks:function(){this.on("dom_init",function(ev){var tags=this.get_tags(null,null,!0),names=[],t=this;$.each(tags,function(idx,tag){var name=tag.get_name();-1===$.inArray(name,names)&&(names.push(name),tag.get_handler().trigger(ev.type,{template:t}))})})},get_theme:function(){return this.get_component("theme")},render:function(init){var v=this.get_theme().get_viewer();if(this.util.is_bool(init)||(init=!1),init)this.trigger("render-init",this.dom_get());else{if(!v.is_active())return!1;var item=v.get_item();if(!this.util.is_type(item,View.Content_Item))return v.close(),!1;if(v.is_active()&&this.has_tags()){var loading_promise=this.trigger("render-loading"),tpl=this,tags=this.get_tags(),tag_promises=[];$.when(item.load(),loading_promise).done(function(){return!!v.is_active()&&($.each(tags,function(idx,tag){if(!v.is_active())return!1;tag_promises.push(tag.render(item).done(function(r){if(!v.is_active())return!1;r.tag.dom_get().html(r.output)}))}),!!v.is_active()&&void $.when.apply($,tag_promises).done(function(){tpl.trigger("render-complete")}))})}}},get_layout:function(parsed){return this.util.is_bool(parsed)||(parsed=!0),parsed?this.parse_layout():this.get_attribute("layout_raw","")},parse_layout:function(){var a="layout_parsed",ret=this.get_attribute(a);return this.util.is_string(ret)||(ret=this.sanitize_layout(this.get_layout(!1)),ret=this.parse_tags(ret),this.set_attribute(a,ret)),ret},sanitize_layout:function(l){if(this.util.is_empty(l))return l;var rtype=this.util.is_string(l)?"string":null,dom=$(l),tag_temp=this.get_tag_temp(),cls=tag_temp.get_class(),cls_new=["x",cls].join("_");switch($(tag_temp.get_selector(),dom).each(function(){$(this).removeClass(cls).addClass(cls_new)}),rtype){case"string":l=dom=dom.wrap("<div />").parent().html();break;default:l=dom}return l},parse_tags:function(l){if(!this.util.is_string(l))return"";for(var match,re=/\{{2}\s*(\w.*?)\s*\}{2}/gim;match=re.exec(l);)l=l.substring(0,match.index)+this.get_tag_container(match[1])+l.substring(match.index+match[0].length);return l},get_tag_container:function(tag){var attr=this.get_tag_attribute();return this.util.format('<span %s="%s"></span>',attr,encodeURI(tag))},get_tag_attribute:function(){return this.get_tag_temp().dom_get_attribute()},get_tag:function(idx){var ret=null;if(this.has_tags()){var tags=this.get_tags();(!this.util.is_int(idx)||idx<0||idx>=tags.length)&&(idx=0),ret=tags[idx]}return ret},get_tags:function(name,prop,isolate){this.util.is_bool(isolate)||(isolate=!1);var a="tags",tags=this.get_attribute(a);if(!this.util.is_array(tags)){tags=[];var d=this.dom_get(),attr=this.get_tag_attribute(),nodes=$(d).find("["+attr+"]");$(nodes).each(function(){var el=$(this),tag=new View.Template_Tag(decodeURI(el.attr(attr)));tag.has_handler()&&(tags.push(tag),isolate||(tag.dom_set(el),el.addClass(tag.get_classes(" ")))),isolate||el.removeAttr(attr)}),isolate||this.set_attribute(a,tags,!1)}if(!this.util.is_empty(tags)&&this.util.is_string(name)){this.util.is_string(prop)||(prop=!1);for(var tags_filtered=[],tc=null,x=0;x<tags.length;x++)name===(tc=tags[x]).get_name()&&(prop&&prop!==tc.get_prop()||tags_filtered.push(tc));tags=tags_filtered}return this.util.is_array(tags,!1)?tags:[]},has_tags:function(){return 0<this.get_tags().length},get_tag_temp:function(){return this.get_controller().get_component_temp(View.Template_Tag)},get_tag_selector:function(name,prop){this.util.is_string(name)||(name=""),this.util.is_string(prop)||(prop="");var tag=this.get_tag_temp();return tag.set_attribute("name",name),tag.set_attribute("prop",prop),tag.get_selector("full")},dom_init:function(){this.dom_set(this.get_layout()),this.trigger("dom_init")},dom_get_tag:function(tag,prop){var ret=$(),tags=this.get_tags(tag,prop);if(tags.length){var level=null;this.util.is_string(tag)&&(level=this.util.is_string(prop)?"full":"tag");var sel="."+tags[0].get_class(level);ret=this.dom_get().find(sel)}return ret}};View.Template=Modeled_Component.extend(Template);var Template_Tag={_slug:"template_tag",_reciprocal:!0,_attr_default:{name:null,prop:null,match:null},handlers:{},_c:function(tag_match){this.parse(tag_match)},parse:function(tag_match){if(!this.util.is_string(tag_match))return!1;var part,parts=tag_match.split("|");if(!parts.length)return null;var attrs={name:null,prop:null,match:tag_match};attrs.name=parts[0],-1!==attrs.name.indexOf(".")&&(attrs.name=attrs.name.split(".",2),attrs.prop=attrs.name[1],attrs.name=attrs.name[0]);for(var x=1;x<parts.length;x++)1<(part=parts[x].split(":",1)).length&&!(part[0]in attrs)&&(attrs[part[0]]=part[1]);this.set_attributes(attrs,!0)},render:function(item){var tag=this;return tag.get_handler().render(item,tag).pipe(function(output){return{tag:tag,output:output}})},get_name:function(){return this.get_attribute("name")},get_prop:function(){return this.get_attribute("prop")},get_handler:function(){return this.has_handler()?this.handlers[this.get_name()]:new View.Template_Tag_Handler("")},has_handler:function(){return this.get_name()in this.handlers},get_classes:function(rtype){var cls=[this.get_class(),this.get_class("tag"),this.get_class("full")];return this.util.is_string(rtype)&&(cls=cls.join(rtype)),cls},get_class:function(level){var cls="";switch(level){case"tag":cls=this.get_name();break;case"full":var i,parts=[this.get_name(),this.get_prop()],a=[];for(i=0;i<parts.length;i++)this.util.is_string(parts[i])&&a.push(parts[i]);cls=a.join("_")}return this.util.is_string(cls)?this.add_ns(cls):this.get_ns()},get_selector:function(level){var ret=this.get_class(level);return ret=this.util.is_string(ret)?"."+ret:""}};View.Template_Tag=Component.extend(Template_Tag);var Template_Tag_Handler={_slug:"template_tag_handler",_attr_default:{supports_modifiers:!1,dynamic:!1,props:{}},render:function(item,instance){var dfr=$.Deferred();return this.call_attribute("render",item,instance,dfr),dfr.promise()},add_prop:function(prop,fn){var a="props",props=this.get_attribute(a);if(!this.util.is_string(prop)||!this.util.is_func(fn))return!1;this.util.is_obj(props,!1)||(props={}),props[prop]=fn,this.set_attribute(a,props)},handle_prop:function(prop,item,instance){var props=this.get_attribute("props");return this.util.is_obj(props)&&prop in props&&this.util.is_func(props[prop])?props[prop].call(this,item,instance):item.get_viewer().get_label(prop)}};View.Template_Tag_Handler=Component.extend(Template_Tag_Handler),View=SLB.attach("View",View)}(jQuery);
1
+ window.SLB&&SLB.attach&&!function($){var View={assets:{},component_defaults:[],loading:[],cache:{},component_temps:{},options:{},_init:function(){this._super(),this.init_refs(),this.init_components()},init_refs:function(){var r,ref,prop;for(prop in this)if(prop=this[prop],this.is_component(prop)&&!this.util.is_empty(prop.prototype._refs))for(r in prop.prototype._refs)ref=prop.prototype._refs[r],this.util.is_string(ref)&&ref in this&&(ref=prop.prototype._refs[r]=this[ref]),this.util.is_class(ref)||delete prop.prototype_refs[r]},init_components:function(){this.component_defaults=[this.Viewer]},init:function(options){var t=this;$.when.apply($,this.loading).always(function(){$.extend(!0,t.options,options),$(window).on("popstate",function(e){var state=e.originalEvent.state;if(t.util.in_obj(state,["item","viewer"]))return t.get_viewer(state.viewer).history_handle(e),e.preventDefault()}),t.init_items()})},can_make_default_component:function(type){return-1!==$.inArray(type,this.component_defaults)},is_component:function(comp){return this.util.is_class(comp,this.Component)},get_components:function(type){var ret={};return this.is_component(type)&&((type=type.prototype._slug+"s")in this.cache||(this.cache[type]={}),ret=this.cache[type]),ret},get_component:function(type,id){var coll,tid,ret=null;return this.util.is_func(type)&&(this.util.is_string(id)||(id=null),coll=this.get_components(type),this.util.is_obj(coll)&&(tid=this.util.is_string(id)?id:this.util.add_prefix("default"))in coll&&(ret=coll[tid]),this.util.is_empty(ret)&&(this.util.is_string(id)||this.can_make_default_component(type))&&(ret=this.add_component(type,id))),ret},add_component:function(type,id,options){if(!this.util.is_func(type))return!1;if(this.util.is_empty(id)&&!this.can_make_default_component(type))return!1;var ret=null,m=(this.util.is_empty(id)&&(id=this.util.add_prefix("default")),this.util.is_obj(options)||(options={}),"component"!==type.prototype._slug?"add_"+type.prototype._slug:null),ret=!this.util.is_empty(m)&&m in this&&this.util.is_func(this[m])?this[m](id,options):new type(id,options);if(this.util.is_type(ret,type)){var coll=this.get_components(type);switch($.type(coll)){case"object":coll[id]=ret;break;case"array":coll.push(ret)}}else ret=null;return ret},add_component_temp:function(type){var ret=null;return this.is_component(type)&&(ret=new type(""),this.component_temps[ret._slug]=ret),ret},get_component_temp:function(type){return this.has_component_temp(type)?this.component_temps[type.prototype._slug]:this.add_component_temp(type)},has_component_temp:function(type){return!!(this.is_component(type)&&type.prototype._slug in this.component_temps)},get_options:function(opts){var ret={};if(this.util.is_string(opts)&&(opts=[opts]),this.util.is_array(opts))for(var x=0;x<opts.length;x++)opts[x]in this.options&&(ret[opts[x]]=this.options[opts[x]]);return ret},get_option:function(opt,def){var ret=this.get_options(opt);return ret=this.util.is_obj(ret)&&opt in ret?ret[opt]:this.util.is_set(def)?def:null},add_viewer:function(id,options){id=new this.Viewer(id,options);return this.get_viewers()[id.get_id()]=id},get_viewers:function(){return this.get_components(this.Viewer)},has_viewer:function(v){return!!(this.util.is_string(v)&&v in this.get_viewers())},get_viewer:function(v){return this.has_viewer(v)||(v=this.util.add_prefix("default"),this.has_viewer(v)||(v=(v=this.add_viewer(v)).get_id())),this.get_viewers()[v]},init_items:function(){var t=this,sel=this.util.format('a[href][%s="%s"]',this.util.get_attribute("active"),1);$(document).on("click",sel,null,function(){var ret=t.show_item(this);return!(ret=t.util.is_bool(ret)?ret:!0)})},get_items:function(){return this.get_components(this.Content_Item)},get_item:function(ref){var item,key;return this.util.is_type(ref,this.Content_Item)?ref:(item=null,this.util.in_obj(ref,"nodeType")?(key=this.get_component_temp(this.Content_Item).get_data_key(),item=$(ref).data(key)):this.util.is_string(ref,!1)&&ref in(key=this.get_items())&&(item=key[ref]),this.util.is_instance(item,this.Content_Item)?item:this.add_item(ref))},add_item:function(el){return new this.Content_Item(el)},show_item:function(el){return this.get_item(el).show()},save_item:function(item){return this.util.is_instance(item,this.Content_Item)&&(this.get_items()[item.get_id()]=item),item},get_content_handlers:function(){return this.get_components(this.Content_Handler)},get_content_handler:function(item){var item=this.util.is_instance(item,this.Content_Item)?item.get_attribute("type",""):item.toString(),types=this.get_content_handlers();return item in types?types[item]:null},extend_content_handler:function(id,attr){var hdl=null;return this.util.is_string(id)&&this.util.is_obj(attr)&&(null===(hdl=this.get_content_handler(id))?this.get_content_handlers()[id]=hdl=new this.Content_Handler(id,attr):hdl.set_attributes(attr),this.util.in_obj(attr,"styles")&&this.load_styles(attr.styles)),hdl},add_group:function(g,attrs){return g=new this.Group(g,attrs),this.get_groups()[g.get_id()]=g},get_groups:function(){return this.get_components(this.Group)},get_group:function(g){return this.has_group(g)?this.get_groups()[g]:this.add_group(g)},has_group:function(g){return this.util.is_string(g)&&g in this.get_groups()},extend_theme:function(id,attr){var dfr,model;return!!this.util.is_string(id)&&(dfr=$.Deferred(),this.loading.push(dfr),model=this.get_theme_model(id),this.util.is_empty(model)&&(model=this.save_theme_model({parent:null,id:id})),this.util.is_obj(attr)&&("id"in attr&&delete attr.id,$.extend(model,attr)),this.util.in_obj(attr,"styles")&&this.load_styles(attr.styles),this.util.is_obj(model.parent)||(model.parent=this.get_theme_model(model.parent)),dfr.resolve(),model)},get_theme_models:function(){return this.Theme.prototype._models},get_theme_model:function(id){var ms=this.get_theme_models();return this.util.in_obj(ms,id)?ms[id]:{}},save_theme_model:function(model){return this.util.in_obj(model,"id")&&this.util.is_string(model.id)&&(this.get_theme_models()[model.id]=model),model},extend_template_tag_handler:function(id,attr){var hdl,hdls;return!(!this.util.is_string(id)||!this.util.is_obj(attr))&&(hdls=this.get_template_tag_handlers(),this.util.in_obj(hdls,id)?(hdl=hdls[id]).set_attributes(attr):hdls[(hdl=new this.Template_Tag_Handler(id,attr)).get_id()]=hdl,this.util.in_obj(attr,"styles")&&this.load_styles(attr.styles),this.util.in_obj(attr,"_hooks")&&attr._hooks.call(hdl),hdl)},get_template_tag_handlers:function(){return this.Template_Tag.prototype.handlers},get_template_tag_handler:function(id){var handlers=this.get_template_tag_handlers();return this.util.in_obj(handlers,id)?handlers[id]:null},load_styles:function(styles){if(this.util.is_array(styles)){for(var style,out=[],x=0;x<styles.length;x++)style=styles[x],this.util.in_obj(style,"uri")&&this.util.is_string(style.uri)&&out.push('<link rel="stylesheet" type="text/css" href="'+style.uri+'" />');$("head").append(out.join(""))}}},Component={_slug:"component",_ns:null,_refs:{},_reciprocal:!1,_dom:null,_attributes:!1,_attr_default:{},_attr_default_parsed:!1,_attr_init:null,_attr_map:{},_events:{},_status:null,_id:"",_c:function(id,attributes){this._set_id(id),this.util.is_obj(attributes)&&(this._attr_init=attributes),this._hooks()},_set_parent:function(){this._super(View)},_hooks:function(){},_set_id:function(id){return this.util.is_empty(this._id)&&(this._id=this.util.is_string(id)?id:this.util.guid()),this._id},get_id:function(ns){var id=this._id;return id=this.util.is_bool(ns)&&ns?this.add_ns(id):id},get_ns:function(){return null===this._ns&&(this._ns=this.util.add_prefix(this._slug)),this._ns},add_ns:function(val){return this.util.is_string(val)?this.get_ns()+"_"+val:""},get_status:function(id,raw){var ret=!1;return ret=this.util.in_obj(this._status,id)?raw?this._status[id]:!!this._status[id]:ret},set_status:function(id,val){return this.util.is_string(id)?(this.util.is_set(val)||(val=!0),this.util.is_obj(this._status,!1)||(this._status={}),this._status[id]=val):this.util.is_set(val)||(val=!1),val},get_controller:function(){return this.get_parent()},has_reference:function(ref){return!!(this.util.is_string(ref)&&ref in this&&ref in this.get_references())},get_references:function(){return this._refs},get_reference:function(ref){return this.has_reference(ref)?this._refs[ref]:null},get_component:function(cname,options){var c=null;if(this.has_reference(cname)){options=$.extend({},{check_attr:!0,get_default:!1},options);var ctype=this.get_reference(cname);if(this.util.is_type(this[cname],ctype))return this[cname];c=this[cname]=null,options.check_attr&&(c=this.get_attribute(cname),this.util.is_empty(c)||(c=this.set_component(cname,c))),this.util.is_empty(c)&&options.get_default&&(c=this.get_controller().get_component(ctype))}return c},set_component:function(name,ref,validate){var ctype;return this.has_reference(name)?((this.util.is_empty(ref)||(ctype=this.get_reference(name),this.util.is_string(ref,!1)&&(ref=this.get_controller().get_component(ctype,ref)),!this.util.is_type(ref,ctype)||this.util.is_func(validate)&&!validate.call(this,ref)))&&(ref=null),this[name]=ref,this[name]):null},clear_component:function(name){this.set_component(name,null)},init_attributes:function(force){!(force=this.util.is_bool(force)?force:!1)&&this.util.is_obj(this._attributes)||(force=this._attributes={},$.extend(force,this.init_default_attributes()),this.util.is_obj(this._attr_init)&&$.extend(force,this._attr_init),$.extend(force,this.get_dom_attributes()))},init_default_attributes:function(){if(!this._attr_default_parsed&&this.util.is_obj(this._attr_map)){var opts=this.get_controller().get_options(this.util.obj_keys(this._attr_map));if(this.util.is_obj(opts)){for(var opt in this._attr_map)opt in opts&&null!==this._attr_map[opt]&&(opts[this._attr_map[opt]]=opts[opt],delete opts[opt]);$.extend(!0,this._attr_default,opts)}this._attr_default_parsed=!0}return this._attr_default},get_dom_attributes:function(){var attr_prefix,attr_key,attrs={},el=this.dom_get(null,{init:!1});return 0<el.length&&(el=$(el).get(0).attributes,this.util.is_obj(el)&&(attr_prefix=this.util.get_attribute(),$.each(el,function(idx,attr){if(-1===attr.name.indexOf(attr_prefix))return!0;attr_key=attr.name.substr(attr_prefix.length+1),attrs[attr_key]=attr.value}))),attrs},get_attributes:function(){return this.init_attributes(),this._attributes},get_attribute:function(key,def,enforce_type){if(this.util.is_set(def)||(def=null),!this.util.is_string(key))return def;this.util.is_bool(enforce_type)||(enforce_type=!0);key=this.has_attribute(key)?this.get_attributes()[key]:def;return enforce_type&&key!==def&&null!==def&&!this.util.is_type(key,$.type(def),!1)&&(this.util.is_scalar(def,!1)&&this.util.is_scalar(key,!1)?this.util.is_string(def,!1)?key=key.toString():this.util.is_num(def,!1)&&!this.util.is_num(key,!1)?(key=(this.util.is_int(def,!1)?parseInt:parseFloat)(key),this.util.is_num(key,!1)||(key=def)):key=this.util.is_bool(def,!1)?this.util.is_string(key)||this.util.is_num(key):def:key=def),key},call_attribute:function(attr,args){return attr=this.get_attribute(attr),this.util.is_func(attr)&&(args=Array.prototype.slice.call(arguments,1),attr=attr.apply(this,args)),attr},has_attribute:function(key){return this.util.is_string(key)&&key in this.get_attributes()},set_attributes:function(attributes,full){this.util.is_bool(full)||(full=!1),this.init_attributes(full),this.util.is_obj(attributes)&&$.extend(this._attributes,attributes)},set_attribute:function(key,val){return this.util.is_string(key)&&this.util.is_set(val)&&(this.get_attributes()[key]=val),val},dom_get_selector:function(element){return this.util.is_string(element)?"."+this.add_ns(element):""},dom_get_attribute:function(){return this.util.get_attribute(this._slug)},dom_set:function(el){return(el=$(el)).data(this.get_data_key(),this),this._reciprocal&&(this._dom=el),el},dom_get:function(element,options){var ch,opts_default={init:!0,put:!1},opts_default=((options=this.util.is_obj(options)?$.extend({},opts_default,options):opts_default).init&&!this.get_status("dom_init")&&(this.set_status("dom_init"),this.dom_init()),this._dom);return opts_default&&this.util.is_string(element)&&((ch=$(opts_default).find(this.dom_get_selector(element))).length?opts_default=ch:!0!==options.put&&!this.util.is_obj(options.put)||(opts_default=this.dom_put(element,options.put))),$(opts_default)},dom_init:function(){},dom_put:function(element,content){var r=null;if(this.dom_has()&&this.util.is_string(element)){for(var strip=["tag","content","success"],options={tag:"div",content:"",class:this.add_ns(element)},attrs=(this.util.is_empty(content)||(this.util.is_type(content,jQuery,!1)||this.util.is_string(content,!1)?options.content=content:this.util.is_obj(content,!1)&&$.extend(options,content)),$.extend({},options)),x=0;x<strip.length;x++)delete attrs[strip[x]];content=this.dom_get();(r=$(this.dom_get_selector(element),content)).length||(r=$(this.util.format("<%s />",options.tag),attrs).appendTo(content)).length&&this.util.is_method(options,"success")&&options.success.call(r,r),$(r).append(options.content)}return $(r)},dom_has:function(){return!!this.dom_get().length},get_data_key:function(){return this.get_ns()},on:function(event,fn,options){var t,args,es;return this.util.is_string(event)&&this.util.is_func(fn)?(this.util.is_obj(options,!1)||(options={}),options=$.extend({},{clear:!1},options),this.util.is_obj(this._events,!1)||(this._events={}),event in(es=this._events)&&this.util.is_obj(es[event],!1)&&!options.clear||(es[event]=[]),es[event].push(fn)):(t=this,args=Array.prototype.slice.call(arguments,1),this.util.is_array(event)?$.each(event,function(idx,val){t.on.apply(t,[val].concat(args))}):this.util.is_obj(event)&&$.each(event,function(ev,hdl){t.on.apply(t,[ev,hdl].concat(args))})),this},trigger:function(event,data){var ev,dfr=$.Deferred(),dfrs=[],t=this;return this.util.is_array(event)?($.each(event,function(idx,val){dfrs.push(t.trigger(val,data))}),$.when.apply(t,dfrs).done(function(){dfr.resolve()})):this.util.is_string(event)&&event in this._events?(ev={type:event,data:null},this.util.is_set(data)&&(ev.data=data),$.each(this._events[event],function(idx,fn){dfrs.push(fn.call(t,ev,t))}),$.when.apply(this,dfrs).done(function(){dfr.resolve()})):dfr.resolve(),dfr.promise()}},Viewer=(View.Component=Component=SLB.Class.extend(Component),{_slug:"viewer",_refs:{item:"Content_Item",theme:"Theme"},_reciprocal:!0,_attr_default:{loop:!0,animate:!0,autofit:!0,overlay_enabled:!0,overlay_opacity:"0.8",title_default:!1,container:null,slideshow_enabled:!0,slideshow_autostart:!1,slideshow_duration:2,slideshow_active:!1,slideshow_timer:null,labels:{close:"close",nav_prev:"&laquo; prev",nav_next:"next &raquo;",slideshow_start:"start slideshow",slideshow_stop:"stop slideshow",group_status:"Image %current% of %total%",loading:"loading"}},_attr_map:{theme:null,group_loop:"loop",ui_autofit:"autofit",ui_animate:"animate",ui_overlay_opacity:"overlay_opacity",ui_labels:"labels",ui_title_default:"title_default",slideshow_enabled:null,slideshow_autostart:null,slideshow_duration:null},item:null,item_queued:null,theme:null,item_working:null,active:!1,init:!1,open:!1,loading:!1,_hooks:function(){var t=this;this.on(["item-prev","item-next"],function(){t.trigger("item-change")}).on(["close","item-change"],function(){t.unload().done(function(){t.unlock()})})},get_item:function(){return this.get_component("item")},set_item:function(item){this.clear_item(!1);item=this.set_component("item",item,function(item){return item.has_type()});return!this.util.is_empty(item)},clear_item:function(full){this.util.is_bool(full)||(full=!0);var item=this.get_item();item&&item.reset(),full&&this.clear_component("item")},get_theme:function(){var ret=this.get_component("theme",{check_attr:!1});return ret=this.util.is_empty(ret)?this.set_component("theme",new View.Theme(this)):ret},set_theme:function(theme){this.set_component("theme",theme)},lock:function(){return this.set_status("item_working",$.Deferred())},get_lock:function(simple,full){this.util.is_bool(simple)||(simple=!1),this.util.is_bool(full)||(full=!1);var s="item_working";return simple?this.get_status(s):(simple=this.get_status(s,!0),this.util.is_promise(simple)||(simple=this.lock()),full?simple:simple.promise())},is_locked:function(){return this.get_lock(!0)},unlock:function(){return this.get_lock(!1,!0).resolve()},set_active:function(mode){return this.util.is_bool(mode)||(mode=!0),this.set_status("active",mode)},is_active:function(){return this.get_status("active")},set_loading:function(mode){var dfr=$.Deferred(),m=(this.util.is_bool(mode)||(mode=!0),this.loading=mode,this.slideshow_active()&&this.slideshow_pause(mode),mode?"addClass":"removeClass");return $(this.dom_get())[m]("loading"),mode?this.get_theme().transition("load").always(function(){dfr.resolve()}):dfr.resolve(),dfr.promise()},unset_loading:function(){return this.set_loading(!1)},get_loading:function(){return!!this.util.is_bool(this.loading)&&this.loading},is_loading:function(){return this.get_loading()},show:function(item){this.item_queued=item;var fin_set="show_deferred",item="theme_valid",valid=!0;if(this.has_attribute(item)?valid=this.get_attribute(item,!0):(valid=!(!this.get_theme()||""===this.get_theme().get_template().get_layout(!1)),this.set_attribute(item,valid)),!valid)return this.close(),!1;function fin(){if(v.lock(),v.set_status(fin_set,!1),!v.set_item(v.item_queued))return v.close();v.history_add(),v.set_active(),v.render()}var v=this;this.is_locked()?this.get_status(fin_set)||(this.set_status(fin_set),this.get_lock().always(function(){fin()})):fin()},history_handle:function(e){var state=e.originalEvent.state;this.util.is_string(state.item,!1)?(this.get_controller().get_item(state.item).show({event:e}),this.trigger("item-change")):(state=this.history_get(!0),this.history_set(0),-1!==state&&this.close())},history_get:function(full){return this.get_status("history_count",full)},history_set:function(val){return this.set_status("history_count",val)},history_add:function(){if(!history.pushState)return!1;var item=this.get_item(),opts=item.get_attribute("options_show"),count=this.history_get()?this.history_get(!0):0;this.util.in_obj(opts,"event")?(opts=opts.event.originalEvent,this.util.in_obj(opts,"state")&&this.util.in_obj(opts.state,"count")&&(count=opts.state.count)):(opts={viewer:this.get_id(),item:null,count:count},count||history.replaceState(opts,null),opts.item=this.get_controller().save_item(item).get_id(),opts.count=++count,history.pushState(opts,"")),this.history_set(count)},history_reset:function(){var count=this.history_get(!0);count&&(this.history_set(-1),history.go(-1*count))},is_open:function(){return"none"!==this.dom_get().css("display")},render:function(){var v=this,thm=this.get_theme();v.dom_prep(),this.get_status("render-events")||(this.set_status("render-events"),thm.on("render-loading",function(ev,thm){var set_pos,always,dfr=$.Deferred();return v.is_active()?(set_pos=function(){v.dom_get().css("top",$(window).scrollTop())},always=function(){v.set_loading().always(function(){dfr.resolve()})},v.is_open()?thm.transition("unload").fail(function(){set_pos(),thm.dom_get_tag("item","content").attr("style","")}).always(always):thm.transition("open").always(function(){always(),v.events_open(),v.open=!0}).fail(function(){set_pos(),v.get_overlay().show(),v.dom_get().show()})):dfr.reject(),dfr.promise()}).on("render-complete",function(ev,thm){if(!v.is_active())return!1;var d=v.dom_get(),classes=["item_single","item_multi"],ms=["addClass","removeClass"];v.get_item().get_group().is_single()||ms.reverse(),$.each(ms,function(idx,val){d[val](classes[idx])}),v.events_complete(),thm.transition("complete").fail(function(){var dims;v.get_attribute("autofit",!0)&&(dims=$.extend({display:"inline-block"},thm.get_item_dimensions()),thm.dom_get_tag("item","content").css(dims))}).always(function(){v.unset_loading(),v.trigger("render-complete"),v.init=!0})})),thm.render()},dom_get_container:function(){var sel=this.get_attribute("container"),c=(this.util.is_empty(sel)&&(sel="#"+this.add_ns("wrap")),$(sel));return c.length||(sel=0===sel.indexOf("#")?sel.substr(1):sel,c=$("<div />",{id:sel}).appendTo("body")),c},dom_init:function(){var d=this.dom_set($("<div/>",{id:this.get_id(!0),class:this.get_ns()})).appendTo(this.dom_get_container()).hide(),thm=this.get_theme(),v=(d.addClass(thm.get_classes(" ")),this);this.get_status("render-init")||(this.set_status("render-init"),thm.on("render-init",function(ev){v.dom_put("layout",ev.data)})),thm.render(!0)},dom_prep:function(mode){mode=this.util.is_bool(mode)&&!mode?"removeClass":"addClass";$("html")[mode](this.util.add_prefix("overlay"))},dom_restore:function(){this.dom_prep(!1)},get_layout:function(){return this.dom_get("layout",{put:{success:function(){$(this).hide()}}})},animation_enabled:function(){return this.get_attribute("animate",!0)},overlay_enabled:function(){var ov=this.get_attribute("overlay_enabled");return!!this.util.is_bool(ov)&&ov},get_overlay:function(){var o=null,v=this;return this.overlay_enabled()&&(o=this.dom_get("overlay",{put:{success:function(){$(this).hide().css("opacity",v.get_attribute("overlay_opacity"))}}})),$(o)},unload:function(){var dfr=$.Deferred();return this.get_theme().dom_get_tag("item").text(""),dfr.resolve(),dfr.promise()},reset:function(){this.dom_get().hide(),this.dom_restore(),this.history_reset(),this.clear_item(),this.set_active(!1),this.set_loading(!1),this.slideshow_stop(),this.keys_disable(),this.unlock()},get_labels:function(){return this.get_attribute("labels",{})},get_label:function(name){var lbls=this.get_labels();return name in lbls?lbls[name]:""},events_open:function(){if(this.keys_enable(),this.open)return!1;function close(){v.close()}var l=this.get_layout(),v=(l.children().click(function(ev){ev.stopPropagation()}),this);l.click(close),this.get_overlay().click(close),this.trigger("events-open")},events_complete:function(){if(this.init)return!1;this.trigger("events-complete")},keys_enable:function(mode){this.util.is_bool(mode)||(mode=!0);var e=["keyup",this.util.get_prefix()].join("."),v=this;mode?$(document).on(e,function(ev){return v.keys_control(ev)}):$(document).off(e)},keys_disable:function(){this.keys_enable(!1)},keys_control:function(ev){var handlers={27:this.close,37:this.item_prev,39:this.item_next};if("rtl"===document.documentElement.getAttribute("dir")&&(handlers[37]=this.item_next,handlers[39]=this.item_prev),ev.which in handlers)return handlers[ev.which].call(this),!1},slideshow_enabled:function(){var o=this.get_attribute("slideshow_enabled");return!(!(this.util.is_bool(o)&&o&&this.get_item())||this.get_item().get_group().is_single())},slideshow_active:function(){return!(!this.slideshow_enabled()||!(this.get_attribute("slideshow_active")||!this.init&&this.get_attribute("slideshow_autostart")))},slideshow_clear_timer:function(){clearInterval(this.get_attribute("slideshow_timer"))},slideshow_set_timer:function(callback){this.set_attribute("slideshow_timer",setInterval(callback,1e3*this.get_attribute("slideshow_duration")))},slideshow_start:function(){if(!this.slideshow_enabled())return!1;this.set_attribute("slideshow_active",!0),this.dom_get().addClass("slideshow_active"),this.slideshow_clear_timer();var v=this;this.slideshow_set_timer(function(){v.slideshow_pause(),v.item_next()}),this.trigger("slideshow-start")},slideshow_stop:function(full){(full=this.util.is_bool(full)?full:!0)&&(this.set_attribute("slideshow_active",!1),this.dom_get().removeClass("slideshow_active")),this.slideshow_clear_timer(),this.trigger("slideshow-stop")},slideshow_toggle:function(){if(!this.slideshow_enabled())return!1;this.slideshow_active()?this.slideshow_stop():this.slideshow_start(),this.trigger("slideshow-toggle")},slideshow_pause:function(mode){this.util.is_bool(mode)||(mode=!0),this.slideshow_active()&&(mode?this.slideshow_stop(!1):this.slideshow_start()),this.trigger("slideshow-pause")},slideshow_resume:function(){this.slideshow_pause(!1)},item_next:function(){var g=this.get_item().get_group(!0),v=this,ev="item-next",st=["events","viewer",ev].join("_");g.get_status(st)||(g.set_status(st),g.on(ev,function(e){v.trigger(e.type)})),g.show_next()},item_prev:function(){var g=this.get_item().get_group(!0),v=this,ev="item-prev",st=["events","viewer",ev].join("_");g.get_status(st)||(g.set_status(st),g.on(ev,function(){v.trigger(ev)})),g.show_prev()},close:function(){this.set_active(!1);var v=this,thm=this.get_theme();return thm.transition("unload").always(function(){thm.transition("close",!0).always(function(){v.reset(),v.trigger("close")})}).fail(function(){thm.dom_get_tag("item","content").attr("style","")}),!1}}),Viewer=(View.Viewer=Component.extend(Viewer),{_slug:"group",_reciprocal:!0,_refs:{current:"Content_Item"},current:null,selector:null,_hooks:function(){var t=this;this.on(["item-prev","item-next"],function(){t.trigger("item-change")})},get_selector:function(){return this.util.is_empty(this.selector)&&(this.selector=this.util.format('a[%s="%s"]',this.dom_get_attribute(),this.get_id())),this.selector},get_items:function(){var items=$(this.get_selector());return items=0===items.length&&this.has_current()?this.get_current().dom_get():items},get_item:function(idx){this.util.is_int(idx)||(idx=0);var items=this.get_items(),max=this.get_size()-1;return items.get(idx=max<idx?max:idx)},get_pos:function(item){return this.util.is_empty(item)&&(item=this.get_current()),this.util.is_type(item,View.Content_Item)?this.get_items().index(item.dom_get()):-1},has_current:function(){return!this.util.is_empty(this.get_current())},get_current:function(){return null===this.current||this.util.is_type(this.current,View.Content_Item)||(this.current=null),this.current},set_current:function(item){this.util.is_type(item,View.Content_Item)&&(this.current=item)},get_next:function(item){var next,pos;return this.util.is_type(item,View.Content_Item)||(item=this.get_current()),1===this.get_size()?item:(next=null,-1===(pos=this.get_pos(item))||0===(pos=pos+1<this.get_size()?pos+1:0)&&!item.get_viewer().get_attribute("loop")?next:this.get_item(pos))},get_prev:function(item){var prev,pos;return this.util.is_type(item,View.Content_Item)||(item=this.get_current()),1===this.get_size()?item:(prev=null,-1===(pos=this.get_pos(item))||0===pos&&!item.get_viewer().get_attribute("loop")||(0===pos&&(pos=this.get_size()),prev=this.get_item(--pos)),prev)},show_next:function(item){var next;1<this.get_size()&&((next=this.get_next(item))||(item=this.util.is_type(item,View.Content_Item)?item:this.get_current()).get_viewer().close(),item=this.get_controller().get_item(next),this.set_current(item),item.show(),this.trigger("item-next"))},show_prev:function(item){var prev;1<this.get_size()&&((prev=this.get_prev(item))||(item=this.util.is_type(item,View.Content_Item)?item:this.get_current()).get_viewer().close(),item=this.get_controller().get_item(prev),this.set_current(item),item.show(),this.trigger("item-prev"))},get_size:function(){return this.get_items().length},is_single:function(){return 1===this.get_size()}}),Viewer=(View.Group=Component.extend(Viewer),{_slug:"content_handler",_refs:{item:"Content_Item"},item:null,template:"",has_item:function(){return!this.util.is_empty(this.get_item())},get_item:function(){return this.get_component("item")},set_item:function(item){return this.set_component("item",item)},clear_item:function(){this.clear_component("item")},match:function(item){var m=this.get_attribute("match");if(!this.util.is_empty(m)){if(this.util.is_string(m)&&(m=new RegExp(m,"i"),this.set_attribute("match",m)),this.util.is_type(m,RegExp))return m.test(item.get_uri());if(this.util.is_func(m))return!!m.call(this,item)}return!1},load:function(item){var dfr=$.Deferred();return null===this.call_attribute("load",item,dfr)&&dfr.resolve(),dfr.promise()},render:function(item){var dfr=$.Deferred();return this.call_attribute("render",item,dfr),dfr.promise()}}),Viewer=(View.Content_Handler=Component.extend(Viewer),{_slug:"content_item",_reciprocal:!0,_refs:{viewer:"Viewer",group:"Group",type:"Content_Handler"},_attr_default:{source:null,permalink:null,dimensions:null,title:"",group:null,internal:!1,output:null},group:null,viewer:null,type:null,data:null,loaded:null,_c:function(el){this.dom_set(el),this._super()},init_default_attributes:function(){this._super();var t,d=this.dom_get(),key=d.attr(this.util.get_attribute("asset"))||null,assets=this.get_controller().assets||null;return this.util.is_string(key)&&(d=[{},this._attr_default,{permalink:d.attr("href")}],this.util.is_obj(assets)&&(t=this,d.push(function(key){var ret={};return ret=key in assets&&t.util.is_obj(assets[key])?assets[key]:ret}(key))),this._attr_default=$.extend.apply(this,d)),this._attr_default},get_output:function(){var item,dfr=$.Deferred(),ret=this.get_attribute("output");return this.util.is_string(ret)?dfr.resolve(ret):this.has_type()?this.get_type().render(item=this).done(function(output){item.set_output(output),dfr.resolve(output)}):dfr.resolve(""),dfr.promise()},set_output:function(out){this.util.is_string(out,!1)&&this.set_attribute("output",out)},get_content:function(){return this.get_output()},get_uri:function(mode){-1===$.inArray(mode,["source","permalink"])&&(mode="source");var ret=this.get_attribute(mode);return ret=(ret=this.util.is_string(ret)?ret:"source"===mode?this.get_attribute("permalink"):"").replace(/&(#38|amp);/,"&")},get_title:function(){if(this.has_attribute("title_cached"))return this.get_attribute("title_cached","");function validate(title){return"string"!=typeof title||""===title.trim()?"":(title=title.trim(),t.get_viewer().get_attribute("title_default")||title===t.get_title_default()&&(title=""),title)}var title="",dom=this.dom_get(),t=this;if(!(title=dom.length?(title=(title=dom.attr("title"))||dom.closest("figure").find("figcaption").first().html())||dom.closest("figure").find(".wp-caption-text").first().html():title))for(var props=["caption","title"],x=0;x<props.length&&(title=validate(this.get_attribute(props[x],"")),this.util.is_empty(title));x++);return title=validate(title=!title&&dom.length?(title=validate(dom.find("img").first().attr("alt")))||validate(dom.get(0).innerText.trim()):title),this.set_attribute("title_cached",title),title},get_title_default:function(){var f,i,prop="title_default";return this.has_attribute(prop)?this.get_attribute(prop):(-1!==(i=(f=this.get_uri("source")).lastIndexOf("/"))&&-1!==(i=(f=f.substr(i+1)).lastIndexOf("."))&&(f=f.substr(0,i)),this.set_attribute(prop,f))},get_dimensions:function(){return $.extend({width:0,height:0},this.get_attribute("dimensions"),{})},set_data:function(data){this.data=data},get_data:function(){return this.data},gallery_type:function(){var type,ret=null,types={wp:".gallery-icon",ngg:".ngg-gallery-thumbnail"},dom=this.dom_get();for(type in types)if(0<dom.parent(types[type]).length){ret=type;break}return ret},in_gallery:function(gType){var type=this.gallery_type();return null!==type&&(!this.util.is_string(gType)||gType===type)},get_viewer:function(){return this.get_component("viewer",{get_default:!0})},set_viewer:function(v){return this.set_component("viewer",v)},get_group:function(set_current){var g=this.get_component("group");return g||(g=this.set_component("group",new View.Group),set_current=!0),set_current&&g.set_current(this),g},set_group:function(g){this.util.is_string(g)&&(g=this.get_controller().get_group(g)),this.group=!!this.util.is_type(g,View.Group)&&g},get_type:function(){return this.get_component("type",{check_attr:!1})||this.set_type(this.get_controller().get_content_handler(this))},set_type:function(type){return this.set_component("type",type)},has_type:function(){return!this.util.is_empty(this.get_type())},show:function(options){if(!this.has_type())return!1;this.set_attribute("options_show",options);options=this.get_viewer();return this.load(),options.show(this)},load:function(){return this.util.is_promise(this.loaded)||(this.loaded=this.get_type().load(this)),this.loaded.promise()},reset:function(){this.set_attribute("options_show",null)}}),Viewer=(View.Content_Item=Component.extend(Viewer),{_slug:"modeled_component",get_attribute:function(key,def,check_model,enforce_type){var ret;return!this.util.is_string(key)||(ret=null,(check_model=this.util.is_bool(check_model)?check_model:!0)&&(check_model=this.get_ancestor(key,!1),this.util.in_obj(check_model,key)&&(ret=check_model[key])),null===ret)?this._super(key,def,enforce_type):ret},get_attribute_recursive:function(key,def,enforce_type){var t,ret=this.get_attribute(key,def,!0,enforce_type);return this.util.is_obj(ret)&&(def=this.get_ancestors(!1),ret=[ret],t=this,$.each(def,function(idx,model){key in model&&t.util.is_obj(model[key])&&ret.push(model[key])}),ret.push({}),ret=$.extend.apply($,ret.reverse())),ret},set_attribute:function(key,val,use_model){return!(!this.util.is_string(key)||!this.util.is_set(val))&&((use_model=this.util.is_bool(use_model)||this.util.is_obj(use_model)?use_model:!0)?(this.util.is_obj(use_model)?use_model:this.get_model())[key]=val:this._super(key,val),val)},get_model:function(){var m=this.get_attribute("model",null,!1);return this.util.is_obj(m)||this.set_attribute("model",m={},!1),m},has_model:function(){return!this.util.is_empty(this.get_model())},in_model:function(key){return!!this.util.in_obj(this.get_model(),key)},get_ancestors:function(inc_current){for(var ret=[],m=this.get_model();this.util.is_obj(m);)ret.push(m),m=this.util.in_obj(m,"parent")&&this.util.is_obj(m.parent)?m.parent:null;return inc_current||ret.shift(),ret},get_ancestor:function(attr,safe_mode){if(!this.util.is_string(attr))return!1;this.util.is_bool(safe_mode)||(safe_mode=!0);for(var mcurr=this.get_model(),m=mcurr,found=!1;this.util.is_obj(m);){if(this.util.in_obj(m,attr)&&!this.util.is_empty(m[attr])){found=!0;break}m=this.util.in_obj(m,"parent")?m.parent:null}return found||(safe_mode?(this.util.is_empty(m)&&(m=mcurr),this.util.in_obj(m,attr)||(m[attr]=null)):m=null),m}}),Viewer=Component.extend(Viewer),Template=(View.Theme=Viewer.extend({_slug:"theme",_refs:{viewer:"Viewer",template:"Template"},_models:{},_attr_default:{template:null,model:null},viewer:null,template:null,_c:function(id,attributes,viewer){1===arguments.length&&this.util.is_type(arguments[0],View.Viewer)&&(viewer=arguments[0],id=null),this._super(id,attributes),this.set_viewer(viewer),this.set_model(id)},get_viewer:function(){return this.get_component("viewer",{check_attr:!1,get_default:!0})},set_viewer:function(v){return this.set_component("viewer",v)},get_template:function(){var attr,ret=this.get_component("template");return this.util.is_empty(ret)&&(attr={theme:this,model:this.get_model()},ret=this.set_component("template",new View.Template(attr))),ret},get_tags:function(name,prop){return this.get_template().get_tags(name,prop)},dom_get_tag:function(tag,prop){return $(this.get_template().dom_get_tag(tag,prop))},get_tag_selector:function(name,prop){return this.get_template().get_tag_selector(name,prop)},get_models:function(){return this._models},get_model:function(id){var models;return!this.util.is_set(id)&&this.util.is_obj(this.get_attribute("model",null,!1))?this._super():(models=this.get_models(),this.util.is_string(id)||(id=this.get_controller().get_option("theme_default")),models[id=this.util.in_obj(models,id)?id:$.map(models,function(v,key){return key})[0]])},set_model:function(id){this.set_attribute("model",this.get_model(id),!1)},get_classes:function(rtype){var cls=[],thm=this,models=this.get_ancestors(!0);return $.each(models,function(idx,model){cls.push(thm.add_ns(model.id))}),cls=this.util.is_string(rtype)?cls.join(rtype):cls},get_measurement:function(attr,def){var meas=null;if(!this.util.is_string(attr))return meas;this.util.is_obj(def,!1)||(def={});var attr_cache=this.util.format("%s_cache",attr),cache=this.get_attribute(attr_cache,{},!1),status="_status",item=this.get_viewer().get_item(),w=$(window),status=(status in cache&&this.util.is_obj(cache._status)&&cache._status.width===w.width()&&cache._status.height===w.height()||(cache={}),this.util.is_empty(cache)&&(cache._status={width:w.width(),height:w.height(),index:[]}),$.inArray(item,cache._status.index));return-1!==status&&status in cache&&(meas=cache[status]),this.util.is_obj(meas)||(meas=this.call_attribute(attr),this.util.is_obj(meas)||(meas=this.get_measurement_default(attr))),meas=this.util.is_obj(meas)?$.extend({},def,meas):def,status=cache._status.index.push(item)-1,cache[status]=meas,this.set_attribute(attr_cache,cache,!1),$.extend({},meas)},get_measurement_default:function(attr){return this.util.is_string(attr)?(attr=this.util.format("get_%s_default",attr),this.util.in_obj(this,attr)?(attr=this[attr],this.util.is_func(attr)&&(attr=attr.call(this))):attr=null,attr):null},get_offset:function(){return this.get_measurement("offset",{width:0,height:0})},get_offset_default:function(){var tags,offset={width:0,height:0},v=this.get_viewer(),vn=v.dom_get(),vn=vn.clone().attr("id","").css({visibility:"hidden",position:"absolute",top:""}).removeClass("loading").appendTo(vn.parent()),l=vn.find(v.dom_get_selector("layout"));return l.length&&(l.find("*").css({width:"",height:"",display:""}),(tags=this.get_tags("item","content")).length&&(v=v.get_item().get_dimensions(),tags=$(l.find(tags[0].get_selector("full")).get(0)).css({width:v.width,height:v.height}),$.each(v,function(key,val){offset[key]=-1*val})),offset.width+=l.width(),offset.height+=l.height(),$.each(offset,function(key,val){val<0&&(offset[key]=0)})),vn.empty().remove(),offset},get_margin:function(){return this.get_measurement("margin",{width:0,height:0})},get_item_dimensions:function(){var offset,factor,v=this.get_viewer(),dims=v.get_item().get_dimensions();return v.get_attribute("autofit",!1)&&(v=this.get_margin(),(offset=this.get_offset()).height+=v.height,offset.width+=v.width,(v={width:$(window).width(),height:$(window).height()}).width>offset.width&&(v.width-=offset.width),v.height>offset.height&&(v.height-=offset.height),(factor=Math.min(v.width/dims.width,v.height/dims.height))<1&&$.each(dims,function(key){dims[key]=Math.round(dims[key]*factor)})),$.extend({},dims)},get_dimensions:function(){var dims=this.get_item_dimensions(),offset=this.get_offset();return $.each(dims,function(key){dims[key]+=offset[key]}),dims},get_breakpoints:function(){return this.get_attribute_recursive("breakpoints")},get_breakpoint:function(target){var b,ret=0;return this.util.is_string(target)&&(b=this.get_attribute_recursive("breakpoints"),this.util.is_obj(b)&&target in b&&(ret=b[target])),ret},render:function(init){var thm=this,tpl=this.get_template(),st="events_render";this.get_status(st)||(this.set_status(st),tpl.on(["render-init","render-loading","render-complete"],function(ev){return thm.trigger(ev.type,ev.data)})),tpl.render(init)},transition:function(event,clear_queue){var models,trns,thm,dfr=null,attr="transition",v=this.get_viewer(),fx_temp=null,anim_on=v.animation_enabled();return v.get_attribute(attr,!0)&&this.util.is_string(event)&&(clear_queue&&v.get_layout().find("*").each(function(){for(var el=$(this);el.queue().length;)el.stop(!1,!0)}),clear_queue=[attr,"set"].join("_"),trns=this.get_attribute(clear_queue)?this.get_attribute(attr,{}):(models=this.get_ancestors(!0),trns=[],this.set_attribute(clear_queue,!0),thm=this,$.each(models,function(idx,model){attr in model&&thm.util.is_obj(model[attr])&&trns.push(model[attr])}),trns.push({}),this.set_attribute(attr,$.extend.apply($,trns.reverse()))),this.util.is_method(trns,event)&&(anim_on||(fx_temp=$.fx.off,$.fx.off=!0),dfr=trns[event].call(this,v,$.Deferred()))),this.util.is_promise(dfr)||(dfr=$.Deferred()).reject(),dfr.always(function(){null!==fx_temp&&($.fx.off=fx_temp)}),dfr.promise()}}),{_slug:"template",_reciprocal:!0,_refs:{theme:"Theme"},_attr_default:{layout_uri:"",layout_raw:"",layout_parsed:"",tags:null,model:null},theme:null,_c:function(attributes){this._super("",attributes)},_hooks:function(){this.on("dom_init",function(ev){var tags=this.get_tags(null,null,!0),names=[],t=this;$.each(tags,function(idx,tag){var name=tag.get_name();-1===$.inArray(name,names)&&(names.push(name),tag.get_handler().trigger(ev.type,{template:t}))})})},get_theme:function(){return this.get_component("theme")},render:function(init){var item,tpl,tags,tag_promises,v=this.get_theme().get_viewer();if(!(init=this.util.is_bool(init)?init:!1))return!!v.is_active()&&(item=v.get_item(),this.util.is_type(item,View.Content_Item)?void(v.is_active()&&this.has_tags()&&(init=this.trigger("render-loading"),tags=(tpl=this).get_tags(),tag_promises=[],$.when(item.load(),init).done(function(){return!!v.is_active()&&($.each(tags,function(idx,tag){if(!v.is_active())return!1;tag_promises.push(tag.render(item).done(function(r){if(!v.is_active())return!1;r.tag.dom_get().html(r.output)}))}),!!v.is_active()&&void $.when.apply($,tag_promises).done(function(){tpl.trigger("render-complete")}))}))):(v.close(),!1));this.trigger("render-init",this.dom_get())},get_layout:function(parsed){return(parsed=this.util.is_bool(parsed)?parsed:!0)?this.parse_layout():this.get_attribute("layout_raw","")},parse_layout:function(){var a="layout_parsed",ret=this.get_attribute(a);return this.util.is_string(ret)||(ret=this.sanitize_layout(this.get_layout(!1)),ret=this.parse_tags(ret),this.set_attribute(a,ret)),ret},sanitize_layout:function(l){var rtype,dom,tag_temp,cls,cls_new;return this.util.is_empty(l)||(rtype=this.util.is_string(l)?"string":null,dom=$(l),tag_temp=this.get_tag_temp(),cls=tag_temp.get_class(),cls_new=["x",cls].join("_"),$(tag_temp.get_selector(),dom).each(function(){$(this).removeClass(cls).addClass(cls_new)}),l="string"===rtype?dom=dom.wrap("<div />").parent().html():dom),l},parse_tags:function(l){if(!this.util.is_string(l))return"";for(var match,re=/\{{2}\s*(\w.*?)\s*\}{2}/gim;match=re.exec(l);)l=l.substring(0,match.index)+this.get_tag_container(match[1])+l.substring(match.index+match[0].length);return l},get_tag_container:function(tag){var attr=this.get_tag_attribute();return this.util.format('<span %s="%s"></span>',attr,encodeURI(tag))},get_tag_attribute:function(){return this.get_tag_temp().dom_get_attribute()},get_tag:function(idx){var tags,ret=null;return ret=this.has_tags()?(tags=this.get_tags())[idx=!this.util.is_int(idx)||idx<0||idx>=tags.length?0:idx]:ret},get_tags:function(name,prop,isolate){this.util.is_bool(isolate)||(isolate=!1);var attr,d,a="tags",tags=this.get_attribute(a);if(this.util.is_array(tags)||(tags=[],d=this.dom_get(),attr=this.get_tag_attribute(),d=$(d).find("["+attr+"]"),$(d).each(function(){var el=$(this),tag=new View.Template_Tag(decodeURI(el.attr(attr)));tag.has_handler()&&(tags.push(tag),isolate||(tag.dom_set(el),el.addClass(tag.get_classes(" ")))),isolate||el.removeAttr(attr)}),isolate||this.set_attribute(a,tags,!1)),!this.util.is_empty(tags)&&this.util.is_string(name)){this.util.is_string(prop)||(prop=!1);for(var tags_filtered=[],tc=null,x=0;x<tags.length;x++)name!==(tc=tags[x]).get_name()||prop&&prop!==tc.get_prop()||tags_filtered.push(tc);tags=tags_filtered}return this.util.is_array(tags,!1)?tags:[]},has_tags:function(){return 0<this.get_tags().length},get_tag_temp:function(){return this.get_controller().get_component_temp(View.Template_Tag)},get_tag_selector:function(name,prop){this.util.is_string(name)||(name=""),this.util.is_string(prop)||(prop="");var tag=this.get_tag_temp();return tag.set_attribute("name",name),tag.set_attribute("prop",prop),tag.get_selector("full")},dom_init:function(){this.dom_set(this.get_layout()),this.trigger("dom_init")},dom_get_tag:function(tag,prop){var level,ret=$(),tags=this.get_tags(tag,prop);return tags.length&&(level=null,this.util.is_string(tag)&&(level=this.util.is_string(prop)?"full":"tag"),tag="."+tags[0].get_class(level),ret=this.dom_get().find(tag)),ret}}),Viewer=(View.Template=Viewer.extend(Template),{_slug:"template_tag",_reciprocal:!0,_attr_default:{name:null,prop:null,match:null},handlers:{},_c:function(tag_match){this.parse(tag_match)},parse:function(tag_match){if(!this.util.is_string(tag_match))return!1;var part,parts=tag_match.split("|");if(!parts.length)return null;var attrs={name:null,prop:null,match:tag_match};attrs.name=parts[0],-1!==attrs.name.indexOf(".")&&(attrs.name=attrs.name.split(".",2),attrs.prop=attrs.name[1],attrs.name=attrs.name[0]);for(var x=1;x<parts.length;x++)1<(part=parts[x].split(":",1)).length&&!(part[0]in attrs)&&(attrs[part[0]]=part[1]);this.set_attributes(attrs,!0)},render:function(item){var tag=this;return tag.get_handler().render(item,tag).pipe(function(output){return{tag:tag,output:output}})},get_name:function(){return this.get_attribute("name")},get_prop:function(){return this.get_attribute("prop")},get_handler:function(){return this.has_handler()?this.handlers[this.get_name()]:new View.Template_Tag_Handler("")},has_handler:function(){return this.get_name()in this.handlers},get_classes:function(rtype){var cls=[this.get_class(),this.get_class("tag"),this.get_class("full")];return cls=this.util.is_string(rtype)?cls.join(rtype):cls},get_class:function(level){var cls="";switch(level){case"tag":cls=this.get_name();break;case"full":for(var parts=[this.get_name(),this.get_prop()],a=[],i=0;i<parts.length;i++)this.util.is_string(parts[i])&&a.push(parts[i]);cls=a.join("_")}return this.util.is_string(cls)?this.add_ns(cls):this.get_ns()},get_selector:function(level){level=this.get_class(level);return level=this.util.is_string(level)?"."+level:""}}),Template=(View.Template_Tag=Component.extend(Viewer),{_slug:"template_tag_handler",_attr_default:{supports_modifiers:!1,dynamic:!1,props:{}},render:function(item,instance){var dfr=$.Deferred();return this.call_attribute("render",item,instance,dfr),dfr.promise()},add_prop:function(prop,fn){var a="props",props=this.get_attribute(a);if(!this.util.is_string(prop)||!this.util.is_func(fn))return!1;(props=this.util.is_obj(props,!1)?props:{})[prop]=fn,this.set_attribute(a,props)},handle_prop:function(prop,item,instance){var props=this.get_attribute("props");return this.util.is_obj(props)&&prop in props&&this.util.is_func(props[prop])?props[prop].call(this,item,instance):item.get_viewer().get_label(prop)}});View.Template_Tag_Handler=Component.extend(Template),View=SLB.attach("View",View)}(jQuery);
client/sass/admin.scss CHANGED
@@ -1,54 +1,54 @@
1
- .slb_section_head {
2
- display: block;
3
- padding: 2em 0 0;
4
- }
5
-
6
- .slb_option_item {
7
- .block {
8
- display: inline-block;
9
- }
10
-
11
- label.title {
12
- width: 200px;
13
- padding: 10px;
14
- }
15
-
16
- .input {
17
- font-size: 11px;
18
- line-height: 20px;
19
- margin-bottom: 9px;
20
- padding: 8px 10px;
21
- }
22
-
23
- .input select {
24
- min-width: 12em;
25
- }
26
- }
27
-
28
- .slb_notice {
29
- color: #f00;
30
- font-weight: bold;
31
- }
32
-
33
- .slb {
34
- .columns-2 {
35
- margin-right: 300px;
36
- .postbox-container {
37
- float: left;
38
- width: 100%;
39
- }
40
- .content-secondary {
41
- margin-right: -300px;
42
- width: 280px;
43
- float: right;
44
- }
45
- }
46
- }
47
-
48
- .slb_admin_action_reset {
49
- color: #a00;
50
- &:hover {
51
- color: #dc3232;
52
- border: none;
53
- }
54
  }
1
+ .slb_section_head {
2
+ display: block;
3
+ padding: 2em 0 0;
4
+ }
5
+
6
+ .slb_option_item {
7
+ .block {
8
+ display: inline-block;
9
+ }
10
+
11
+ label.title {
12
+ width: 200px;
13
+ padding: 10px;
14
+ }
15
+
16
+ .input {
17
+ font-size: 11px;
18
+ line-height: 20px;
19
+ margin-bottom: 9px;
20
+ padding: 8px 10px;
21
+ }
22
+
23
+ .input select {
24
+ min-width: 12em;
25
+ }
26
+ }
27
+
28
+ .slb_notice {
29
+ color: #f00;
30
+ font-weight: bold;
31
+ }
32
+
33
+ .slb {
34
+ .columns-2 {
35
+ margin-right: 300px;
36
+ .postbox-container {
37
+ float: left;
38
+ width: 100%;
39
+ }
40
+ .content-secondary {
41
+ margin-right: -300px;
42
+ width: 280px;
43
+ float: right;
44
+ }
45
+ }
46
+ }
47
+
48
+ .slb_admin_action_reset {
49
+ color: #a00;
50
+ &:hover {
51
+ color: #dc3232;
52
+ border: none;
53
+ }
54
  }
client/sass/app.scss CHANGED
@@ -1,10 +1,10 @@
1
- html.slb_overlay {
2
- object,embed,iframe {
3
- visibility: hidden;
4
- }
5
- #slb_viewer_wrap {
6
- object,embed,iframe {
7
- visibility: visible;
8
- }
9
- }
10
  }
1
+ html.slb_overlay {
2
+ object,embed,iframe {
3
+ visibility: hidden;
4
+ }
5
+ #slb_viewer_wrap {
6
+ object,embed,iframe {
7
+ visibility: visible;
8
+ }
9
+ }
10
  }
composer.json CHANGED
@@ -1,8 +1,8 @@
1
- {
2
- "require-dev": {
3
- "squizlabs/php_codesniffer": "^3.5",
4
- "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2",
5
- "wp-coding-standards/wpcs": "^2.2",
6
- "phpcompatibility/phpcompatibility-wp": "*"
7
- }
8
- }
1
+ {
2
+ "require-dev": {
3
+ "squizlabs/php_codesniffer": "^3.5",
4
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2",
5
+ "wp-coding-standards/wpcs": "^2.2",
6
+ "phpcompatibility/phpcompatibility-wp": "*"
7
+ }
8
+ }
composer.lock CHANGED
@@ -1,340 +1,342 @@
1
- {
2
- "_readme": [
3
- "This file locks the dependencies of your project to a known state",
4
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
- "This file is @generated automatically"
6
- ],
7
- "content-hash": "d58ae19bd7c4cf76ada3935405677c26",
8
- "packages": [],
9
- "packages-dev": [
10
- {
11
- "name": "dealerdirect/phpcodesniffer-composer-installer",
12
- "version": "v0.6.2",
13
- "source": {
14
- "type": "git",
15
- "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
16
- "reference": "8001af8eb107fbfcedc31a8b51e20b07d85b457a"
17
- },
18
- "dist": {
19
- "type": "zip",
20
- "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/8001af8eb107fbfcedc31a8b51e20b07d85b457a",
21
- "reference": "8001af8eb107fbfcedc31a8b51e20b07d85b457a",
22
- "shasum": ""
23
- },
24
- "require": {
25
- "composer-plugin-api": "^1.0",
26
- "php": "^5.3|^7",
27
- "squizlabs/php_codesniffer": "^2|^3"
28
- },
29
- "require-dev": {
30
- "composer/composer": "*",
31
- "phpcompatibility/php-compatibility": "^9.0",
32
- "sensiolabs/security-checker": "^4.1.0"
33
- },
34
- "type": "composer-plugin",
35
- "extra": {
36
- "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
37
- },
38
- "autoload": {
39
- "psr-4": {
40
- "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
41
- }
42
- },
43
- "notification-url": "https://packagist.org/downloads/",
44
- "license": [
45
- "MIT"
46
- ],
47
- "authors": [
48
- {
49
- "name": "Franck Nijhof",
50
- "email": "franck.nijhof@dealerdirect.com",
51
- "homepage": "http://www.frenck.nl",
52
- "role": "Developer / IT Manager"
53
- }
54
- ],
55
- "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
56
- "homepage": "http://www.dealerdirect.com",
57
- "keywords": [
58
- "PHPCodeSniffer",
59
- "PHP_CodeSniffer",
60
- "code quality",
61
- "codesniffer",
62
- "composer",
63
- "installer",
64
- "phpcs",
65
- "plugin",
66
- "qa",
67
- "quality",
68
- "standard",
69
- "standards",
70
- "style guide",
71
- "stylecheck",
72
- "tests"
73
- ],
74
- "time": "2020-01-29T20:22:20+00:00"
75
- },
76
- {
77
- "name": "phpcompatibility/php-compatibility",
78
- "version": "9.3.5",
79
- "source": {
80
- "type": "git",
81
- "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
82
- "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
83
- },
84
- "dist": {
85
- "type": "zip",
86
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
87
- "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
88
- "shasum": ""
89
- },
90
- "require": {
91
- "php": ">=5.3",
92
- "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
93
- },
94
- "conflict": {
95
- "squizlabs/php_codesniffer": "2.6.2"
96
- },
97
- "require-dev": {
98
- "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
99
- },
100
- "suggest": {
101
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
102
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
103
- },
104
- "type": "phpcodesniffer-standard",
105
- "notification-url": "https://packagist.org/downloads/",
106
- "license": [
107
- "LGPL-3.0-or-later"
108
- ],
109
- "authors": [
110
- {
111
- "name": "Wim Godden",
112
- "homepage": "https://github.com/wimg",
113
- "role": "lead"
114
- },
115
- {
116
- "name": "Juliette Reinders Folmer",
117
- "homepage": "https://github.com/jrfnl",
118
- "role": "lead"
119
- },
120
- {
121
- "name": "Contributors",
122
- "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
123
- }
124
- ],
125
- "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
126
- "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
127
- "keywords": [
128
- "compatibility",
129
- "phpcs",
130
- "standards"
131
- ],
132
- "time": "2019-12-27T09:44:58+00:00"
133
- },
134
- {
135
- "name": "phpcompatibility/phpcompatibility-paragonie",
136
- "version": "1.3.0",
137
- "source": {
138
- "type": "git",
139
- "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
140
- "reference": "b862bc32f7e860d0b164b199bd995e690b4b191c"
141
- },
142
- "dist": {
143
- "type": "zip",
144
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/b862bc32f7e860d0b164b199bd995e690b4b191c",
145
- "reference": "b862bc32f7e860d0b164b199bd995e690b4b191c",
146
- "shasum": ""
147
- },
148
- "require": {
149
- "phpcompatibility/php-compatibility": "^9.0"
150
- },
151
- "require-dev": {
152
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5",
153
- "paragonie/random_compat": "dev-master",
154
- "paragonie/sodium_compat": "dev-master"
155
- },
156
- "suggest": {
157
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
158
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
159
- },
160
- "type": "phpcodesniffer-standard",
161
- "notification-url": "https://packagist.org/downloads/",
162
- "license": [
163
- "LGPL-3.0-or-later"
164
- ],
165
- "authors": [
166
- {
167
- "name": "Wim Godden",
168
- "role": "lead"
169
- },
170
- {
171
- "name": "Juliette Reinders Folmer",
172
- "role": "lead"
173
- }
174
- ],
175
- "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
176
- "homepage": "http://phpcompatibility.com/",
177
- "keywords": [
178
- "compatibility",
179
- "paragonie",
180
- "phpcs",
181
- "polyfill",
182
- "standards"
183
- ],
184
- "time": "2019-11-04T15:17:54+00:00"
185
- },
186
- {
187
- "name": "phpcompatibility/phpcompatibility-wp",
188
- "version": "2.1.0",
189
- "source": {
190
- "type": "git",
191
- "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
192
- "reference": "41bef18ba688af638b7310666db28e1ea9158b2f"
193
- },
194
- "dist": {
195
- "type": "zip",
196
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/41bef18ba688af638b7310666db28e1ea9158b2f",
197
- "reference": "41bef18ba688af638b7310666db28e1ea9158b2f",
198
- "shasum": ""
199
- },
200
- "require": {
201
- "phpcompatibility/php-compatibility": "^9.0",
202
- "phpcompatibility/phpcompatibility-paragonie": "^1.0"
203
- },
204
- "require-dev": {
205
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5"
206
- },
207
- "suggest": {
208
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
209
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
210
- },
211
- "type": "phpcodesniffer-standard",
212
- "notification-url": "https://packagist.org/downloads/",
213
- "license": [
214
- "LGPL-3.0-or-later"
215
- ],
216
- "authors": [
217
- {
218
- "name": "Wim Godden",
219
- "role": "lead"
220
- },
221
- {
222
- "name": "Juliette Reinders Folmer",
223
- "role": "lead"
224
- }
225
- ],
226
- "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
227
- "homepage": "http://phpcompatibility.com/",
228
- "keywords": [
229
- "compatibility",
230
- "phpcs",
231
- "standards",
232
- "wordpress"
233
- ],
234
- "time": "2019-08-28T14:22:28+00:00"
235
- },
236
- {
237
- "name": "squizlabs/php_codesniffer",
238
- "version": "3.5.4",
239
- "source": {
240
- "type": "git",
241
- "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
242
- "reference": "dceec07328401de6211037abbb18bda423677e26"
243
- },
244
- "dist": {
245
- "type": "zip",
246
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dceec07328401de6211037abbb18bda423677e26",
247
- "reference": "dceec07328401de6211037abbb18bda423677e26",
248
- "shasum": ""
249
- },
250
- "require": {
251
- "ext-simplexml": "*",
252
- "ext-tokenizer": "*",
253
- "ext-xmlwriter": "*",
254
- "php": ">=5.4.0"
255
- },
256
- "require-dev": {
257
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
258
- },
259
- "bin": [
260
- "bin/phpcs",
261
- "bin/phpcbf"
262
- ],
263
- "type": "library",
264
- "extra": {
265
- "branch-alias": {
266
- "dev-master": "3.x-dev"
267
- }
268
- },
269
- "notification-url": "https://packagist.org/downloads/",
270
- "license": [
271
- "BSD-3-Clause"
272
- ],
273
- "authors": [
274
- {
275
- "name": "Greg Sherwood",
276
- "role": "lead"
277
- }
278
- ],
279
- "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
280
- "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
281
- "keywords": [
282
- "phpcs",
283
- "standards"
284
- ],
285
- "time": "2020-01-30T22:20:29+00:00"
286
- },
287
- {
288
- "name": "wp-coding-standards/wpcs",
289
- "version": "2.2.1",
290
- "source": {
291
- "type": "git",
292
- "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
293
- "reference": "b5a453203114cc2284b1a614c4953456fbe4f546"
294
- },
295
- "dist": {
296
- "type": "zip",
297
- "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b5a453203114cc2284b1a614c4953456fbe4f546",
298
- "reference": "b5a453203114cc2284b1a614c4953456fbe4f546",
299
- "shasum": ""
300
- },
301
- "require": {
302
- "php": ">=5.4",
303
- "squizlabs/php_codesniffer": "^3.3.1"
304
- },
305
- "require-dev": {
306
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6",
307
- "phpcompatibility/php-compatibility": "^9.0",
308
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
309
- },
310
- "suggest": {
311
- "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
312
- },
313
- "type": "phpcodesniffer-standard",
314
- "notification-url": "https://packagist.org/downloads/",
315
- "license": [
316
- "MIT"
317
- ],
318
- "authors": [
319
- {
320
- "name": "Contributors",
321
- "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
322
- }
323
- ],
324
- "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
325
- "keywords": [
326
- "phpcs",
327
- "standards",
328
- "wordpress"
329
- ],
330
- "time": "2020-02-04T02:52:06+00:00"
331
- }
332
- ],
333
- "aliases": [],
334
- "minimum-stability": "stable",
335
- "stability-flags": [],
336
- "prefer-stable": false,
337
- "prefer-lowest": false,
338
- "platform": [],
339
- "platform-dev": []
340
- }
 
 
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "content-hash": "d58ae19bd7c4cf76ada3935405677c26",
8
+ "packages": [],
9
+ "packages-dev": [
10
+ {
11
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
12
+ "version": "v0.6.2",
13
+ "source": {
14
+ "type": "git",
15
+ "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
16
+ "reference": "8001af8eb107fbfcedc31a8b51e20b07d85b457a"
17
+ },
18
+ "dist": {
19
+ "type": "zip",
20
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/8001af8eb107fbfcedc31a8b51e20b07d85b457a",
21
+ "reference": "8001af8eb107fbfcedc31a8b51e20b07d85b457a",
22
+ "shasum": ""
23
+ },
24
+ "require": {
25
+ "composer-plugin-api": "^1.0",
26
+ "php": "^5.3|^7",
27
+ "squizlabs/php_codesniffer": "^2|^3"
28
+ },
29
+ "require-dev": {
30
+ "composer/composer": "*",
31
+ "phpcompatibility/php-compatibility": "^9.0",
32
+ "sensiolabs/security-checker": "^4.1.0"
33
+ },
34
+ "type": "composer-plugin",
35
+ "extra": {
36
+ "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
37
+ },
38
+ "autoload": {
39
+ "psr-4": {
40
+ "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
41
+ }
42
+ },
43
+ "notification-url": "https://packagist.org/downloads/",
44
+ "license": [
45
+ "MIT"
46
+ ],
47
+ "authors": [
48
+ {
49
+ "name": "Franck Nijhof",
50
+ "email": "franck.nijhof@dealerdirect.com",
51
+ "homepage": "http://www.frenck.nl",
52
+ "role": "Developer / IT Manager"
53
+ }
54
+ ],
55
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
56
+ "homepage": "http://www.dealerdirect.com",
57
+ "keywords": [
58
+ "PHPCodeSniffer",
59
+ "PHP_CodeSniffer",
60
+ "code quality",
61
+ "codesniffer",
62
+ "composer",
63
+ "installer",
64
+ "phpcs",
65
+ "plugin",
66
+ "qa",
67
+ "quality",
68
+ "standard",
69
+ "standards",
70
+ "style guide",
71
+ "stylecheck",
72
+ "tests"
73
+ ],
74
+ "time": "2020-01-29T20:22:20+00:00"
75
+ },
76
+ {
77
+ "name": "phpcompatibility/php-compatibility",
78
+ "version": "9.3.5",
79
+ "source": {
80
+ "type": "git",
81
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
82
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
83
+ },
84
+ "dist": {
85
+ "type": "zip",
86
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
87
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
88
+ "shasum": ""
89
+ },
90
+ "require": {
91
+ "php": ">=5.3",
92
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
93
+ },
94
+ "conflict": {
95
+ "squizlabs/php_codesniffer": "2.6.2"
96
+ },
97
+ "require-dev": {
98
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
99
+ },
100
+ "suggest": {
101
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
102
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
103
+ },
104
+ "type": "phpcodesniffer-standard",
105
+ "notification-url": "https://packagist.org/downloads/",
106
+ "license": [
107
+ "LGPL-3.0-or-later"
108
+ ],
109
+ "authors": [
110
+ {
111
+ "name": "Wim Godden",
112
+ "homepage": "https://github.com/wimg",
113
+ "role": "lead"
114
+ },
115
+ {
116
+ "name": "Juliette Reinders Folmer",
117
+ "homepage": "https://github.com/jrfnl",
118
+ "role": "lead"
119
+ },
120
+ {
121
+ "name": "Contributors",
122
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
123
+ }
124
+ ],
125
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
126
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
127
+ "keywords": [
128
+ "compatibility",
129
+ "phpcs",
130
+ "standards"
131
+ ],
132
+ "time": "2019-12-27T09:44:58+00:00"
133
+ },
134
+ {
135
+ "name": "phpcompatibility/phpcompatibility-paragonie",
136
+ "version": "1.3.0",
137
+ "source": {
138
+ "type": "git",
139
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
140
+ "reference": "b862bc32f7e860d0b164b199bd995e690b4b191c"
141
+ },
142
+ "dist": {
143
+ "type": "zip",
144
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/b862bc32f7e860d0b164b199bd995e690b4b191c",
145
+ "reference": "b862bc32f7e860d0b164b199bd995e690b4b191c",
146
+ "shasum": ""
147
+ },
148
+ "require": {
149
+ "phpcompatibility/php-compatibility": "^9.0"
150
+ },
151
+ "require-dev": {
152
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5",
153
+ "paragonie/random_compat": "dev-master",
154
+ "paragonie/sodium_compat": "dev-master"
155
+ },
156
+ "suggest": {
157
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
158
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
159
+ },
160
+ "type": "phpcodesniffer-standard",
161
+ "notification-url": "https://packagist.org/downloads/",
162
+ "license": [
163
+ "LGPL-3.0-or-later"
164
+ ],
165
+ "authors": [
166
+ {
167
+ "name": "Wim Godden",
168
+ "role": "lead"
169
+ },
170
+ {
171
+ "name": "Juliette Reinders Folmer",
172
+ "role": "lead"
173
+ }
174
+ ],
175
+ "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
176
+ "homepage": "http://phpcompatibility.com/",
177
+ "keywords": [
178
+ "compatibility",
179
+ "paragonie",
180
+ "phpcs",
181
+ "polyfill",
182
+ "standards"
183
+ ],
184
+ "time": "2019-11-04T15:17:54+00:00"
185
+ },
186
+ {
187
+ "name": "phpcompatibility/phpcompatibility-wp",
188
+ "version": "2.1.0",
189
+ "source": {
190
+ "type": "git",
191
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
192
+ "reference": "41bef18ba688af638b7310666db28e1ea9158b2f"
193
+ },
194
+ "dist": {
195
+ "type": "zip",
196
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/41bef18ba688af638b7310666db28e1ea9158b2f",
197
+ "reference": "41bef18ba688af638b7310666db28e1ea9158b2f",
198
+ "shasum": ""
199
+ },
200
+ "require": {
201
+ "phpcompatibility/php-compatibility": "^9.0",
202
+ "phpcompatibility/phpcompatibility-paragonie": "^1.0"
203
+ },
204
+ "require-dev": {
205
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5"
206
+ },
207
+ "suggest": {
208
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
209
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
210
+ },
211
+ "type": "phpcodesniffer-standard",
212
+ "notification-url": "https://packagist.org/downloads/",
213
+ "license": [
214
+ "LGPL-3.0-or-later"
215
+ ],
216
+ "authors": [
217
+ {
218
+ "name": "Wim Godden",
219
+ "role": "lead"
220
+ },
221
+ {
222
+ "name": "Juliette Reinders Folmer",
223
+ "role": "lead"
224
+ }
225
+ ],
226
+ "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
227
+ "homepage": "http://phpcompatibility.com/",
228
+ "keywords": [
229
+ "compatibility",
230
+ "phpcs",
231
+ "standards",
232
+ "wordpress"
233
+ ],
234
+ "time": "2019-08-28T14:22:28+00:00"
235
+ },
236
+ {
237
+ "name": "squizlabs/php_codesniffer",
238
+ "version": "3.5.5",
239
+ "source": {
240
+ "type": "git",
241
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
242
+ "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6"
243
+ },
244
+ "dist": {
245
+ "type": "zip",
246
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/73e2e7f57d958e7228fce50dc0c61f58f017f9f6",
247
+ "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6",
248
+ "shasum": ""
249
+ },
250
+ "require": {
251
+ "ext-simplexml": "*",
252
+ "ext-tokenizer": "*",
253
+ "ext-xmlwriter": "*",
254
+ "php": ">=5.4.0"
255
+ },
256
+ "require-dev": {
257
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
258
+ },
259
+ "bin": [
260
+ "bin/phpcs",
261
+ "bin/phpcbf"
262
+ ],
263
+ "type": "library",
264
+ "extra": {
265
+ "branch-alias": {
266
+ "dev-master": "3.x-dev"
267
+ }
268
+ },
269
+ "notification-url": "https://packagist.org/downloads/",
270
+ "license": [
271
+ "BSD-3-Clause"
272
+ ],
273
+ "authors": [
274
+ {
275
+ "name": "Greg Sherwood",
276
+ "role": "lead"
277
+ }
278
+ ],
279
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
280
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
281
+ "keywords": [
282
+ "phpcs",
283
+ "standards"
284
+ ],
285
+ "time": "2020-04-17T01:09:41+00:00"
286
+ },
287
+ {
288
+ "name": "wp-coding-standards/wpcs",
289
+ "version": "2.3.0",
290
+ "source": {
291
+ "type": "git",
292
+ "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
293
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62"
294
+ },
295
+ "dist": {
296
+ "type": "zip",
297
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62",
298
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62",
299
+ "shasum": ""
300
+ },
301
+ "require": {
302
+ "php": ">=5.4",
303
+ "squizlabs/php_codesniffer": "^3.3.1"
304
+ },
305
+ "require-dev": {
306
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6",
307
+ "phpcompatibility/php-compatibility": "^9.0",
308
+ "phpcsstandards/phpcsdevtools": "^1.0",
309
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
310
+ },
311
+ "suggest": {
312
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
313
+ },
314
+ "type": "phpcodesniffer-standard",
315
+ "notification-url": "https://packagist.org/downloads/",
316
+ "license": [
317
+ "MIT"
318
+ ],
319
+ "authors": [
320
+ {
321
+ "name": "Contributors",
322
+ "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
323
+ }
324
+ ],
325
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
326
+ "keywords": [
327
+ "phpcs",
328
+ "standards",
329
+ "wordpress"
330
+ ],
331
+ "time": "2020-05-13T23:57:56+00:00"
332
+ }
333
+ ],
334
+ "aliases": [],
335
+ "minimum-stability": "stable",
336
+ "stability-flags": [],
337
+ "prefer-stable": false,
338
+ "prefer-lowest": false,
339
+ "platform": [],
340
+ "platform-dev": [],
341
+ "plugin-api-version": "1.1.0"
342
+ }
content-handlers/image/js/dev/handler.image.js CHANGED
@@ -1,33 +1,33 @@
1
- if ( !!window.SLB && SLB.has_child('View.extend_content_handler') ) {(function($) {
2
- SLB.View.extend_content_handler('image', {
3
- /**
4
- * Render images
5
- * @param obj item Content Item
6
- * @param obj dfr Promise for rendering process
7
- * @return obj Promise for rendering process (Resolved when content is loaded)
8
- */
9
- render: function(item, dfr) {
10
- // Create image object
11
- var img = new Image();
12
- // Set load event
13
- var handler = function() {
14
- // Save Data
15
- item.set_data(img);
16
- // Set attributes
17
- item.set_attribute('dimensions', {'width': img.width, 'height': img.height});
18
- // Build output
19
- var out = $('<img />', {'src': item.get_uri()});
20
- // Resolve deferred
21
- dfr.resolve(out);
22
- };
23
-
24
- // Attach event handler
25
- $(img).on('load', function(e) { handler(e); });
26
- // Load image
27
- img.src = item.get_uri();
28
- // Return promise
29
- return dfr.promise();
30
- }
31
- });
32
- })(jQuery);
33
  }
1
+ if ( !!window.SLB && SLB.has_child('View.extend_content_handler') ) {(function($) {
2
+ SLB.View.extend_content_handler('image', {
3
+ /**
4
+ * Render images
5
+ * @param obj item Content Item
6
+ * @param obj dfr Promise for rendering process
7
+ * @return obj Promise for rendering process (Resolved when content is loaded)
8
+ */
9
+ render: function(item, dfr) {
10
+ // Create image object
11
+ var img = new Image();
12
+ // Set load event
13
+ var handler = function() {
14
+ // Save Data
15
+ item.set_data(img);
16
+ // Set attributes
17
+ item.set_attribute('dimensions', {'width': img.width, 'height': img.height});
18
+ // Build output
19
+ var out = $('<img />', {'src': item.get_uri()});
20
+ // Resolve deferred
21
+ dfr.resolve(out);
22
+ };
23
+
24
+ // Attach event handler
25
+ $(img).on('load', function(e) { handler(e); });
26
+ // Load image
27
+ img.src = item.get_uri();
28
+ // Return promise
29
+ return dfr.promise();
30
+ }
31
+ });
32
+ })(jQuery);
33
  }
content-handlers/image/js/prod/handler.image.js CHANGED
@@ -1 +1 @@
1
- window.SLB&&SLB.has_child("View.extend_content_handler")&&function($){SLB.View.extend_content_handler("image",{render:function(item,dfr){var img=new Image;return $(img).on("load",function(e){!function(){item.set_data(img),item.set_attribute("dimensions",{width:img.width,height:img.height});var out=$("<img />",{src:item.get_uri()});dfr.resolve(out)}()}),img.src=item.get_uri(),dfr.promise()}})}(jQuery);
1
+ window.SLB&&SLB.has_child("View.extend_content_handler")&&!function($){SLB.View.extend_content_handler("image",{render:function(item,dfr){var img=new Image;return $(img).on("load",function(e){var out;item.set_data(img),item.set_attribute("dimensions",{width:img.width,height:img.height}),out=$("<img />",{src:item.get_uri()}),dfr.resolve(out)}),img.src=item.get_uri(),dfr.promise()}})}(jQuery);
controller.php CHANGED
@@ -1,1705 +1,1891 @@
1
- <?php
2
- /**
3
- * Controller
4
- * @package Simple Lightbox
5
- * @author Archetyped
6
- */
7
- class SLB_Lightbox extends SLB_Base {
8
-
9
- /*-** Properties **-*/
10
-
11
- protected $model = true;
12
-
13
- /**
14
- * Fields
15
- * @var SLB_Fields
16
- */
17
- public $fields = null;
18
-
19
- /**
20
- * Themes collection
21
- * @var SLB_Themes
22
- */
23
- var $themes = null;
24
-
25
- /**
26
- * Content types
27
- * @var SLB_Content_Handlers
28
- */
29
- var $handlers = null;
30
-
31
- /**
32
- * Template tags
33
- * @var SLB_Template_Tags
34
- */
35
- var $template_tags = null;
36
-
37
- /**
38
- * Media item template.
39
- *
40
- * @var array {
41
- * Media item properties.
42
- *
43
- * @type int $id WP post ID. Default null.
44
- * @type string $type Item type. Default null.
45
- * @type string $source Source URI.
46
- * @type bool $internal Internal resource. Default false.
47
- * @type string $title Item title.
48
- * @type string $caption Item caption.
49
- * @type string $description Item description.
50
- * @type array $media {
51
- * Media properties (indexed by size name).
52
- * Original size = 'full'.
53
- *
54
- * @type string $file File URI.
55
- * @type int $width File width in pixels.
56
- * @type int $height File height in pixels.
57
- * }
58
- * @type array $meta {
59
- * Item metadata.
60
- * }
61
- * }
62
- */
63
- private $media_item_template = [
64
- 'id' => null,
65
- 'type' => null,
66
- 'internal' => false,
67
- 'source' => '',
68
- /* @future: Metadata
69
- 'media' => [],
70
- 'meta' => []
71
- END @future: Metadata */
72
- ];
73
-
74
- /**
75
- * Processed media items for output to client.
76
- *
77
- * @var object[string] {
78
- * Media item properties.
79
- *
80
- * @index string Unique ID (system-generated).
81
- *
82
- * @see $media_item_template.
83
- * }
84
- */
85
- private $media_items = [];
86
-
87
- /**
88
- * Collection of unprocessed media items.
89
- *
90
- * @var array {
91
- * @type object[string] $props {
92
- * Media item properties.
93
- *
94
- * @index string Unique ID (system-generated).
95
- *
96
- * @see $media_item_template
97
- * }
98
- * @type string[string] $uri {
99
- * Cached URIs.
100
- *
101
- * @index string URI.
102
- *
103
- * @type string Item ID (points to item in `props` array)
104
- * }
105
- * }
106
- */
107
- private $media_items_raw = [ 'props' => [], 'uri' => [] ];
108
-
109
- /**
110
- * Manage excluded content
111
- * @var object
112
- */
113
- private $exclude = null;
114
-
115
- private $groups = array (
116
- 'auto' => 0,
117
- 'manual' => array(),
118
- );
119
-
120
- /**
121
- * Validated URIs
122
- * Caches validation of parsed URIs
123
- * > Key: URI
124
- * > Value: (bool) TRUE if valid
125
- * @var array
126
- */
127
- private $validated_uris = array();
128
-
129
- /* Widget properties */
130
-
131
- /**
132
- * Used to track if widget is currently being processed or not
133
- * Set to Widget ID currently being processed
134
- * @var bool|string
135
- */
136
- private $widget_processing = false;
137
-
138
- /**
139
- * Parameters for widget being processed
140
- * @param array
141
- */
142
- private $widget_processing_params = null;
143
-
144
- /**
145
- * Manage nested widget processing
146
- * Used to avoid premature widget output
147
- * @var int
148
- */
149
- private $widget_processing_level = 0;
150
-
151
- /**
152
- * Constructor
153
- */
154
- public function __construct() {
155
- parent::__construct();
156
- // Init instances
157
- $this->fields = new SLB_Fields();
158
- $this->themes = new SLB_Themes($this);
159
- if ( !is_admin() ) {
160
- $this->template_tags = new SLB_Template_Tags($this);
161
- }
162
- }
163
-
164
- /* Init */
165
-
166
- public function _init() {
167
- parent::_init();
168
- $this->util->do_action('init');
169
- }
170
-
171
- /**
172
- * Declare client files (scripts, styles)
173
- * @uses parent::_client_files()
174
- * @return void
175
- */
176
- protected function _client_files($files = null) {
177
- $js_path = 'client/js/';
178
- $js_path .= ( SLB_DEV ) ? 'dev' : 'prod';
179
- $files = array (
180
- 'scripts' => array (
181
- 'core' => array (
182
- 'file' => "$js_path/lib.core.js",
183
- 'deps' => 'jquery',
184
- 'enqueue' => false,
185
- 'in_footer' => true,
186
- ),
187
- 'view' => array (
188
- 'file' => "$js_path/lib.view.js",
189
- 'deps' => array('[core]'),
190
- 'context' => array( array('public', $this->m('is_request_valid')) ),
191
- 'in_footer' => true,
192
- ),
193
- ),
194
- 'styles' => array (
195
- 'core' => array (
196
- 'file' => 'client/css/app.css',
197
- 'context' => array('public'),
198
- )
199
- )
200
- );
201
- parent::_client_files($files);
202
- }
203
-
204
- /**
205
- * Register hooks
206
- * @uses parent::_hooks()
207
- */
208
- protected function _hooks() {
209
- parent::_hooks();
210
-
211
- /* Admin */
212
- add_action('admin_menu', $this->m('admin_menus'));
213
- $this->util->add_filter('admin_plugin_row_meta_support', $this->m('admin_plugin_row_meta_support'));
214
-
215
- /* Init */
216
- add_action('wp', $this->m('_hooks_init'));
217
- }
218
-
219
- /**
220
- * Init Hooks
221
- */
222
- public function _hooks_init() {
223
- if ( $this->is_enabled() ) {
224
- $priority = $this->util->priority('low');
225
-
226
- // Init lightbox
227
- add_action('wp_footer', $this->m('client_footer'));
228
- $this->util->add_action('footer_script', $this->m('client_init'), 1);
229
- $this->util->add_filter('footer_script', $this->m('client_script_media'), 2);
230
- // Link activation
231
- add_filter('the_content', $this->m('activate_links'), $priority);
232
- add_filter('get_post_galleries', $this->m('activate_galleries'), $priority);
233
- $this->util->add_filter('post_process_links', $this->m('activate_groups'), 11);
234
- $this->util->add_filter('validate_uri_regex', $this->m('validate_uri_regex_default'), 1);
235
- // Content exclusion
236
- $this->util->add_filter('pre_process_links', $this->m('exclude_content'));
237
- $this->util->add_filter('pre_exclude_content', $this->m('exclude_shortcodes'));
238
- $this->util->add_filter('post_process_links', $this->m('restore_excluded_content'));
239
-
240
- // Grouping
241
- if ( $this->options->get_bool('group_post') ) {
242
- $this->util->add_filter('get_group_id', $this->m('post_group_id'), 1);
243
- }
244
-
245
- // Shortcode grouping
246
- if ( $this->options->get_bool('group_gallery') ) {
247
- add_filter('the_content', $this->m('group_shortcodes'), 1);
248
- }
249
-
250
- // Widgets
251
- if ( $this->options->get_bool('enabled_widget') ) {
252
- add_action('dynamic_sidebar_before', $this->m('widget_process_nested'));
253
- add_action('dynamic_sidebar', $this->m('widget_process_start'), PHP_INT_MAX);
254
- add_filter('dynamic_sidebar_params', $this->m('widget_process_inter'), PHP_INT_MAX);
255
- add_action('dynamic_sidebar_after', $this->m('widget_process_finish'), PHP_INT_MAX - 1);
256
- add_action('dynamic_sidebar_after', $this->m('widget_process_nested_finish'), PHP_INT_MAX);
257
- } else {
258
- add_action('dynamic_sidebar_before', $this->m('widget_block_start'));
259
- add_action('dynamic_sidebar_after', $this->m('widget_block_finish'));
260
- }
261
-
262
- // Menus
263
- if ( $this->options->get_bool('enabled_menu') ) {
264
- add_filter('wp_nav_menu', $this->m('menu_process'), $priority, 2);
265
- }
266
- }
267
- }
268
-
269
- /**
270
- * Add post ID to link group ID
271
- * @uses `SLB::get_group_id` filter
272
- * @param array $group_segments Group ID segments
273
- * @return array Modified group ID segments
274
- */
275
- public function post_group_id($group_segments) {
276
- if ( in_the_loop() ) {
277
- // Prepend post ID to group ID
278
- $post = get_post();
279
- if ( $post ) {
280
- array_unshift($group_segments, $post->ID);
281
- }
282
- }
283
- return $group_segments;
284
- }
285
-
286
- /**
287
- * Init options
288
- */
289
- protected function _options() {
290
- // Setup options
291
- $opts = array (
292
- 'groups' => array (
293
- 'activation' => array ( 'title' => __('Activation', 'simple-lightbox'), 'priority' => 10),
294
- 'grouping' => array ( 'title' => __('Grouping', 'simple-lightbox'), 'priority' => 20),
295
- 'ui' => array ( 'title' => __('UI', 'simple-lightbox'), 'priority' => 30),
296
- 'labels' => array ( 'title' => __('Labels', 'simple-lightbox'), 'priority' => 40),
297
- ),
298
- 'items' => array (
299
- 'enabled' => array('title' => __('Enable Lightbox Functionality', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 10)),
300
- 'enabled_home' => array('title' => __('Enable on Home page', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 20)),
301
- 'enabled_post' => array('title' => __('Enable on Single Posts', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 30)),
302
- 'enabled_page' => array('title' => __('Enable on Pages', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 40)),
303
- 'enabled_archive' => array('title' => __('Enable on Archive Pages (tags, categories, etc.)', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 50)),
304
- 'enabled_widget' => array('title' => __('Enable for Widgets', 'simple-lightbox'), 'default' => false, 'group' => array('activation', 60)),
305
- 'enabled_menu' => array('title' => __('Enable for Menus', 'simple-lightbox'), 'default' => false, 'group' => array('activation', 60)),
306
- 'group_links' => array('title' => __('Group items (for displaying as a slideshow)', 'simple-lightbox'), 'default' => true, 'group' => array('grouping', 10)),
307
- 'group_post' => array('title' => __('Group items by Post (e.g. on pages with multiple posts)', 'simple-lightbox'), 'default' => true, 'group' => array('grouping', 20)),
308
- 'group_gallery' => array('title' => __('Group gallery items separately', 'simple-lightbox'), 'default' => false, 'group' => array('grouping', 30)),
309
- 'group_widget' => array('title' => __('Group widget items separately', 'simple-lightbox'), 'default' => false, 'group' => array('grouping', 40)),
310
- 'group_menu' => array('title' => __('Group menu items separately', 'simple-lightbox'), 'default' => false, 'group' => array('grouping', 50)),
311
- 'ui_autofit' => array('title' => __('Resize lightbox to fit in window', 'simple-lightbox'), 'default' => true, 'group' => array('ui', 10), 'in_client' => true),
312
- 'ui_animate' => array('title' => __('Enable animations', 'simple-lightbox'), 'default' => true, 'group' => array('ui', 20), 'in_client' => true),
313
- 'slideshow_autostart' => array('title' => __('Start Slideshow Automatically', 'simple-lightbox'), 'default' => true, 'group' => array('ui', 30), 'in_client' => true),
314
- 'slideshow_duration' => array('title' => __('Slide Duration (Seconds)', 'simple-lightbox'), 'default' => '6', 'attr' => array('size' => 3, 'maxlength' => 3), 'group' => array('ui', 40), 'in_client' => true),
315
- 'group_loop' => array('title' => __('Loop through items', 'simple-lightbox'),'default' => true, 'group' => array('ui', 50), 'in_client' => true),
316
- 'ui_overlay_opacity' => array('title' => __('Overlay Opacity (0 - 1)', 'simple-lightbox'), 'default' => '0.8', 'attr' => array('size' => 3, 'maxlength' => 3), 'group' => array('ui', 60), 'in_client' => true),
317
- 'ui_title_default' => array('title' => __('Enable default title', 'simple-lightbox'), 'default' => false, 'group' => array('ui', 70), 'in_client' => true),
318
- 'txt_loading' => array('title' => __('Loading indicator', 'simple-lightbox'), 'default' => 'Loading', 'group' => array('labels', 20)),
319
- 'txt_close' => array('title' => __('Close button', 'simple-lightbox'), 'default' => 'Close', 'group' => array('labels', 10)),
320
- 'txt_nav_next' => array('title' => __('Next Item button', 'simple-lightbox'), 'default' => 'Next', 'group' => array('labels', 30)),
321
- 'txt_nav_prev' => array('title' => __('Previous Item button', 'simple-lightbox'), 'default' => 'Previous', 'group' => array('labels', 40)),
322
- 'txt_slideshow_start' => array('title' => __('Start Slideshow button', 'simple-lightbox'), 'default' => 'Start slideshow', 'group' => array('labels', 50)),
323
- 'txt_slideshow_stop' => array('title' => __('Stop Slideshow button', 'simple-lightbox'),'default' => 'Stop slideshow', 'group' => array('labels', 60)),
324
- 'txt_group_status' => array('title' => __('Slideshow status format', 'simple-lightbox'), 'default' => 'Item %current% of %total%', 'group' => array('labels', 70))
325
- ),
326
- 'legacy' => array (
327
- 'header_activation' => null,
328
- 'header_enabled' => null,
329
- 'header_strings' => null,
330
- 'header_ui' => null,
331
- 'activate_attachments' => null,
332
- 'validate_links' => null,
333
- 'enabled_compat' => null,
334
- 'enabled_single' => array('enabled_post', 'enabled_page'),
335
- 'enabled_caption' => null,
336
- 'enabled_desc' => null,
337
- 'ui_enabled_caption' => null,
338
- 'ui_caption_src' => null,
339
- 'ui_enabled_desc' => null,
340
- 'caption_src' => null,
341
- 'animate' => 'ui_animate',
342
- 'overlay_opacity' => 'ui_overlay_opacity',
343
- 'loop' => 'group_loop',
344
- 'autostart' => 'slideshow_autostart',
345
- 'duration' => 'slideshow_duration',
346
- 'txt_numDisplayPrefix' => null,
347
- 'txt_numDisplaySeparator' => null,
348
- 'txt_closeLink' => 'txt_link_close',
349
- 'txt_nextLink' => 'txt_link_next',
350
- 'txt_prevLink' => 'txt_link_prev',
351
- 'txt_startSlideshow' => 'txt_slideshow_start',
352
- 'txt_stopSlideshow' => 'txt_slideshow_stop',
353
- 'txt_loadingMsg' => 'txt_loading',
354
- 'txt_link_next' => 'txt_nav_next',
355
- 'txt_link_prev' => 'txt_nav_prev',
356
- 'txt_link_close' => 'txt_close',
357
- )
358
- );
359
-
360
- parent::_set_options($opts);
361
- }
362
-
363
- /* Methods */
364
-
365
- /*-** Admin **-*/
366
-
367
- /**
368
- * Add admin menus
369
- * @uses this->admin->add_theme_page
370
- */
371
- function admin_menus() {
372
- // Build options page
373
- $lbls_opts = array(
374
- 'menu' => __('Lightbox', 'simple-lightbox'),
375
- 'header' => __('Lightbox Settings', 'simple-lightbox'),
376
- 'plugin_action' => __('Settings', 'simple-lightbox')
377
- );
378
- $pg_opts = $this->admin->add_theme_page('options', $lbls_opts)
379
- ->require_form()
380
- ->add_content('options', 'Options', $this->options);
381
-
382
- // Add Support information
383
- $support = $this->util->get_plugin_info('SupportURI');
384
- if ( !empty($support) ) {
385
- $pg_opts->add_content('support', __('Feedback & Support', 'simple-lightbox'), $this->m('theme_page_callback_support'), 'secondary');
386
- }
387
-
388
- // Add Actions
389
- $lbls_reset = array (
390
- 'title' => __('Reset', 'simple-lightbox'),
391
- 'confirm' => __('Are you sure you want to reset Simple Lightbox\'s settings?', 'simple-lightbox'),
392
- 'success' => __('Settings have been reset', 'simple-lightbox'),
393
- 'failure' => __('Settings were not reset', 'simple-lightbox')
394
- );
395
- $this->admin->add_action('reset', $lbls_reset, $this->options);
396
- }
397
-
398
- /**
399
- * Support information
400
- */
401
- public function theme_page_callback_support() {
402
- // Description
403
- $desc = __("<p>Simple Lightbox thrives on your feedback!</p><p>Click the button below to <strong>get help</strong>, <strong>request a feature</strong>, or <strong>provide some feedback</strong>!</p>", 'simple-lightbox');
404
- echo $desc;
405
- // Link
406
- $lnk_uri = $this->util->get_plugin_info('SupportURI');
407
- $lnk_txt = __('Get Support &amp; Provide Feedback', 'simple-lightbox');
408
- echo $this->util->build_html_link($lnk_uri, $lnk_txt, array('target' => '_blank', 'class' => 'button'));
409
- }
410
-
411
- /**
412
- * Filter support link text in plugin metadata
413
- * @param string $text Original link text
414
- * @return string Modified link text
415
- */
416
- public function admin_plugin_row_meta_support($text) {
417
- return __("Feedback &amp; Support", 'simple-lightbox');
418
- }
419
-
420
- /*-** Functionality **-*/
421
-
422
- /**
423
- * Checks whether lightbox is currently enabled/disabled
424
- * @return bool TRUE if lightbox is currently enabled, FALSE otherwise
425
- */
426
- function is_enabled() {
427
- static $ret = null;
428
- if ( is_null($ret) ) {
429
- $ret = ( !is_admin() && $this->options->get_bool('enabled') && !is_feed() ) ? true : false;
430
- if ( $ret ) {
431
- $opt = '';
432
- // Determine option to check
433
- if ( is_home() || is_front_page() ) {
434
- $opt = 'home';
435
- }
436
- elseif ( is_singular() ) {
437
- $opt = ( is_page() ) ? 'page' : 'post';
438
- }
439
- elseif ( is_archive() || is_search() ) {
440
- $opt = 'archive';
441
- }
442
- // Check sub-option
443
- if ( !empty($opt) && ( $opt = 'enabled_' . $opt ) && $this->options->has($opt) ) {
444
- $ret = $this->options->get_bool($opt);
445
- }
446
- }
447
- }
448
- // Filter return value
449
- if ( !is_admin() ) {
450
- $ret = $this->util->apply_filters('is_enabled', $ret);
451
- }
452
- // Return value (force boolean)
453
- return !!$ret;
454
- }
455
-
456
- /**
457
- * Make sure content is valid for processing/activation
458
- *
459
- * @param string $content Content to validate
460
- * @return bool TRUE if content is valid (FALSE otherwise)
461
- */
462
- protected function is_content_valid($content) {
463
- // Invalid hooks
464
- if ( doing_filter('get_the_excerpt') )
465
- return false;
466
-
467
- // Non-string value
468
- if ( !is_string($content) )
469
- return false;
470
-
471
- // Empty string
472
- $content = trim($content);
473
- if ( empty($content) )
474
- return false;
475
-
476
- // Content is valid
477
- return $this->util->apply_filters('is_content_valid', true, $content);
478
- }
479
-
480
- /**
481
- * Activates galleries extracted from post
482
- * @see get_post_galleries()
483
- * @param array $galleries A list of galleries in post
484
- * @return A list of galleries with links activated
485
- */
486
- function activate_galleries($galleries) {
487
- // Validate
488
- if ( empty($galleries) ) {
489
- return $galleries;
490
- }
491
- // Check galleries for HTML output
492
- $gallery = reset($galleries);
493
- if ( is_array($gallery) ) {
494
- return $galleries;
495
- }
496
-
497
- // Activate galleries
498
- $group = ( $this->options->get_bool('group_gallery') ) ? true : null;
499
- foreach ( $galleries as $key => $val ) {
500
- if ( !is_null($group) ) {
501
- $group = 'gallery_' . $key;
502
- }
503
- // Activate links in gallery
504
- $gallery = $this->process_links($val, $group);
505
-
506
- // Save modified gallery
507
- $galleries[$key] = $gallery;
508
- }
509
-
510
- return $galleries;
511
- }
512
-
513
- /**
514
- * Scans post content for image links and activates them
515
- *
516
- * Lightbox will not be activated for feeds
517
- * @param string $content Content to activate
518
- * @param string (optonal) $group Group ID for content
519
- * @return string Post content
520
- */
521
- public function activate_links($content, $group = null) {
522
- // Validate content
523
- if ( !$this->is_content_valid($content) ) {
524
- return $content;
525
- }
526
- // Filter content before processing links
527
- $content = $this->util->apply_filters('pre_process_links', $content);
528
-
529
- // Process links
530
- $content = $this->process_links($content, $group);
531
-
532
- // Filter content after processing links
533
- $content = $this->util->apply_filters('post_process_links', $content);
534
-
535
- return $content;
536
- }
537
-
538
- /**
539
- * Process links in content
540
- * @global obj $wpdb DB instance
541
- * @global obj $post Current post
542
- * @param string $content Text containing links
543
- * @param string (optional) $group Group to add links to (Default: none)
544
- * @return string Content with processed links
545
- */
546
- protected function process_links($content, $group = null) {
547
- // Extract links
548
- $links = $this->get_links($content, true);
549
- // Do not process content without links
550
- if ( empty($links) ) {
551
- return $content;
552
- }
553
- // Process links
554
- static $protocol = array('http://', 'https://');
555
- static $qv_att = 'attachment_id';
556
- static $uri_origin = null;
557
- if ( !is_array($uri_origin) ) {
558
- $uri_parts = array_fill_keys(array('scheme', 'host', 'path'), '');
559
- $uri_origin = wp_parse_args(parse_url( strtolower(home_url()) ), $uri_parts);
560
- }
561
- static $uri_proto = null;
562
- if ( empty($uri_proto) ) {
563
- $uri_proto = (object) array('raw' => '', 'source' => '', 'parts' => '');
564
- }
565
- $uri_parts_required = array('host' => '');
566
-
567
- // Setup group properties
568
- $g_props = (object) array(
569
- 'enabled' => $this->options->get_bool('group_links'),
570
- 'attr' => 'group',
571
- 'base' => '',
572
- 'legacy_prefix' => 'lightbox[',
573
- 'legacy_suffix' => ']'
574
- );
575
- if ( $g_props->enabled ) {
576
- $g_props->base = ( is_scalar($group) ) ? trim(strval($group)) : '';
577
- }
578
-
579
- // Initialize content handlers
580
- if ( !( $this->handlers instanceof SLB_Content_Handlers ) ) {
581
- $this->handlers = new SLB_Content_Handlers($this);
582
- }
583
-
584
- // Iterate through and activate supported links
585
-
586
- foreach ( $links as $link ) {
587
- // Init vars
588
- $pid = 0;
589
- $link_new = $link;
590
- $uri = clone $uri_proto;
591
- $type = false;
592
- $props_extra = array();
593
- $key = null;
594
- $internal = false;
595
-
596
- // Parse link attributes
597
- $attrs = $this->util->parse_attribute_string($link_new, array('href' => ''));
598
- // Get URI
599
- $uri->raw = $attrs['href'];
600
-
601
- // Stop processing invalid links
602
- if ( !$this->validate_uri($uri->raw)
603
- || $this->has_attribute($attrs, 'active', false) // Previously-processed
604
- ) {
605
- continue;
606
- }
607
-
608
- // Normalize URI (make absolute)
609
- $uri->source = WP_HTTP::make_absolute_url($uri->raw, $uri_origin['scheme'] . '://' . $uri_origin['host']);
610
-
611
- // URI cached?
612
- $key = $this->get_media_item_id($uri->source);
613
-
614
- // Internal URI? (e.g. attachments)
615
- if ( !$key ) {
616
- $uri->parts = array_merge( $uri_parts_required, (array) parse_url($uri->source) );
617
- $internal = ( $uri->parts['host'] === $uri_origin['host'] ) ? true : false;
618
-
619
- // Attachment?
620
- if ( $internal && is_local_attachment($uri->source) ) {
621
- $pid = url_to_postid($uri->source);
622
- $src = wp_get_attachment_url($pid);
623
- if ( !!$src ) {
624
- $uri->source = $src;
625
- $props_extra['id'] = $pid;
626
- // Check cache for attachment source URI
627
- $key = $this->get_media_item_id($uri->source);
628
- }
629
- unset($src);
630
- }
631
- }
632
-
633
- // Determine content type
634
- if ( !$key ) {
635
- // Get handler match
636
- $hdl_result = $this->handlers->match($uri->source);
637
- if ( !!$hdl_result->handler ) {
638
- $type = $hdl_result->handler->get_id();
639
- $props_extra = $hdl_result->props;
640
- // Updated source URI
641
- if ( isset($props_extra['uri']) ) {
642
- $uri->source = $props_extra['uri'];
643
- unset($props_extra['uri']);
644
- }
645
- }
646
-
647
- // Cache valid item
648
- if ( !!$type ) {
649
- $key = $this->cache_media_item($uri, $type, $internal, $props_extra);
650
- }
651
- }
652
-
653
- // Stop processing invalid links
654
- if ( !$key ) {
655
- // Cache invalid URI
656
- $this->validated_uris[$uri->source] = false;
657
- if ( $uri->raw !== $uri->source ) {
658
- $this->validated_uris[$uri->raw] = false;
659
- }
660
- continue;
661
- }
662
-
663
- // Activate link
664
- $this->set_attribute($attrs, 'active');
665
- $this->set_attribute($attrs, 'asset', $key);
666
- // Mark internal links
667
- if ( $internal ) {
668
- $this->set_attribute($attrs, 'internal', $pid);
669
- }
670
-
671
- // Set group (if enabled)
672
- if ( $g_props->enabled ) {
673
- $group = array();
674
- // Get preset group attribute
675
- $g = ( $this->has_attribute($attrs, $g_props->attr) ) ? $this->get_attribute($attrs, $g_props->attr) : '';
676
- if ( is_string($g) && ($g = trim($g)) && !empty($g) ) {
677
- $group[] = $g;
678
- } elseif ( !empty($g_props->base) ) {
679
- $group[] = $g_props->base;
680
- }
681
-
682
- /**
683
- * Filter group ID components
684
- *
685
- * @see process_links()
686
- *
687
- * @param array $group Components used to build group ID
688
- */
689
- $group = $this->util->apply_filters('get_group_id', $group);
690
-
691
- // Default group
692
- if ( empty($group) || !is_array($group) ) {
693
- $group = $this->get_prefix();
694
- } else {
695
- $group = implode('_', $group);
696
- }
697
-
698
- // Set group attribute
699
- $this->set_attribute($attrs, $g_props->attr, $group);
700
- unset($g);
701
- }
702
-
703
- // Filter attributes
704
- $attrs = $this->util->apply_filters('process_link_attributes', $attrs);
705
-
706
- // Update link in content
707
- $link_new = '<a ' . $this->util->build_attribute_string($attrs) . '>';
708
- $content = str_replace($link, $link_new, $content);
709
- }
710
-
711
- // Handle widget content
712
- if ( !!$this->widget_processing && 'the_content' == current_filter() ) {
713
- $content = $this->exclude_wrap($content);
714
- }
715
-
716
- return $content;
717
- }
718
-
719
- /**
720
- * Retrieve HTML links in content
721
- * @param string $content Content to get links from
722
- * @param bool (optional) $unique Remove duplicates from returned links (Default: FALSE)
723
- * @return array Links in content
724
- */
725
- function get_links($content, $unique = false) {
726
- $rgx = "/\<a\b(?:(?!\shref=|\>).)*\shref=[^\>\<]++\>/i";
727
- $links = [];
728
- preg_match_all($rgx, $content, $links);
729
- $links = $links[0];
730
- if ( $unique )
731
- $links = array_unique($links);
732
- return $links;
733
- }
734
-
735
- /**
736
- * Validate URI
737
- * Matches specified URI against internal & external regex patterns
738
- * URI is **invalid** if it matches a regex
739
- *
740
- * @param string $uri URI to validate
741
- * @return bool TRUE if URI is valid
742
- */
743
- protected function validate_uri($uri) {
744
- static $patterns = null;
745
- // Previously-validated URI
746
- if ( isset($this->validated_uris[$uri]) )
747
- return $this->validated_uris[$uri];
748
-
749
- $valid = true;
750
- // Boilerplate validation
751
- if ( empty($uri) // Empty
752
- || 0 === strpos($uri, '#') // Anchor
753
- )
754
- $valid = false;
755
-
756
- // Regex matching
757
- if ( $valid ) {
758
- // Get patterns
759
- if ( is_null($patterns) ) {
760
- $patterns = $this->util->apply_filters('validate_uri_regex', array());
761
- }
762
- // Iterate through patterns until match found
763
- foreach ( $patterns as $pattern ) {
764
- if ( 1 === preg_match($pattern, $uri) ) {
765
- $valid = false;
766
- break;
767
- }
768
- }
769
- }
770
-
771
- // Cache
772
- $this->validated_uris[$uri] = $valid;
773
- return $valid;
774
- }
775
-
776
- /**
777
- * Add URI validation regex pattern
778
- * @param
779
- */
780
- public function validate_uri_regex_default($patterns) {
781
- $patterns[] = '@^https?://[^/]*(wikipedia|wikimedia)\.org/wiki/file:.*$@i';
782
- return $patterns;
783
- }
784
-
785
- /* Client */
786
-
787
- /**
788
- * Checks if output should be loaded in current request
789
- * @uses `is_enabled()`
790
- * @uses `has_cached_media_items()`
791
- * @return bool TRUE if output is being loaded into client
792
- */
793
- public function is_request_valid() {
794
- return ( $this->is_enabled() && $this->has_cached_media_items() ) ? true : false;
795
- }
796
-
797
- /**
798
- * Sets options/settings to initialize lightbox functionality on page load
799
- * @return void
800
- */
801
- function client_init($client_script) {
802
- // Get options
803
- $options = $this->options->build_client_output();
804
-
805
- // Load UI Strings
806
- if ( ($labels = $this->build_labels()) && !empty($labels) ) {
807
- $options['ui_labels'] = $labels;
808
- }
809
-
810
- // Build client output
811
- $client_script[] = $this->util->call_client_method('View.init', $options);
812
- return $client_script;
813
- }
814
-
815
- /**
816
- * Output code in footer
817
- * > Media attachment URLs
818
- * @uses `_wp_attached_file` to match attachment ID to URI
819
- * @uses `_wp_attachment_metadata` to retrieve attachment metadata
820
- */
821
- function client_footer() {
822
- if ( !$this->has_cached_media_items() )
823
- return false;
824
-
825
- // Set up hooks
826
- add_action('wp_print_footer_scripts', $this->m('client_footer_script'));
827
-
828
- // Build client output
829
- $this->util->do_action('footer');
830
- }
831
-
832
- /**
833
- * Output client footer scripts
834
- */
835
- function client_footer_script() {
836
- $client_script = $this->util->apply_filters('footer_script', array());
837
- if ( !empty($client_script) ) {
838
- echo $this->util->build_script_element($client_script, 'footer', true, true);
839
- }
840
- }
841
-
842
- /**
843
- * Add media information to client output
844
- *
845
- * @param array $client_script Client script commands.
846
- * @return array Modified script commands.
847
- * TODO Refactor
848
- */
849
- public function client_script_media($client_script) {
850
- global $wpdb;
851
-
852
- // Init.
853
- $this->media_items = $this->get_cached_media_items();
854
-
855
- // Extract internal links for additional processing.
856
- $m_internals = [];
857
- foreach ( $this->media_items as $key => $p ) {
858
- if ( $p->internal ) {
859
- $m_internals[$key] =& $this->media_items[$key];
860
- }
861
- }
862
- // Cleanup.
863
- unset($key, $p);
864
-
865
- // Process internal links.
866
- if ( !empty($m_internals) ) {
867
- $uris_base = [];
868
- $uri_prefix = wp_upload_dir();
869
- $uri_prefix = $this->util->normalize_path($uri_prefix['baseurl'], true);
870
- foreach ( $m_internals as $key => $p ) {
871
- // Prepare internal links.
872
- // Create relative URIs for attachment data retrieval.
873
- if ( !$p->id && strpos($p->source, $uri_prefix) === 0 ) {
874
- $uris_base[str_replace($uri_prefix, '', $p->source)] = $key;
875
- }
876
- }
877
- // Cleanup.
878
- unset($key, $p);
879
-
880
- // Retrieve attachment IDs.
881
- $uris_flat = "('" . implode("','", array_keys($uris_base)) . "')";
882
- $q = $wpdb->prepare("SELECT post_id, meta_value FROM $wpdb->postmeta WHERE `meta_key` = %s AND LOWER(`meta_value`) IN $uris_flat LIMIT %d", '_wp_attached_file', count($uris_base));
883
- $pids = $wpdb->get_results($q);
884
- // Match IDs to URIs.
885
- if ( $pids ) {
886
- foreach ( $pids as $pd ) {
887
- $file =& $pd->meta_value;
888
- if ( isset($uris_base[$file]) ) {
889
- $m_internals[ $uris_base[$file] ]->id = absint($pd->post_id);
890
- }
891
- }
892
- }
893
- // Cleanup.
894
- unset($uris_base, $uris_flat, $q, $pids, $pd, $file);
895
- }
896
-
897
- // Process items with attachment IDs.
898
- $pids = [];
899
- foreach ( $this->media_items as $key => $p ) {
900
- // Add post ID to query.
901
- if ( !!$p->id ) {
902
- // Create array for ID (support multiple URIs per ID).
903
- if ( !isset($pids[$p->id]) ) {
904
- $pids[$p->id] = [];
905
- }
906
- // Add URI to ID.
907
- $pids[$p->id][] = $key;
908
- }
909
- }
910
- // Cleanup.
911
- unset($key, $p);
912
-
913
- // Retrieve attachment properties.
914
- if ( !empty($pids) ) {
915
- $pids_flat = array_keys($pids);
916
- // Retrieve attachment post data.
917
- $atts = get_posts(array('post_type' => 'attachment', 'include' => $pids_flat));
918
-
919
- // Process attachments.
920
- if ( $atts ) {
921
- /* @future: Metadata
922
- // Retrieve attachment metadata.
923
- $pids_flat = "('" . implode("','", $pids_flat) . "')";
924
- $atts_meta = $wpdb->get_results($wpdb->prepare("SELECT `post_id`,`meta_value` FROM $wpdb->postmeta WHERE `post_id` IN $pids_flat AND `meta_key` = %s LIMIT %d", '_wp_attachment_metadata', count($atts)));
925
- // Reindex metadata array by attachment ID.
926
- if ( $atts_meta ) {
927
- $meta = [];
928
- foreach ( $atts_meta as $att_meta ) {
929
- $meta[$att_meta->post_id] = $att_meta->meta_value;
930
- }
931
- $atts_meta = $meta;
932
- // Cleanup.
933
- unset($meta);
934
- } else {
935
- $atts_meta = [];
936
- }
937
- END @future: Metadata */
938
-
939
- /* @future: Metadata
940
- // Process attachment data.
941
- $media_props = array('file', 'width', 'height');
942
- $media_original_name = 'full';
943
- END @future: Metadata */
944
-
945
- $props_post_map = [ 'title' => 'post_title', 'caption' => 'post_excerpt', 'description' => 'post_content' ];
946
-
947
- foreach ( $atts as $att ) {
948
- $data = [];
949
- /* @future: Metadata
950
- $data = [ 'meta' => [], 'media' => [ $media_original_name => [] ] ];
951
- END @future: Metadata */
952
- // Remap post data to metadata.
953
- foreach ( $props_post_map as $props_post_key => $props_post_source ) {
954
- $data[$props_post_key] = $att->{$props_post_source};
955
- }
956
- // Cleanup.
957
- unset($props_post_key, $props_post_source);
958
-
959
- /* @future: Metadata
960
- // Process metadata.
961
- if ( isset( $atts_meta[$att->ID] ) && ( $a = unserialize( $atts_meta[$att->ID] ) ) && is_array( $a ) ) {
962
- // Media properties.
963
- // Source file.
964
- foreach ( $media_props as $d ) {
965
- if ( isset($a[$d]) ) {
966
- $data['media'][$media_original_name][$d] = $a[$d];
967
- }
968
- }
969
- // Cleanup.
970
- unset( $a, $d );
971
- }
972
- END @future: Metadata */
973
- // Save data to corresponding media item(s).
974
- if ( isset($pids[$att->ID]) ) {
975
- foreach ( $pids[$att->ID] as $key ) {
976
- $this->media_items[$key] = (object) array_merge( (array) $this->media_items[$key], $data );
977
- }
978
- }
979
- }
980
- // Cleanup.
981
- unset($att, $data);
982
- }
983
- // Cleanup.
984
- unset($atts, $atts_meta, $m, $a, $uri, $pids, $pids_flat);
985
- }
986
-
987
- // Filter media items.
988
- $this->media_items = $this->util->apply_filters('media_items', $this->media_items);
989
-
990
- // Build client output.
991
- $obj = 'View.assets';
992
- $client_script[] = $this->util->extend_client_object($obj, $this->media_items);
993
- return $client_script;
994
- }
995
-
996
- /*-** Media **-*/
997
-
998
- /**
999
- * Cache media properties for later processing
1000
- * @uses array self::$media_items_raw Stores media items for output
1001
- * @param object $uri URI to cache
1002
- * Members
1003
- * > raw: Raw Link URI
1004
- * > source: Source URI (e.g. for attachment URIs)
1005
- * @param string $type Media type (image, attachment, etc.)
1006
- * @param bool $internal TRUE if media is internal (e.g. attachment)
1007
- * @param array $props (optional) Properties to store for item (Default: NULL)
1008
- * @return string Unique ID for cached media item
1009
- */
1010
- private function cache_media_item($uri, $type, $internal, $props = null) {
1011
- // Validate.
1012
- if ( !is_object($uri) || !is_string($type) ) {
1013
- return false;
1014
- }
1015
- // Check if URI already cached.
1016
- $key = $this->get_media_item_id($uri->source);
1017
- // Cache new item.
1018
- if ( null == $key ) {
1019
- // Generate Unique ID.
1020
- do {
1021
- $key = (string) mt_rand();
1022
- } while ( isset($this->media_items_raw['props'][$key]) );
1023
- // Build properties object.
1024
- $i = $this->media_item_template;
1025
- if ( is_array($props) && !empty($props) ) {
1026
- $i = array_merge($i, $props);
1027
- }
1028
- $i = array_merge( $i, [
1029
- 'type' => $type,
1030
- 'source' => $uri->source,
1031
- 'internal' => $internal,
1032
- ] );
1033
- // Cache item properties.
1034
- $this->media_items_raw['props'][$key] = (object) $i;
1035
- // Cache Source URI (point to properties object).
1036
- $this->media_items_raw['uri'][$uri->source] = $key;
1037
- }
1038
- return $key;
1039
- }
1040
-
1041
- /**
1042
- * Retrieve ID for media item
1043
- * @uses self::$media_items_raw
1044
- * @param string $uri Media item URI
1045
- * @return string|null Media item ID (Default: NULL if URI doesn't exist in collection)
1046
- */
1047
- private function get_media_item_id($uri) {
1048
- if ( $this->media_item_cached($uri) ) {
1049
- return $this->media_items_raw['uri'][$uri];
1050
- }
1051
- return null;
1052
- }
1053
-
1054
- /**
1055
- * Checks if media item has already been cached
1056
- * @param string $uri URI of media item
1057
- * @return boolean Whether media item has been cached
1058
- */
1059
- private function media_item_cached($uri) {
1060
- return ( is_string($uri) && !empty($uri) && isset($this->media_items_raw['uri'][$uri]) ) ? true : false;
1061
- }
1062
-
1063
- /**
1064
- * Retrieve cached media item
1065
- * @param string $uri Media item URI
1066
- * @return object|null Media item properties (NULL if not set)
1067
- */
1068
- private function get_cached_media_item($uri) {
1069
- $key = $this->get_media_item_id($uri);
1070
- if ( null != $key ) {
1071
- return $this->media_items_raw['props'][$key];
1072
- }
1073
- return null;
1074
- }
1075
-
1076
- /**
1077
- * Retrieve cached media items (properties)
1078
- * @uses self::$media_items_raw
1079
- * @return array Cached media items (objects)
1080
- */
1081
- private function &get_cached_media_items() {
1082
- return $this->media_items_raw['props'];
1083
- }
1084
-
1085
- /**
1086
- * Check if media items have been cached
1087
- * @return boolean
1088
- */
1089
- private function has_cached_media_items() {
1090
- return ( empty($this->media_items_raw['props']) ) ? false : true;
1091
- }
1092
-
1093
- /*-** Exclusion **-*/
1094
-
1095
- /**
1096
- * Retrieve exclude object
1097
- * Initialize object properties if necessary
1098
- * @return object Exclude properties
1099
- */
1100
- private function get_exclude() {
1101
- // Initialize exclude data
1102
- if ( !is_object($this->exclude) ) {
1103
- $this->exclude = (object) array (
1104
- 'tags' => $this->get_exclude_tags(),
1105
- 'ph' => $this->get_exclude_placeholder(),
1106
- 'group_default' => 'default',
1107
- 'cache' => array(),
1108
- );
1109
- }
1110
- return $this->exclude;
1111
- }
1112
-
1113
- /**
1114
- * Get exclusion tags (open/close)
1115
- * Example: open => [slb_exclude], close => [/slb_exclude]
1116
- *
1117
- * @return object Exclusion tags
1118
- */
1119
- private function get_exclude_tags() {
1120
- static $tags = null;
1121
- if ( null == $tags ) {
1122
- $base = $this->add_prefix('exclude');
1123
- $tags = (object) array (
1124
- 'base' => $base,
1125
- 'open' => $this->util->add_wrapper($base),
1126
- 'close' => $this->util->add_wrapper($base, '[/', ']')
1127
- );
1128
- $tags->search ='#' . preg_quote($tags->open) . '(.*?)' . preg_quote($tags->close) . '#s';
1129
- }
1130
- return $tags;
1131
- }
1132
-
1133
- /**
1134
- * Get exclusion tag ("[slb_exclude]")
1135
- * @uses `get_exclude_tags()` to retrieve tag
1136
- *
1137
- * @param string $type (optional) Tag to retrieve (open or close)
1138
- * @return string Exclusion tag
1139
- */
1140
- private function get_exclude_tag( $type = "open" ) {
1141
- // Validate
1142
- $tags = $this->get_exclude_tags();
1143
- if ( !isset($tags->{$type}) ) {
1144
- $type = "open";
1145
- }
1146
- return $tags->{$type};
1147
- }
1148
-
1149
- /**
1150
- * Build exclude placeholder
1151
- * @return object Exclude placeholder properties
1152
- */
1153
- private function get_exclude_placeholder() {
1154
- static $ph;
1155
- if ( !is_object($ph) ) {
1156
- $ph = (object) array (
1157
- 'base' => $this->add_prefix('exclude_temp'),
1158
- 'open' => '{{',
1159
- 'close' => '}}',
1160
- 'attrs' => array ( 'group' => '', 'key' => '' ),
1161
- );
1162
- // Search Patterns
1163
- $sub = '(.+?)';
1164
- $ph->search = '#' . preg_quote($ph->open) . $ph->base . '\s+' . $sub . preg_quote($ph->close) . '#s';
1165
- $ph->search_group = str_replace($sub, '(group="%s"\s+.?)', $ph->search);
1166
- // Templates
1167
- $attr_string = '';
1168
- foreach ( $ph->attrs as $attr => $val ) {
1169
- $attr_string .= ' ' . $attr . '="%s"';
1170
- }
1171
- $ph->template = $ph->open . $ph->base . $attr_string . $ph->close;
1172
- }
1173
- return $ph;
1174
- }
1175
-
1176
- /**
1177
- * Wrap content in exclusion tags
1178
- * @uses `get_exclude_tag()` to wrap content with exclusion tag
1179
- * @param string $content Content to exclude
1180
- * @return string Content wrapped in exclusion tags
1181
- */
1182
- private function exclude_wrap($content) {
1183
- // Validate
1184
- if ( !is_string($content) ) {
1185
- $content = "";
1186
- }
1187
- // Wrap
1188
- $tags = $this->get_exclude_tags();
1189
- return $tags->open . $content . $tags->close;
1190
- }
1191
-
1192
- /**
1193
- * Remove excluded content
1194
- * Caches content for restoring later
1195
- * @param string $content Content to remove excluded content from
1196
- * @return string Updated content
1197
- */
1198
- public function exclude_content($content, $group = null) {
1199
- $ex = $this->get_exclude();
1200
- // Setup cache
1201
- if ( !is_string($group) || empty($group) ) {
1202
- $group = $ex->group_default;
1203
- }
1204
- if ( !isset($ex->cache[$group]) ) {
1205
- $ex->cache[$group] = array();
1206
- }
1207
- $cache =& $ex->cache[$group];
1208
-
1209
- $content = $this->util->apply_filters('pre_exclude_content', $content);
1210
-
1211
- // Search content
1212
- $matches = null;
1213
- if ( false !== strpos($content, $ex->tags->open) && preg_match_all($ex->tags->search, $content, $matches) ) {
1214
- // Determine index
1215
- $idx = ( !!end($cache) ) ? key($cache) : -1;
1216
- $ph = array();
1217
- foreach ( $matches[1] as $midx => $match ) {
1218
- // Update index
1219
- $idx++;
1220
- // Cache content
1221
- $cache[$idx] = $match;
1222
- // Build placeholder
1223
- $ph[] = sprintf($ex->ph->template, $group, $idx);
1224
- }
1225
- unset($midx, $match);
1226
- // Replace content with placeholder
1227
- $content = str_replace($matches[0], $ph, $content);
1228
-
1229
- // Cleanup
1230
- unset($matches, $ph);
1231
- }
1232
-
1233
- return $content;
1234
- }
1235
-
1236
- /**
1237
- * Exclude shortcodes from link activation
1238
- * @param string $content Content to exclude shortcodes from
1239
- * @return string Content with shortcodes excluded
1240
- */
1241
- public function exclude_shortcodes($content) {
1242
- // Get shortcodes to exclude
1243
- $shortcodes = $this->util->apply_filters('exclude_shortcodes', array( $this->add_prefix('group') ));
1244
- // Set callback
1245
- $shortcodes = array_fill_keys($shortcodes, $this->m('exclude_shortcodes_handler'));
1246
- return $this->util->do_shortcode($content, $shortcodes);
1247
- }
1248
-
1249
- /**
1250
- * Wrap shortcode in exclude tags
1251
- * @uses Util->make_shortcode() to rebuild original shortcode
1252
- *
1253
- * @param array $attr Shortcode attributes
1254
- * @param string $content Content enclosed in shortcode
1255
- * @param string $tag Shortcode name
1256
- * @return string Excluded shortcode
1257
- */
1258
- public function exclude_shortcodes_handler($attr, $content, $tag) {
1259
- $code = $this->util->make_shortcode($tag, $attr, $content);
1260
- // Exclude shortcode
1261
- return $this->exclude_wrap($code);
1262
- }
1263
-
1264
- /**
1265
- * Restore excluded content
1266
- * @param string $content Content to restore excluded content to
1267
- * @return string Content with excluded content restored
1268
- */
1269
- public function restore_excluded_content($content, $group = null) {
1270
- $ex = $this->get_exclude();
1271
- // Setup cache
1272
- if ( !is_string($group) || empty($group) ) {
1273
- $group = $ex->group_default;
1274
- }
1275
- // Nothing to restore if cache group doesn't exist
1276
- if ( !isset($ex->cache[$group]) ) {
1277
- return $content;
1278
- }
1279
- $cache =& $ex->cache[$group];
1280
-
1281
- // Search content for placeholders
1282
- $matches = null;
1283
- if ( false !== strpos($content, $ex->ph->open . $ex->ph->base) && preg_match_all($ex->ph->search, $content, $matches) ) {
1284
- // Restore placeholders
1285
- foreach ( $matches[1] as $idx => $ph ) {
1286
- // Parse placeholder attributes
1287
- $attrs = $this->util->parse_attribute_string($ph, $ex->ph->attrs);
1288
- // Validate
1289
- if ( $attrs['group'] !== $group ) {
1290
- continue;
1291
- }
1292
- // Restore content
1293
- $key = $attrs['key'] = intval($attrs['key']);
1294
- if ( isset($cache[$key]) ) {
1295
- $content = str_replace($matches[0][$idx], $cache[$key], $content);
1296
- }
1297
- }
1298
- // Cleanup
1299
- unset($idx, $ph, $matches, $key);
1300
- }
1301
-
1302
- return $content;
1303
- }
1304
-
1305
- /*-** Grouping **-*/
1306
-
1307
- /**
1308
- * Builds wrapper for grouping
1309
- * @return string Format for wrapping content in group
1310
- */
1311
- function group_get_wrapper() {
1312
- static $fmt = null;
1313
- if ( is_null($fmt) ) {
1314
- $fmt = $this->util->make_shortcode($this->add_prefix('group'), null, '%s');
1315
- }
1316
- return $fmt;
1317
- }
1318
-
1319
- /**
1320
- * Wraps shortcodes for automatic grouping
1321
- * @uses `the_content` Filter hook
1322
- * @uses group_shortcodes_handler to Wrap shortcodes for grouping
1323
- * @param string $content Post content
1324
- * @return string Modified post content
1325
- */
1326
- function group_shortcodes($content) {
1327
- if ( !$this->is_content_valid($content) ) {
1328
- return $content;
1329
- }
1330
- // Setup shortcodes to wrap
1331
- $shortcodes = $this->util->apply_filters('group_shortcodes', array( 'gallery', 'nggallery' ));
1332
- // Set custom callback
1333
- $shortcodes = array_fill_keys($shortcodes, $this->m('group_shortcodes_handler'));
1334
- // Process gallery shortcodes
1335
- return $this->util->do_shortcode($content, $shortcodes);
1336
- }
1337
-
1338
- /**
1339
- * Groups shortcodes for later processing
1340
- * @param array $attr Shortcode attributes
1341
- * @param string $content Content enclosed in shortcode
1342
- * @param string $tag Shortcode name
1343
- * @return string Grouped shortcode
1344
- */
1345
- function group_shortcodes_handler($attr, $content, $tag) {
1346
- $code = $this->util->make_shortcode($tag, $attr, $content);
1347
- // Wrap shortcode
1348
- return sprintf( $this->group_get_wrapper(), $code);
1349
- }
1350
-
1351
- /**
1352
- * Activate groups in content
1353
- * @param string $content Content to activate
1354
- * @return string Updated content
1355
- */
1356
- public function activate_groups($content) {
1357
- return $this->util->do_shortcode($content, array( $this->add_prefix('group') => $this->m('activate_groups_handler') ) );
1358
- }
1359
-
1360
- /**
1361
- * Groups shortcodes for later processing
1362
- * @param array $attr Shortcode attributes
1363
- * @param string $content Content enclosed in shortcode
1364
- * @param string $tag Shortcode name
1365
- * @return string Grouped shortcode
1366
- */
1367
- function activate_groups_handler($attr, $content, $tag) {
1368
- // Get Group ID
1369
- // Custom group
1370
- if ( isset($attr['id']) ) {
1371
- $group = $attr['id'];
1372
- trim($group);
1373
- }
1374
- // Automatically-generated group
1375
- if ( empty($group) ) {
1376
- $group = 'auto_' . ++$this->groups['auto'];
1377
- }
1378
- return $this->process_links($content, $group);
1379
- }
1380
-
1381
- /*-** Widgets **-*/
1382
-
1383
- /**
1384
- * Set widget up for processing/activation
1385
- * Buffers widget output for further processing
1386
- * @param array $widget_args Widget arguments
1387
- * @return void
1388
- */
1389
- public function widget_process_start($widget_args) {
1390
- // Do not continue if a widget is currently being processed (avoid nested processing)
1391
- if ( 0 < $this->widget_processing_level ) {
1392
- return;
1393
- }
1394
- // Start widget processing
1395
- $this->widget_processing = true;
1396
- $this->widget_processing_params = $widget_args;
1397
- // Enable widget grouping
1398
- if ( $this->options->get_bool('group_widget') ) {
1399
- $this->util->add_filter('get_group_id', $this->m('widget_group_id'));
1400
- }
1401
- // Begin output buffer
1402
- ob_start();
1403
- }
1404
-
1405
- /**
1406
- * Handles inter-widget processing
1407
- * After widget output generated, Before next widget starts
1408
- * @param array $params New widget parameters
1409
- */
1410
- public function widget_process_inter( $params ) {
1411
- $this->widget_process_finish();
1412
- return $params;
1413
- }
1414
-
1415
- /**
1416
- * Complete widget processing
1417
- * Activate widget output
1418
- * @uses $widget_processing
1419
- * @uses $widget_processing_level
1420
- * @uses $widget_processing_params
1421
- * @return void
1422
- */
1423
- public function widget_process_finish() {
1424
- /**
1425
- * Stop processing on conditions:
1426
- * - No widget is being processed
1427
- * - Processing a nested widget
1428
- */
1429
- if ( !$this->widget_processing || 0 < $this->widget_processing_level ) {
1430
- return;
1431
- }
1432
- // Activate widget output
1433
- $out = $this->activate_links(ob_get_clean());
1434
-
1435
- // Clear grouping callback
1436
- if ( $this->options->get_bool('group_widget') ) {
1437
- $this->util->remove_filter('get_group_id', $this->m('widget_group_id'));
1438
- }
1439
- // End widget processing
1440
- $this->widget_processing = false;
1441
- $this->widget_processing_params = null;
1442
- // Output widget
1443
- echo $out;
1444
- }
1445
-
1446
- /**
1447
- * Add widget ID to link group ID
1448
- * Widget ID precedes all other group segments
1449
- * @uses `SLB::get_group_id` filter
1450
- * @param array $group_segments Group ID segments
1451
- * @return array Modified group ID segments
1452
- */
1453
- public function widget_group_id($group_segments) {
1454
- // Add current widget ID to group ID
1455
- if ( isset($this->widget_processing_params['id']) ) {
1456
- array_unshift($group_segments, $this->widget_processing_params['id']);
1457
- }
1458
- return $group_segments;
1459
- }
1460
-
1461
- /**
1462
- * Handles nested activation in widgets
1463
- * @uses widget_processing
1464
- * @uses $widget_processing_level
1465
- * @return void
1466
- */
1467
- public function widget_process_nested() {
1468
- // Stop if no widget is being processed
1469
- if ( !$this->widget_processing ) {
1470
- return;
1471
- }
1472
-
1473
- // Increment nesting level
1474
- $this->widget_processing_level++;
1475
- }
1476
-
1477
- /**
1478
- * Mark the end of a nested widget
1479
- * @uses $widget_processing_level
1480
- */
1481
- public function widget_process_nested_finish() {
1482
- // Decrement nesting level
1483
- if ( 0 < $this->widget_processing_level ) {
1484
- $this->widget_processing_level--;
1485
- }
1486
- }
1487
-
1488
- /**
1489
- * Begin blocking widget activation
1490
- * @return void
1491
- */
1492
- public function widget_block_start() {
1493
- $this->util->add_filter('is_content_valid', $this->m('widget_block_handle'));
1494
- }
1495
-
1496
- /**
1497
- * Stop blocking widget activation
1498
- * @return void
1499
- */
1500
- public function widget_block_finish() {
1501
- $this->util->remove_filter('is_content_valid', $this->m('widget_block_handle'));
1502
- }
1503
-
1504
- /**
1505
- * Handle widget activation blocking
1506
- */
1507
- public function widget_block_handle($is_content_valid) {
1508
- return false;
1509
- }
1510
-
1511
- /*-** Menus **-*/
1512
-
1513
- /**
1514
- * Process navigation menu links
1515
- *
1516
- * @see wp_nav_menu()/filter: wp_nav_menu
1517
- *
1518
- * @param string $nav_menu HTML content for navigation menu.
1519
- * @param object $args Navigation menu's arguments.
1520
- */
1521
- public function menu_process($nav_menu, $args) {
1522
- // Grouping
1523
- if ( $this->options->get_bool('group_menu') ) {
1524
- // Generate group ID for menu
1525
- $group = 'menu';
1526
- $sep = '_';
1527
- if ( !empty( $args->menu_id ) ) {
1528
- $group .= $sep . $args->menu_id;
1529
- } elseif ( !empty( $args->menu ) ) {
1530
- $group .= $sep . ( ( is_object($args->menu) ) ? $args->menu->slug : $args->menu );
1531
- }
1532
- $group = $this->group_id_unique( $group );
1533
- } else {
1534
- $group = null;
1535
- }
1536
-
1537
- // Process menu
1538
- $nav_menu = $this->activate_links($nav_menu, $group);
1539
-
1540
- return $nav_menu;
1541
- }
1542
-
1543
- /**
1544
- * Generate unique group ID
1545
- *
1546
- * @param string $group Group ID to check
1547
- * @return string Unique group ID
1548
- */
1549
- public function group_id_unique($group) {
1550
- static $groups = array();
1551
- while ( in_array($group, $groups) ) {
1552
- $patt = '#-(\d+)$#';
1553
- if ( preg_match( $patt, $group, $matches ) )
1554
- $group = preg_replace($patt, '-' . ++$matches[1], $group );
1555
- else
1556
- $group = $group . '-1';
1557
- }
1558
- return $group;
1559
- }
1560
-
1561
- /*-** Helpers **-*/
1562
-
1563
- /**
1564
- * Build attribute name
1565
- * Makes sure name is only prefixed once
1566
- * @param string $name (optional) Attribute base name
1567
- * @return string Formatted attribute name
1568
- */
1569
- function make_attribute_name($name = '') {
1570
- // Validate
1571
- if ( !is_string($name) ) {
1572
- $name = '';
1573
- } else {
1574
- $name = trim($name);
1575
- }
1576
- // Setup
1577
- $sep = '-';
1578
- $top = 'data';
1579
- // Generate valid name
1580
- if ( strpos($name, $top . $sep . $this->get_prefix()) !== 0 ) {
1581
- $name = $top . $sep . $this->add_prefix($name, $sep);
1582
- }
1583
- return $name;
1584
- }
1585
-
1586
- /**
1587
- * Set attribute to array
1588
- * Attribute is added to array if it does not exist
1589
- * @param array $attrs Array to add attribute to (Passed by reference)
1590
- * @param string $name Name of attribute to add
1591
- * @param string (optional) $value Attribute value
1592
- * @return array Updated attribute array
1593
- */
1594
- function set_attribute(&$attrs, $name, $value = true) {
1595
- // Validate
1596
- $attrs = $this->get_attributes($attrs, false);
1597
- if ( !is_string($name) || empty($name) ) {
1598
- return $attrs;
1599
- }
1600
- if ( !is_scalar($value) ) {
1601
- $value = true;
1602
- }
1603
- // Add attribute
1604
- $attrs = array_merge($attrs, array( $this->make_attribute_name($name) => strval($value) ));
1605
-
1606
- return $attrs;
1607
- }
1608
-
1609
- /**
1610
- * Convert attribute string into array
1611
- * @param string $attr_string Attribute string
1612
- * @param bool (optional) $internal Whether only internal attributes should be evaluated (Default: TRUE)
1613
- * @return array Attributes as associative array
1614
- */
1615
- function get_attributes($attr_string, $internal = true) {
1616
- if ( is_string($attr_string) ) {
1617
- $attr_string = $this->util->parse_attribute_string($attr_string);
1618
- }
1619
- $ret = ( is_array($attr_string) ) ? $attr_string : array();
1620
- // Filter out external attributes
1621
- if ( !empty($ret) && is_bool($internal) && $internal ) {
1622
- $ret_f = array();
1623
- foreach ( $ret as $key => $val ) {
1624
- if ( strpos($key, $this->make_attribute_name()) == 0 ) {
1625
- $ret_f[$key] = $val;
1626
- }
1627
- }
1628
- if ( !empty($ret_f) ) {
1629
- $ret = $ret_f;
1630
- }
1631
- }
1632
-
1633
- return $ret;
1634
- }
1635
-
1636
- /**
1637
- * Retrieve attribute value
1638
- * @param string|array $attrs Attributes to retrieve attribute value from
1639
- * @param string $attr Attribute name to retrieve
1640
- * @param bool (optional) $internal Whether only internal attributes should be evaluated (Default: TRUE)
1641
- * @return string|bool Attribute value (Default: FALSE)
1642
- */
1643
- function get_attribute($attrs, $attr, $internal = true) {
1644
- $ret = false;
1645
- // Validate
1646
- $attrs = $this->get_attributes($attrs, $internal);
1647
- if ( $internal ) {
1648
- $attr = $this->make_attribute_name($attr);
1649
- }
1650
- if ( isset($attrs[$attr]) ) {
1651
- $ret = $attrs[$attr];
1652
- }
1653
- return $ret;
1654
- }
1655
-
1656
- /**
1657
- * Checks if attribute exists
1658
- * If supplied, the attribute's value is also validated
1659
- * @param string|array $attrs Attributes to retrieve attribute value from
1660
- * @param string $attr Attribute name to retrieve
1661
- * @param mixed $value (optional) Attribute value to check for
1662
- * @param bool $internal (optional) Whether to check only internal attributes (Default: TRUE)
1663
- * @see get_attribute()
1664
- * @return bool Whether or not attribute (with matching value if specified) exists
1665
- */
1666
- function has_attribute($attrs, $attr, $value = null, $internal = true) {
1667
- $a = $this->get_attribute($attrs, $attr, $internal);
1668
- $ret = false;
1669
- if ( $a !== false ) {
1670
- $ret = true;
1671
- // Check value
1672
- if ( !is_null($value) ) {
1673
- if ( is_string($value) ) {
1674
- $ret = ( $a == strval($value) ) ? true : false;
1675
- } elseif ( is_bool($value) ) {
1676
- $ret = ( !!$a == $value ) ? true : false;
1677
- } else {
1678
- $ret = false;
1679
- }
1680
- }
1681
- }
1682
- return $ret;
1683
- }
1684
-
1685
- /**
1686
- * Build JS object of UI strings when initializing lightbox
1687
- * @return array UI strings
1688
- */
1689
- private function build_labels() {
1690
- $ret = array();
1691
- /* Get all UI options */
1692
- $prefix = 'txt_';
1693
- $opt_strings = array_filter( array_keys( $this->options->get_items() ), function ( $opt ) use ( $prefix ) {
1694
- return ( strpos( $opt, $prefix ) === 0 );
1695
- } );
1696
- if ( count( $opt_strings ) ) {
1697
- /* Build array of UI options */
1698
- foreach ( $opt_strings as $key ) {
1699
- $name = substr( $key, strlen( $prefix ) );
1700
- $ret[ $name ] = $this->options->get_value( $key );
1701
- }
1702
- }
1703
- return $ret;
1704
- }
1705
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Controller
4
+ * @package Simple Lightbox
5
+ * @author Archetyped
6
+ */
7
+ class SLB_Lightbox extends SLB_Base {
8
+
9
+ /*-** Properties **-*/
10
+
11
+ protected $model = true;
12
+
13
+ /**
14
+ * Fields
15
+ * @var SLB_Fields
16
+ */
17
+ public $fields = null;
18
+
19
+ /**
20
+ * Themes collection
21
+ * @var SLB_Themes
22
+ */
23
+ public $themes = null;
24
+
25
+ /**
26
+ * Content types
27
+ * @var SLB_Content_Handlers
28
+ */
29
+ public $handlers = null;
30
+
31
+ /**
32
+ * Template tags
33
+ * @var SLB_Template_Tags
34
+ */
35
+ public $template_tags = null;
36
+
37
+ /**
38
+ * Media item template.
39
+ *
40
+ * @var array {
41
+ * Media item properties.
42
+ *
43
+ * @type int $id WP post ID. Default null.
44
+ * @type string $type Item type. Default null.
45
+ * @type string $source Source URI.
46
+ * @type bool $internal Internal resource. Default false.
47
+ * @type string $title Item title.
48
+ * @type string $caption Item caption.
49
+ * @type string $description Item description.
50
+ * @type array $media {
51
+ * Media properties (indexed by size name).
52
+ * Original size = 'full'.
53
+ *
54
+ * @type string $file File URI.
55
+ * @type int $width File width in pixels.
56
+ * @type int $height File height in pixels.
57
+ * }
58
+ * @type array $meta {
59
+ * Item metadata.
60
+ * }
61
+ * }
62
+ */
63
+ private $media_item_template = [
64
+ 'id' => null,
65
+ 'type' => null,
66
+ 'internal' => false,
67
+ 'source' => '',
68
+ ];
69
+
70
+ /**
71
+ * Processed media items for output to client.
72
+ *
73
+ * @var object[string] {
74
+ * Media item properties.
75
+ *
76
+ * @index string Unique ID (system-generated).
77
+ *
78
+ * @see $media_item_template.
79
+ * }
80
+ */
81
+ private $media_items = [];
82
+
83
+ /**
84
+ * Collection of unprocessed media items.
85
+ *
86
+ * @var array {
87
+ * @type object[string] $props {
88
+ * Media item properties.
89
+ *
90
+ * @index string Unique ID (system-generated).
91
+ *
92
+ * @see $media_item_template
93
+ * }
94
+ * @type string[string] $uri {
95
+ * Cached URIs.
96
+ *
97
+ * @index string URI.
98
+ *
99
+ * @type string Item ID (points to item in `props` array)
100
+ * }
101
+ * }
102
+ */
103
+ private $media_items_raw = [
104
+ 'props' => [],
105
+ 'uri' => [],
106
+ ];
107
+
108
+ /**
109
+ * Manage excluded content
110
+ * @var object
111
+ */
112
+ private $exclude = null;
113
+
114
+ private $groups = array(
115
+ 'auto' => 0,
116
+ 'manual' => array(),
117
+ );
118
+
119
+ /**
120
+ * Validated URIs
121
+ * Caches validation of parsed URIs
122
+ * > Key: URI
123
+ * > Value: (bool) TRUE if valid
124
+ * @var array
125
+ */
126
+ private $validated_uris = array();
127
+
128
+ /* Widget properties */
129
+
130
+ /**
131
+ * Used to track if widget is currently being processed or not
132
+ * Set to Widget ID currently being processed
133
+ * @var bool|string
134
+ */
135
+ private $widget_processing = false;
136
+
137
+ /**
138
+ * Parameters for widget being processed
139
+ * @param array
140
+ */
141
+ private $widget_processing_params = null;
142
+
143
+ /**
144
+ * Manage nested widget processing
145
+ * Used to avoid premature widget output
146
+ * @var int
147
+ */
148
+ private $widget_processing_level = 0;
149
+
150
+ /**
151
+ * Constructor
152
+ */
153
+ public function __construct() {
154
+ parent::__construct();
155
+ // Init instances
156
+ $this->fields = new SLB_Fields();
157
+ $this->themes = new SLB_Themes( $this );
158
+ if ( ! is_admin() ) {
159
+ $this->template_tags = new SLB_Template_Tags( $this );
160
+ }
161
+ }
162
+
163
+ /* Init */
164
+
165
+ public function _init() {
166
+ parent::_init();
167
+ $this->util->do_action( 'init' );
168
+ }
169
+
170
+ /**
171
+ * Declare client files (scripts, styles)
172
+ * @uses parent::_client_files()
173
+ * @return void
174
+ */
175
+ protected function _client_files( $files = null ) {
176
+ $js_path = 'client/js/';
177
+ $js_path .= ( SLB_DEV ) ? 'dev' : 'prod';
178
+ $files = array(
179
+ 'scripts' => array(
180
+ 'core' => array(
181
+ 'file' => "$js_path/lib.core.js",
182
+ 'deps' => 'jquery',
183
+ 'enqueue' => false,
184
+ 'in_footer' => true,
185
+ ),
186
+ 'view' => array(
187
+ 'file' => "$js_path/lib.view.js",
188
+ 'deps' => array( '[core]' ),
189
+ 'context' => array( array( 'public', $this->m( 'is_request_valid' ) ) ),
190
+ 'in_footer' => true,
191
+ ),
192
+ ),
193
+ 'styles' => array(
194
+ 'core' => array(
195
+ 'file' => 'client/css/app.css',
196
+ 'context' => array( 'public' ),
197
+ ),
198
+ ),
199
+ );
200
+ parent::_client_files( $files );
201
+ }
202
+
203
+ /**
204
+ * Register hooks
205
+ * @uses parent::_hooks()
206
+ */
207
+ protected function _hooks() {
208
+ parent::_hooks();
209
+
210
+ /* Admin */
211
+ add_action( 'admin_menu', $this->m( 'admin_menus' ) );
212
+
213
+ /* Init */
214
+ add_action( 'wp', $this->m( '_hooks_init' ) );
215
+ }
216
+
217
+ /**
218
+ * Initialize hooks.
219
+ *
220
+ * @return void
221
+ */
222
+ public function _hooks_init() {
223
+ if ( ! $this->is_enabled() ) {
224
+ return;
225
+ }
226
+ // Callback for hooks that need to have method added to end of stack.
227
+ $cb_hooks_add_last = $this->m( 'hooks_add_last' );
228
+
229
+ // Init lightbox
230
+ add_action( 'wp_footer', $this->m( 'client_footer' ) );
231
+ $this->util->add_action( 'footer_script', $this->m( 'client_init' ), 1 );
232
+ $this->util->add_filter( 'footer_script', $this->m( 'client_script_media' ), 2 );
233
+ // Link activation
234
+ add_filter( 'the_content', $cb_hooks_add_last );
235
+ add_filter( 'get_post_galleries', $cb_hooks_add_last );
236
+ $this->util->add_filter( 'post_process_links', $this->m( 'activate_groups' ), 11 );
237
+ $this->util->add_filter( 'validate_uri_regex', $this->m( 'validate_uri_regex_default' ), 1 );
238
+ // Content exclusion
239
+ $this->util->add_filter( 'pre_process_links', $this->m( 'exclude_content' ) );
240
+ $this->util->add_filter( 'pre_exclude_content', $this->m( 'exclude_shortcodes' ) );
241
+ $this->util->add_filter( 'post_process_links', $this->m( 'restore_excluded_content' ) );
242
+
243
+ // Grouping
244
+ if ( $this->options->get_bool( 'group_post' ) ) {
245
+ $this->util->add_filter( 'get_group_id', $this->m( 'post_group_id' ), 1 );
246
+ }
247
+
248
+ // Shortcode grouping
249
+ if ( $this->options->get_bool( 'group_gallery' ) ) {
250
+ add_filter( 'the_content', $this->m( 'group_shortcodes' ), 1 );
251
+ }
252
+
253
+ // Widgets
254
+ if ( $this->options->get_bool( 'enabled_widget' ) ) {
255
+ add_action( 'dynamic_sidebar_before', $this->m( 'widget_process_nested' ) );
256
+ add_action( 'dynamic_sidebar', $cb_hooks_add_last );
257
+ add_filter( 'dynamic_sidebar_params', $this->m( 'widget_process_inter' ), 1 );
258
+ add_action( 'dynamic_sidebar_after', $cb_hooks_add_last );
259
+ add_action( 'dynamic_sidebar_after', $cb_hooks_add_last );
260
+ } else {
261
+ add_action( 'dynamic_sidebar_before', $this->m( 'widget_block_start' ) );
262
+ add_action( 'dynamic_sidebar_after', $this->m( 'widget_block_finish' ) );
263
+ }
264
+
265
+ // Menus
266
+ if ( $this->options->get_bool( 'enabled_menu' ) ) {
267
+ add_filter( 'wp_nav_menu', $cb_hooks_add_last );
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Adds hook(s) to end of filter/action stack.
273
+ *
274
+ * @param string $data Optional. Data being filtered.
275
+ * @return string Filtered content.
276
+ */
277
+ public function hooks_add_last( $data = null ) {
278
+ global $wp_filter;
279
+
280
+ $tag = current_filter();
281
+
282
+ // Stop processing on invalid hook.
283
+ if ( empty( $tag ) || ! is_string( $tag ) ) {
284
+ return $data;
285
+ }
286
+
287
+ // Get lowest priority for filter.
288
+ $max_priority = max( array_keys( $wp_filter[ $tag ]->callbacks ) ) + 123;
289
+
290
+ switch ( $tag ) {
291
+ case 'the_content':
292
+ add_filter( $tag, $this->m( 'activate_links' ), $max_priority );
293
+ break;
294
+ case 'get_post_galleries':
295
+ add_filter( $tag, $this->m( 'activate_galleries' ), $max_priority );
296
+ break;
297
+ case 'dynamic_sidebar':
298
+ add_action( $tag, $this->m( 'widget_process_start' ), $max_priority );
299
+ break;
300
+ case 'dynamic_sidebar_after':
301
+ add_action( $tag, $this->m( 'widget_process_finish' ), $max_priority );
302
+ add_action( $tag, $this->m( 'widget_process_nested_finish' ), $max_priority );
303
+ break;
304
+ case 'wp_nav_menu':
305
+ add_filter( $tag, $this->m( 'menu_process' ), $max_priority, 2 );
306
+ break;
307
+ }
308
+
309
+ // Remove init hook.
310
+ remove_filter( $tag, $this->m( __FUNCTION__ ) );
311
+
312
+ // Return content (for filters).
313
+ return $data;
314
+ }
315
+
316
+ /**
317
+ * Add post ID to link group ID
318
+ * @uses `SLB::get_group_id` filter
319
+ * @param array $group_segments Group ID segments
320
+ * @return array Modified group ID segments
321
+ */
322
+ public function post_group_id( $group_segments ) {
323
+ if ( in_the_loop() ) {
324
+ // Prepend post ID to group ID
325
+ $post = get_post();
326
+ if ( $post ) {
327
+ array_unshift( $group_segments, $post->ID );
328
+ }
329
+ }
330
+ return $group_segments;
331
+ }
332
+
333
+ /**
334
+ * Init options
335
+ */
336
+ protected function _options() {
337
+ // Setup options
338
+ $opts = array(
339
+ 'groups' => array(
340
+ 'activation' => array(
341
+ 'title' => __( 'Activation', 'simple-lightbox' ),
342
+ 'priority' => 10,
343
+ ),
344
+ 'grouping' => array(
345
+ 'title' => __( 'Grouping', 'simple-lightbox' ),
346
+ 'priority' => 20,
347
+ ),
348
+ 'ui' => array(
349
+ 'title' => __( 'UI', 'simple-lightbox' ),
350
+ 'priority' => 30,
351
+ ),
352
+ 'labels' => array(
353
+ 'title' => __( 'Labels', 'simple-lightbox' ),
354
+ 'priority' => 40,
355
+ ),
356
+ ),
357
+ 'items' => array(
358
+ 'enabled' => array(
359
+ 'title' => __( 'Enable Lightbox Functionality', 'simple-lightbox' ),
360
+ 'default' => true,
361
+ 'group' => array( 'activation', 10 ),
362
+ ),
363
+ 'enabled_home' => array(
364
+ 'title' => __( 'Enable on Home page', 'simple-lightbox' ),
365
+ 'default' => true,
366
+ 'group' => array( 'activation', 20 ),
367
+ ),
368
+ 'enabled_post' => array(
369
+ 'title' => __( 'Enable on Single Posts', 'simple-lightbox' ),
370
+ 'default' => true,
371
+ 'group' => array( 'activation', 30 ),
372
+ ),
373
+ 'enabled_page' => array(
374
+ 'title' => __( 'Enable on Pages', 'simple-lightbox' ),
375
+ 'default' => true,
376
+ 'group' => array( 'activation', 40 ),
377
+ ),
378
+ 'enabled_archive' => array(
379
+ 'title' => __( 'Enable on Archive Pages (tags, categories, etc.)', 'simple-lightbox' ),
380
+ 'default' => true,
381
+ 'group' => array( 'activation', 50 ),
382
+ ),
383
+ 'enabled_widget' => array(
384
+ 'title' => __( 'Enable for Widgets', 'simple-lightbox' ),
385
+ 'default' => false,
386
+ 'group' => array( 'activation', 60 ),
387
+ ),
388
+ 'enabled_menu' => array(
389
+ 'title' => __( 'Enable for Menus', 'simple-lightbox' ),
390
+ 'default' => false,
391
+ 'group' => array( 'activation', 60 ),
392
+ ),
393
+ 'group_links' => array(
394
+ 'title' => __( 'Group items (for displaying as a slideshow)', 'simple-lightbox' ),
395
+ 'default' => true,
396
+ 'group' => array( 'grouping', 10 ),
397
+ ),
398
+ 'group_post' => array(
399
+ 'title' => __( 'Group items by Post (e.g. on pages with multiple posts)', 'simple-lightbox' ),
400
+ 'default' => true,
401
+ 'group' => array( 'grouping', 20 ),
402
+ ),
403
+ 'group_gallery' => array(
404
+ 'title' => __( 'Group gallery items separately', 'simple-lightbox' ),
405
+ 'default' => false,
406
+ 'group' => array( 'grouping', 30 ),
407
+ ),
408
+ 'group_widget' => array(
409
+ 'title' => __( 'Group widget items separately', 'simple-lightbox' ),
410
+ 'default' => false,
411
+ 'group' => array( 'grouping', 40 ),
412
+ ),
413
+ 'group_menu' => array(
414
+ 'title' => __( 'Group menu items separately', 'simple-lightbox' ),
415
+ 'default' => false,
416
+ 'group' => array( 'grouping', 50 ),
417
+ ),
418
+ 'ui_autofit' => array(
419
+ 'title' => __( 'Resize lightbox to fit in window', 'simple-lightbox' ),
420
+ 'default' => true,
421
+ 'group' => array( 'ui', 10 ),
422
+ 'in_client' => true,
423
+ ),
424
+ 'ui_animate' => array(
425
+ 'title' => __( 'Enable animations', 'simple-lightbox' ),
426
+ 'default' => true,
427
+ 'group' => array( 'ui', 20 ),
428
+ 'in_client' => true,
429
+ ),
430
+ 'slideshow_autostart' => array(
431
+ 'title' => __( 'Start Slideshow Automatically', 'simple-lightbox' ),
432
+ 'default' => true,
433
+ 'group' => array( 'ui', 30 ),
434
+ 'in_client' => true,
435
+ ),
436
+ 'slideshow_duration' => array(
437
+ 'title' => __( 'Slide Duration (Seconds)', 'simple-lightbox' ),
438
+ 'default' => '6',
439
+ 'attr' => array(
440
+ 'size' => 3,
441
+ 'maxlength' => 3,
442
+ ),
443
+ 'group' => array( 'ui', 40 ),
444
+ 'in_client' => true,
445
+ ),
446
+ 'group_loop' => array(
447
+ 'title' => __( 'Loop through items', 'simple-lightbox' ),
448
+ 'default' => true,
449
+ 'group' => array( 'ui', 50 ),
450
+ 'in_client' => true,
451
+ ),
452
+ 'ui_overlay_opacity' => array(
453
+ 'title' => __( 'Overlay Opacity (0 - 1)', 'simple-lightbox' ),
454
+ 'default' => '0.8',
455
+ 'attr' => array(
456
+ 'size' => 3,
457
+ 'maxlength' => 3,
458
+ ),
459
+ 'group' => array( 'ui', 60 ),
460
+ 'in_client' => true,
461
+ ),
462
+ 'ui_title_default' => array(
463
+ 'title' => __( 'Enable default title', 'simple-lightbox' ),
464
+ 'default' => false,
465
+ 'group' => array( 'ui', 70 ),
466
+ 'in_client' => true,
467
+ ),
468
+ 'txt_loading' => array(
469
+ 'title' => __( 'Loading indicator', 'simple-lightbox' ),
470
+ 'default' => 'Loading',
471
+ 'group' => array( 'labels', 20 ),
472
+ ),
473
+ 'txt_close' => array(
474
+ 'title' => __( 'Close button', 'simple-lightbox' ),
475
+ 'default' => 'Close',
476
+ 'group' => array( 'labels', 10 ),
477
+ ),
478
+ 'txt_nav_next' => array(
479
+ 'title' => __( 'Next Item button', 'simple-lightbox' ),
480
+ 'default' => 'Next',
481
+ 'group' => array( 'labels', 30 ),
482
+ ),
483
+ 'txt_nav_prev' => array(
484
+ 'title' => __( 'Previous Item button', 'simple-lightbox' ),
485
+ 'default' => 'Previous',
486
+ 'group' => array( 'labels', 40 ),
487
+ ),
488
+ 'txt_slideshow_start' => array(
489
+ 'title' => __( 'Start Slideshow button', 'simple-lightbox' ),
490
+ 'default' => 'Start slideshow',
491
+ 'group' => array( 'labels', 50 ),
492
+ ),
493
+ 'txt_slideshow_stop' => array(
494
+ 'title' => __( 'Stop Slideshow button', 'simple-lightbox' ),
495
+ 'default' => 'Stop slideshow',
496
+ 'group' => array( 'labels', 60 ),
497
+ ),
498
+ 'txt_group_status' => array(
499
+ 'title' => __( 'Slideshow status format', 'simple-lightbox' ),
500
+ 'default' => 'Item %current% of %total%',
501
+ 'group' => array( 'labels', 70 ),
502
+ ),
503
+ ),
504
+ 'legacy' => array(
505
+ 'header_activation' => null,
506
+ 'header_enabled' => null,
507
+ 'header_strings' => null,
508
+ 'header_ui' => null,
509
+ 'activate_attachments' => null,
510
+ 'validate_links' => null,
511
+ 'enabled_compat' => null,
512
+ 'enabled_single' => array( 'enabled_post', 'enabled_page' ),
513
+ 'enabled_caption' => null,
514
+ 'enabled_desc' => null,
515
+ 'ui_enabled_caption' => null,
516
+ 'ui_caption_src' => null,
517
+ 'ui_enabled_desc' => null,
518
+ 'caption_src' => null,
519
+ 'animate' => 'ui_animate',
520
+ 'overlay_opacity' => 'ui_overlay_opacity',
521
+ 'loop' => 'group_loop',
522
+ 'autostart' => 'slideshow_autostart',
523
+ 'duration' => 'slideshow_duration',
524
+ 'txt_numDisplayPrefix' => null,
525
+ 'txt_numDisplaySeparator' => null,
526
+ 'txt_closeLink' => 'txt_link_close',
527
+ 'txt_nextLink' => 'txt_link_next',
528
+ 'txt_prevLink' => 'txt_link_prev',
529
+ 'txt_startSlideshow' => 'txt_slideshow_start',
530
+ 'txt_stopSlideshow' => 'txt_slideshow_stop',
531
+ 'txt_loadingMsg' => 'txt_loading',
532
+ 'txt_link_next' => 'txt_nav_next',
533
+ 'txt_link_prev' => 'txt_nav_prev',
534
+ 'txt_link_close' => 'txt_close',
535
+ ),
536
+ );
537
+
538
+ parent::_set_options( $opts );
539
+ }
540
+
541
+ /* Methods */
542
+
543
+ /*-** Admin **-*/
544
+
545
+ /**
546
+ * Add admin menus
547
+ * @uses this->admin->add_theme_page
548
+ */
549
+ function admin_menus() {
550
+ // Build options page
551
+ $lbls_opts = array(
552
+ 'menu' => __( 'Lightbox', 'simple-lightbox' ),
553
+ 'header' => __( 'Lightbox Settings', 'simple-lightbox' ),
554
+ 'plugin_action' => __( 'Settings', 'simple-lightbox' ),
555
+ );
556
+ $pg_opts = $this->admin->add_theme_page( 'options', $lbls_opts )
557
+ ->require_form()
558
+ ->add_content( 'options', 'Options', $this->options );
559
+
560
+ // Add Support information
561
+ $support = $this->util->get_plugin_info( 'SupportURI' );
562
+ if ( ! empty( $support ) ) {
563
+ $pg_opts->add_content( 'support', __( 'Feedback & Support', 'simple-lightbox' ), $this->m( 'theme_page_callback_support' ), 'secondary' );
564
+ }
565
+
566
+ // Add Actions
567
+ $lbls_reset = array(
568
+ 'title' => __( 'Reset', 'simple-lightbox' ),
569
+ 'confirm' => __( 'Are you sure you want to reset Simple Lightbox\'s settings?', 'simple-lightbox' ),
570
+ 'success' => __( 'Settings have been reset', 'simple-lightbox' ),
571
+ 'failure' => __( 'Settings were not reset', 'simple-lightbox' ),
572
+ );
573
+ $this->admin->add_action( 'reset', $lbls_reset, $this->options );
574
+ }
575
+
576
+ /**
577
+ * Support information
578
+ */
579
+ public function theme_page_callback_support() {
580
+ // Description
581
+ $desc = __( '<p>Simple Lightbox thrives on your feedback!</p><p>Click the button below to <strong>get help</strong>, <strong>request a feature</strong>, or <strong>provide some feedback</strong>!</p>', 'simple-lightbox' );
582
+ echo $desc;
583
+ // Link
584
+ $lnk_uri = $this->util->get_plugin_info( 'SupportURI' );
585
+ $lnk_txt = __( 'Get Support &amp; Provide Feedback', 'simple-lightbox' );
586
+ echo $this->util->build_html_link(
587
+ $lnk_uri,
588
+ $lnk_txt,
589
+ array(
590
+ 'target' => '_blank',
591
+ 'class' => 'button',
592
+ )
593
+ );
594
+ }
595
+
596
+ /*-** Functionality **-*/
597
+
598
+ /**
599
+ * Checks whether lightbox is currently enabled/disabled
600
+ * @return bool TRUE if lightbox is currently enabled, FALSE otherwise
601
+ */
602
+ function is_enabled() {
603
+ static $ret = null;
604
+ if ( is_null( $ret ) ) {
605
+ $ret = ( ! is_admin() && $this->options->get_bool( 'enabled' ) && ! is_feed() ) ? true : false;
606
+ if ( $ret ) {
607
+ $opt = '';
608
+ // Determine option to check
609
+ if ( is_home() || is_front_page() ) {
610
+ $opt = 'home';
611
+ } elseif ( is_singular() ) {
612
+ $opt = ( is_page() ) ? 'page' : 'post';
613
+ } elseif ( is_archive() || is_search() ) {
614
+ $opt = 'archive';
615
+ }
616
+ // Check sub-option
617
+ if ( ! empty( $opt ) ) {
618
+ // Prefix option name.
619
+ $opt = 'enabled_' . $opt;
620
+ if ( $this->options->has( $opt ) ) {
621
+ $ret = $this->options->get_bool( $opt );
622
+ }
623
+ }
624
+ }
625
+ }
626
+ // Filter return value
627
+ if ( ! is_admin() ) {
628
+ $ret = $this->util->apply_filters( 'is_enabled', $ret );
629
+ }
630
+ // Return value (force boolean)
631
+ return ! ! $ret;
632
+ }
633
+
634
+ /**
635
+ * Make sure content is valid for processing/activation
636
+ *
637
+ * @param string $content Content to validate
638
+ * @return bool TRUE if content is valid (FALSE otherwise)
639
+ */
640
+ protected function is_content_valid( $content ) {
641
+ // Invalid hooks
642
+ if ( doing_filter( 'get_the_excerpt' ) ) {
643
+ return false;
644
+ }
645
+
646
+ // Non-string value
647
+ if ( ! is_string( $content ) ) {
648
+ return false;
649
+ }
650
+
651
+ // Empty string
652
+ $content = trim( $content );
653
+ if ( empty( $content ) ) {
654
+ return false;
655
+ }
656
+
657
+ // Content is valid
658
+ return $this->util->apply_filters( 'is_content_valid', true, $content );
659
+ }
660
+
661
+ /**
662
+ * Activates galleries extracted from post
663
+ * @see get_post_galleries()
664
+ * @param array $galleries A list of galleries in post
665
+ * @return A list of galleries with links activated
666
+ */
667
+ function activate_galleries( $galleries ) {
668
+ // Validate
669
+ if ( empty( $galleries ) ) {
670
+ return $galleries;
671
+ }
672
+ // Check galleries for HTML output
673
+ $gallery = reset( $galleries );
674
+ if ( is_array( $gallery ) ) {
675
+ return $galleries;
676
+ }
677
+
678
+ // Activate galleries
679
+ $group = ( $this->options->get_bool( 'group_gallery' ) ) ? true : null;
680
+ foreach ( $galleries as $key => $val ) {
681
+ if ( ! is_null( $group ) ) {
682
+ $group = 'gallery_' . $key;
683
+ }
684
+ // Activate links in gallery
685
+ $gallery = $this->process_links( $val, $group );
686
+
687
+ // Save modified gallery
688
+ $galleries[ $key ] = $gallery;
689
+ }
690
+
691
+ return $galleries;
692
+ }
693
+
694
+ /**
695
+ * Scans post content for image links and activates them
696
+ *
697
+ * Lightbox will not be activated for feeds
698
+ * @param string $content Content to activate
699
+ * @param string (optonal) $group Group ID for content
700
+ * @return string Post content
701
+ */
702
+ public function activate_links( $content, $group = null ) {
703
+ // Validate content
704
+ if ( ! $this->is_content_valid( $content ) ) {
705
+ return $content;
706
+ }
707
+ // Filter content before processing links
708
+ $content = $this->util->apply_filters( 'pre_process_links', $content );
709
+
710
+ // Process links
711
+ $content = $this->process_links( $content, $group );
712
+
713
+ // Filter content after processing links
714
+ $content = $this->util->apply_filters( 'post_process_links', $content );
715
+
716
+ return $content;
717
+ }
718
+
719
+ /**
720
+ * Process links in content
721
+ * @global obj $wpdb DB instance
722
+ * @global obj $post Current post
723
+ * @param string $content Text containing links
724
+ * @param string (optional) $group Group to add links to (Default: none)
725
+ * @return string Content with processed links
726
+ */
727
+ protected function process_links( $content, $group = null ) {
728
+ // Extract links
729
+ $links = $this->get_links( $content, true );
730
+ // Do not process content without links
731
+ if ( empty( $links ) ) {
732
+ return $content;
733
+ }
734
+ // Process links
735
+ static $protocol = array( 'http://', 'https://' );
736
+ static $qv_att = 'attachment_id';
737
+ static $uri_origin = null;
738
+ if ( ! is_array( $uri_origin ) ) {
739
+ $uri_parts = array_fill_keys( array( 'scheme', 'host', 'path' ), '' );
740
+ $uri_origin = wp_parse_args( wp_parse_url( strtolower( home_url() ) ), $uri_parts );
741
+ }
742
+ static $uri_proto = null;
743
+ if ( empty( $uri_proto ) ) {
744
+ $uri_proto = (object) array(
745
+ 'raw' => '',
746
+ 'source' => '',
747
+ 'parts' => '',
748
+ );
749
+ }
750
+ $uri_parts_required = array( 'host' => '' );
751
+
752
+ // Setup group properties
753
+ $g_props = (object) array(
754
+ 'enabled' => $this->options->get_bool( 'group_links' ),
755
+ 'attr' => 'group',
756
+ 'base' => '',
757
+ 'legacy_prefix' => 'lightbox[',
758
+ 'legacy_suffix' => ']',
759
+ );
760
+ if ( $g_props->enabled ) {
761
+ $g_props->base = ( is_scalar( $group ) ) ? trim( strval( $group ) ) : '';
762
+ }
763
+
764
+ // Initialize content handlers
765
+ if ( ! ( $this->handlers instanceof SLB_Content_Handlers ) ) {
766
+ $this->handlers = new SLB_Content_Handlers( $this );
767
+ }
768
+
769
+ // Iterate through and activate supported links
770
+
771
+ foreach ( $links as $link ) {
772
+ // Init vars
773
+ $pid = 0;
774
+ $link_new = $link;
775
+ $uri = clone $uri_proto;
776
+ $type = false;
777
+ $props_extra = array();
778
+ $key = null;
779
+ $internal = false;
780
+
781
+ // Parse link attributes
782
+ $attrs = $this->util->parse_attribute_string( $link_new, array( 'href' => '' ) );
783
+ // Get URI
784
+ $uri->raw = $attrs['href'];
785
+
786
+ // Stop processing invalid links
787
+ if ( ! $this->validate_uri( $uri->raw )
788
+ || $this->has_attribute( $attrs, 'active' ) // Previously-processed.
789
+ ) {
790
+ continue;
791
+ }
792
+
793
+ // Normalize URI (make absolute)
794
+ $uri->source = WP_HTTP::make_absolute_url( $uri->raw, $uri_origin['scheme'] . '://' . $uri_origin['host'] );
795
+
796
+ // URI cached?
797
+ $key = $this->get_media_item_id( $uri->source );
798
+
799
+ // Internal URI? (e.g. attachments)
800
+ if ( ! $key ) {
801
+ $uri->parts = array_merge( $uri_parts_required, (array) wp_parse_url( $uri->source ) );
802
+ $internal = ( $uri->parts['host'] === $uri_origin['host'] ) ? true : false;
803
+
804
+ // Attachment?
805
+ if ( $internal && is_local_attachment( $uri->source ) ) {
806
+ $pid = url_to_postid( $uri->source );
807
+ $src = wp_get_attachment_url( $pid );
808
+ if ( ! ! $src ) {
809
+ $uri->source = $src;
810
+ $props_extra['id'] = $pid;
811
+ // Check cache for attachment source URI
812
+ $key = $this->get_media_item_id( $uri->source );
813
+ }
814
+ unset( $src );
815
+ }
816
+ }
817
+
818
+ // Determine content type
819
+ if ( ! $key ) {
820
+ // Get handler match
821
+ $hdl_result = $this->handlers->match( $uri->source );
822
+ if ( ! ! $hdl_result->handler ) {
823
+ $type = $hdl_result->handler->get_id();
824
+ $props_extra = $hdl_result->props;
825
+ // Updated source URI
826
+ if ( isset( $props_extra['uri'] ) ) {
827
+ $uri->source = $props_extra['uri'];
828
+ unset( $props_extra['uri'] );
829
+ }
830
+ }
831
+
832
+ // Cache valid item
833
+ if ( ! ! $type ) {
834
+ $key = $this->cache_media_item( $uri, $type, $internal, $props_extra );
835
+ }
836
+ }
837
+
838
+ // Stop processing invalid links
839
+ if ( ! $key ) {
840
+ // Cache invalid URI
841
+ $this->validated_uris[ $uri->source ] = false;
842
+ if ( $uri->raw !== $uri->source ) {
843
+ $this->validated_uris[ $uri->raw ] = false;
844
+ }
845
+ continue;
846
+ }
847
+
848
+ // Activate link
849
+ $this->set_attribute( $attrs, 'active' );
850
+ $this->set_attribute( $attrs, 'asset', $key );
851
+ // Mark internal links
852
+ if ( $internal ) {
853
+ $this->set_attribute( $attrs, 'internal', $pid );
854
+ }
855
+
856
+ // Set group (if enabled)
857
+ if ( $g_props->enabled ) {
858
+ $group = array();
859
+ // Get preset group attribute
860
+ $g = ( $this->has_attribute( $attrs, $g_props->attr ) ) ? $this->get_attribute( $attrs, $g_props->attr ) : '';
861
+ if ( ! empty( $g ) ) {
862
+ $group[] = $g;
863
+ } elseif ( ! empty( $g_props->base ) ) {
864
+ $group[] = $g_props->base;
865
+ }
866
+
867
+ /**
868
+ * Filter group ID components
869
+ *
870
+ * @see process_links()
871
+ *
872
+ * @param array $group Components used to build group ID
873
+ */
874
+ $group = $this->util->apply_filters( 'get_group_id', $group );
875
+
876
+ // Default group
877
+ if ( empty( $group ) || ! is_array( $group ) ) {
878
+ $group = $this->get_prefix();
879
+ } else {
880
+ $group = implode( '_', $group );
881
+ }
882
+
883
+ // Set group attribute
884
+ $this->set_attribute( $attrs, $g_props->attr, $group );
885
+ unset( $g );
886
+ }
887
+
888
+ // Filter attributes
889
+ $attrs = $this->util->apply_filters( 'process_link_attributes', $attrs );
890
+
891
+ // Update link in content
892
+ $link_new = '<a ' . $this->util->build_attribute_string( $attrs ) . '>';
893
+ $content = str_replace( $link, $link_new, $content );
894
+ }
895
+
896
+ // Handle widget content
897
+ if ( ! ! $this->widget_processing && 'the_content' === current_filter() ) {
898
+ $content = $this->exclude_wrap( $content );
899
+ }
900
+
901
+ return $content;
902
+ }
903
+
904
+ /**
905
+ * Retrieve HTML links in content
906
+ * @param string $content Content to get links from
907
+ * @param bool (optional) $unique Remove duplicates from returned links (Default: FALSE)
908
+ * @return array Links in content
909
+ */
910
+ function get_links( $content, $unique = false ) {
911
+ $rgx = "/\<a\b(?:(?!\shref=|\>).)*\shref=[^\>\<]++\>/i";
912
+ $links = [];
913
+ preg_match_all( $rgx, $content, $links );
914
+ $links = $links[0];
915
+ if ( $unique ) {
916
+ $links = array_unique( $links );
917
+ }
918
+ return $links;
919
+ }
920
+
921
+ /**
922
+ * Validate URI
923
+ * Matches specified URI against internal & external regex patterns
924
+ * URI is **invalid** if it matches a regex
925
+ *
926
+ * @param string $uri URI to validate
927
+ * @return bool TRUE if URI is valid
928
+ */
929
+ protected function validate_uri( $uri ) {
930
+ static $patterns = null;
931
+ // Previously-validated URI
932
+ if ( isset( $this->validated_uris[ $uri ] ) ) {
933
+ return $this->validated_uris[ $uri ];
934
+ }
935
+
936
+ $valid = true;
937
+ // Boilerplate validation
938
+ if ( empty( $uri ) // Empty
939
+ || 0 === strpos( $uri, '#' ) // Anchor
940
+ ) {
941
+ $valid = false;
942
+ }
943
+
944
+ // Regex matching
945
+ if ( $valid ) {
946
+ // Get patterns
947
+ if ( is_null( $patterns ) ) {
948
+ $patterns = $this->util->apply_filters( 'validate_uri_regex', array() );
949
+ }
950
+ // Iterate through patterns until match found
951
+ foreach ( $patterns as $pattern ) {
952
+ if ( 1 === preg_match( $pattern, $uri ) ) {
953
+ $valid = false;
954
+ break;
955
+ }
956
+ }
957
+ }
958
+
959
+ // Cache
960
+ $this->validated_uris[ $uri ] = $valid;
961
+ return $valid;
962
+ }
963
+
964
+ /**
965
+ * Add URI validation regex pattern
966
+ * @param
967
+ */
968
+ public function validate_uri_regex_default( $patterns ) {
969
+ $patterns[] = '@^https?://[^/]*(wikipedia|wikimedia)\.org/wiki/file:.*$@i';
970
+ return $patterns;
971
+ }
972
+
973
+ /* Client */
974
+
975
+ /**
976
+ * Checks if output should be loaded in current request
977
+ * @uses `is_enabled()`
978
+ * @uses `has_cached_media_items()`
979
+ * @return bool TRUE if output is being loaded into client
980
+ */
981
+ public function is_request_valid() {
982
+ return ( $this->is_enabled() && $this->has_cached_media_items() ) ? true : false;
983
+ }
984
+
985
+ /**
986
+ * Sets options/settings to initialize lightbox functionality on page load
987
+ * @return void
988
+ */
989
+ function client_init( $client_script ) {
990
+ // Get options
991
+ $options = $this->options->build_client_output();
992
+
993
+ // Load UI Strings
994
+ $labels = $this->build_labels();
995
+ if ( ! empty( $labels ) ) {
996
+ $options['ui_labels'] = $labels;
997
+ }
998
+
999
+ // Build client output
1000
+ $client_script[] = $this->util->call_client_method( 'View.init', $options );
1001
+ return $client_script;
1002
+ }
1003
+
1004
+ /**
1005
+ * Output code in footer
1006
+ * > Media attachment URLs
1007
+ * @uses `_wp_attached_file` to match attachment ID to URI
1008
+ * @uses `_wp_attachment_metadata` to retrieve attachment metadata
1009
+ */
1010
+ function client_footer() {
1011
+ if ( ! $this->has_cached_media_items() ) {
1012
+ return false;
1013
+ }
1014
+
1015
+ // Set up hooks
1016
+ add_action( 'wp_print_footer_scripts', $this->m( 'client_footer_script' ) );
1017
+
1018
+ // Build client output
1019
+ $this->util->do_action( 'footer' );
1020
+ }
1021
+
1022
+ /**
1023
+ * Output client footer scripts
1024
+ */
1025
+ function client_footer_script() {
1026
+ $client_script = $this->util->apply_filters( 'footer_script', array() );
1027
+ if ( ! empty( $client_script ) ) {
1028
+ echo $this->util->build_script_element( $client_script, 'footer', true, true );
1029
+ }
1030
+ }
1031
+
1032
+ /**
1033
+ * Add media information to client output
1034
+ *
1035
+ * @param array $client_script Client script commands.
1036
+ * @return array Modified script commands.
1037
+ * TODO Refactor
1038
+ */
1039
+ public function client_script_media( $client_script ) {
1040
+ global $wpdb;
1041
+
1042
+ // Init.
1043
+ $this->media_items = $this->get_cached_media_items();
1044
+
1045
+ // Extract internal links for additional processing.
1046
+ $m_internals = [];
1047
+ foreach ( $this->media_items as $key => $p ) {
1048
+ if ( $p->internal ) {
1049
+ $m_internals[ $key ] =& $this->media_items[ $key ];
1050
+ }
1051
+ }
1052
+ // Cleanup.
1053
+ unset( $key, $p );
1054
+
1055
+ // Process internal links.
1056
+ if ( ! empty( $m_internals ) ) {
1057
+ $uris_base = [];
1058
+ $uri_prefix = wp_upload_dir();
1059
+ $uri_prefix = $this->util->normalize_path( $uri_prefix['baseurl'], true );
1060
+ foreach ( $m_internals as $key => $p ) {
1061
+ // Prepare internal links.
1062
+ // Create relative URIs for attachment data retrieval.
1063
+ if ( ! $p->id && strpos( $p->source, $uri_prefix ) === 0 ) {
1064
+ $uris_base[ str_replace( $uri_prefix, '', $p->source ) ] = $key;
1065
+ }
1066
+ }
1067
+ // Cleanup.
1068
+ unset( $key, $p );
1069
+
1070
+ // Retrieve attachment IDs.
1071
+ $uris_flat = "('" . implode( "','", array_keys( $uris_base ) ) . "')";
1072
+ $q = $wpdb->prepare( "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE `meta_key` = %s AND LOWER(`meta_value`) IN $uris_flat LIMIT %d", '_wp_attached_file', count( $uris_base ) );
1073
+ $pids = $wpdb->get_results( $q );
1074
+ // Match IDs to URIs.
1075
+ if ( $pids ) {
1076
+ foreach ( $pids as $pd ) {
1077
+ $file =& $pd->meta_value;
1078
+ if ( isset( $uris_base[ $file ] ) ) {
1079
+ $m_internals[ $uris_base[ $file ] ]->id = absint( $pd->post_id );
1080
+ }
1081
+ }
1082
+ }
1083
+ // Cleanup.
1084
+ unset( $uris_base, $uris_flat, $q, $pids, $pd, $file );
1085
+ }
1086
+
1087
+ // Process items with attachment IDs.
1088
+ $pids = [];
1089
+ foreach ( $this->media_items as $key => $p ) {
1090
+ // Add post ID to query.
1091
+ if ( ! ! $p->id ) {
1092
+ // Create array for ID (support multiple URIs per ID).
1093
+ if ( ! isset( $pids[ $p->id ] ) ) {
1094
+ $pids[ $p->id ] = [];
1095
+ }
1096
+ // Add URI to ID.
1097
+ $pids[ $p->id ][] = $key;
1098
+ }
1099
+ }
1100
+ // Cleanup.
1101
+ unset( $key, $p );
1102
+
1103
+ // Retrieve attachment properties.
1104
+ if ( ! empty( $pids ) ) {
1105
+ $pids_flat = array_keys( $pids );
1106
+ // Retrieve attachment post data.
1107
+ $atts = get_posts(
1108
+ array(
1109
+ 'post_type' => 'attachment',
1110
+ 'include' => $pids_flat,
1111
+ )
1112
+ );
1113
+
1114
+ // Process attachments.
1115
+ if ( $atts ) {
1116
+ $props_post_map = [
1117
+ 'title' => 'post_title',
1118
+ 'caption' => 'post_excerpt',
1119
+ 'description' => 'post_content',
1120
+ ];
1121
+
1122
+ foreach ( $atts as $att ) {
1123
+ $data = [];
1124
+ // Remap post data to metadata.
1125
+ foreach ( $props_post_map as $props_post_key => $props_post_source ) {
1126
+ $data[ $props_post_key ] = $att->{$props_post_source};
1127
+ }
1128
+ // Cleanup.
1129
+ unset( $props_post_key, $props_post_source );
1130
+
1131
+ // Save data to corresponding media item(s).
1132
+ if ( isset( $pids[ $att->ID ] ) ) {
1133
+ foreach ( $pids[ $att->ID ] as $key ) {
1134
+ $this->media_items[ $key ] = (object) array_merge( (array) $this->media_items[ $key ], $data );
1135
+ }
1136
+ }
1137
+ }
1138
+ // Cleanup.
1139
+ unset( $att, $data );
1140
+ }
1141
+ // Cleanup.
1142
+ unset( $atts, $atts_meta, $m, $a, $uri, $pids, $pids_flat );
1143
+ }
1144
+
1145
+ // Filter media items.
1146
+ $this->media_items = $this->util->apply_filters( 'media_items', $this->media_items );
1147
+
1148
+ // Build client output.
1149
+ $obj = 'View.assets';
1150
+ $client_script[] = $this->util->extend_client_object( $obj, $this->media_items );
1151
+ return $client_script;
1152
+ }
1153
+
1154
+ /*-** Media **-*/
1155
+
1156
+ /**
1157
+ * Cache media properties for later processing
1158
+ * @uses array self::$media_items_raw Stores media items for output
1159
+ * @param object $uri URI to cache
1160
+ * Members
1161
+ * > raw: Raw Link URI
1162
+ * > source: Source URI (e.g. for attachment URIs)
1163
+ * @param string $type Media type (image, attachment, etc.)
1164
+ * @param bool $internal TRUE if media is internal (e.g. attachment)
1165
+ * @param array $props (optional) Properties to store for item (Default: NULL)
1166
+ * @return string Unique ID for cached media item
1167
+ */
1168
+ private function cache_media_item( $uri, $type, $internal, $props = null ) {
1169
+ // Validate.
1170
+ if ( ! is_object( $uri ) || ! is_string( $type ) ) {
1171
+ return false;
1172
+ }
1173
+ // Check if URI already cached.
1174
+ $key = $this->get_media_item_id( $uri->source );
1175
+ // Cache new item.
1176
+ if ( null === $key ) {
1177
+ // Generate Unique ID.
1178
+ do {
1179
+ $key = (string) wp_rand();
1180
+ } while ( isset( $this->media_items_raw['props'][ $key ] ) );
1181
+ // Build properties object.
1182
+ $i = $this->media_item_template;
1183
+ if ( is_array( $props ) && ! empty( $props ) ) {
1184
+ $i = array_merge( $i, $props );
1185
+ }
1186
+ $i = array_merge(
1187
+ $i,
1188
+ [
1189
+ 'type' => $type,
1190
+ 'source' => $uri->source,
1191
+ 'internal' => $internal,
1192
+ ]
1193
+ );
1194
+ // Cache item properties.
1195
+ $this->media_items_raw['props'][ $key ] = (object) $i;
1196
+ // Cache Source URI (point to properties object).
1197
+ $this->media_items_raw['uri'][ $uri->source ] = $key;
1198
+ }
1199
+ return $key;
1200
+ }
1201
+
1202
+ /**
1203
+ * Retrieve ID for media item
1204
+ * @uses self::$media_items_raw
1205
+ * @param string $uri Media item URI
1206
+ * @return string|null Media item ID (Default: NULL if URI doesn't exist in collection)
1207
+ */
1208
+ private function get_media_item_id( $uri ) {
1209
+ if ( $this->media_item_cached( $uri ) ) {
1210
+ return $this->media_items_raw['uri'][ $uri ];
1211
+ }
1212
+ return null;
1213
+ }
1214
+
1215
+ /**
1216
+ * Checks if media item has already been cached
1217
+ * @param string $uri URI of media item
1218
+ * @return boolean Whether media item has been cached
1219
+ */
1220
+ private function media_item_cached( $uri ) {
1221
+ return ( is_string( $uri ) && ! empty( $uri ) && isset( $this->media_items_raw['uri'][ $uri ] ) ) ? true : false;
1222
+ }
1223
+
1224
+ /**
1225
+ * Retrieve cached media item
1226
+ * @param string $uri Media item URI
1227
+ * @return object|null Media item properties (NULL if not set)
1228
+ */
1229
+ private function get_cached_media_item( $uri ) {
1230
+ $key = $this->get_media_item_id( $uri );
1231
+ if ( null !== $key ) {
1232
+ return $this->media_items_raw['props'][ $key ];
1233
+ }
1234
+ return null;
1235
+ }
1236
+
1237
+ /**
1238
+ * Retrieve cached media items (properties)
1239
+ * @uses self::$media_items_raw
1240
+ * @return array Cached media items (objects)
1241
+ */
1242
+ private function &get_cached_media_items() {
1243
+ return $this->media_items_raw['props'];
1244
+ }
1245
+
1246
+ /**
1247
+ * Check if media items have been cached
1248
+ * @return boolean
1249
+ */
1250
+ private function has_cached_media_items() {
1251
+ return ( empty( $this->media_items_raw['props'] ) ) ? false : true;
1252
+ }
1253
+
1254
+ /*-** Exclusion **-*/
1255
+
1256
+ /**
1257
+ * Retrieve exclude object
1258
+ * Initialize object properties if necessary
1259
+ * @return object Exclude properties
1260
+ */
1261
+ private function get_exclude() {
1262
+ // Initialize exclude data
1263
+ if ( ! is_object( $this->exclude ) ) {
1264
+ $this->exclude = (object) array(
1265
+ 'tags' => $this->get_exclude_tags(),
1266
+ 'ph' => $this->get_exclude_placeholder(),
1267
+ 'group_default' => 'default',
1268
+ 'cache' => array(),
1269
+ );
1270
+ }
1271
+ return $this->exclude;
1272
+ }
1273
+
1274
+ /**
1275
+ * Get exclusion tags (open/close)
1276
+ * Example: open => [slb_exclude], close => [/slb_exclude]
1277
+ *
1278
+ * @return object Exclusion tags
1279
+ */
1280
+ private function get_exclude_tags() {
1281
+ static $tags = null;
1282
+ if ( null === $tags ) {
1283
+ /* Init tag elements */
1284
+ $tags = (object) [
1285
+ // Tag base.
1286
+ 'base' => $this->add_prefix( 'exclude' ),
1287
+ ];
1288
+ // Opening tag.
1289
+ $tags->open = $this->util->add_wrapper( $tags->base );
1290
+ // Closing tag.
1291
+ $tags->close = $this->util->add_wrapper( $tags->base, '[/', ']' );
1292
+
1293
+ /* Build tag search pattern */
1294
+
1295
+ // Pattern delimeter.
1296
+ $dlm = '#';
1297
+ // Pattern flags.
1298
+ $flags = 's';
1299
+ // Tag search pattern.
1300
+ $tags->search = $dlm . preg_quote( $tags->open, $dlm ) . '(.*?)' . preg_quote( $tags->close, $dlm ) . $dlm . $flags;
1301
+ }
1302
+ return $tags;
1303
+ }
1304
+
1305
+
1306
+ /**
1307
+ * Get exclusion tag ("[slb_exclude]")
1308
+ * @uses `get_exclude_tags()` to retrieve tag
1309
+ *
1310
+ * @param string $type (optional) Tag to retrieve (open or close)
1311
+ * @return string Exclusion tag
1312
+ */
1313
+ private function get_exclude_tag( $type = 'open' ) {
1314
+ // Validate
1315
+ $tags = $this->get_exclude_tags();
1316
+ if ( ! isset( $tags->{$type} ) ) {
1317
+ $type = 'open';
1318
+ }
1319
+ return $tags->{$type};
1320
+ }
1321
+
1322
+ /**
1323
+ * Build exclude placeholder
1324
+ * @return object Exclude placeholder properties
1325
+ */
1326
+ private function get_exclude_placeholder() {
1327
+ static $ph;
1328
+ if ( ! is_object( $ph ) ) {
1329
+ $ph = (object) array(
1330
+ 'base' => $this->add_prefix( 'exclude_temp' ),
1331
+ 'open' => '{{',
1332
+ 'close' => '}}',
1333
+ 'attrs' => array(
1334
+ 'group' => '',
1335
+ 'key' => '',
1336
+ ),
1337
+ );
1338
+ // Search Patterns
1339
+ $sub = '(.+?)';
1340
+ $dlm = '#';
1341
+ $flags = 's';
1342
+ $ph->search = $dlm . preg_quote( $ph->open, $dlm ) . $ph->base . '\s+' . $sub . preg_quote( $ph->close, $dlm ) . $dlm . $flags;
1343
+ $ph->search_group = str_replace( $sub, '(group="%s"\s+.?)', $ph->search );
1344
+ // Templates
1345
+ $attr_string = '';
1346
+ foreach ( $ph->attrs as $attr => $val ) {
1347
+ $attr_string .= ' ' . $attr . '="%s"';
1348
+ }
1349
+ $ph->template = $ph->open . $ph->base . $attr_string . $ph->close;
1350
+ }
1351
+ return $ph;
1352
+ }
1353
+
1354
+ /**
1355
+ * Wrap content in exclusion tags
1356
+ * @uses `get_exclude_tag()` to wrap content with exclusion tag
1357
+ * @param string $content Content to exclude
1358
+ * @return string Content wrapped in exclusion tags
1359
+ */
1360
+ private function exclude_wrap( $content ) {
1361
+ // Validate
1362
+ if ( ! is_string( $content ) ) {
1363
+ $content = '';
1364
+ }
1365
+ // Wrap
1366
+ $tags = $this->get_exclude_tags();
1367
+ return $tags->open . $content . $tags->close;
1368
+ }
1369
+
1370
+ /**
1371
+ * Remove excluded content
1372
+ * Caches content for restoring later
1373
+ * @param string $content Content to remove excluded content from
1374
+ * @return string Updated content
1375
+ */
1376
+ public function exclude_content( $content, $group = null ) {
1377
+ $ex = $this->get_exclude();
1378
+ // Setup cache
1379
+ if ( ! is_string( $group ) || empty( $group ) ) {
1380
+ $group = $ex->group_default;
1381
+ }
1382
+ if ( ! isset( $ex->cache[ $group ] ) ) {
1383
+ $ex->cache[ $group ] = array();
1384
+ }
1385
+ $cache =& $ex->cache[ $group ];
1386
+
1387
+ $content = $this->util->apply_filters( 'pre_exclude_content', $content );
1388
+
1389
+ // Search content
1390
+ $matches = null;
1391
+ if ( false !== strpos( $content, $ex->tags->open ) && preg_match_all( $ex->tags->search, $content, $matches ) ) {
1392
+ // Determine index
1393
+ $idx = ( ! ! end( $cache ) ) ? key( $cache ) : -1;
1394
+ $ph = array();
1395
+ foreach ( $matches[1] as $midx => $match ) {
1396
+ // Update index
1397
+ $idx++;
1398
+ // Cache content
1399
+ $cache[ $idx ] = $match;
1400
+ // Build placeholder
1401
+ $ph[] = sprintf( $ex->ph->template, $group, $idx );
1402
+ }
1403
+ unset( $midx, $match );
1404
+ // Replace content with placeholder
1405
+ $content = str_replace( $matches[0], $ph, $content );
1406
+
1407
+ // Cleanup
1408
+ unset( $matches, $ph );
1409
+ }
1410
+
1411
+ return $content;
1412
+ }
1413
+
1414
+ /**
1415
+ * Exclude shortcodes from link activation
1416
+ * @param string $content Content to exclude shortcodes from
1417
+ * @return string Content with shortcodes excluded
1418
+ */
1419
+ public function exclude_shortcodes( $content ) {
1420
+ // Get shortcodes to exclude
1421
+ $shortcodes = $this->util->apply_filters( 'exclude_shortcodes', array( $this->add_prefix( 'group' ) ) );
1422
+ // Set callback
1423
+ $shortcodes = array_fill_keys( $shortcodes, $this->m( 'exclude_shortcodes_handler' ) );
1424
+ return $this->util->do_shortcode( $content, $shortcodes );
1425
+ }
1426
+
1427
+ /**
1428
+ * Wrap shortcode in exclude tags
1429
+ * @uses Util->make_shortcode() to rebuild original shortcode
1430
+ *
1431
+ * @param array $attr Shortcode attributes
1432
+ * @param string $content Content enclosed in shortcode
1433
+ * @param string $tag Shortcode name
1434
+ * @return string Excluded shortcode
1435
+ */
1436
+ public function exclude_shortcodes_handler( $attr, $content, $tag ) {
1437
+ $code = $this->util->make_shortcode( $tag, $attr, $content );
1438
+ // Exclude shortcode
1439
+ return $this->exclude_wrap( $code );
1440
+ }
1441
+
1442
+ /**
1443
+ * Restore excluded content
1444
+ * @param string $content Content to restore excluded content to
1445
+ * @return string Content with excluded content restored
1446
+ */
1447
+ public function restore_excluded_content( $content, $group = null ) {
1448
+ $ex = $this->get_exclude();
1449
+ // Setup cache
1450
+ if ( ! is_string( $group ) || empty( $group ) ) {
1451
+ $group = $ex->group_default;
1452
+ }
1453
+ // Nothing to restore if cache group doesn't exist
1454
+ if ( ! isset( $ex->cache[ $group ] ) ) {
1455
+ return $content;
1456
+ }
1457
+ $cache =& $ex->cache[ $group ];
1458
+
1459
+ // Search content for placeholders
1460
+ $matches = null;
1461
+ if ( false !== strpos( $content, $ex->ph->open . $ex->ph->base ) && preg_match_all( $ex->ph->search, $content, $matches ) ) {
1462
+ // Restore placeholders
1463
+ foreach ( $matches[1] as $idx => $ph ) {
1464
+ // Parse placeholder attributes
1465
+ $attrs = $this->util->parse_attribute_string( $ph, $ex->ph->attrs );
1466
+ // Validate
1467
+ if ( $attrs['group'] !== $group ) {
1468
+ continue;
1469
+ }
1470
+ // Restore content
1471
+ $attrs['key'] = intval( $attrs['key'] );
1472
+ $key = $attrs['key'];
1473
+
1474
+ if ( isset( $cache[ $key ] ) ) {
1475
+ $content = str_replace( $matches[0][ $idx ], $cache[ $key ], $content );
1476
+ }
1477
+ }
1478
+ // Cleanup
1479
+ unset( $idx, $ph, $matches, $key );
1480
+ }
1481
+
1482
+ return $content;
1483
+ }
1484
+
1485
+ /*-** Grouping **-*/
1486
+
1487
+ /**
1488
+ * Builds wrapper for grouping
1489
+ * @return string Format for wrapping content in group
1490
+ */
1491
+ function group_get_wrapper() {
1492
+ static $fmt = null;
1493
+ if ( is_null( $fmt ) ) {
1494
+ $fmt = $this->util->make_shortcode( $this->add_prefix( 'group' ), null, '%s' );
1495
+ }
1496
+ return $fmt;
1497
+ }
1498
+
1499
+ /**
1500
+ * Wraps shortcodes for automatic grouping
1501
+ * @uses `the_content` Filter hook
1502
+ * @uses group_shortcodes_handler to Wrap shortcodes for grouping
1503
+ * @param string $content Post content
1504
+ * @return string Modified post content
1505
+ */
1506
+ function group_shortcodes( $content ) {
1507
+ if ( ! $this->is_content_valid( $content ) ) {
1508
+ return $content;
1509
+ }
1510
+ // Setup shortcodes to wrap
1511
+ $shortcodes = $this->util->apply_filters( 'group_shortcodes', array( 'gallery', 'nggallery' ) );
1512
+ // Set custom callback
1513
+ $shortcodes = array_fill_keys( $shortcodes, $this->m( 'group_shortcodes_handler' ) );
1514
+ // Process gallery shortcodes
1515
+ return $this->util->do_shortcode( $content, $shortcodes );
1516
+ }
1517
+
1518
+ /**
1519
+ * Groups shortcodes for later processing
1520
+ * @param array $attr Shortcode attributes
1521
+ * @param string $content Content enclosed in shortcode
1522
+ * @param string $tag Shortcode name
1523
+ * @return string Grouped shortcode
1524
+ */
1525
+ function group_shortcodes_handler( $attr, $content, $tag ) {
1526
+ $code = $this->util->make_shortcode( $tag, $attr, $content );
1527
+ // Wrap shortcode
1528
+ return sprintf( $this->group_get_wrapper(), $code );
1529
+ }
1530
+
1531
+ /**
1532
+ * Activate groups in content
1533
+ * @param string $content Content to activate
1534
+ * @return string Updated content
1535
+ */
1536
+ public function activate_groups( $content ) {
1537
+ return $this->util->do_shortcode( $content, array( $this->add_prefix( 'group' ) => $this->m( 'activate_groups_handler' ) ) );
1538
+ }
1539
+
1540
+ /**
1541
+ * Groups shortcodes for later processing
1542
+ * @param array $attr Shortcode attributes
1543
+ * @param string $content Content enclosed in shortcode
1544
+ * @param string $tag Shortcode name
1545
+ * @return string Grouped shortcode
1546
+ */
1547
+ function activate_groups_handler( $attr, $content, $tag ) {
1548
+ // Get Group ID
1549
+ // Custom group
1550
+ if ( isset( $attr['id'] ) ) {
1551
+ $group = $attr['id'];
1552
+ trim( $group );
1553
+ }
1554
+ // Automatically-generated group
1555
+ if ( empty( $group ) ) {
1556
+ $group = 'auto_' . ( ++$this->groups['auto'] );
1557
+ }
1558
+ return $this->process_links( $content, $group );
1559
+ }
1560
+
1561
+ /*-** Widgets **-*/
1562
+
1563
+ /**
1564
+ * Set widget up for processing/activation
1565
+ * Buffers widget output for further processing
1566
+ * @param array $widget_args Widget arguments
1567
+ * @return void
1568
+ */
1569
+ public function widget_process_start( $widget_args ) {
1570
+ // Do not continue if a widget is currently being processed (avoid nested processing)
1571
+ if ( 0 < $this->widget_processing_level ) {
1572
+ return;
1573
+ }
1574
+ // Start widget processing
1575
+ $this->widget_processing = true;
1576
+ $this->widget_processing_params = $widget_args;
1577
+ // Enable widget grouping
1578
+ if ( $this->options->get_bool( 'group_widget' ) ) {
1579
+ $this->util->add_filter( 'get_group_id', $this->m( 'widget_group_id' ) );
1580
+ }
1581
+ // Begin output buffer
1582
+ ob_start();
1583
+ }
1584
+
1585
+ /**
1586
+ * Handles inter-widget processing
1587
+ * After widget output generated, Before next widget starts
1588
+ * @param array $params New widget parameters
1589
+ */
1590
+ public function widget_process_inter( $params ) {
1591
+ $this->widget_process_finish();
1592
+ return $params;
1593
+ }
1594
+
1595
+ /**
1596
+ * Complete widget processing
1597
+ * Activate widget output
1598
+ * @uses $widget_processing
1599
+ * @uses $widget_processing_level
1600
+ * @uses $widget_processing_params
1601
+ * @return void
1602
+ */
1603
+ public function widget_process_finish() {
1604
+ /**
1605
+ * Stop processing on conditions:
1606
+ * - No widget is being processed
1607
+ * - Processing a nested widget
1608
+ */
1609
+ if ( ! $this->widget_processing || 0 < $this->widget_processing_level ) {
1610
+ return;
1611
+ }
1612
+ // Activate widget output
1613
+ $out = $this->activate_links( ob_get_clean() );
1614
+
1615
+ // Clear grouping callback
1616
+ if ( $this->options->get_bool( 'group_widget' ) ) {
1617
+ $this->util->remove_filter( 'get_group_id', $this->m( 'widget_group_id' ) );
1618
+ }
1619
+ // End widget processing
1620
+ $this->widget_processing = false;
1621
+ $this->widget_processing_params = null;
1622
+ // Output widget
1623
+ echo $out;
1624
+ }
1625
+
1626
+ /**
1627
+ * Add widget ID to link group ID
1628
+ * Widget ID precedes all other group segments
1629
+ * @uses `SLB::get_group_id` filter
1630
+ * @param array $group_segments Group ID segments
1631
+ * @return array Modified group ID segments
1632
+ */
1633
+ public function widget_group_id( $group_segments ) {
1634
+ // Add current widget ID to group ID
1635
+ if ( isset( $this->widget_processing_params['id'] ) ) {
1636
+ array_unshift( $group_segments, $this->widget_processing_params['id'] );
1637
+ }
1638
+ return $group_segments;
1639
+ }
1640
+
1641
+ /**
1642
+ * Handles nested activation in widgets
1643
+ * @uses widget_processing
1644
+ * @uses $widget_processing_level
1645
+ * @return void
1646
+ */
1647
+ public function widget_process_nested() {
1648
+ // Stop if no widget is being processed
1649
+ if ( ! $this->widget_processing ) {
1650
+ return;
1651
+ }
1652
+
1653
+ // Increment nesting level
1654
+ $this->widget_processing_level++;
1655
+ }
1656
+
1657
+ /**
1658
+ * Mark the end of a nested widget
1659
+ * @uses $widget_processing_level
1660
+ */
1661
+ public function widget_process_nested_finish() {
1662
+ // Decrement nesting level
1663
+ if ( 0 < $this->widget_processing_level ) {
1664
+ $this->widget_processing_level--;
1665
+ }
1666
+ }
1667
+
1668
+ /**
1669
+ * Begin blocking widget activation
1670
+ * @return void
1671
+ */
1672
+ public function widget_block_start() {
1673
+ $this->util->add_filter( 'is_content_valid', $this->m( 'widget_block_handle' ) );
1674
+ }
1675
+
1676
+ /**
1677
+ * Stop blocking widget activation
1678
+ * @return void
1679
+ */
1680
+ public function widget_block_finish() {
1681
+ $this->util->remove_filter( 'is_content_valid', $this->m( 'widget_block_handle' ) );
1682
+ }
1683
+
1684
+ /**
1685
+ * Handle widget activation blocking
1686
+ */
1687
+ public function widget_block_handle( $is_content_valid ) {
1688
+ return false;
1689
+ }
1690
+
1691
+ /*-** Menus **-*/
1692
+
1693
+ /**
1694
+ * Process navigation menu links
1695
+ *
1696
+ * @see wp_nav_menu()/filter: wp_nav_menu
1697
+ *
1698
+ * @param string $nav_menu HTML content for navigation menu.
1699
+ * @param object $args Navigation menu's arguments.
1700
+ */
1701
+ public function menu_process( $nav_menu, $args ) {
1702
+ // Grouping
1703
+ if ( $this->options->get_bool( 'group_menu' ) ) {
1704
+ // Generate group ID for menu
1705
+ $group = 'menu';
1706
+ $sep = '_';
1707
+ if ( ! empty( $args->menu_id ) ) {
1708
+ $group .= $sep . $args->menu_id;
1709
+ } elseif ( ! empty( $args->menu ) ) {
1710
+ $group .= $sep . ( ( is_object( $args->menu ) ) ? $args->menu->slug : $args->menu );
1711
+ }
1712
+ $group = $this->group_id_unique( $group );
1713
+ } else {
1714
+ $group = null;
1715
+ }
1716
+
1717
+ // Process menu
1718
+ $nav_menu = $this->activate_links( $nav_menu, $group );
1719
+
1720
+ return $nav_menu;
1721
+ }
1722
+
1723
+ /**
1724
+ * Generate unique group ID
1725
+ *
1726
+ * @param string $group Group ID to check
1727
+ * @return string Unique group ID
1728
+ */
1729
+ public function group_id_unique( $group ) {
1730
+ static $groups = array();
1731
+ while ( in_array( $group, $groups, true ) ) {
1732
+ $patt = '#-(\d+)$#';
1733
+ if ( preg_match( $patt, $group, $matches ) ) {
1734
+ $group = preg_replace( $patt, '-' . ( ++$matches[1] ), $group );
1735
+ } else {
1736
+ $group = $group . '-1';
1737
+ }
1738
+ }
1739
+ // Add final group ID to array
1740
+ $groups[] = $group;
1741
+ return $group;
1742
+ }
1743
+
1744
+ /*-** Helpers **-*/
1745
+
1746
+ /**
1747
+ * Build attribute name
1748
+ * Makes sure name is only prefixed once
1749
+ * @param string $name (optional) Attribute base name
1750
+ * @return string Formatted attribute name
1751
+ */
1752
+ function make_attribute_name( $name = '' ) {
1753
+ // Validate
1754
+ if ( ! is_string( $name ) ) {
1755
+ $name = '';
1756
+ } else {
1757
+ $name = trim( $name );
1758
+ }
1759
+ // Setup
1760
+ $sep = '-';
1761
+ $top = 'data';
1762
+ // Generate valid name
1763
+ if ( strpos( $name, $top . $sep . $this->get_prefix() ) !== 0 ) {
1764
+ $name = $top . $sep . $this->add_prefix( $name, $sep );
1765
+ }
1766
+ return $name;
1767
+ }
1768
+
1769
+ /**
1770
+ * Sets attribute value.
1771
+ *
1772
+ * Attribute is added to array if it does not already exist.
1773
+ *
1774
+ * @param string|array $attrs Array to set attribute on (Passed by reference).
1775
+ * @param string $name Name of attribute to set.
1776
+ * @param scalar $value Optional. Attribute value.
1777
+ * @return array Updated attributes.
1778
+ */
1779
+ function set_attribute( &$attrs, $name, $value = true ) {
1780
+ // Validate
1781
+ $attrs = $this->get_attributes( $attrs, false );
1782
+ // Stop if attribute name or value is invalid.
1783
+ if ( ! is_string( $name ) || empty( trim( $name ) ) || ! is_scalar( $value ) ) {
1784
+ return $attrs;
1785
+ }
1786
+ // Set attribute value.
1787
+ $attrs = array_merge( $attrs, array( $this->make_attribute_name( $name ) => $value ) );
1788
+
1789
+ return $attrs;
1790
+ }
1791
+
1792
+ /**
1793
+ * Converts attribute string into array.
1794
+ *
1795
+ * @param string|array $attrs Attribute string to convert. Associative array also accepted.
1796
+ * @param bool $internal Optional. Return only internal attributes. Default True.
1797
+ * @return array Associative array of attributes (`attribute-name => attribute-value`).
1798
+ */
1799
+ function get_attributes( $attrs, $internal = true ) {
1800
+ // Parse attribute string.
1801
+ $attrs = $this->util->parse_attribute_string( $attrs );
1802
+ // Include only internal attributes (if necessary).
1803
+ if ( ! ! $internal && ! empty( $attrs ) ) {
1804
+ $prefix = $this->make_attribute_name();
1805
+ $attrs = array_filter(
1806
+ $attrs,
1807
+ function( $key ) use ( $prefix ) {
1808
+ return ( strpos( $key, $prefix ) === 0 );
1809
+ },
1810
+ ARRAY_FILTER_USE_KEY
1811
+ );
1812
+ }
1813
+
1814
+ return $attrs;
1815
+ }
1816
+
1817
+ /**
1818
+ * Retrieves an attributes value from attribute string or array.
1819
+ *
1820
+ * @param string|array $attrs Attribute string to retrieve attribute value from.
1821
+ * Associative array also accepted.
1822
+ * @param string $attr Attribute to retrieve value for.
1823
+ * @param bool $internal Optional. Retrieve internal attribute. Default true.
1824
+ * @return scalar|null Attribute value. Null if attribute is invalid or does not exist.
1825
+ */
1826
+ function get_attribute( $attrs, $attr, $internal = true ) {
1827
+ // Validate.
1828
+ $invalid = null;
1829
+ if ( ! is_string( $attr ) || empty( trim( $attr ) ) ) {
1830
+ return $invalid;
1831
+ }
1832
+ $attrs = $this->get_attributes( $attrs, $internal );
1833
+ if ( empty( $attrs ) ) {
1834
+ return $invalid;
1835
+ }
1836
+ // Format attribute name.
1837
+ $attr = ( ! ! $internal ) ? $this->make_attribute_name( $attr ) : trim( $attr );
1838
+ // Stop if attribute does not exist or value is invalid.
1839
+ if ( ! isset( $attrs[ $attr ] ) || ! is_scalar( $attrs[ $attr ] ) ) {
1840
+ return $invalid;
1841
+ }
1842
+ // Retreive value.
1843
+ $ret = $attrs[ $attr ];
1844
+ // Validate value (type-specific).
1845
+ if ( is_string( $ret ) ) {
1846
+ $ret = trim( $ret );
1847
+ if ( '' === $ret ) {
1848
+ return $invalid;
1849
+ }
1850
+ }
1851
+
1852
+ return $ret;
1853
+ }
1854
+
1855
+ /**
1856
+ * Checks if attribute exists.
1857
+ *
1858
+ * @param string|array $attrs Attribute string to retrieve attribute value from.
1859
+ * Associative array also accepted.
1860
+ * @param string $attr Attribute to retrieve value for.
1861
+ * @param bool $internal Optional. Retrieve internal attribute. Default true.
1862
+ * @return bool True if attribute exists. False if attribute does not exist.
1863
+ */
1864
+ function has_attribute( $attrs, $attr, $internal = true ) {
1865
+ return ( null !== $this->get_attribute( $attrs, $attr, $internal ) );
1866
+ }
1867
+
1868
+ /**
1869
+ * Build JS object of UI strings when initializing lightbox
1870
+ * @return array UI strings
1871
+ */
1872
+ private function build_labels() {
1873
+ $ret = array();
1874
+ /* Get all UI options */
1875
+ $prefix = 'txt_';
1876
+ $opt_strings = array_filter(
1877
+ array_keys( $this->options->get_items() ),
1878
+ function ( $opt ) use ( $prefix ) {
1879
+ return ( strpos( $opt, $prefix ) === 0 );
1880
+ }
1881
+ );
1882
+ if ( count( $opt_strings ) ) {
1883
+ /* Build array of UI options */
1884
+ foreach ( $opt_strings as $key ) {
1885
+ $name = substr( $key, strlen( $prefix ) );
1886
+ $ret[ $name ] = $this->options->get_value( $key );
1887
+ }
1888
+ }
1889
+ return $ret;
1890
+ }
1891
+ }
functions.php CHANGED
@@ -1,24 +1,24 @@
1
- <?php
2
- /**
3
- * Functions
4
- * Provides global access to specific functionality
5
- * @package Simple Lightbox
6
- * @author Archetyped
7
- */
8
-
9
- /* Template Tags */
10
-
11
- /**
12
- * Activate links in user-defined content
13
- * @param string $content
14
- * @return string Updated content with activated links
15
- */
16
- function slb_activate($content, $group = null) {
17
- // Validate
18
- if ( empty($content) ) {
19
- return $content;
20
- }
21
- // Activate links
22
- $content = $GLOBALS['slb']->activate_links($content, $group);
23
- return $content;
24
- }
1
+ <?php
2
+ /**
3
+ * Functions
4
+ * Provides global access to specific functionality
5
+ * @package Simple Lightbox
6
+ * @author Archetyped
7
+ */
8
+
9
+ /* Template Tags */
10
+
11
+ /**
12
+ * Activate links in user-defined content
13
+ * @param string $content
14
+ * @return string Updated content with activated links
15
+ */
16
+ function slb_activate( $content, $group = null ) {
17
+ // Validate
18
+ if ( empty( $content ) ) {
19
+ return $content;
20
+ }
21
+ // Activate links
22
+ $content = $GLOBALS['slb']->activate_links( $content, $group );
23
+ return $content;
24
+ }
grunt/jshint.js CHANGED
@@ -1,38 +1,38 @@
1
- module.exports = function(grunt) {
2
-
3
- grunt.config('jshint', {
4
- options : {
5
- reporter: require('jshint-stylish'),
6
- curly : true,
7
- eqeqeq : true,
8
- immed : true,
9
- latedef : true,
10
- newcap : false,
11
- noarg : true,
12
- sub : true,
13
- undef : true,
14
- unused : true,
15
- boss : true,
16
- eqnull : true,
17
- browser : true,
18
- jquery : true,
19
- globals : {}
20
- },
21
- grunt : {
22
- options : {
23
- node : true
24
- },
25
- src : ['Gruntfile.js', 'grunt/*.js']
26
- },
27
- all : {
28
- options : {
29
- globals : {
30
- 'SLB' : true,
31
- 'console' : true
32
- }
33
- },
34
- src : ['<%= paths.js.files %>']
35
- },
36
- });
37
-
38
  };
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('jshint', {
4
+ options : {
5
+ reporter: require('jshint-stylish'),
6
+ curly : true,
7
+ eqeqeq : true,
8
+ immed : true,
9
+ latedef : true,
10
+ newcap : false,
11
+ noarg : true,
12
+ sub : true,
13
+ undef : true,
14
+ unused : true,
15
+ boss : true,
16
+ eqnull : true,
17
+ browser : true,
18
+ jquery : true,
19
+ globals : {}
20
+ },
21
+ grunt : {
22
+ options : {
23
+ node : true
24
+ },
25
+ src : ['Gruntfile.js', 'grunt/*.js']
26
+ },
27
+ all : {
28
+ options : {
29
+ globals : {
30
+ 'SLB' : true,
31
+ 'console' : true
32
+ }
33
+ },
34
+ src : ['<%= paths.js.files %>']
35
+ },
36
+ });
37
+
38
  };
grunt/phplint.js CHANGED
@@ -1,14 +1,14 @@
1
- module.exports = function(grunt) {
2
-
3
- grunt.config('phplint', {
4
- options : {
5
- phpArgs : {
6
- '-f': null
7
- }
8
- },
9
- all : {
10
- src : '<%= paths.php.files %>'
11
- }
12
- });
13
-
14
  };
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('phplint', {
4
+ options : {
5
+ phpArgs : {
6
+ '-f': null
7
+ }
8
+ },
9
+ all : {
10
+ src : '<%= paths.php.files %>'
11
+ }
12
+ });
13
+
14
  };
grunt/sass.js CHANGED
@@ -1,37 +1,37 @@
1
- module.exports = function(grunt) {
2
-
3
- const sass = require('node-sass');
4
-
5
- grunt.config('sass', {
6
- options : {
7
- implementation: sass,
8
- outputStyle : 'compressed',
9
- },
10
- core : {
11
- files : [{
12
- expand : true,
13
- cwd : '<%= paths.sass.base_src %>/',
14
- dest : '<%= paths.sass.base_dest %>/',
15
- src : ['<%= paths.sass.target %>', '<%= paths.sass.exclude %>'],
16
- ext : '<%= paths.sass.ext %>'
17
- }]
18
- },
19
- themes : {
20
- options : {
21
- },
22
- files : [{
23
- expand : true,
24
- cwd : 'themes/',
25
- src : ['*/**/*.scss', '<%= paths.sass.exclude %>'],
26
- dest : '<%= paths.sass.dest %>/',
27
- srcd : '<%= paths.sass.src %>/',
28
- ext : '<%= paths.sass.ext %>',
29
- rename : function(dest, matchedSrcPath, options) {
30
- var path = [options.cwd, matchedSrcPath.replace(options.srcd, dest)].join('');
31
- return path;
32
- }
33
- }]
34
- }
35
- });
36
-
37
  };
1
+ module.exports = function(grunt) {
2
+
3
+ const sass = require('node-sass');
4
+
5
+ grunt.config('sass', {
6
+ options : {
7
+ implementation: sass,
8
+ outputStyle : 'compressed',
9
+ },
10
+ core : {
11
+ files : [{
12
+ expand : true,
13
+ cwd : '<%= paths.sass.base_src %>/',
14
+ dest : '<%= paths.sass.base_dest %>/',
15
+ src : ['<%= paths.sass.target %>', '<%= paths.sass.exclude %>'],
16
+ ext : '<%= paths.sass.ext %>'
17
+ }]
18
+ },
19
+ themes : {
20
+ options : {
21
+ },
22
+ files : [{
23
+ expand : true,
24
+ cwd : 'themes/',
25
+ src : ['*/**/*.scss', '<%= paths.sass.exclude %>'],
26
+ dest : '<%= paths.sass.dest %>/',
27
+ srcd : '<%= paths.sass.src %>/',
28
+ ext : '<%= paths.sass.ext %>',
29
+ rename : function(dest, matchedSrcPath, options) {
30
+ var path = [options.cwd, matchedSrcPath.replace(options.srcd, dest)].join('');
31
+ return path;
32
+ }
33
+ }]
34
+ }
35
+ });
36
+
37
  };
grunt/uglify.js CHANGED
@@ -1,21 +1,21 @@
1
- module.exports = function(grunt) {
2
-
3
- grunt.config('uglify', {
4
- options : {
5
- mangle: false,
6
- report: 'min'
7
- },
8
- all : {
9
- files : [{
10
- expand : true,
11
- cwd : '',
12
- dest : '',
13
- src : ['<%= paths.js.files %>'],
14
- rename : function(dest, srcPath) {
15
- return srcPath.replace('/' + grunt.config.get('paths.js.src') + '/', '/' + grunt.config.get('paths.js.dest') + '/');
16
- }
17
- }]
18
- },
19
- });
20
-
21
  };
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('uglify', {
4
+ options : {
5
+ mangle: false,
6
+ report: 'min'
7
+ },
8
+ all : {
9
+ files : [{
10
+ expand : true,
11
+ cwd : '',
12
+ dest : '',
13
+ src : ['<%= paths.js.files %>'],
14
+ rename : function(dest, srcPath) {
15
+ return srcPath.replace('/' + grunt.config.get('paths.js.src') + '/', '/' + grunt.config.get('paths.js.dest') + '/');
16
+ }
17
+ }]
18
+ },
19
+ });
20
+
21
  };
grunt/watch.js CHANGED
@@ -1,57 +1,57 @@
1
- module.exports = function(grunt) {
2
-
3
- grunt.config('watch', {
4
- phplint : {
5
- files : '<%= paths.php.files_std %>',
6
- tasks : ['phplint'],
7
- options : {
8
- spawn : false
9
- }
10
- },
11
- sass_core : {
12
- files : ['<%= paths.sass.base_src %>/**/*.scss'],
13
- tasks : ['sass:core']
14
- },
15
- sass_themes : {
16
- files : ['themes/**/<%= paths.sass.src %>/**/*.scss'],
17
- tasks : ['sass:themes']
18
- },
19
- jshint : {
20
- files : '<%= paths.js.files_std %>',
21
- tasks : ['jshint:all'],
22
- options : {
23
- spawn : false
24
- }
25
- },
26
- js : {
27
- files : '<%= paths.js.files_std %>',
28
- tasks : ['jshint:all', 'uglify:all'],
29
- options : {
30
- spawn : false
31
- }
32
- }
33
- });
34
-
35
- grunt.event.on('watch', function(action, filepath) {
36
- // Determine task based on filepath
37
- var get_ext = function(path) {
38
- var ret = '';
39
- var i = path.lastIndexOf('.');
40
- if ( -1 !== i && i <= path.length ) {
41
- ret = path.substr(i + 1);
42
- }
43
- return ret;
44
- };
45
- switch ( get_ext(filepath) ) {
46
- // PHP
47
- case 'php' :
48
- grunt.config('paths.php.files', [filepath]);
49
- break;
50
- // JavaScript
51
- case 'js' :
52
- grunt.config('paths.js.files', [filepath]);
53
- break;
54
- }
55
- });
56
-
57
  };
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('watch', {
4
+ phplint : {
5
+ files : '<%= paths.php.files_std %>',
6
+ tasks : ['phplint'],
7
+ options : {
8
+ spawn : false
9
+ }
10
+ },
11
+ sass_core : {
12
+ files : ['<%= paths.sass.base_src %>/**/*.scss'],
13
+ tasks : ['sass:core']
14
+ },
15
+ sass_themes : {
16
+ files : ['themes/**/<%= paths.sass.src %>/**/*.scss'],
17
+ tasks : ['sass:themes']
18
+ },
19
+ jshint : {
20
+ files : '<%= paths.js.files_std %>',
21
+ tasks : ['jshint:all'],
22
+ options : {
23
+ spawn : false
24
+ }
25
+ },
26
+ js : {
27
+ files : '<%= paths.js.files_std %>',
28
+ tasks : ['jshint:all', 'uglify:all'],
29
+ options : {
30
+ spawn : false
31
+ }
32
+ }
33
+ });
34
+
35
+ grunt.event.on('watch', function(action, filepath) {
36
+ // Determine task based on filepath
37
+ var get_ext = function(path) {
38
+ var ret = '';
39
+ var i = path.lastIndexOf('.');
40
+ if ( -1 !== i && i <= path.length ) {
41
+ ret = path.substr(i + 1);
42
+ }
43
+ return ret;
44
+ };
45
+ switch ( get_ext(filepath) ) {
46
+ // PHP
47
+ case 'php' :
48
+ grunt.config('paths.php.files', [filepath]);
49
+ break;
50
+ // JavaScript
51
+ case 'js' :
52
+ grunt.config('paths.js.files', [filepath]);
53
+ break;
54
+ }
55
+ });
56
+
57
  };
includes/class-requirements-check.php CHANGED
@@ -1,168 +1,168 @@
1
- <?php
2
- /**
3
- * Requirements Validation
4
- *
5
- * Used to ensure environment meets plugin requirements.
6
- *
7
- * @package Simple Lightbox
8
- * @since 2.7.0
9
- */
10
-
11
- /**
12
- * Plugin Requirements Validation class
13
- *
14
- * @since 2.7.0
15
- */
16
- class SLB_Requirements_Check {
17
- /**
18
- * Plugin name
19
- *
20
- * @var string
21
- */
22
- private $name = '';
23
-
24
- /**
25
- * Plugin file
26
- *
27
- * @var string
28
- */
29
- private $file = '';
30
-
31
- /**
32
- * Plugin dependencies
33
- *
34
- * @var array
35
- */
36
- private $deps = array(
37
- 'php' => '5.6.20',
38
- );
39
-
40
- /**
41
- * Dependency failures log
42
- *
43
- * @var array
44
- */
45
- private $fail = array();
46
-
47
- /**
48
- * URIs for notices, etc.
49
- *
50
- * @var array
51
- */
52
- private $uri = array();
53
-
54
- /**
55
- * Constructor
56
- *
57
- * @param array $args Requirements data.
58
- * @return void
59
- */
60
- public function __construct( $args ) {
61
- $args = (array) $args;
62
- // Set properties.
63
- foreach ( array_keys( get_class_vars( get_class( $this ) ) ) as $prop ) {
64
- if ( ! isset( $args[ $prop ] ) ) {
65
- continue;
66
- }
67
- // Merge array properties.
68
- if ( is_array( $this->$prop ) && is_array( $args[ $prop ] ) ) {
69
- $this->$prop = array_merge( $this->$prop, $args[ $prop ] );
70
- continue;
71
- }
72
-
73
- // Set string properties.
74
- if ( is_string( $this->$prop ) && is_scalar( $args[ $prop ] ) ) {
75
- $this->$prop = (string) $args[ $prop ];
76
- continue;
77
- }
78
- }
79
- }
80
-
81
- /**
82
- * Check if plugin passes all requirements
83
- *
84
- * @return bool Requirements check result.
85
- */
86
- public function passes() {
87
- $result = true;
88
- foreach ( $this->deps as $dep => $req ) {
89
- $m = $dep . '_passes';
90
- if ( ! method_exists( $this, $m ) ) {
91
- continue;
92
- }
93
- $passes = $this->$m();
94
- if ( ! $passes ) {
95
- // Requirements do not pass.
96
- $result = $passes;
97
- // Log dependency failures.
98
- $this->fail[] = $dep;
99
- }
100
- }
101
- // Handle requirements failure.
102
- if ( ! $result ) {
103
- add_action( 'load-plugins.php', array( $this, 'handle_failure' ) );
104
- }
105
- return $result;
106
- }
107
-
108
- /**
109
- * Handle requirements failure
110
- *
111
- * @return void
112
- */
113
- public function handle_failure() {
114
- // Handle each failed dependency.
115
- foreach ( $this->fail as $dep ) {
116
- $m = $dep . '_handle_failure';
117
- if ( method_exists( $this, $m ) ) {
118
- $this->$m();
119
- }
120
- }
121
- // Deactivate plugin.
122
- deactivate_plugins( plugin_basename( $this->file ) );
123
- }
124
-
125
- /**
126
- * Validates PHP version.
127
- *
128
- * @return bool PHP requirement passes.
129
- */
130
- private function php_passes() {
131
- return version_compare( PHP_VERSION, $this->deps['php'], '>=' );
132
- }
133
-
134
- /**
135
- * Handle PHP requirement failure
136
- *
137
- * @return void
138
- */
139
- private function php_handle_failure() {
140
- // Clear activation query variable from request (stop UI notices).
141
- unset( $_GET['activate'] );
142
- // Display notice to user.
143
- add_action( 'admin_notices', array( $this, 'php_notice' ) );
144
- }
145
-
146
- /**
147
- * Display requirements failure notice and deactivate plugin.
148
- *
149
- * @return void
150
- */
151
- public function php_notice() {
152
- global $slb_requirements;
153
- // Display message to user.
154
- $link = (object) array(
155
- /* translators: 1: Plugin name */
156
- 'title' => sprintf( __( 'Learn more about %1$s\'s requirements', 'simple-lightbox' ), $this->name ),
157
- /* translators: Plugin requirements link text. */
158
- 'text' => __( 'Learn More', 'simple-lightbox' ),
159
- );
160
- // Full link.
161
- $link = sprintf( '<a target="_blank" href="%1$s" title="%2$s">%3$s</a>', $this->uri['reference'], esc_attr( $link->title ), esc_html( $link->text ) );
162
- /* translators: 1: Plugin name. 2: PHP version requirement. 3: Plugin requirements link. */
163
- $err_msg = sprintf( __( '%1$s requires PHP %2$s or higher. Please have your hosting provider update PHP to enable Simple Lightbox. (%3$s)', 'simple-lightbox' ), $this->name, $this->deps['php'], $link );
164
- ?>
165
- <div class="error"><p><?php echo $err_msg; ?></p></div>
166
- <?php
167
- }
168
- }
1
+ <?php
2
+ /**
3
+ * Requirements Validation
4
+ *
5
+ * Used to ensure environment meets plugin requirements.
6
+ *
7
+ * @package Simple Lightbox
8
+ * @since 2.7.0
9
+ */
10
+
11
+ /**
12
+ * Plugin Requirements Validation class
13
+ *
14
+ * @since 2.7.0
15
+ */
16
+ class SLB_Requirements_Check {
17
+ /**
18
+ * Plugin name
19
+ *
20
+ * @var string
21
+ */
22
+ private $name = '';
23
+
24
+ /**
25
+ * Plugin file
26
+ *
27
+ * @var string
28
+ */
29
+ private $file = '';
30
+
31
+ /**
32
+ * Plugin dependencies
33
+ *
34
+ * @var array
35
+ */
36
+ private $deps = array(
37
+ 'php' => '5.6.20',
38
+ );
39
+
40
+ /**
41
+ * Dependency failures log
42
+ *
43
+ * @var array
44
+ */
45
+ private $fail = array();
46
+
47
+ /**
48
+ * URIs for notices, etc.
49
+ *
50
+ * @var array
51
+ */
52
+ private $uri = array();
53
+
54
+ /**
55
+ * Constructor
56
+ *
57
+ * @param array $args Requirements data.
58
+ * @return void
59
+ */
60
+ public function __construct( $args ) {
61
+ $args = (array) $args;
62
+ // Set properties.
63
+ foreach ( array_keys( get_class_vars( get_class( $this ) ) ) as $prop ) {
64
+ if ( ! isset( $args[ $prop ] ) ) {
65
+ continue;
66
+ }
67
+ // Merge array properties.
68
+ if ( is_array( $this->$prop ) && is_array( $args[ $prop ] ) ) {
69
+ $this->$prop = array_merge( $this->$prop, $args[ $prop ] );
70
+ continue;
71
+ }
72
+
73
+ // Set string properties.
74
+ if ( is_string( $this->$prop ) && is_scalar( $args[ $prop ] ) ) {
75
+ $this->$prop = (string) $args[ $prop ];
76
+ continue;
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Check if plugin passes all requirements
83
+ *
84
+ * @return bool Requirements check result.
85
+ */
86
+ public function passes() {
87
+ $result = true;
88
+ foreach ( $this->deps as $dep => $req ) {
89
+ $m = $dep . '_passes';
90
+ if ( ! method_exists( $this, $m ) ) {
91
+ continue;
92
+ }
93
+ $passes = $this->$m();
94
+ if ( ! $passes ) {
95
+ // Requirements do not pass.
96
+ $result = $passes;
97
+ // Log dependency failures.
98
+ $this->fail[] = $dep;
99
+ }
100
+ }
101
+ // Handle requirements failure.
102
+ if ( ! $result ) {
103
+ add_action( 'load-plugins.php', array( $this, 'handle_failure' ) );
104
+ }
105
+ return $result;
106
+ }
107
+
108
+ /**
109
+ * Handle requirements failure
110
+ *
111
+ * @return void
112
+ */
113
+ public function handle_failure() {
114
+ // Handle each failed dependency.
115
+ foreach ( $this->fail as $dep ) {
116
+ $m = $dep . '_handle_failure';
117
+ if ( method_exists( $this, $m ) ) {
118
+ $this->$m();
119
+ }
120
+ }
121
+ // Deactivate plugin.
122
+ deactivate_plugins( plugin_basename( $this->file ) );
123
+ }
124
+
125
+ /**
126
+ * Validates PHP version.
127
+ *
128
+ * @return bool PHP requirement passes.
129
+ */
130
+ private function php_passes() {
131
+ return version_compare( PHP_VERSION, $this->deps['php'], '>=' );
132
+ }
133
+
134
+ /**
135
+ * Handle PHP requirement failure
136
+ *
137
+ * @return void
138
+ */
139
+ private function php_handle_failure() {
140
+ // Clear activation query variable from request (stop UI notices).
141
+ unset( $_GET['activate'] );
142
+ // Display notice to user.
143
+ add_action( 'admin_notices', array( $this, 'php_notice' ) );
144
+ }
145
+
146
+ /**
147
+ * Display requirements failure notice and deactivate plugin.
148
+ *
149
+ * @return void
150
+ */
151
+ public function php_notice() {
152
+ global $slb_requirements;
153
+ // Display message to user.
154
+ $link = (object) array(
155
+ /* translators: 1: Plugin name */
156
+ 'title' => sprintf( __( 'Learn more about %1$s\'s requirements', 'simple-lightbox' ), $this->name ),
157
+ /* translators: Plugin requirements link text. */
158
+ 'text' => __( 'Learn More', 'simple-lightbox' ),
159
+ );
160
+ // Full link.
161
+ $link = sprintf( '<a target="_blank" href="%1$s" title="%2$s">%3$s</a>', $this->uri['reference'], esc_attr( $link->title ), esc_html( $link->text ) );
162
+ /* translators: 1: Plugin name. 2: PHP version requirement. 3: Plugin requirements link. */
163
+ $err_msg = sprintf( __( '%1$s requires PHP %2$s or higher. Please have your hosting provider update PHP to enable Simple Lightbox. (%3$s)', 'simple-lightbox' ), $this->name, $this->deps['php'], $link );
164
+ ?>
165
+ <div class="error"><p><?php echo $err_msg; ?></p></div>
166
+ <?php
167
+ }
168
+ }
includes/class.admin.php CHANGED
@@ -1,661 +1,686 @@
1
- <?php
2
-
3
- /**
4
- * Admin functionality
5
- * @package Simple Lightbox
6
- * @subpackage Admin
7
- * @author Archetyped
8
- */
9
- class SLB_Admin extends SLB_Base {
10
- /* Configuration */
11
-
12
- protected $mode = 'sub';
13
-
14
- /* Properties */
15
-
16
- /**
17
- * Parent object
18
- * Set on initialization
19
- * @var obj
20
- */
21
- protected $parent = null;
22
-
23
- /**
24
- * Messages
25
- * @var array
26
- */
27
- protected $messages = array(
28
- 'reset' => 'The settings have been reset',
29
- 'beta' => '<strong class="%1$s">Notice:</strong> This update is a <strong class="%1$s">Beta version</strong>. It is highly recommended that you test the update on a test server before updating the plugin on a production server.',
30
- 'access_denied' => 'You do not have sufficient permissions'
31
- );
32
-
33
- /* Views */
34
-
35
- /**
36
- * Custom admin top-level menus
37
- * Associative Array
38
- * > Key: Menu ID
39
- * > Val: Menu properties
40
- * @var array
41
- */
42
- protected $menus = array();
43
-
44
- /**
45
- * Custom admin pages
46
- * Associative Array
47
- * > Key: Page ID
48
- * > Val: Page properties
49
- * @var array
50
- */
51
- protected $pages = array();
52
-
53
- /**
54
- * Custom admin sections
55
- * Associative Array
56
- * > Key: Section ID
57
- * > Val: Section properties
58
- * @var array
59
- */
60
- protected $sections = array();
61
-
62
- /**
63
- * Actions
64
- * Index Array
65
- * @var array
66
- */
67
- protected $actions = array();
68
-
69
- /* Constructor */
70
-
71
- public function __construct(&$parent) {
72
- parent::__construct();
73
- // Set parent
74
- if ( is_object($parent) )
75
- $this->parent = $parent;
76
- }
77
-
78
- /* Init */
79
-
80
- protected function _hooks() {
81
- parent::_hooks();
82
- // Init
83
- add_action('admin_menu', $this->m('init_menus'), 11);
84
-
85
- // Plugin actions
86
- add_action('admin_action_' . $this->add_prefix('admin'), $this->m('handle_action'));
87
-
88
- // Notices
89
- add_action('admin_notices', $this->m('handle_notices'));
90
-
91
- // Plugin listing
92
- add_filter('plugin_action_links_' . $this->util->get_plugin_base_name(), $this->m('plugin_action_links'), 10, 4);
93
- add_filter('plugin_row_meta', $this->m('plugin_row_meta'), 10, 4);
94
- add_action('in_plugin_update_message-' . $this->util->get_plugin_base_name(), $this->m('plugin_update_message'), 10, 2);
95
- add_filter('site_transient_update_plugins', $this->m('plugin_update_transient'));
96
- }
97
-
98
- /**
99
- * Declare client files (scripts, styles)
100
- * @uses parent::_client_files()
101
- * @return void
102
- */
103
- protected function _client_files($files = null) {
104
- $js_path = 'client/js/';
105
- $js_path .= ( SLB_DEV ) ? 'dev' : 'prod';
106
- $pfx = $this->get_prefix();
107
- $files = array (
108
- 'scripts' => array (
109
- 'admin' => array (
110
- 'file' => "$js_path/lib.admin.js",
111
- 'deps' => array('[core]'),
112
- 'context' => array( "admin_page_$pfx" ),
113
- 'in_footer' => true,
114
- ),
115
- ),
116
- 'styles' => array (
117
- 'admin' => array (
118
- 'file' => 'client/css/admin.css',
119
- 'context' => array( "admin_page_$pfx", 'admin_page_plugins' )
120
- )
121
- )
122
- );
123
- parent::_client_files($files);
124
- }
125
-
126
- /* Handlers */
127
-
128
- /**
129
- * Handle routing of internal action to appropriate handler
130
- */
131
- public function handle_action() {
132
- // Parse action
133
- $t = 'type';
134
- $g = 'group';
135
- $o = 'obj';
136
- $this->add_prefix_ref($t);
137
- $this->add_prefix_ref($g);
138
- $this->add_prefix_ref($o);
139
- $r =& $_REQUEST;
140
-
141
- // Retrieve view that initiated the action
142
- if ( isset($r[$t]) && 'view' == $r[$t] ) {
143
- if ( isset($r[$g]) && ( $prop = $r[$g] . 's' ) && property_exists($this, $prop) && is_array($this->{$prop}) && isset($r[$o]) && isset($this->{$prop}[$r[$o]]) ) {
144
- $view =& $this->{$prop}[$r[$o]];
145
- // Pass request to view
146
- $view->do_callback();
147
- }
148
- }
149
- }
150
-
151
- /**
152
- * Display notices
153
- * Messages are localized upon display
154
- * @uses `admin_notices` action hook to display messages
155
- */
156
- public function handle_notices() {
157
- $msgs = $this->util->apply_filters('admin_messages', array());
158
- foreach ( $msgs as $mid => $msg ) {
159
- // Filter out empty messages
160
- if ( empty($msg) )
161
- continue;
162
- // Build and display message
163
- $mid = $this->add_prefix('msg_' . $mid);
164
- ?>
165
- <div id="<?php echo esc_attr($mid); ?>" class="updated fade">
166
- <p>
167
- <?php echo esc_html($msg);?>
168
- </p>
169
- </div>
170
- <?php
171
- }
172
- }
173
-
174
- /* Views */
175
-
176
- /**
177
- * Adds settings section for plugin functionality
178
- * Section is added to specified admin section/menu
179
- * @uses `admin_init` hook
180
- */
181
- public function init_menus() {
182
- // Add top level menus (when necessary)
183
- $menu;
184
- foreach ( $this->menus as $menu ) {
185
- // Register menu
186
- $hook = add_menu_page($menu->get_label('title'), $menu->get_label('menu'), $menu->get_capability(), $menu->get_id(), $menu->get_callback());
187
- // Add hook to menu object
188
- $menu->set_hookname($hook);
189
- $this->menus[$menu->get_id_raw()] =& $menu;
190
- }
191
-
192
- $page;
193
- // Add subpages
194
- foreach ( $this->pages as $page ) {
195
- // Build Arguments
196
- $args = array ( $page->get_label('header'), $page->get_label('menu'), $page->get_capability(), $page->get_id(), $page->get_callback() );
197
- $f = null;
198
- // Handle pages for default WP menus
199
- if ( $page->is_parent_wp() ) {
200
- $f = 'add_' . $page->get_parent() . '_page';
201
- }
202
-
203
- // Handle pages for custom menus
204
- if ( ! function_exists($f) ) {
205
- array_unshift( $args, $page->get_parent() );
206
- $f = 'add_submenu_page';
207
- }
208
-
209
- // Add admin page
210
- $hook = call_user_func_array($f, $args);
211
- // Save hook to page properties
212
- $page->set_hookname($hook);
213
- $this->pages[$page->get_id_raw()] =& $page;
214
- }
215
-
216
- // Add sections
217
- $section;
218
- foreach ( $this->sections as $section ) {
219
- add_settings_section($section->get_id(), $section->get_title(), $section->get_callback(), $section->get_parent());
220
- }
221
- }
222
-
223
-
224
- /* Methods */
225
-
226
- /**
227
- * Add a new view
228
- * @param string $type View type
229
- * @param string $id Unique view ID
230
- * @param array $args Arguments to pass to view constructor
231
- * @return Admin_View|bool View instance (FALSE if view was not properly initialized)
232
- */
233
- protected function add_view($type, $id, $args) {
234
- // Validate request
235
- $class = $this->add_prefix('admin_' . $type);
236
- $collection = $type . 's';
237
- if ( !class_exists($class) ) {
238
- $class = $this->add_prefix('admin_view');
239
- $collection = null;
240
- }
241
- // Create new instance
242
- $r = new ReflectionClass($class);
243
- $view = $r->newInstanceArgs($args);
244
- if ( $view->is_valid() && !empty($collection) && property_exists($this, $collection) && is_array($this->{$collection}) )
245
- $this->{$collection}[$id] =& $view;
246
- unset($r);
247
- return $view;
248
- }
249
-
250
- /**
251
- * Add plugin action link
252
- * @uses `add_view()` to init/attach action instance
253
- * @param string $id Action ID
254
- * @param array $labels Text for action
255
- * > title - Link text (also title attribute value)
256
- * > confirm - Confirmation message
257
- * > success - Success message
258
- * > failure - Failure message
259
- * @param array $data Additional data for action
260
- * @return obj Action instance
261
- */
262
- public function add_action($id, $labels, $data = null) {
263
- $args = func_get_args();
264
- return $this->add_view('action', $id, $args);
265
- }
266
-
267
- /*-** Menus **-*/
268
-
269
- /**
270
- * Adds custom admin panel
271
- * @param string $id Menu ID
272
- * @param string|array $labels Text labels
273
- * @param int $pos (optional) Menu position in navigation (index order)
274
- * @return Admin_Menu Menu instance
275
- */
276
- public function add_menu($id, $labels, $position = null) {
277
- $args = array ( $id, $labels, null, null, null, $position );
278
- return $this->add_view('menu', $id, $args);
279
- }
280
-
281
- /* Page */
282
-
283
- /**
284
- * Add admin page
285
- * @uses this->pages
286
- * @param string $id Page ID (unique)
287
- * @param string $parent Menu ID to add page to
288
- * @param string|array $labels Text labels (Associative array for multiple labels)
289
- * > menu: Menu title
290
- * > header: Page header
291
- * @param string $capability (optional) Custom capability for accessing page
292
- * @return Admin_Page Page instance
293
- */
294
- public function add_page($id, $parent, $labels, $callback = null, $capability = null) {
295
- $args = func_get_args();
296
- return $this->add_view('page', $id, $args);
297
- }
298
-
299
- /* WP Pages */
300
-
301
- /**
302
- * Add admin page to a standard WP menu
303
- * @uses this->add_page()
304
- * @param string $id Page ID (unique)
305
- * @param string $parent Name of WP menu to add page to
306
- * @param string|array $labels Text labels (Associative array for multiple labels)
307
- * > menu: Menu title
308
- * > header: Page header
309
- * @param string $capability (optional) Custom capability for accessing page
310
- * @return Admin_Page Page instance
311
- */
312
- public function add_wp_page($id, $parent, $labels, $callback = null, $capability = null) {
313
- // Add page
314
- $pg = $this->add_page($id, $parent, $labels, $capability);
315
- // Set parent as WP
316
- if ( $pg ) {
317
- $pg->set_parent_wp();
318
- }
319
- return $pg;
320
- }
321
-
322
- /**
323
- * Add admin page to Dashboard menu
324
- * @see add_dashboard_page()
325
- * @uses this->add_wp_page()
326
- * @param string $id Page ID (unique)
327
- * @param string|array $labels Text labels (Associative array for multiple labels)
328
- * @param string $capability (optional) Custom capability for accessing page
329
- * @return Admin_Page Page instance
330
- */
331
- public function add_dashboard_page($id, $labels, $callback = null, $capability = null) {
332
- return $this->add_wp_page($id, 'dashboard', $labels, $capability);
333
- }
334
-
335
- /**
336
- * Add admin page to Comments menu
337
- * @see add_comments_page()
338
- * @uses this->add_wp_page()
339
- * @param string $id Page ID (unique)
340
- * @param string|array $labels Text labels (Associative array for multiple labels)
341
- * @param string $capability (optional) Custom capability for accessing page
342
- * @return string Page ID
343
- */
344
- public function add_comments_page($id, $labels, $callback = null, $capability = null) {
345
- return $this->add_wp_page($id, 'comments', $labels, $capability);
346
- }
347
-
348
- /**
349
- * Add admin page to Links menu
350
- * @see add_links_page()
351
- * @uses this->add_wp_page()
352
- * @param string $id Page ID (unique)
353
- * @param string|array $labels Text labels (Associative array for multiple labels)
354
- * @param string $capability (optional) Custom capability for accessing page
355
- * @return string Page ID
356
- */
357
- public function add_links_page($id, $labels, $callback = null, $capability = null) {
358
- return $this->add_wp_page($id, 'links', $labels, $capability);
359
- }
360
-
361
-
362
- /**
363
- * Add admin page to Posts menu
364
- * @see add_posts_page()
365
- * @uses this->add_wp_page()
366
- * @param string $id Page ID (unique)
367
- * @param string|array $labels Text labels (Associative array for multiple labels)
368
- * @param string $capability (optional) Custom capability for accessing page
369
- * @return string Page ID
370
- */
371
- public function add_posts_page($id, $labels, $callback = null, $capability = null) {
372
- return $this->add_wp_page($id, 'posts', $labels, $capability);
373
- }
374
-
375
- /**
376
- * Add admin page to Pages menu
377
- * @see add_pages_page()
378
- * @uses this->add_wp_page()
379
- * @param string $id Page ID (unique)
380
- * @param string|array $labels Text labels (Associative array for multiple labels)
381
- * @param string $capability (optional) Custom capability for accessing page
382
- * @return string Page ID
383
- */
384
- public function add_pages_page($id, $labels, $callback = null, $capability = null) {
385
- return $this->add_wp_page($id, 'pages', $labels, $capability);
386
- }
387
-
388
- /**
389
- * Add admin page to Media menu
390
- * @see add_media_page()
391
- * @uses this->add_wp_page()
392
- * @param string $id Page ID (unique)
393
- * @param string|array $labels Text labels (Associative array for multiple labels)
394
- * @param string $capability (optional) Custom capability for accessing page
395
- * @return string Page ID
396
- */
397
- public function add_media_page($id, $labels, $callback = null, $capability = null) {
398
- return $this->add_wp_page($id, 'media', $labels, $capability);
399
- }
400
-
401
- /**
402
- * Add admin page to Themes menu
403
- * @see add_theme_page()
404
- * @uses this->add_wp_page()
405
- * @param string $id Page ID (unique)
406
- * @param string|array $labels Text labels (Associative array for multiple labels)
407
- * @param string $capability (optional) Custom capability for accessing page
408
- * @return string Page ID
409
- */
410
- public function add_theme_page($id, $labels, $callback = null, $capability = null) {
411
- return $this->add_wp_page($id, 'theme', $labels, $capability);
412
- }
413
-
414
- /**
415
- * Add admin page to Plugins menu
416
- * @see add_plugins_page()
417
- * @uses this->add_wp_page()
418
- * @param string $id Page ID (unique)
419
- * @param string|array $labels Text labels (Associative array for multiple labels)
420
- * @param string $capability (optional) Custom capability for accessing page
421
- * @return string Page ID
422
- */
423
- public function add_plugins_page($id, $labels, $callback = null, $capability = null) {
424
- return $this->add_wp_page($id, 'plugins', $labels, $capability);
425
- }
426
-
427
- /**
428
- * Add admin page to Options menu
429
- * @see add_options_page()
430
- * @uses this->add_wp_page()
431
- * @param string $id Page ID (unique)
432
- * @param string|array $labels Text labels (Associative array for multiple labels)
433
- * @param string $capability (optional) Custom capability for accessing page
434
- * @return string Page ID
435
- */
436
- public function add_options_page($id, $labels, $callback = null, $capability = null) {
437
- return $this->add_wp_page($id, 'options', $labels, $capability);
438
- }
439
-
440
- /**
441
- * Add admin page to Tools menu
442
- * @see add_management_page()
443
- * @uses this->add_wp_page()
444
- * @param string $id Page ID (unique)
445
- * @param string|array $labels Text labels (Associative array for multiple labels)
446
- * @param string $capability (optional) Custom capability for accessing page
447
- * @return string Page ID
448
- */
449
- public function add_management_page($id, $labels, $callback = null, $capability = null) {
450
- return $this->add_wp_page($id, 'management', $labels, $capability);
451
- }
452
-
453
- /**
454
- * Add admin page to Users menu
455
- * @uses this->add_wp_page()
456
- * @param string $id Page ID (unique)
457
- * @param string|array $labels Text labels (Associative array for multiple labels)
458
- * @param string $capability (optional) Custom capability for accessing page
459
- * @return string Page ID
460
- */
461
- public function add_users_page($id, $labels, $callback = null, $capability = null) {
462
- return $this->add_wp_page($id, 'users', $labels, $capability);
463
- }
464
-
465
- /* Section */
466
-
467
- /**
468
- * Add section
469
- * @uses this->sections
470
- * @param string $id Unique section ID
471
- * @param string $page Page ID
472
- * @param string $labels Label text
473
- * @return obj Section instance
474
- */
475
- public function add_section($id, $parent, $labels) {
476
- $args = func_get_args();
477
- $section = $this->add_view('section', $id, $args);
478
-
479
- // Add Section
480
- if ( $section->is_valid() )
481
- $this->sections[$id] = $section;
482
-
483
- return $section;
484
- }
485
-
486
- /* Operations */
487
-
488
- /**
489
- * Adds custom links below plugin on plugin listing page
490
- * @uses `plugin_action_links_$plugin-name` Filter hook
491
- * @param $actions
492
- * @param $plugin_file
493
- * @param $plugin_data
494
- * @param $context
495
- */
496
- public function plugin_action_links($actions, $plugin_file, $plugin_data, $context) {
497
- global $admin_page_hooks;
498
- // Add link to settings (only if active)
499
- if ( is_plugin_active($this->util->get_plugin_base_name()) ) {
500
- /* Get Actions */
501
-
502
- $acts = array();
503
- $type = 'plugin_action';
504
-
505
- /* Get view links */
506
- foreach ( array('menus', 'pages', 'sections') as $views ) {
507
- foreach ( $this->{$views} as $view ) {
508
- if ( !$view->has_label($type) )
509
- continue;
510
- $acts[] = (object) array (
511
- 'id' => $views . '_' . $view->get_id(),
512
- 'label' => $view->get_label($type),
513
- 'uri' => $view->get_uri(),
514
- 'attributes' => array()
515
- );
516
- }
517
- }
518
-
519
- /* Get action links */
520
- $type = 'title';
521
- foreach ( $this->actions as $a ) {
522
- if ( !$a->has_label($type) )
523
- continue;
524
- $id = 'action_' . $a->get_id();
525
- $acts[] = (object) array (
526
- 'id' => $id,
527
- 'label' => $a->get_label($type),
528
- 'uri' => $a->get_uri(),
529
- 'attributes' => $a->get_link_attr()
530
- );
531
- }
532
- unset($a);
533
-
534
- // Add links
535
- $links = array();
536
- foreach ( $acts as $act ) {
537
- $links[$act->id] = $this->util->build_html_link($act->uri, $act->label, $act->attributes);
538
- }
539
-
540
- // Add links
541
- $actions = array_merge($links, $actions);
542
- }
543
- return $actions;
544
- }
545
-
546
- /**
547
- * Update plugin listings metadata
548
- * @param array $plugin_meta Plugin metadata
549
- * @param string $plugin_file Plugin file
550
- * @param array $plugin_data Plugin Data
551
- * @param string $status Plugin status
552
- * @return array Updated plugin metadata
553
- */
554
- public function plugin_row_meta($plugin_meta, $plugin_file, $plugin_data, $status) {
555
- $u = ( is_object($this->parent) && isset($this->parent->util) ) ? $this->parent->util : $this->util;
556
- $hook_base = 'admin_plugin_row_meta_';
557
- if ( $plugin_file == $u->get_plugin_base_name() ) {
558
- // Add metadata
559
- // Support
560
- $l = $u->get_plugin_info('SupportURI');
561
- if ( !empty($l) ) {
562
- $t = __( $this->util->apply_filters($hook_base . 'support', 'Get Support'), 'simple-lightbox');
563
- $plugin_meta[] = $u->build_html_link($l, $t);
564
- }
565
- }
566
- return $plugin_meta;
567
- }
568
-
569
- /**
570
- * Adds additional message for plugin updates
571
- * @uses `in_plugin_update_message-$plugin-name` Action hook
572
- * @uses this->plugin_update_get_message()
573
- * @var array $plugin_data Current plugin data
574
- * @var object $r Update response data
575
- */
576
- public function plugin_update_message($plugin_data, $r) {
577
- if ( !isset($r->new_version) )
578
- return false;
579
- if ( stripos($r->new_version, 'beta') !== false ) {
580
- $cls_notice = $this->add_prefix('notice');
581
- echo '<br />' . $this->plugin_update_get_message($r);
582
- }
583
- }
584
-
585
- /**
586
- * Modify update plugins response data if necessary
587
- * @uses `site_transient_update_plugins` Filter hook
588
- * @uses this->plugin_update_get_message()
589
- * @param obj $transient Transient data
590
- * @return obj Modified transient data
591
- */
592
- public function plugin_update_transient($transient) {
593
- $n = $this->util->get_plugin_base_name();
594
- if ( isset($transient->response) && isset($transient->response[$n]) && is_object($transient->response[$n]) && !isset($transient->response[$n]->upgrade_notice) ) {
595
- $r =& $transient->response[$n];
596
- $r->upgrade_notice = $this->plugin_update_get_message($r);
597
- }
598
- return $transient;
599
- }
600
-
601
- /**
602
- * Retrieve custom update message
603
- * @uses this->get_message()
604
- * @param obj $r Response data from plugin update API
605
- * @return string Message (Default: empty string)
606
- */
607
- protected function plugin_update_get_message($r) {
608
- $msg = '';
609
- $cls_notice = $this->add_prefix('notice');
610
- if ( !is_object($r) || !isset($r->new_version) )
611
- return $msg;
612
- if ( stripos($r->new_version, 'beta') !== false ) {
613
- $msg = sprintf($this->get_message('beta'), $cls_notice);
614
- }
615
- return $msg;
616
- }
617
-
618
- /*-** Messages **-*/
619
-
620
- /**
621
- * Retrieve stored messages
622
- * @param string $msg_id Message ID
623
- * @return string Message text
624
- */
625
- public function get_message($msg_id) {
626
- $msg = '';
627
- $msgs = $this->get_messages();
628
- if ( is_string($msg_id) && isset($msgs[$msg_id]) ) {
629
- $msg = $msgs[$msg_id];
630
- }
631
- return $msg;
632
- }
633
-
634
- /**
635
- * Retrieve all messages
636
- * Initializes messages if necessary
637
- * @uses $messages
638
- * @return array Messages
639
- */
640
- function get_messages() {
641
- if ( empty($this->messages) ) {
642
- // Initialize messages if necessary
643
- $this->messages = array(
644
- 'reset' => __('The settings have been reset', 'simple-lightbox'),
645
- 'beta' => __('<strong class="%1$s">Notice:</strong> This update is a <strong class="%1$s">Beta version</strong>. It is highly recommended that you test the update on a test server before updating the plugin on a production server.', 'simple-lightbox'),
646
- 'access_denied' => __('Access Denied', 'simple-lightbox'),
647
- );
648
- }
649
- return $this->messages;
650
- }
651
-
652
- /**
653
- * Set message text
654
- * @uses this->messages
655
- * @param string $id Message ID
656
- * @param string $text Message text
657
- */
658
- public function set_message($id, $text) {
659
- $this->messages[trim($id)] = $text;
660
- }
661
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Admin functionality
5
+ * @package Simple Lightbox
6
+ * @subpackage Admin
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Admin extends SLB_Base {
10
+ /* Configuration */
11
+
12
+ protected $mode = 'sub';
13
+
14
+ /* Properties */
15
+
16
+ /**
17
+ * Parent object
18
+ * Set on initialization
19
+ * @var obj
20
+ */
21
+ protected $parent = null;
22
+
23
+ /**
24
+ * Messages
25
+ * @var array
26
+ */
27
+ protected $messages = array(
28
+ 'reset' => 'The settings have been reset',
29
+ 'beta' => '<strong class="%1$s">Notice:</strong> This update is a <strong class="%1$s">Beta version</strong>. It is highly recommended that you test the update on a test server before updating the plugin on a production server.',
30
+ 'access_denied' => 'You do not have sufficient permissions',
31
+ );
32
+
33
+ /* Views */
34
+
35
+ /**
36
+ * Custom admin top-level menus
37
+ * Associative Array
38
+ * > Key: Menu ID
39
+ * > Val: Menu properties
40
+ * @var array
41
+ */
42
+ protected $menus = array();
43
+
44
+ /**
45
+ * Custom admin pages
46
+ * Associative Array
47
+ * > Key: Page ID
48
+ * > Val: Page properties
49
+ * @var array
50
+ */
51
+ protected $pages = array();
52
+
53
+ /**
54
+ * Custom admin sections
55
+ * Associative Array
56
+ * > Key: Section ID
57
+ * > Val: Section properties
58
+ * @var array
59
+ */
60
+ protected $sections = array();
61
+
62
+ /**
63
+ * Actions
64
+ * Index Array
65
+ * @var array
66
+ */
67
+ protected $actions = array();
68
+
69
+ /* Constructor */
70
+
71
+ public function __construct( &$parent ) {
72
+ parent::__construct();
73
+ // Set parent
74
+ if ( is_object( $parent ) ) {
75
+ $this->parent = $parent;
76
+ }
77
+ }
78
+
79
+ /* Init */
80
+
81
+ protected function _hooks() {
82
+ parent::_hooks();
83
+ // Init
84
+ add_action( 'admin_menu', $this->m( 'init_menus' ), 11 );
85
+
86
+ // Plugin actions
87
+ add_action( 'admin_action_' . $this->add_prefix( 'admin' ), $this->m( 'handle_action' ) );
88
+
89
+ // Notices
90
+ add_action( 'admin_notices', $this->m( 'handle_notices' ) );
91
+
92
+ // Plugin listing
93
+ add_filter( 'plugin_action_links_' . $this->util->get_plugin_base_name(), $this->m( 'plugin_action_links' ), 10, 4 );
94
+ add_filter( 'plugin_row_meta', $this->m( 'plugin_row_meta' ), 10, 4 );
95
+ add_action( 'in_plugin_update_message-' . $this->util->get_plugin_base_name(), $this->m( 'plugin_update_message' ), 10, 2 );
96
+ add_filter( 'site_transient_update_plugins', $this->m( 'plugin_update_transient' ) );
97
+ }
98
+
99
+ /**
100
+ * Declare client files (scripts, styles)
101
+ * @uses parent::_client_files()
102
+ * @return void
103
+ */
104
+ protected function _client_files( $files = null ) {
105
+ $js_path = 'client/js/';
106
+ $js_path .= ( SLB_DEV ) ? 'dev' : 'prod';
107
+ $pfx = $this->get_prefix();
108
+ $files = array(
109
+ 'scripts' => array(
110
+ 'admin' => array(
111
+ 'file' => "$js_path/lib.admin.js",
112
+ 'deps' => array( '[core]' ),
113
+ 'context' => array( "admin_page_$pfx" ),
114
+ 'in_footer' => true,
115
+ ),
116
+ ),
117
+ 'styles' => array(
118
+ 'admin' => array(
119
+ 'file' => 'client/css/admin.css',
120
+ 'context' => array( "admin_page_$pfx", 'admin_page_plugins' ),
121
+ ),
122
+ ),
123
+ );
124
+ parent::_client_files( $files );
125
+ }
126
+
127
+ /* Handlers */
128
+
129
+ /**
130
+ * Handles routing of internal action to appropriate handler.
131
+ *
132
+ * @return void
133
+ */
134
+ public function handle_action() {
135
+ // Parse action
136
+ $t = 'type';
137
+ $g = 'group';
138
+ $o = 'obj';
139
+ $this->add_prefix_ref( $t );
140
+ $this->add_prefix_ref( $g );
141
+ $this->add_prefix_ref( $o );
142
+ $r =& $_REQUEST;
143
+
144
+ // Confirm request contains necessary parameters.
145
+ if (
146
+ ! isset( $r[ $t ], $r[ $g ], $r[ $o ] )
147
+ || 'view' !== $r[ $t ]
148
+ ) {
149
+ return;
150
+ }
151
+ // Confirm specified view instance exists.
152
+ $prop = $r[ $g ] . 's';
153
+ if (
154
+ ! property_exists( $this, $prop )
155
+ || ! is_array( $this->{$prop} )
156
+ || ! isset( $this->{$prop}[ $r[ $o ] ] )
157
+ ) {
158
+ return;
159
+ }
160
+ // Get view instance.
161
+ $view =& $this->{$prop}[ $r[ $o ] ];
162
+ // Pass request to view instance.
163
+ if ( $view instanceof SLB_Admin_View ) {
164
+ $view->do_callback();
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Display notices
170
+ * Messages are localized upon display
171
+ * @uses `admin_notices` action hook to display messages
172
+ */
173
+ public function handle_notices() {
174
+ $msgs = $this->util->apply_filters( 'admin_messages', array() );
175
+ foreach ( $msgs as $mid => $msg ) {
176
+ // Filter out empty messages
177
+ if ( empty( $msg ) ) {
178
+ continue;
179
+ }
180
+ // Build and display message
181
+ $mid = $this->add_prefix( 'msg_' . $mid );
182
+ ?>
183
+ <div id="<?php echo esc_attr( $mid ); ?>" class="updated fade">
184
+ <p>
185
+ <?php echo esc_html( $msg ); ?>
186
+ </p>
187
+ </div>
188
+ <?php
189
+ }
190
+ }
191
+
192
+ /* Views */
193
+
194
+ /**
195
+ * Adds settings section for plugin functionality
196
+ * Section is added to specified admin section/menu
197
+ * @uses `admin_init` hook
198
+ */
199
+ public function init_menus() {
200
+ // Add top level menus (when necessary)
201
+ $menu;
202
+ foreach ( $this->menus as $menu ) {
203
+ // Register menu
204
+ $hook = add_menu_page( $menu->get_label( 'title' ), $menu->get_label( 'menu' ), $menu->get_capability(), $menu->get_id(), $menu->get_callback() );
205
+ // Add hook to menu object
206
+ $menu->set_hookname( $hook );
207
+ $this->menus[ $menu->get_id_raw() ] =& $menu;
208
+ }
209
+
210
+ $page;
211
+ // Add subpages
212
+ foreach ( $this->pages as $page ) {
213
+ // Build Arguments
214
+ $args = array( $page->get_label( 'header' ), $page->get_label( 'menu' ), $page->get_capability(), $page->get_id(), $page->get_callback() );
215
+ $f = null;
216
+ // Handle pages for default WP menus
217
+ if ( $page->is_parent_wp() ) {
218
+ $f = 'add_' . $page->get_parent() . '_page';
219
+ }
220
+
221
+ // Handle pages for custom menus
222
+ if ( ! function_exists( $f ) ) {
223
+ array_unshift( $args, $page->get_parent() );
224
+ $f = 'add_submenu_page';
225
+ }
226
+
227
+ // Add admin page
228
+ $hook = call_user_func_array( $f, $args );
229
+ // Save hook to page properties
230
+ $page->set_hookname( $hook );
231
+ $this->pages[ $page->get_id_raw() ] =& $page;
232
+ }
233
+
234
+ // Add sections
235
+ $section;
236
+ foreach ( $this->sections as $section ) {
237
+ add_settings_section( $section->get_id(), $section->get_title(), $section->get_callback(), $section->get_parent() );
238
+ }
239
+ }
240
+
241
+
242
+ /* Methods */
243
+
244
+ /**
245
+ * Add a new view
246
+ * @param string $type View type
247
+ * @param string $id Unique view ID
248
+ * @param array $args Arguments to pass to view constructor
249
+ * @return Admin_View|bool View instance (FALSE if view was not properly initialized)
250
+ */
251
+ protected function add_view( $type, $id, $args ) {
252
+ // Validate request
253
+ $class = $this->add_prefix( 'admin_' . $type );
254
+ $collection = $type . 's';
255
+ if ( ! class_exists( $class ) ) {
256
+ $class = $this->add_prefix( 'admin_view' );
257
+ $collection = null;
258
+ }
259
+ // Create new instance
260
+ $r = new ReflectionClass( $class );
261
+ $view = $r->newInstanceArgs( $args );
262
+ if ( $view->is_valid() && ! empty( $collection ) && property_exists( $this, $collection ) && is_array( $this->{$collection} ) ) {
263
+ $this->{$collection}[ $id ] =& $view;
264
+ }
265
+ unset( $r );
266
+ return $view;
267
+ }
268
+
269
+ /**
270
+ * Add plugin action link
271
+ * @uses `add_view()` to init/attach action instance
272
+ * @param string $id Action ID
273
+ * @param array $labels Text for action
274
+ * > title - Link text (also title attribute value)
275
+ * > confirm - Confirmation message
276
+ * > success - Success message
277
+ * > failure - Failure message
278
+ * @param array $data Additional data for action
279
+ * @return obj Action instance
280
+ */
281
+ public function add_action( $id, $labels, $data = null ) {
282
+ $args = func_get_args();
283
+ return $this->add_view( 'action', $id, $args );
284
+ }
285
+
286
+ /*-** Menus **-*/
287
+
288
+ /**
289
+ * Adds custom admin panel
290
+ * @param string $id Menu ID
291
+ * @param string|array $labels Text labels
292
+ * @param int $pos (optional) Menu position in navigation (index order)
293
+ * @return Admin_Menu Menu instance
294
+ */
295
+ public function add_menu( $id, $labels, $position = null ) {
296
+ $args = array( $id, $labels, null, null, null, $position );
297
+ return $this->add_view( 'menu', $id, $args );
298
+ }
299
+
300
+ /* Page */
301
+
302
+ /**
303
+ * Add admin page
304
+ * @uses this->pages
305
+ * @param string $id Page ID (unique)
306
+ * @param string $parent Menu ID to add page to
307
+ * @param string|array $labels Text labels (Associative array for multiple labels)
308
+ * > menu: Menu title
309
+ * > header: Page header
310
+ * @param string $capability (optional) Custom capability for accessing page
311
+ * @return Admin_Page Page instance
312
+ */
313
+ public function add_page( $id, $parent, $labels, $callback = null, $capability = null ) {
314
+ $args = func_get_args();
315
+ return $this->add_view( 'page', $id, $args );
316
+ }
317
+
318
+ /* WP Pages */
319
+
320
+ /**
321
+ * Add admin page to a standard WP menu
322
+ * @uses this->add_page()
323
+ * @param string $id Page ID (unique)
324
+ * @param string $parent Name of WP menu to add page to
325
+ * @param string|array $labels Text labels (Associative array for multiple labels)
326
+ * > menu: Menu title
327
+ * > header: Page header
328
+ * @param string $capability (optional) Custom capability for accessing page
329
+ * @return Admin_Page Page instance
330
+ */
331
+ public function add_wp_page( $id, $parent, $labels, $callback = null, $capability = null ) {
332
+ // Add page
333
+ $pg = $this->add_page( $id, $parent, $labels, $capability );
334
+ // Set parent as WP
335
+ if ( $pg ) {
336
+ $pg->set_parent_wp();
337
+ }
338
+ return $pg;
339
+ }
340
+
341
+ /**
342
+ * Add admin page to Dashboard menu
343
+ * @see add_dashboard_page()
344
+ * @uses this->add_wp_page()
345
+ * @param string $id Page ID (unique)
346
+ * @param string|array $labels Text labels (Associative array for multiple labels)
347
+ * @param string $capability (optional) Custom capability for accessing page
348
+ * @return Admin_Page Page instance
349
+ */
350
+ public function add_dashboard_page( $id, $labels, $callback = null, $capability = null ) {
351
+ return $this->add_wp_page( $id, 'dashboard', $labels, $capability );
352
+ }
353
+
354
+ /**
355
+ * Add admin page to Comments menu
356
+ * @see add_comments_page()
357
+ * @uses this->add_wp_page()
358
+ * @param string $id Page ID (unique)
359
+ * @param string|array $labels Text labels (Associative array for multiple labels)
360
+ * @param string $capability (optional) Custom capability for accessing page
361
+ * @return string Page ID
362
+ */
363
+ public function add_comments_page( $id, $labels, $callback = null, $capability = null ) {
364
+ return $this->add_wp_page( $id, 'comments', $labels, $capability );
365
+ }
366
+
367
+ /**
368
+ * Add admin page to Links menu
369
+ * @see add_links_page()
370
+ * @uses this->add_wp_page()
371
+ * @param string $id Page ID (unique)
372
+ * @param string|array $labels Text labels (Associative array for multiple labels)
373
+ * @param string $capability (optional) Custom capability for accessing page
374
+ * @return string Page ID
375
+ */
376
+ public function add_links_page( $id, $labels, $callback = null, $capability = null ) {
377
+ return $this->add_wp_page( $id, 'links', $labels, $capability );
378
+ }
379
+
380
+
381
+ /**
382
+ * Add admin page to Posts menu
383
+ * @see add_posts_page()
384
+ * @uses this->add_wp_page()
385
+ * @param string $id Page ID (unique)
386
+ * @param string|array $labels Text labels (Associative array for multiple labels)
387
+ * @param string $capability (optional) Custom capability for accessing page
388
+ * @return string Page ID
389
+ */
390
+ public function add_posts_page( $id, $labels, $callback = null, $capability = null ) {
391
+ return $this->add_wp_page( $id, 'posts', $labels, $capability );
392
+ }
393
+
394
+ /**
395
+ * Add admin page to Pages menu
396
+ * @see add_pages_page()
397
+ * @uses this->add_wp_page()
398
+ * @param string $id Page ID (unique)
399
+ * @param string|array $labels Text labels (Associative array for multiple labels)
400
+ * @param string $capability (optional) Custom capability for accessing page
401
+ * @return string Page ID
402
+ */
403
+ public function add_pages_page( $id, $labels, $callback = null, $capability = null ) {
404
+ return $this->add_wp_page( $id, 'pages', $labels, $capability );
405
+ }
406
+
407
+ /**
408
+ * Add admin page to Media menu
409
+ * @see add_media_page()
410
+ * @uses this->add_wp_page()
411
+ * @param string $id Page ID (unique)
412
+ * @param string|array $labels Text labels (Associative array for multiple labels)
413
+ * @param string $capability (optional) Custom capability for accessing page
414
+ * @return string Page ID
415
+ */
416
+ public function add_media_page( $id, $labels, $callback = null, $capability = null ) {
417
+ return $this->add_wp_page( $id, 'media', $labels, $capability );
418
+ }
419
+
420
+ /**
421
+ * Add admin page to Themes menu
422
+ * @see add_theme_page()
423
+ * @uses this->add_wp_page()
424
+ * @param string $id Page ID (unique)
425
+ * @param string|array $labels Text labels (Associative array for multiple labels)
426
+ * @param string $capability (optional) Custom capability for accessing page
427
+ * @return string Page ID
428
+ */
429
+ public function add_theme_page( $id, $labels, $callback = null, $capability = null ) {
430
+ return $this->add_wp_page( $id, 'theme', $labels, $capability );
431
+ }
432
+
433
+ /**
434
+ * Add admin page to Plugins menu
435
+ * @see add_plugins_page()
436
+ * @uses this->add_wp_page()
437
+ * @param string $id Page ID (unique)
438
+ * @param string|array $labels Text labels (Associative array for multiple labels)
439
+ * @param string $capability (optional) Custom capability for accessing page
440
+ * @return string Page ID
441
+ */
442
+ public function add_plugins_page( $id, $labels, $callback = null, $capability = null ) {
443
+ return $this->add_wp_page( $id, 'plugins', $labels, $capability );
444
+ }
445
+
446
+ /**
447
+ * Add admin page to Options menu
448
+ * @see add_options_page()
449
+ * @uses this->add_wp_page()
450
+ * @param string $id Page ID (unique)
451
+ * @param string|array $labels Text labels (Associative array for multiple labels)
452
+ * @param string $capability (optional) Custom capability for accessing page
453
+ * @return string Page ID
454
+ */
455
+ public function add_options_page( $id, $labels, $callback = null, $capability = null ) {
456
+ return $this->add_wp_page( $id, 'options', $labels, $capability );
457
+ }
458
+
459
+ /**
460
+ * Add admin page to Tools menu
461
+ * @see add_management_page()
462
+ * @uses this->add_wp_page()
463
+ * @param string $id Page ID (unique)
464
+ * @param string|array $labels Text labels (Associative array for multiple labels)
465
+ * @param string $capability (optional) Custom capability for accessing page
466
+ * @return string Page ID
467
+ */
468
+ public function add_management_page( $id, $labels, $callback = null, $capability = null ) {
469
+ return $this->add_wp_page( $id, 'management', $labels, $capability );
470
+ }
471
+
472
+ /**
473
+ * Add admin page to Users menu
474
+ * @uses this->add_wp_page()
475
+ * @param string $id Page ID (unique)
476
+ * @param string|array $labels Text labels (Associative array for multiple labels)
477
+ * @param string $capability (optional) Custom capability for accessing page
478
+ * @return string Page ID
479
+ */
480
+ public function add_users_page( $id, $labels, $callback = null, $capability = null ) {
481
+ return $this->add_wp_page( $id, 'users', $labels, $capability );
482
+ }
483
+
484
+ /* Section */
485
+
486
+ /**
487
+ * Add section
488
+ * @uses this->sections
489
+ * @param string $id Unique section ID
490
+ * @param string $page Page ID
491
+ * @param string $labels Label text
492
+ * @return obj Section instance
493
+ */
494
+ public function add_section( $id, $parent, $labels ) {
495
+ $args = func_get_args();
496
+ $section = $this->add_view( 'section', $id, $args );
497
+
498
+ // Add Section
499
+ if ( $section->is_valid() ) {
500
+ $this->sections[ $id ] = $section;
501
+ }
502
+
503
+ return $section;
504
+ }
505
+
506
+ /* Operations */
507
+
508
+ /**
509
+ * Adds custom links below plugin on plugin listing page
510
+ * @uses `plugin_action_links_$plugin-name` Filter hook
511
+ * @param $actions
512
+ * @param $plugin_file
513
+ * @param $plugin_data
514
+ * @param $context
515
+ */
516
+ public function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
517
+ global $admin_page_hooks;
518
+ // Add link to settings (only if active)
519
+ if ( is_plugin_active( $this->util->get_plugin_base_name() ) ) {
520
+ /* Get Actions */
521
+
522
+ $acts = array();
523
+ $type = 'plugin_action';
524
+
525
+ /* Get view links */
526
+ foreach ( array( 'menus', 'pages', 'sections' ) as $views ) {
527
+ foreach ( $this->{$views} as $view ) {
528
+ if ( ! $view->has_label( $type ) ) {
529
+ continue;
530
+ }
531
+ $acts[] = (object) array(
532
+ 'id' => $views . '_' . $view->get_id(),
533
+ 'label' => $view->get_label( $type ),
534
+ 'uri' => $view->get_uri(),
535
+ 'attributes' => array(),
536
+ );
537
+ }
538
+ }
539
+
540
+ /* Get action links */
541
+ $type = 'title';
542
+ foreach ( $this->actions as $a ) {
543
+ if ( ! $a->has_label( $type ) ) {
544
+ continue;
545
+ }
546
+ $id = 'action_' . $a->get_id();
547
+ $acts[] = (object) array(
548
+ 'id' => $id,
549
+ 'label' => $a->get_label( $type ),
550
+ 'uri' => $a->get_uri(),
551
+ 'attributes' => $a->get_link_attr(),
552
+ );
553
+ }
554
+ unset( $a );
555
+
556
+ // Add links
557
+ $links = array();
558
+ foreach ( $acts as $act ) {
559
+ $links[ $act->id ] = $this->util->build_html_link( $act->uri, $act->label, $act->attributes );
560
+ }
561
+
562
+ // Add links
563
+ $actions = array_merge( $links, $actions );
564
+ }
565
+ return $actions;
566
+ }
567
+
568
+ /**
569
+ * Update plugin listings metadata
570
+ * @param array $plugin_meta Plugin metadata
571
+ * @param string $plugin_file Plugin file
572
+ * @param array $plugin_data Plugin Data
573
+ * @param string $status Plugin status
574
+ * @return array Updated plugin metadata
575
+ */
576
+ public function plugin_row_meta( $plugin_meta, $plugin_file, $plugin_data, $status ) {
577
+ $u = ( is_object( $this->parent ) && isset( $this->parent->util ) ) ? $this->parent->util : $this->util;
578
+ $hook_base = 'admin_plugin_row_meta_';
579
+ if ( $plugin_file === $u->get_plugin_base_name() ) {
580
+ // Add metadata
581
+ // Support
582
+ $l = $u->get_plugin_info( 'SupportURI' );
583
+ if ( ! empty( $l ) ) {
584
+ $t = __( 'Feedback &amp; Support', 'simple-lightbox' );
585
+ $plugin_meta[] = $u->build_html_link( $l, $t );
586
+ }
587
+ }
588
+ return $plugin_meta;
589
+ }
590
+
591
+ /**
592
+ * Adds additional message for plugin updates
593
+ * @uses `in_plugin_update_message-$plugin-name` Action hook
594
+ * @uses this->plugin_update_get_message()
595
+ * @var array $plugin_data Current plugin data
596
+ * @var object $r Update response data
597
+ */
598
+ public function plugin_update_message( $plugin_data, $r ) {
599
+ if ( ! isset( $r->new_version ) ) {
600
+ return false;
601
+ }
602
+ if ( stripos( $r->new_version, 'beta' ) !== false ) {
603
+ $cls_notice = $this->add_prefix( 'notice' );
604
+ echo '<br />' . $this->plugin_update_get_message( $r );
605
+ }
606
+ }
607
+
608
+ /**
609
+ * Modify update plugins response data if necessary
610
+ * @uses `site_transient_update_plugins` Filter hook
611
+ * @uses this->plugin_update_get_message()
612
+ * @param obj $transient Transient data
613
+ * @return obj Modified transient data
614
+ */
615
+ public function plugin_update_transient( $transient ) {
616
+ $n = $this->util->get_plugin_base_name();
617
+ if ( isset( $transient->response ) && isset( $transient->response[ $n ] ) && is_object( $transient->response[ $n ] ) && ! isset( $transient->response[ $n ]->upgrade_notice ) ) {
618
+ $r =& $transient->response[ $n ];
619
+ $r->upgrade_notice = $this->plugin_update_get_message( $r );
620
+ }
621
+ return $transient;
622
+ }
623
+
624
+ /**
625
+ * Retrieve custom update message
626
+ * @uses this->get_message()
627
+ * @param obj $r Response data from plugin update API
628
+ * @return string Message (Default: empty string)
629
+ */
630
+ protected function plugin_update_get_message( $r ) {
631
+ $msg = '';
632
+ $cls_notice = $this->add_prefix( 'notice' );
633
+ if ( ! is_object( $r ) || ! isset( $r->new_version ) ) {
634
+ return $msg;
635
+ }
636
+ if ( stripos( $r->new_version, 'beta' ) !== false ) {
637
+ $msg = sprintf( $this->get_message( 'beta' ), $cls_notice );
638
+ }
639
+ return $msg;
640
+ }
641
+
642
+ /*-** Messages **-*/
643
+
644
+ /**
645
+ * Retrieve stored messages
646
+ * @param string $msg_id Message ID
647
+ * @return string Message text
648
+ */
649
+ public function get_message( $msg_id ) {
650
+ $msg = '';
651
+ $msgs = $this->get_messages();
652
+ if ( is_string( $msg_id ) && isset( $msgs[ $msg_id ] ) ) {
653
+ $msg = $msgs[ $msg_id ];
654
+ }
655
+ return $msg;
656
+ }
657
+
658
+ /**
659
+ * Retrieve all messages
660
+ * Initializes messages if necessary
661
+ * @uses $messages
662
+ * @return array Messages
663
+ */
664
+ function get_messages() {
665
+ if ( empty( $this->messages ) ) {
666
+ // Initialize messages if necessary
667
+ $this->messages = array(
668
+ 'reset' => __( 'The settings have been reset', 'simple-lightbox' ),
669
+ /* translators: 1: Notice CSS class */
670
+ 'beta' => __( '<strong class="%1$s">Notice:</strong> This update is a <strong class="%1$s">Beta version</strong>. It is highly recommended that you test the update on a test server before updating the plugin on a production server.', 'simple-lightbox' ),
671
+ 'access_denied' => __( 'Access Denied', 'simple-lightbox' ),
672
+ );
673
+ }
674
+ return $this->messages;
675
+ }
676
+
677
+ /**
678
+ * Set message text
679
+ * @uses this->messages
680
+ * @param string $id Message ID
681
+ * @param string $text Message text
682
+ */
683
+ public function set_message( $id, $text ) {
684
+ $this->messages[ trim( $id ) ] = $text;
685
+ }
686
+ }
includes/class.admin_action.php CHANGED
@@ -1,109 +1,110 @@
1
- <?php
2
-
3
- /**
4
- * Plugin action functionality
5
- * Used for adding action links to plugin listing, etc.
6
- * @package Simple Lightbox
7
- * @subpackage Admin
8
- * @author Archetyped
9
- */
10
- class SLB_Admin_Action extends SLB_Admin_View {
11
- /* Properties */
12
-
13
- protected $parent_required = false;
14
-
15
- public $hook_prefix = 'admin_action';
16
-
17
- /* Init */
18
-
19
- /**
20
- * Init
21
- * @param string $id ID
22
- * @param array $labels Labels
23
- * @param obj $data Action data
24
- * @return obj Current instance
25
- */
26
- function __construct($id, $labels, $data = null) {
27
- parent::__construct($id, $labels);
28
- // Default options instance
29
- if ( !empty($data) ) {
30
- $this->add_content('data', $data);
31
- }
32
- return $this;
33
- }
34
-
35
- /* Handlers */
36
-
37
- /**
38
- * Default handler
39
- * Handles action
40
- * @return string Status message (success, fail, etc.)
41
- */
42
- public function handle() {
43
- // Validate user
44
- if ( ! current_user_can('activate_plugins') || ! check_admin_referer($this->get_id()) )
45
- wp_die(__('Access Denied', 'simple-lightbox'));
46
-
47
- // Get data
48
- $content = $this->get_content();
49
-
50
- $success = true;
51
-
52
- // Iterate through data
53
- $hook = $this->util->get_hook($this->get_id_raw());
54
- foreach ( $content as $c ) {
55
- // Trigger action
56
- $res = apply_filters($hook, $success, $c->data, $this);
57
- // Set result
58
- if ( !!$success ) {
59
- $success = $res;
60
- }
61
- }
62
-
63
- // Set Status Message
64
- $lbl = ( $success ) ? 'success' : 'failure';
65
- $this->set_message($this->get_label($lbl));
66
- }
67
-
68
- /**
69
- * Get URI
70
- * @see Admin_View::get_uri()
71
- */
72
- public function get_uri($file = null, $format = null) {
73
- return wp_nonce_url(add_query_arg($this->get_query_args(), remove_query_arg($this->get_query_args_remove(), $_SERVER['REQUEST_URI'])), $this->get_id());
74
- }
75
-
76
- protected function get_query_args() {
77
- return array (
78
- 'action' => $this->add_prefix('admin'),
79
- $this->add_prefix('type') => 'view',
80
- $this->add_prefix('group') => 'action',
81
- $this->add_prefix('obj') => $this->get_id_raw()
82
- );
83
- }
84
-
85
- protected function get_query_args_remove() {
86
- $args_r = array (
87
- '_wpnonce',
88
- $this->add_prefix('action')
89
- );
90
-
91
- return array_unique( array_merge( array_keys( $this->get_query_args() ), $args_r ) );
92
- }
93
-
94
- public function get_link_attr() {
95
- return array (
96
- 'class' => $this->util->get_hook($this->get_id_raw()),
97
- 'onclick' => "return confirm('" . esc_js( $this->get_label('confirm') ) . "')"
98
- );
99
- }
100
-
101
- /* Content */
102
-
103
- /**
104
- * Save options
105
- */
106
- public function add_content($id, $data) {
107
- return parent::add_content($id, array('data' => $data));
108
- }
109
- }
 
1
+ <?php
2
+
3
+ /**
4
+ * Plugin action functionality
5
+ * Used for adding action links to plugin listing, etc.
6
+ * @package Simple Lightbox
7
+ * @subpackage Admin
8
+ * @author Archetyped
9
+ */
10
+ class SLB_Admin_Action extends SLB_Admin_View {
11
+ /* Properties */
12
+
13
+ protected $parent_required = false;
14
+
15
+ public $hook_prefix = 'admin_action';
16
+
17
+ /* Init */
18
+
19
+ /**
20
+ * Init
21
+ * @param string $id ID
22
+ * @param array $labels Labels
23
+ * @param obj $data Action data
24
+ * @return obj Current instance
25
+ */
26
+ function __construct( $id, $labels, $data = null ) {
27
+ parent::__construct( $id, $labels );
28
+ // Default options instance
29
+ if ( ! empty( $data ) ) {
30
+ $this->add_content( 'data', $data );
31
+ }
32
+ return $this;
33
+ }
34
+
35
+ /* Handlers */
36
+
37
+ /**
38
+ * Default handler
39
+ * Handles action
40
+ * @return string Status message (success, fail, etc.)
41
+ */
42
+ public function handle() {
43
+ // Validate user
44
+ if ( ! current_user_can( 'activate_plugins' ) || ! check_admin_referer( $this->get_id() ) ) {
45
+ wp_die( __( 'Access Denied', 'simple-lightbox' ) );
46
+ }
47
+
48
+ // Get data
49
+ $content = $this->get_content();
50
+
51
+ $success = true;
52
+
53
+ // Iterate through data
54
+ $hook = $this->util->get_hook( $this->get_id_raw() );
55
+ foreach ( $content as $c ) {
56
+ // Trigger action
57
+ $res = apply_filters( $hook, $success, $c->data, $this );
58
+ // Set result
59
+ if ( ! ! $success ) {
60
+ $success = $res;
61
+ }
62
+ }
63
+
64
+ // Set Status Message
65
+ $lbl = ( $success ) ? 'success' : 'failure';
66
+ $this->set_message( $this->get_label( $lbl ) );
67
+ }
68
+
69
+ /**
70
+ * Get URI
71
+ * @see Admin_View::get_uri()
72
+ */
73
+ public function get_uri( $file = null, $format = null ) {
74
+ return wp_nonce_url( add_query_arg( $this->get_query_args(), remove_query_arg( $this->get_query_args_remove(), $_SERVER['REQUEST_URI'] ) ), $this->get_id() );
75
+ }
76
+
77
+ protected function get_query_args() {
78
+ return array(
79
+ 'action' => $this->add_prefix( 'admin' ),
80
+ $this->add_prefix( 'type' ) => 'view',
81
+ $this->add_prefix( 'group' ) => 'action',
82
+ $this->add_prefix( 'obj' ) => $this->get_id_raw(),
83
+ );
84
+ }
85
+
86
+ protected function get_query_args_remove() {
87
+ $args_r = array(
88
+ '_wpnonce',
89
+ $this->add_prefix( 'action' ),
90
+ );
91
+
92
+ return array_unique( array_merge( array_keys( $this->get_query_args() ), $args_r ) );
93
+ }
94
+
95
+ public function get_link_attr() {
96
+ return array(
97
+ 'class' => $this->util->get_hook( $this->get_id_raw() ),
98
+ 'onclick' => "return confirm('" . esc_js( $this->get_label( 'confirm' ) ) . "')",
99
+ );
100
+ }
101
+
102
+ /* Content */
103
+
104
+ /**
105
+ * Save options
106
+ */
107
+ public function add_content( $id, $data ) {
108
+ return parent::add_content( $id, array( 'data' => $data ) );
109
+ }
110
+ }
includes/class.admin_menu.php CHANGED
@@ -1,40 +1,41 @@
1
- <?php
2
-
3
- /**
4
- * Admin Menu
5
- * Menus are top-level views in the Admin UI
6
- * @package Simple Lightbox
7
- * @subpackage Admin
8
- * @author Archetyped
9
- */
10
- class SLB_Admin_Menu extends SLB_Admin_View {
11
- /* Properties */
12
-
13
- /**
14
- * Menu position
15
- * @var int
16
- */
17
- protected $position = null;
18
-
19
- /* Init */
20
-
21
- public function __construct($id, $labels, $callback = null, $capability = null, $icon = null, $position = null) {
22
- // Default
23
- parent::__construct($id, $labels, $callback, $capability, $icon);
24
- // Class specific
25
- $this->set_position($position);
26
- return $this;
27
- }
28
-
29
- /* Getters/Setters */
30
-
31
- /**
32
- * Set menu position
33
- * @return obj Current instance
34
- */
35
- public function set_position($position) {
36
- if ( is_int($position) )
37
- $this->position = $position;
38
- return $this;
39
- }
40
- }
 
1
+ <?php
2
+
3
+ /**
4
+ * Admin Menu
5
+ * Menus are top-level views in the Admin UI
6
+ * @package Simple Lightbox
7
+ * @subpackage Admin
8
+ * @author Archetyped
9
+ */
10
+ class SLB_Admin_Menu extends SLB_Admin_View {
11
+ /* Properties */
12
+
13
+ /**
14
+ * Menu position
15
+ * @var int
16
+ */
17
+ protected $position = null;
18
+
19
+ /* Init */
20
+
21
+ public function __construct( $id, $labels, $callback = null, $capability = null, $icon = null, $position = null ) {
22
+ // Default
23
+ parent::__construct( $id, $labels, $callback, $capability, $icon );
24
+ // Class specific
25
+ $this->set_position( $position );
26
+ return $this;
27
+ }
28
+
29
+ /* Getters/Setters */
30
+
31
+ /**
32
+ * Set menu position
33
+ * @return obj Current instance
34
+ */
35
+ public function set_position( $position ) {
36
+ if ( is_int( $position ) ) {
37
+ $this->position = $position;
38
+ }
39
+ return $this;
40
+ }
41
+ }
includes/class.admin_page.php CHANGED
@@ -1,187 +1,188 @@
1
- <?php
2
-
3
- /**
4
- * Admin Page
5
- * Pages are part of a Menu
6
- * @package Simple Lightbox
7
- * @subpackage Admin
8
- * @author Archetyped
9
- */
10
- class SLB_Admin_Page extends SLB_Admin_View {
11
- /* Properties */
12
-
13
- protected $parent_required = true;
14
-
15
- public $hook_prefix = 'admin_page';
16
-
17
- /**
18
- * Required features/elements
19
- */
20
- private $_required = array();
21
-
22
- /* Init */
23
-
24
- public function __construct($id, $parent, $labels, $callback = null, $capability = null) {
25
- // Default
26
- parent::__construct($id, $labels, $callback, $capability);
27
- // Class specific
28
- $this->set_parent($parent);
29
- return $this;
30
- }
31
-
32
- /* Operations */
33
-
34
- /**
35
- * Add content to page
36
- * @uses parent::add_content()
37
- * @param string $id Module ID
38
- * @param string $title Module title
39
- * @param mixed $callback Callback method or other data for building module UI
40
- * @param string $context (optional) Context to add module to (Default: primary)
41
- * @param string $priority (optional) Controls module ordering (Default: default)
42
- * @param array $callback_args (optional) Additional data to pass callback (Default: NULL)
43
- * @return object Page instance reference
44
- */
45
- public function add_content($id, $title, $callback = null, $context = 'primary', $priority = 'default', $callback_args = null) {
46
- return parent::add_content($id, array(
47
- 'id' => $id,
48
- 'title' => $title,
49
- 'callback' => $callback,
50
- 'context' => $context,
51
- 'priority' => $priority,
52
- 'callback_args' => $callback_args
53
- )
54
- );
55
- }
56
-
57
- /**
58
- * Parse content by parameters
59
- * Sets content value
60
- */
61
- protected function parse_content() {
62
- // Get raw content
63
- $raw = $this->get_content(false);
64
- // Group by context
65
- $content = array();
66
- foreach ( $raw as $c ) {
67
- // Add new context
68
- if ( !isset($content[$c->context]) ) {
69
- $content[$c->context] = array();
70
- }
71
- // Add item to context
72
- $content[$c->context][] = $c;
73
- }
74
- return $content;
75
- }
76
-
77
- /**
78
- * Render content blocks
79
- * @param string $context (optional) Context to render
80
- */
81
- protected function render_content($context = 'primary') {
82
- // Get content
83
- $content = $this->get_content();
84
- // Check for context
85
- if ( !isset($content[$context]) ) {
86
- return false;
87
- }
88
- $content = $content[$context];
89
- $out = '';
90
- // Render content
91
- ?>
92
- <div class="content-wrap">
93
- <?php
94
- // Add meta boxes
95
- $screen = get_current_screen();
96
- foreach ( $content as $c ) {
97
- $c->screen = $screen;
98
- // Callback
99
- if ( is_callable($c->callback) ) {
100
- $callback = $c->callback;
101
- add_meta_box($c->id, $c->title, $c->callback, $c->screen, $c->context, $c->priority, $c->callback_args);
102
- } else {
103
- // Let handlers build output
104
- $this->util->do_action('render_content', $c->callback, $this, $c);
105
- }
106
- }
107
- // Output meta boxes
108
- do_meta_boxes($screen, $context, null);
109
- ?>
110
- </div>
111
- <?php
112
- }
113
-
114
- /**
115
- * Require form submission support
116
- * @return obj Page instance
117
- */
118
- public function require_form() {
119
- $this->_require('form_submit');
120
- return $this;
121
- }
122
-
123
- /**
124
- * Check if form submission is required
125
- * @return bool TRUE if form submission required
126
- */
127
- private function is_required_form() {
128
- return $this->_is_required('form_submit');
129
- }
130
-
131
- /* Handlers */
132
-
133
- /**
134
- * Default Page handler
135
- * Builds content blocks
136
- * @see this->init_menus() Set as callback for custom admin pages
137
- * @uses current_user_can() to check if user has access to current page
138
- * @uses wp_die() to end execution when user does not have permission to access page
139
- */
140
- public function handle() {
141
- if ( !current_user_can($this->get_capability()) )
142
- wp_die(__('Access Denied', 'simple-lightbox'));
143
- wp_enqueue_script('postbox');
144
- ?>
145
- <div class="wrap slb">
146
- <h2><?php esc_html_e( $this->get_label('header') ); ?></h2>
147
- <?php
148
- // Form submission support
149
- if ( $this->is_required_form() ) {
150
- // Build form output
151
- $form_id = $this->add_prefix('admin_form_' . $this->get_id_raw());
152
- $nonce = (object) [
153
- 'action' => $this->get_id(),
154
- 'name' => $this->get_id() . '_nonce',
155
- ];
156
- ?>
157
- <form id="<?php esc_attr_e($form_id); ?>" name="<?php esc_attr_e($form_id); ?>" action="" method="post">
158
- <?php
159
- wp_nonce_field( $nonce->action, $nonce->name );
160
- }
161
- ?>
162
- <div class="metabox-holder columns-2">
163
- <div class="content-primary postbox-container">
164
- <?php
165
- $this->render_content('primary');
166
- ?>
167
- </div>
168
- <div class="content-secondary postbox-container">
169
- <?php
170
- $this->render_content('secondary');
171
- ?>
172
- </div>
173
- </div>
174
- <br class="clear" />
175
- <?php
176
- // Form submission support
177
- if ( $this->is_required_form() ) {
178
- submit_button();
179
- ?>
180
- </form>
181
- <?php
182
- }
183
- ?>
184
- </div>
185
- <?php
186
- }
187
- }
 
1
+ <?php
2
+
3
+ /**
4
+ * Admin Page
5
+ * Pages are part of a Menu
6
+ * @package Simple Lightbox
7
+ * @subpackage Admin
8
+ * @author Archetyped
9
+ */
10
+ class SLB_Admin_Page extends SLB_Admin_View {
11
+ /* Properties */
12
+
13
+ protected $parent_required = true;
14
+
15
+ public $hook_prefix = 'admin_page';
16
+
17
+ /**
18
+ * Required features/elements
19
+ */
20
+ private $_required = array();
21
+
22
+ /* Init */
23
+
24
+ public function __construct( $id, $parent, $labels, $callback = null, $capability = null ) {
25
+ // Default
26
+ parent::__construct( $id, $labels, $callback, $capability );
27
+ // Class specific
28
+ $this->set_parent( $parent );
29
+ return $this;
30
+ }
31
+
32
+ /* Operations */
33
+
34
+ /**
35
+ * Add content to page
36
+ * @uses parent::add_content()
37
+ * @param string $id Module ID
38
+ * @param string $title Module title
39
+ * @param mixed $callback Callback method or other data for building module UI
40
+ * @param string $context (optional) Context to add module to (Default: primary)
41
+ * @param string $priority (optional) Controls module ordering (Default: default)
42
+ * @param array $callback_args (optional) Additional data to pass callback (Default: NULL)
43
+ * @return object Page instance reference
44
+ */
45
+ public function add_content( $id, $title, $callback = null, $context = 'primary', $priority = 'default', $callback_args = null ) {
46
+ $opts = [
47
+ 'id' => $id,
48
+ 'title' => $title,
49
+ 'callback' => $callback,
50
+ 'context' => $context,
51
+ 'priority' => $priority,
52
+ 'callback_args' => $callback_args,
53
+ ];
54
+ return parent::add_content( $id, $opts );
55
+ }
56
+
57
+ /**
58
+ * Parse content by parameters
59
+ * Sets content value
60
+ */
61
+ protected function parse_content() {
62
+ // Get raw content
63
+ $raw = $this->get_content( false );
64
+ // Group by context
65
+ $content = array();
66
+ foreach ( $raw as $c ) {
67
+ // Add new context
68
+ if ( ! isset( $content[ $c->context ] ) ) {
69
+ $content[ $c->context ] = array();
70
+ }
71
+ // Add item to context
72
+ $content[ $c->context ][] = $c;
73
+ }
74
+ return $content;
75
+ }
76
+
77
+ /**
78
+ * Render content blocks
79
+ * @param string $context (optional) Context to render
80
+ */
81
+ protected function render_content( $context = 'primary' ) {
82
+ // Get content
83
+ $content = $this->get_content();
84
+ // Check for context
85
+ if ( ! isset( $content[ $context ] ) ) {
86
+ return false;
87
+ }
88
+ $content = $content[ $context ];
89
+ $out = '';
90
+ // Render content
91
+ ?>
92
+ <div class="content-wrap">
93
+ <?php
94
+ // Add meta boxes
95
+ $screen = get_current_screen();
96
+ foreach ( $content as $c ) {
97
+ $c->screen = $screen;
98
+ // Callback
99
+ if ( is_callable( $c->callback ) ) {
100
+ $callback = $c->callback;
101
+ add_meta_box( $c->id, $c->title, $c->callback, $c->screen, $c->context, $c->priority, $c->callback_args );
102
+ } else {
103
+ // Let handlers build output
104
+ $this->util->do_action( 'render_content', $c->callback, $this, $c );
105
+ }
106
+ }
107
+ // Output meta boxes
108
+ do_meta_boxes( $screen, $context, null );
109
+ ?>
110
+ </div>
111
+ <?php
112
+ }
113
+
114
+ /**
115
+ * Require form submission support
116
+ * @return obj Page instance
117
+ */
118
+ public function require_form() {
119
+ $this->_require( 'form_submit' );
120
+ return $this;
121
+ }
122
+
123
+ /**
124
+ * Check if form submission is required
125
+ * @return bool TRUE if form submission required
126
+ */
127
+ private function is_required_form() {
128
+ return $this->_is_required( 'form_submit' );
129
+ }
130
+
131
+ /* Handlers */
132
+
133
+ /**
134
+ * Default Page handler
135
+ * Builds content blocks
136
+ * @see this->init_menus() Set as callback for custom admin pages
137
+ * @uses current_user_can() to check if user has access to current page
138
+ * @uses wp_die() to end execution when user does not have permission to access page
139
+ */
140
+ public function handle() {
141
+ if ( ! current_user_can( $this->get_capability() ) ) {
142
+ wp_die( __( 'Access Denied', 'simple-lightbox' ) );
143
+ }
144
+ wp_enqueue_script( 'postbox' );
145
+ ?>
146
+ <div class="wrap slb">
147
+ <h2><?php echo esc_html( $this->get_label( 'header' ) ); ?></h2>
148
+ <?php
149
+ // Form submission support
150
+ if ( $this->is_required_form() ) {
151
+ // Build form output
152
+ $form_id = $this->add_prefix( 'admin_form_' . $this->get_id_raw() );
153
+ $nonce = (object) [
154
+ 'action' => $this->get_id(),
155
+ 'name' => $this->get_id() . '_nonce',
156
+ ];
157
+ ?>
158
+ <form id="<?php echo esc_attr( $form_id ); ?>" name="<?php echo esc_attr( $form_id ); ?>" action="" method="post">
159
+ <?php
160
+ wp_nonce_field( $nonce->action, $nonce->name );
161
+ }
162
+ ?>
163
+ <div class="metabox-holder columns-2">
164
+ <div class="content-primary postbox-container">
165
+ <?php
166
+ $this->render_content( 'primary' );
167
+ ?>
168
+ </div>
169
+ <div class="content-secondary postbox-container">
170
+ <?php
171
+ $this->render_content( 'secondary' );
172
+ ?>
173
+ </div>
174
+ </div>
175
+ <br class="clear" />
176
+ <?php
177
+ // Form submission support
178
+ if ( $this->is_required_form() ) {
179
+ submit_button();
180
+ ?>
181
+ </form>
182
+ <?php
183
+ }
184
+ ?>
185
+ </div>
186
+ <?php
187
+ }
188
+ }
includes/class.admin_section.php CHANGED
@@ -1,51 +1,53 @@
1
- <?php
2
-
3
- /**
4
- * Admin Section
5
- * Sections are part of a Page
6
- * @package Simple Lightbox
7
- * @subpackage Admin
8
- * @author Archetyped
9
- */
10
- class SLB_Admin_Section extends SLB_Admin_View {
11
- /* Properties */
12
-
13
- protected $parent_required = true;
14
- protected $parent_custom = false;
15
-
16
- /* Init */
17
-
18
- public function __construct($id, $parent, $labels, $callback = null, $capability = null) {
19
- // Default
20
- parent::__construct($id, $labels, $callback, $capability);
21
- // Class specific
22
- $this->set_parent($parent);
23
- return $this;
24
- }
25
-
26
- /* Getters/Setters */
27
-
28
- /**
29
- * Retrieve URI
30
- * @uses Admin_View::get_uri()
31
- * @param string $file (optional) Base file name
32
- * @param string $format (optional) String format
33
- * @return string Section URI
34
- */
35
- public function get_uri($file = null, $format = null) {
36
- if ( !is_string($file) )
37
- $file = 'options-' . $this->get_parent() . '.php';
38
- if ( !is_string($format) )
39
- $format = '%1$s#%2$s';
40
- return parent::get_uri($file, $format);
41
- }
42
-
43
- /**
44
- * Retrieve formatted title for section
45
- * Wraps title text in element with anchor so that it can be linked to
46
- * @return string Title
47
- */
48
- public function get_title() {
49
- return sprintf('<div id="%1$s" class="%2$s">%3$s</div>', $this->get_id(), $this->add_prefix('section_head'), $this->get_label('title'));
50
- }
51
- }
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Admin Section
5
+ * Sections are part of a Page
6
+ * @package Simple Lightbox
7
+ * @subpackage Admin
8
+ * @author Archetyped
9
+ */
10
+ class SLB_Admin_Section extends SLB_Admin_View {
11
+ /* Properties */
12
+
13
+ protected $parent_required = true;
14
+ protected $parent_custom = false;
15
+
16
+ /* Init */
17
+
18
+ public function __construct( $id, $parent, $labels, $callback = null, $capability = null ) {
19
+ // Default
20
+ parent::__construct( $id, $labels, $callback, $capability );
21
+ // Class specific
22
+ $this->set_parent( $parent );
23
+ return $this;
24
+ }
25
+
26
+ /* Getters/Setters */
27
+
28
+ /**
29
+ * Retrieve URI
30
+ * @uses Admin_View::get_uri()
31
+ * @param string $file (optional) Base file name
32
+ * @param string $format (optional) String format
33
+ * @return string Section URI
34
+ */
35
+ public function get_uri( $file = null, $format = null ) {
36
+ if ( ! is_string( $file ) ) {
37
+ $file = 'options-' . $this->get_parent() . '.php';
38
+ }
39
+ if ( ! is_string( $format ) ) {
40
+ $format = '%1$s#%2$s';
41
+ }
42
+ return parent::get_uri( $file, $format );
43
+ }
44
+
45
+ /**
46
+ * Retrieve formatted title for section
47
+ * Wraps title text in element with anchor so that it can be linked to
48
+ * @return string Title
49
+ */
50
+ public function get_title() {
51
+ return sprintf( '<div id="%1$s" class="%2$s">%3$s</div>', $this->get_id(), $this->add_prefix( 'section_head' ), $this->get_label( 'title' ) );
52
+ }
53
+ }
includes/class.admin_view.php CHANGED
@@ -1,529 +1,553 @@
1
- <?php
2
-
3
- /**
4
- * Admin View Base
5
- * Core functionality Admin UI components
6
- * @package Simple Lightbox
7
- * @subpackage Admin
8
- * @author Archetyped
9
- */
10
- class SLB_Admin_View extends SLB_Base_Object {
11
- /* Properties */
12
-
13
- /**
14
- * Labels
15
- * @var array (Associative)
16
- */
17
- protected $labels = array();
18
-
19
- /**
20
- * Function to handle building UI
21
- * @var callback
22
- */
23
- protected $callback = null;
24
-
25
- /**
26
- * Capability for access control
27
- * @var string
28
- */
29
- protected $capability = 'manage_options';
30
-
31
- /**
32
- * Icon to use
33
- * @var string
34
- */
35
- protected $icon = null;
36
-
37
- /**
38
- * View parent ID/Slug
39
- * @var string
40
- */
41
- protected $parent = null;
42
-
43
- /**
44
- * Whether parent is a custom view or a default WP one
45
- * @var bool
46
- */
47
- protected $parent_custom = true;
48
-
49
- /**
50
- * If view requires a parent
51
- * @var bool
52
- */
53
- protected $parent_required = false;
54
-
55
- /**
56
- * WP-Generated hook name for view
57
- * @var string
58
- */
59
- protected $hookname = null;
60
-
61
- /**
62
- * Raw content parameters
63
- * Stores pre-rendered content parameters
64
- * Items stored by ID (key)
65
- * @var array
66
- */
67
- protected $content_raw = array();
68
-
69
- /**
70
- * Parsed content parameters
71
- * @var array
72
- */
73
- protected $content = array();
74
-
75
- /**
76
- * Messages to be displayed
77
- * Indexed Array
78
- * @var array
79
- */
80
- protected $messages = array();
81
-
82
- /**
83
- * Required properties
84
- * Associative array
85
- * > Key: Property name
86
- * > Value: Required data type
87
- * @var array
88
- */
89
- private $required = array();
90
-
91
- /**
92
- * Default required properties
93
- * Merged into $required array with this->init_required()
94
- * @see this->required for more information
95
- * @var array
96
- */
97
- private $_required = array ( 'id' => 'string', 'labels' => 'array' );
98
-
99
- /* Init */
100
-
101
- /**
102
- * Constructor
103
- * @return obj Current instance
104
- */
105
- public function __construct($id, $labels, $callback = null, $capability = null, $icon = null) {
106
- $props = array(
107
- 'labels' => $labels,
108
- 'callback' => $callback,
109
- 'capability' => $capability,
110
- 'icon' => $icon,
111
- );
112
- parent::__construct($id, $props);
113
- $this->init_required();
114
- return $this;
115
- }
116
-
117
- protected function init_required() {
118
- $this->required = array_merge($this->_required, $this->required);
119
- // Check for parent requirement
120
- if ( $this->parent_required )
121
- $this->required['parent'] = 'string';
122
- }
123
-
124
- /**
125
- * Set required feature
126
- * @param string $feature Required feature
127
- */
128
- protected function _require($feature) {
129
- if ( !isset($this->_required[$feature]) ) {
130
- $this->_required[$feature] = true;
131
- }
132
- return $this;
133
- }
134
-
135
- /**
136
- * Check if feature is required
137
- * @param string $feature Feature to check for
138
- * @return bool TRUE if feature required
139
- */
140
- protected function _is_required($feature) {
141
- return ( isset($this->_required[$feature]) ) ? true : false;
142
- }
143
-
144
- /* Property Methods */
145
-
146
- /**
147
- * Retrieve ID (Formatted by default)
148
- * @param bool $formatted (optional) Whether ID should be formatted for external use or not
149
- * @return string ID
150
- */
151
- public function get_id($formatted = true) {
152
- $id = parent::get_id();
153
- if ( $formatted )
154
- $this->add_prefix_ref($id);
155
- return $id;
156
- }
157
-
158
- /**
159
- * Retrieve raw ID
160
- * @return string Raw ID
161
- */
162
- public function get_id_raw() {
163
- return $this->get_id(false);
164
- }
165
-
166
- /**
167
- * Retrieve label
168
- * Uses first label (or default if defined) if specified type does not exist
169
- * @param string $type Label type to retrieve
170
- * @param string $default (optional) Default value if label type does not exist
171
- * @return string Label text
172
- */
173
- public function get_label($type, $default = null) {
174
- // Retrieve existing label type
175
- if ( $this->has_label($type) )
176
- return $this->labels[$type];
177
- // Use default label if type is not set
178
- if ( empty($default) && !empty($this->labels) ) {
179
- reset($this->labels);
180
- $default = current($this->labels);
181
- }
182
-
183
- return ( empty($default) ) ? '' : $default;
184
- }
185
-
186
- /**
187
- * Set text labels
188
- * @param array|string $labels
189
- * @return obj Current instance
190
- */
191
- public function set_labels($labels) {
192
- if ( empty($labels) )
193
- return this;
194
- // Single string
195
- if ( is_string($labels) ) {
196
- $labels = array ( $labels );
197
- }
198
-
199
- // Array
200
- if ( is_array($labels) ) {
201
- // Merge with existing labels
202
- if ( empty($this->labels) || !is_array($this->labels) ) {
203
- $this->labels = array();
204
- }
205
- $this->labels = array_merge($this->labels, $labels);
206
- }
207
- return $this;
208
- }
209
-
210
- /**
211
- * Set single text label
212
- * @uses this->set_labels()
213
- * @param string $type Label type to set
214
- * @param string $value Label value
215
- * @return obj Current instance
216
- */
217
- public function set_label($type, $value) {
218
- if ( is_string($type) && is_string($value) ) {
219
- $label = array( $type => $value );
220
- $this->set_labels($label);
221
- }
222
- return $this;
223
- }
224
-
225
- /**
226
- * Checks if specified label is set on view
227
- * @param string $type Label type
228
- * @return bool TRUE if label exists, FALSE otherwise
229
- */
230
- public function has_label($type) {
231
- return ( isset($this->labels[$type]) );
232
- }
233
-
234
- /* Content */
235
-
236
- /**
237
- * Add content block to view
238
- * Child classes define method functionality
239
- * @param string $id Content block ID
240
- * @param array $args Content arguments (Defined by child class), converted to an object
241
- * @return obj Current View instance
242
- */
243
- public function add_content($id, $args) {
244
- // Save parameters
245
- $this->content_raw[$id] = (object) $args;
246
- // Clear parsed content
247
- $this->content = array();
248
- // Return instance reference
249
- return $this;
250
- }
251
-
252
- /**
253
- * Retrieve content
254
- */
255
- protected function get_content($parsed = true) {
256
- $content = $this->content_raw;
257
- if ( $parsed ) {
258
- // Return previously parsed content
259
- if ( !empty($this->content) ) {
260
- $content = $this->content;
261
- }
262
- elseif ( !empty($this->content_raw) ) {
263
- // Parse content before returning
264
- $content = $this->content = $this->parse_content();
265
- }
266
- }
267
- return $content;
268
- }
269
-
270
- /**
271
- * Parse content
272
- * Child classes define functionality
273
- * @return array Parsed content
274
- */
275
- protected function parse_content() {
276
- return $this->get_content(false);
277
- }
278
-
279
- /**
280
- * Check if content has been added to view
281
- * @return bool TRUE if content added
282
- */
283
- protected function has_content() {
284
- $raw = $this->get_content(false);
285
- return !empty($raw);
286
- }
287
-
288
- /**
289
- * Render content
290
- */
291
- protected function render_content($context = 'default') {}
292
-
293
- /**
294
- * Retrieve view messages
295
- * @return array Messages
296
- */
297
- protected function &get_messages() {
298
- if ( !is_array($this->messages) )
299
- $this->messages = array();
300
- return $this->messages;
301
- }
302
-
303
- /**
304
- * Save message
305
- * @param string $text Message text
306
- * @return obj Current instance
307
- */
308
- public function set_message($text) {
309
- $msgs =& $this->get_messages();
310
- $text = trim($text);
311
- if ( empty($msgs) && !empty($text) )
312
- $this->util->add_filter('admin_messages', $this->m('do_messages'), 10, 1, false);
313
- $msgs[] = $text;
314
- return $this;
315
- }
316
-
317
- /**
318
- * Add messages to array
319
- * Called by internal `admin_messages` filter hook
320
- * @param array $msgs Aggregated messages
321
- * @return array Merged messages array
322
- */
323
- public function do_messages($msgs = array()) {
324
- $m =& $this->get_messages();
325
- if ( !empty($m) )
326
- $msgs = array_merge($msgs, $m);
327
- return $msgs;
328
- }
329
-
330
- /**
331
- * Retrieve view callback
332
- * @return callback Callback (Default: standard handler method)
333
- */
334
- public function get_callback() {
335
- return ( $this->has_callback() ) ? $this->callback : $this->m('handle');
336
- }
337
-
338
- /**
339
- * Set callback function for building item
340
- * @param callback $callback Callback function to use
341
- * @return obj Current instance
342
- */
343
- public function set_callback($callback) {
344
- $this->callback = ( is_callable($callback) ) ? $callback : null;
345
- return $this;
346
- }
347
-
348
- /**
349
- * Check if callback set
350
- * @return bool TRUE if callback is set
351
- */
352
- protected function has_callback() {
353
- return ( !empty($this->callback) ) ? true : false;
354
- }
355
-
356
- /**
357
- * Run callback
358
- */
359
- public function do_callback() {
360
- call_user_func($this->get_callback());
361
- }
362
-
363
- /**
364
- * Retrieve capability
365
- * @return string Capability
366
- */
367
- public function get_capability() {
368
- return $this->capability;
369
- }
370
-
371
- /**
372
- * Set capability for access control
373
- * @param string $capability Capability
374
- * @return obj Current instance
375
- */
376
- public function set_capability($capability) {
377
- if ( is_string($capability) && !empty($capability) )
378
- $this->capability = $capability;
379
- return $this;
380
- }
381
-
382
- /**
383
- * Set icon
384
- * @param string $icon Icon URI
385
- * @return obj Current instance
386
- */
387
- public function set_icon($icon) {
388
- if ( !empty($icon) && is_string($icon) )
389
- $this->icon = $icon;
390
- return $this;
391
- }
392
-
393
- protected function get_hookname() {
394
- return ( empty($this->hookname) ) ? '' : $this->hookname;
395
- }
396
-
397
- /**
398
- * Set hookname
399
- * @param string $hookname Hookname value
400
- * @return obj Current instance
401
- */
402
- public function set_hookname($hookname) {
403
- if ( !empty($hookname) && is_string($hookname) )
404
- $this->hookname = $hookname;
405
- return $this;
406
- }
407
-
408
- /**
409
- * Retrieve parent
410
- * Formats parent ID for custom parents
411
- * @uses parent::get_parent()
412
- * @return string Parent ID
413
- */
414
- public function get_parent() {
415
- $parent = parent::get_parent();
416
- return ( $this->is_parent_custom() ) ? $this->add_prefix($parent) : $parent;
417
- }
418
-
419
- /**
420
- * Set parent for view
421
- * @param string $parent Parent ID
422
- * @return obj Current instance
423
- */
424
- public function set_parent($parent) {
425
- if ( $this->parent_required ) {
426
- if ( !empty($parent) && is_string($parent) )
427
- $this->parent = $parent;
428
- } else {
429
- $this->parent = null;
430
- }
431
- return $this;
432
- }
433
-
434
- /**
435
- * Specify whether parent is a custom view or a WP view
436
- * @param bool $custom (optional) TRUE if custom, FALSE if WP
437
- * @return obj Current instance
438
- */
439
- protected function set_parent_custom($custom = true) {
440
- if ( $this->parent_required ) {
441
- $this->parent_custom = !!$custom;
442
- }
443
- return $this;
444
- }
445
-
446
- /**
447
- * Set parent as WP view
448
- * @uses this->set_parent_custom()
449
- * @return obj Current instance
450
- */
451
- public function set_parent_wp() {
452
- $this->set_parent_custom(false);
453
- return $this;
454
- }
455
-
456
- /**
457
- * Get view URI
458
- * URI Structures:
459
- * > Top Level Menus: admin.php?page={menu_id}
460
- * > Pages: [parent_page_file.php|admin.php]?page={page_id}
461
- * > Section: [parent_menu_uri]#{section_id}
462
- *
463
- * @uses $admin_page_hooks to determine if page is child of default WP page
464
- * @param string $file (optional) Base file name
465
- * @param string $format (optional) Format string for URI
466
- * @return string Object URI
467
- */
468
- public function get_uri($file = null, $format = null) {
469
- static $page_hooks = null;
470
- $uri = '';
471
- if ( empty($file) )
472
- $file = 'admin.php';
473
- if ( $this->is_child() ) {
474
- $parent = str_replace('_page_' . $this->get_id(), '', $this->get_hookname());
475
- if ( is_null($page_hooks) ) {
476
- $page_hooks = array_flip($GLOBALS['admin_page_hooks']);
477
- }
478
- if ( isset($page_hooks[$parent]) )
479
- $file = $page_hooks[$parent];
480
- }
481
-
482
- if ( empty($format) ) {
483
- $delim = ( strpos($file, '?') === false ) ? '?' : '&amp;';
484
- $format = '%1$s' . $delim . 'page=%2$s';
485
- }
486
- $uri = sprintf($format, $file, $this->get_id());
487
-
488
- return $uri;
489
- }
490
-
491
- /* Handlers */
492
-
493
- /**
494
- * Default View handler
495
- * Used as callback when none set
496
- */
497
- public function handle() {}
498
-
499
- /* Validation */
500
-
501
- /**
502
- * Check if instance is valid based on required properties/data types
503
- * @return bool TRUE if valid, FALSE if not valid
504
- */
505
- public function is_valid() {
506
- $valid = true;
507
- foreach ( $this->required as $prop => $type ) {
508
- if ( empty($this->{$prop} )
509
- || ( !empty($type) && is_string($type) && ( $f = 'is_' . $type ) && function_exists($f) && !$f($this->{$prop}) ) ) {
510
- $valid = false;
511
- break;
512
- }
513
- }
514
- return $valid;
515
- }
516
-
517
- protected function is_child() {
518
- return $this->parent_required;
519
- }
520
-
521
- protected function is_parent_custom() {
522
- return ( $this->is_child() && $this->parent_custom ) ? true : false;
523
- }
524
-
525
- public function is_parent_wp() {
526
- return ( $this->is_child() && !$this->parent_custom ) ? true : false;
527
- }
528
-
529
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Admin View Base
5
+ * Core functionality Admin UI components
6
+ * @package Simple Lightbox
7
+ * @subpackage Admin
8
+ * @author Archetyped
9
+ */
10
+ class SLB_Admin_View extends SLB_Base_Object {
11
+ /* Properties */
12
+
13
+ /**
14
+ * Labels
15
+ * @var array (Associative)
16
+ */
17
+ protected $labels = array();
18
+
19
+ /**
20
+ * Function to handle building UI
21
+ * @var callback
22
+ */
23
+ protected $callback = null;
24
+
25
+ /**
26
+ * Capability for access control
27
+ * @var string
28
+ */
29
+ protected $capability = 'manage_options';
30
+
31
+ /**
32
+ * Icon to use
33
+ * @var string
34
+ */
35
+ protected $icon = null;
36
+
37
+ /**
38
+ * View parent ID/Slug
39
+ * @var string
40
+ */
41
+ protected $parent = null;
42
+
43
+ /**
44
+ * Whether parent is a custom view or a default WP one
45
+ * @var bool
46
+ */
47
+ protected $parent_custom = true;
48
+
49
+ /**
50
+ * If view requires a parent
51
+ * @var bool
52
+ */
53
+ protected $parent_required = false;
54
+
55
+ /**
56
+ * WP-Generated hook name for view
57
+ * @var string
58
+ */
59
+ protected $hookname = null;
60
+
61
+ /**
62
+ * Raw content parameters
63
+ * Stores pre-rendered content parameters
64
+ * Items stored by ID (key)
65
+ * @var array
66
+ */
67
+ protected $content_raw = array();
68
+
69
+ /**
70
+ * Parsed content parameters
71
+ * @var array
72
+ */
73
+ protected $content = array();
74
+
75
+ /**
76
+ * Messages to be displayed
77
+ * Indexed Array
78
+ * @var array
79
+ */
80
+ protected $messages = array();
81
+
82
+ /**
83
+ * Required properties
84
+ * Associative array
85
+ * > Key: Property name
86
+ * > Value: Required data type
87
+ * @var array
88
+ */
89
+ private $required = array();
90
+
91
+ /**
92
+ * Default required properties
93
+ * Merged into $required array with this->init_required()
94
+ * @see this->required for more information
95
+ * @var array
96
+ */
97
+ private $_required = array(
98
+ 'id' => 'string',
99
+ 'labels' => 'array',
100
+ );
101
+
102
+ /* Init */
103
+
104
+ /**
105
+ * Constructor
106
+ * @return obj Current instance
107
+ */
108
+ public function __construct( $id, $labels, $callback = null, $capability = null, $icon = null ) {
109
+ $props = array(
110
+ 'labels' => $labels,
111
+ 'callback' => $callback,
112
+ 'capability' => $capability,
113
+ 'icon' => $icon,
114
+ );
115
+ parent::__construct( $id, $props );
116
+ $this->init_required();
117
+ return $this;
118
+ }
119
+
120
+ protected function init_required() {
121
+ $this->required = array_merge( $this->_required, $this->required );
122
+ // Check for parent requirement
123
+ if ( $this->parent_required ) {
124
+ $this->required['parent'] = 'string';
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Set required feature
130
+ * @param string $feature Required feature
131
+ */
132
+ protected function _require( $feature ) {
133
+ if ( ! isset( $this->_required[ $feature ] ) ) {
134
+ $this->_required[ $feature ] = true;
135
+ }
136
+ return $this;
137
+ }
138
+
139
+ /**
140
+ * Check if feature is required
141
+ * @param string $feature Feature to check for
142
+ * @return bool TRUE if feature required
143
+ */
144
+ protected function _is_required( $feature ) {
145
+ return ( isset( $this->_required[ $feature ] ) ) ? true : false;
146
+ }
147
+
148
+ /* Property Methods */
149
+
150
+ /**
151
+ * Retrieve ID (Formatted by default)
152
+ * @param bool $formatted (optional) Whether ID should be formatted for external use or not
153
+ * @return string ID
154
+ */
155
+ public function get_id( $formatted = true ) {
156
+ $id = parent::get_id();
157
+ if ( $formatted ) {
158
+ $this->add_prefix_ref( $id );
159
+ }
160
+ return $id;
161
+ }
162
+
163
+ /**
164
+ * Retrieve raw ID
165
+ * @return string Raw ID
166
+ */
167
+ public function get_id_raw() {
168
+ return $this->get_id( false );
169
+ }
170
+
171
+ /**
172
+ * Retrieve label
173
+ * Uses first label (or default if defined) if specified type does not exist
174
+ * @param string $type Label type to retrieve
175
+ * @param string $default (optional) Default value if label type does not exist
176
+ * @return string Label text
177
+ */
178
+ public function get_label( $type, $default = null ) {
179
+ // Retrieve existing label type
180
+ if ( $this->has_label( $type ) ) {
181
+ return $this->labels[ $type ];
182
+ }
183
+ // Use default label if type is not set
184
+ if ( empty( $default ) && ! empty( $this->labels ) ) {
185
+ reset( $this->labels );
186
+ $default = current( $this->labels );
187
+ }
188
+
189
+ return ( empty( $default ) ) ? '' : $default;
190
+ }
191
+
192
+ /**
193
+ * Set text labels
194
+ * @param array|string $labels
195
+ * @return obj Current instance
196
+ */
197
+ public function set_labels( $labels ) {
198
+ if ( empty( $labels ) ) {
199
+ return this;
200
+ }
201
+ // Single string
202
+ if ( is_string( $labels ) ) {
203
+ $labels = array( $labels );
204
+ }
205
+
206
+ // Array
207
+ if ( is_array( $labels ) ) {
208
+ // Merge with existing labels
209
+ if ( empty( $this->labels ) || ! is_array( $this->labels ) ) {
210
+ $this->labels = array();
211
+ }
212
+ $this->labels = array_merge( $this->labels, $labels );
213
+ }
214
+ return $this;
215
+ }
216
+
217
+ /**
218
+ * Set single text label
219
+ * @uses this->set_labels()
220
+ * @param string $type Label type to set
221
+ * @param string $value Label value
222
+ * @return obj Current instance
223
+ */
224
+ public function set_label( $type, $value ) {
225
+ if ( is_string( $type ) && is_string( $value ) ) {
226
+ $label = array( $type => $value );
227
+ $this->set_labels( $label );
228
+ }
229
+ return $this;
230
+ }
231
+
232
+ /**
233
+ * Checks if specified label is set on view
234
+ * @param string $type Label type
235
+ * @return bool TRUE if label exists, FALSE otherwise
236
+ */
237
+ public function has_label( $type ) {
238
+ return ( isset( $this->labels[ $type ] ) );
239
+ }
240
+
241
+ /* Content */
242
+
243
+ /**
244
+ * Add content block to view
245
+ * Child classes define method functionality
246
+ * @param string $id Content block ID
247
+ * @param array $args Content arguments (Defined by child class), converted to an object
248
+ * @return obj Current View instance
249
+ */
250
+ public function add_content( $id, $args ) {
251
+ // Save parameters
252
+ $this->content_raw[ $id ] = (object) $args;
253
+ // Clear parsed content
254
+ $this->content = array();
255
+ // Return instance reference
256
+ return $this;
257
+ }
258
+
259
+ /**
260
+ * Retrieve content
261
+ */
262
+ protected function get_content( $parsed = true ) {
263
+ // Return raw content.
264
+ if ( ! $parsed ) {
265
+ return $this->content_raw;
266
+ }
267
+
268
+ // Return parsed content.
269
+ if ( empty( $this->content ) && ! empty( $this->content_raw ) ) {
270
+ $this->content = $this->parse_content();
271
+ }
272
+
273
+ return $this->content;
274
+ }
275
+
276
+ /**
277
+ * Parse content
278
+ * Child classes define functionality
279
+ * @return array Parsed content
280
+ */
281
+ protected function parse_content() {
282
+ return $this->get_content( false );
283
+ }
284
+
285
+ /**
286
+ * Check if content has been added to view
287
+ * @return bool TRUE if content added
288
+ */
289
+ protected function has_content() {
290
+ $raw = $this->get_content( false );
291
+ return ! empty( $raw );
292
+ }
293
+
294
+ /**
295
+ * Render content
296
+ */
297
+ protected function render_content( $context = 'default' ) {}
298
+
299
+ /**
300
+ * Retrieve view messages
301
+ * @return array Messages
302
+ */
303
+ protected function &get_messages() {
304
+ if ( ! is_array( $this->messages ) ) {
305
+ $this->messages = array();
306
+ }
307
+ return $this->messages;
308
+ }
309
+
310
+ /**
311
+ * Save message
312
+ * @param string $text Message text
313
+ * @return obj Current instance
314
+ */
315
+ public function set_message( $text ) {
316
+ $msgs =& $this->get_messages();
317
+ $text = trim( $text );
318
+ if ( empty( $msgs ) && ! empty( $text ) ) {
319
+ $this->util->add_filter( 'admin_messages', $this->m( 'do_messages' ), 10, 1, false );
320
+ }
321
+ $msgs[] = $text;
322
+ return $this;
323
+ }
324
+
325
+ /**
326
+ * Add messages to array
327
+ * Called by internal `admin_messages` filter hook
328
+ * @param array $msgs Aggregated messages
329
+ * @return array Merged messages array
330
+ */
331
+ public function do_messages( $msgs = array() ) {
332
+ $m =& $this->get_messages();
333
+ if ( ! empty( $m ) ) {
334
+ $msgs = array_merge( $msgs, $m );
335
+ }
336
+ return $msgs;
337
+ }
338
+
339
+ /**
340
+ * Retrieve view callback
341
+ * @return callback Callback (Default: standard handler method)
342
+ */
343
+ public function get_callback() {
344
+ return ( $this->has_callback() ) ? $this->callback : $this->m( 'handle' );
345
+ }
346
+
347
+ /**
348
+ * Set callback function for building item
349
+ * @param callback $callback Callback function to use
350
+ * @return obj Current instance
351
+ */
352
+ public function set_callback( $callback ) {
353
+ $this->callback = ( is_callable( $callback ) ) ? $callback : null;
354
+ return $this;
355
+ }
356
+
357
+ /**
358
+ * Check if callback set
359
+ * @return bool TRUE if callback is set
360
+ */
361
+ protected function has_callback() {
362
+ return ( ! empty( $this->callback ) ) ? true : false;
363
+ }
364
+
365
+ /**
366
+ * Run callback
367
+ */
368
+ public function do_callback() {
369
+ call_user_func( $this->get_callback() );
370
+ }
371
+
372
+ /**
373
+ * Retrieve capability
374
+ * @return string Capability
375
+ */
376
+ public function get_capability() {
377
+ return $this->capability;
378
+ }
379
+
380
+ /**
381
+ * Set capability for access control
382
+ * @param string $capability Capability
383
+ * @return obj Current instance
384
+ */
385
+ public function set_capability( $capability ) {
386
+ if ( is_string( $capability ) && ! empty( $capability ) ) {
387
+ $this->capability = $capability;
388
+ }
389
+ return $this;
390
+ }
391
+
392
+ /**
393
+ * Set icon
394
+ * @param string $icon Icon URI
395
+ * @return obj Current instance
396
+ */
397
+ public function set_icon( $icon ) {
398
+ if ( ! empty( $icon ) && is_string( $icon ) ) {
399
+ $this->icon = $icon;
400
+ }
401
+ return $this;
402
+ }
403
+
404
+ protected function get_hookname() {
405
+ return ( empty( $this->hookname ) ) ? '' : $this->hookname;
406
+ }
407
+
408
+ /**
409
+ * Set hookname
410
+ * @param string $hookname Hookname value
411
+ * @return obj Current instance
412
+ */
413
+ public function set_hookname( $hookname ) {
414
+ if ( ! empty( $hookname ) && is_string( $hookname ) ) {
415
+ $this->hookname = $hookname;
416
+ }
417
+ return $this;
418
+ }
419
+
420
+ /**
421
+ * Retrieve parent
422
+ * Formats parent ID for custom parents
423
+ * @uses parent::get_parent()
424
+ * @return string Parent ID
425
+ */
426
+ public function get_parent() {
427
+ $parent = parent::get_parent();
428
+ return ( $this->is_parent_custom() ) ? $this->add_prefix( $parent ) : $parent;
429
+ }
430
+
431
+ /**
432
+ * Set parent for view
433
+ * @param string $parent Parent ID
434
+ * @return obj Current instance
435
+ */
436
+ public function set_parent( $parent ) {
437
+ if ( $this->parent_required ) {
438
+ if ( ! empty( $parent ) && is_string( $parent ) ) {
439
+ $this->parent = $parent;
440
+ }
441
+ } else {
442
+ $this->parent = null;
443
+ }
444
+ return $this;
445
+ }
446
+
447
+ /**
448
+ * Specify whether parent is a custom view or a WP view
449
+ * @param bool $custom (optional) TRUE if custom, FALSE if WP
450
+ * @return obj Current instance
451
+ */
452
+ protected function set_parent_custom( $custom = true ) {
453
+ if ( $this->parent_required ) {
454
+ $this->parent_custom = ! ! $custom;
455
+ }
456
+ return $this;
457
+ }
458
+
459
+ /**
460
+ * Set parent as WP view
461
+ * @uses this->set_parent_custom()
462
+ * @return obj Current instance
463
+ */
464
+ public function set_parent_wp() {
465
+ $this->set_parent_custom( false );
466
+ return $this;
467
+ }
468
+
469
+ /**
470
+ * Get view URI
471
+ * URI Structures:
472
+ * > Top Level Menus: admin.php?page={menu_id}
473
+ * > Pages: [parent_page_file.php|admin.php]?page={page_id}
474
+ * > Section: [parent_menu_uri]#{section_id}
475
+ *
476
+ * @uses $admin_page_hooks to determine if page is child of default WP page
477
+ * @param string $file (optional) Base file name
478
+ * @param string $format (optional) Format string for URI
479
+ * @return string Object URI
480
+ */
481
+ public function get_uri( $file = null, $format = null ) {
482
+ static $page_hooks = null;
483
+ $uri = '';
484
+ if ( empty( $file ) ) {
485
+ $file = 'admin.php';
486
+ }
487
+ if ( $this->is_child() ) {
488
+ $parent = str_replace( '_page_' . $this->get_id(), '', $this->get_hookname() );
489
+ if ( is_null( $page_hooks ) ) {
490
+ $page_hooks = array_flip( $GLOBALS['admin_page_hooks'] );
491
+ }
492
+ if ( isset( $page_hooks[ $parent ] ) ) {
493
+ $file = $page_hooks[ $parent ];
494
+ }
495
+ }
496
+
497
+ if ( empty( $format ) ) {
498
+ $delim = ( strpos( $file, '?' ) === false ) ? '?' : '&amp;';
499
+ $format = '%1$s' . $delim . 'page=%2$s';
500
+ }
501
+ $uri = sprintf( $format, $file, $this->get_id() );
502
+
503
+ return $uri;
504
+ }
505
+
506
+ /* Handlers */
507
+
508
+ /**
509
+ * Default View handler
510
+ * Used as callback when none set
511
+ */
512
+ public function handle() {}
513
+
514
+ /* Validation */
515
+
516
+ /**
517
+ * Check if instance is valid based on required properties/data types
518
+ * @return bool TRUE if valid, FALSE if not valid
519
+ */
520
+ public function is_valid() {
521
+ $invalid = false;
522
+ foreach ( $this->required as $prop => $type ) {
523
+ // Baseline validation.
524
+ if (
525
+ empty( $this->{$prop} )
526
+ || empty( $type )
527
+ || ! is_string( $type )
528
+ ) {
529
+ return $invalid;
530
+ }
531
+
532
+ // Validate data type.
533
+ $f = 'is_' . $type;
534
+ if ( ! function_exists( $f ) || ! $f( $this->{$prop} ) ) {
535
+ return $invalid;
536
+ }
537
+ }
538
+ return true;
539
+ }
540
+
541
+ protected function is_child() {
542
+ return $this->parent_required;
543
+ }
544
+
545
+ protected function is_parent_custom() {
546
+ return ( $this->is_child() && $this->parent_custom ) ? true : false;
547
+ }
548
+
549
+ public function is_parent_wp() {
550
+ return ( $this->is_child() && ! $this->parent_custom ) ? true : false;
551
+ }
552
+
553
+ }
includes/class.base.php CHANGED
@@ -1,555 +1,561 @@
1
- <?php
2
-
3
- /**
4
- * @package Simple Lightbox
5
- * @subpackage Base
6
- * @author Archetyped
7
- *
8
- */
9
- class SLB_Base {
10
- /* Configuration */
11
-
12
- /**
13
- * Class type
14
- * Controls initialization, etc.
15
- * > full - Fully-functional class
16
- * > sub - Sub-class (attached to an instance)
17
- * > object - Simple object class (no hooks, etc.)
18
- * @var string
19
- */
20
- protected $mode = 'full';
21
-
22
- /**
23
- * Indicates that instance is model (main controller)
24
- * @var bool
25
- */
26
- protected $model = false;
27
-
28
- /* Properties */
29
-
30
- /**
31
- * Variable name of base object in global scope
32
- * @var string
33
- */
34
- protected $base = 'slb';
35
-
36
- /**
37
- * Prefix for plugin-related data (attributes, DB tables, etc.)
38
- * @var string
39
- */
40
- public $prefix = 'slb';
41
-
42
- /**
43
- * Prefix to be added when creating internal hook (action/filter) tags
44
- * Used by Utilities
45
- * @var string
46
- */
47
- public $hook_prefix = '';
48
-
49
- /**
50
- * Global data
51
- * Facilitates sharing between decoupled objects
52
- * @var array
53
- */
54
- private static $globals = array();
55
-
56
- protected $shared = array('options', 'admin');
57
-
58
- /**
59
- * Capabilities
60
- * @var array
61
- */
62
- protected $caps = null;
63
-
64
- protected $_init = false;
65
-
66
- private static $_init_passed = false;
67
-
68
- /* Client */
69
-
70
- /**
71
- * Client files
72
- * @var array
73
- * Structure
74
- * > Key: unique file ID
75
- * > Properties
76
- * > file (string) File path (Relative to plugin base)
77
- * > deps (array) Script dependencies
78
- * > Internal dependencies are wrapped in square brackets ([])
79
- * > context (string|array)
80
- * > Context in which the script should be included
81
- * > in_footer (bool) optional [Default: FALSE]
82
- * > If TRUE, file will be included in footer of page, otherwise it will be included in the header
83
- *
84
- * Array is processed and converted to an object on init
85
- */
86
- private $client_files = array (
87
- 'scripts' => array(),
88
- 'styles' => array()
89
- );
90
-
91
- /*-** Instances **-*/
92
-
93
- /**
94
- * Utilities
95
- * @var SLB_Utilities
96
- */
97
- var $util = null;
98
-
99
- /**
100
- * Options
101
- * @var SLB_Options
102
- */
103
- protected $options = null;
104
-
105
- /**
106
- * Admin
107
- * @var SLB_Admin
108
- */
109
- var $admin = null;
110
-
111
- /*-** Initialization **-*/
112
-
113
- /**
114
- * Constructor
115
- */
116
- function __construct() {
117
- $this->util = new SLB_Utilities($this);
118
- if ( $this->can('init') ) {
119
- $hook = 'init';
120
- if ( did_action($hook) || self::$_init_passed ) {
121
- $this->_init();
122
- } else {
123
- add_action($hook, $this->m('_init'), 1);
124
- }
125
- }
126
- }
127
-
128
- /**
129
- * Default initialization method
130
- * @uses _init_passed
131
- * @uses _env()
132
- * @uses _options()
133
- * @uses _admin()
134
- * @uses _hooks()
135
- * @uses _client_files()
136
- */
137
- public function _init() {
138
- self::$_init_passed = true;
139
- if ( $this->_init || !isset($this) || !$this->can('init') )
140
- return false;
141
- $this->_init = true;
142
- // Environment
143
- $this->_env();
144
-
145
- if ( $this->can('control') ) {
146
- // Options
147
- $this->_options();
148
-
149
- // Admin
150
- if ( is_admin() )
151
- $this->_admin();
152
- }
153
-
154
- // Hooks
155
- $this->_hooks();
156
-
157
- // Client files
158
- $this->_client_files();
159
- }
160
-
161
- /**
162
- * Initialize environment (Localization, etc.)
163
- */
164
- private function _env() {
165
- if ( !$this->can('singleton') ) {
166
- return false;
167
- }
168
- // Localization
169
- $ldir = 'l10n';
170
- $lpath = $this->util->get_plugin_file_path($ldir, array(false, false));
171
- $lpath_abs = $this->util->get_file_path($ldir);
172
- if ( is_dir($lpath_abs) ) {
173
- load_plugin_textdomain('simple-lightbox', false, $lpath);
174
- }
175
-
176
- // Context
177
- add_action( ( is_admin() ) ? 'admin_print_footer_scripts' : 'wp_footer', $this->util->m('set_client_context'), $this->util->priority('client_footer_output') );
178
- }
179
-
180
- /**
181
- * Initialize options
182
- * To be implemented in child classes
183
- */
184
- protected function _options() {}
185
-
186
- /**
187
- * Initialize options
188
- * To be called by child class
189
- */
190
- protected function _set_options($options_config = null) {
191
- $class = $this->util->get_class('Options');
192
- $key = 'options';
193
- if ( $this->shares($key) ) {
194
- $opts = $this->gvar($key);
195
- // Setup options instance
196
- if ( !($opts instanceof $class) ) {
197
- $opts = $this->gvar($key, new $class());
198
- }
199
- } else {
200
- $opts = new $class();
201
- }
202
- // Load options
203
- if ( $this->is_options_valid($options_config, false) ) {
204
- $opts->load($options_config);
205
- }
206
- // Set instance property
207
- $this->options = $opts;
208
- }
209
-
210
- /**
211
- * Initialize admin
212
- * To be called by child class
213
- */
214
- private function _admin() {
215
- if ( !is_admin() ) {
216
- return false;
217
- }
218
- $class = $this->util->get_class('Admin');
219
- $key = 'admin';
220
- if ( $this->shares($key) ) {
221
- $adm = $this->gvar($key);
222
- // Setup options instance
223
- if ( !($adm instanceof $class) ) {
224
- $adm = $this->gvar($key, new $class($this));
225
- }
226
- } else {
227
- $adm = new $class($this);
228
- }
229
- // Set instance property
230
- $this->admin = $adm;
231
- }
232
-
233
- /**
234
- * Register default hooks
235
- */
236
- protected function _hooks() {
237
- $base = $this->util->get_plugin_base_file();
238
- // Activation
239
- $func_activate = '_activate';
240
- if ( method_exists($this, $func_activate) )
241
- register_activation_hook($base, $this->m($func_activate));
242
-
243
- // Deactivation
244
- $func_deactivate = '_deactivate';
245
- if ( method_exists($this, $func_deactivate) )
246
- register_deactivation_hook($base, $this->m($func_deactivate));
247
- }
248
-
249
- /**
250
- * Initialize client files
251
- */
252
- protected function _client_files($files = null) {
253
- // Validation
254
- if ( !is_array($files) || empty($files) ) {
255
- return false;
256
- }
257
- foreach ( $this->client_files as $key => $val ) {
258
- if ( isset($files[$key]) && is_array($files[$key]) || !empty($files[$key]) ) {
259
- $this->client_files[$key] = $this->util->parse_client_files($files[$key], $key);
260
- }
261
- // Remove empty file groups
262
- if ( empty($this->client_files[$key]) ) {
263
- unset($this->client_files[$key]);
264
- }
265
- }
266
-
267
-
268
- // Stop if no files are set for registration
269
- if ( empty($this->client_files) ) {
270
- return false;
271
- }
272
-
273
- // Register
274
- add_action('init', $this->m('register_client_files'));
275
-
276
- // Enqueue
277
- $hk_prfx = ( ( is_admin() ) ? 'admin' : 'wp' );
278
- $hk_enqueue = $hk_prfx . '_enqueue_scripts' ;
279
- $hk_enqueue_ft = $hk_prfx . '_footer';
280
- add_action($hk_enqueue, $this->m('enqueue_client_files'), 10, 0);
281
- add_action($hk_enqueue_ft, $this->m('enqueue_client_files_footer'), 1);
282
- }
283
-
284
- /**
285
- * Register client files
286
- * @see enqueue_client_files() for actual loading of files based on context
287
- * @uses `init` Action hook for execution
288
- * @return void
289
- */
290
- public function register_client_files() {
291
- $v = $this->util->get_plugin_version();
292
- foreach ( $this->client_files as $type => $files ) {
293
- $func = $this->get_client_files_handler($type, 'register');
294
- if ( !$func )
295
- continue;
296
- foreach ( $files as $f ) {
297
- // Get file URI
298
- $f->file = ( !$this->util->is_file($f->file) && is_callable($f->file) ) ? call_user_func($f->file) : $this->util->get_file_url($f->file, true);
299
- $params = array($f->id, $f->file, $f->deps, $v);
300
- // Set additional parameters based on file type (script, style, etc.)
301
- switch ( $type ) {
302
- case 'scripts':
303
- $params[] = $f->in_footer;
304
- break;
305
- case 'styles':
306
- $params[] = $f->media;
307
- break;
308
- }
309
- // Register file
310
- call_user_func_array($func, $params);
311
- }
312
- }
313
- }
314
-
315
- /**
316
- * Enqueues files for client output (scripts/styles) based on context
317
- * @uses `admin_enqueue_scripts` Action hook depending on context
318
- * @uses `wp_enqueue_scripts` Action hook depending on context
319
- * @param bool $footer (optional) Whether to enqueue footer files (Default: No)
320
- * @return void
321
- */
322
- function enqueue_client_files($footer = false) {
323
- // Validate
324
- if ( !is_bool($footer) ) {
325
- $footer = false;
326
- }
327
- // Enqueue files
328
- foreach ( $this->client_files as $type => $files ) {
329
- $func = $this->get_client_files_handler($type, 'enqueue');
330
- if ( !$func ) {
331
- continue;
332
- }
333
- foreach ( $files as $fkey => $f ) {
334
- // Skip previously-enqueued files and shadow files
335
- if ( $f->enqueued || !$f->enqueue ) {
336
- continue;
337
- }
338
- // Enqueue files only for current location (header/footer)
339
- if ( isset($f->in_footer) ) {
340
- if ( $f->in_footer != $footer ) {
341
- continue;
342
- }
343
- } elseif ( $footer ) {
344
- continue;
345
- }
346
- $load = true;
347
- // Global Callback
348
- if ( is_callable($f->callback) && !call_user_func($f->callback) ) {
349
- $load = false;
350
- }
351
- // Context
352
- if ( $load && !empty($f->context) ) {
353
- // Reset $load before evaluating context
354
- $load = false;
355
- // Iterate through contexts
356
- foreach ( $f->context as $ctx ) {
357
- // Context + Callback
358
- if ( is_array($ctx) ) {
359
- // Stop checking context if callback is invalid
360
- if ( !is_callable($ctx[1]) || !call_user_func($ctx[1]) )
361
- continue;
362
- $ctx = $ctx[0];
363
- }
364
- // Stop checking context if valid context found
365
- if ( $this->util->is_context($ctx) ) {
366
- $load = true;
367
- break;
368
- }
369
- }
370
- }
371
- // Load valid file
372
- if ( $load ) {
373
- // Mark file as enqueued
374
- $this->client_files[$type]->{$fkey}->enqueued = true;
375
- $func($f->id);
376
- }
377
- }
378
- }
379
- }
380
-
381
- /**
382
- * Enqueue client files in the footer
383
- */
384
- public function enqueue_client_files_footer() {
385
- $this->enqueue_client_files(true);
386
- }
387
-
388
- /**
389
- * Build function name for handling client operations
390
- */
391
- function get_client_files_handler($type, $action) {
392
- $func = 'wp_' . $action . '_' . substr($type, 0, -1);
393
- if ( !function_exists($func) )
394
- $func = false;
395
- return $func;
396
- }
397
-
398
- /*-** Reflection **-*/
399
-
400
- /**
401
- * Retrieve base object
402
- * @return object|bool Base object (FALSE if object does not exist)
403
- */
404
- function &get_base() {
405
- $base = false;
406
- if ( isset($GLOBALS[$this->base]) )
407
- $base =& $GLOBALS[$this->base];
408
- return $base;
409
- }
410
-
411
- /*-** Method/Function calling **-*/
412
-
413
- /**
414
- * Returns callback to instance method
415
- * @param string $method Method name
416
- * @return array Callback array
417
- */
418
- function m($method) {
419
- return $this->util->m($this, $method);
420
- }
421
-
422
- /*-** Prefix **-*/
423
-
424
- /**
425
- * Retrieve class prefix (with separator if set)
426
- * @param bool|string $sep Separator to append to class prefix (Default: no separator)
427
- * @return string Class prefix
428
- */
429
- function get_prefix($sep = null) {
430
- $args = func_get_args();
431
- return call_user_func_array($this->util->m($this->util, 'get_prefix'), $args);
432
- }
433
-
434
- /**
435
- * Check if a string is prefixed
436
- * @param string $text Text to check for prefix
437
- * @param string $sep (optional) Separator used
438
- */
439
- function has_prefix($text, $sep = null) {
440
- $args = func_get_args();
441
- return call_user_func_array($this->util->m($this->util, 'has_prefix'), $args);
442
- }
443
-
444
- /**
445
- * Prepend plugin prefix to some text
446
- * @param string $text Text to add to prefix
447
- * @param string $sep (optional) Text used to separate prefix and text
448
- * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
449
- * @return string Text with prefix prepended
450
- */
451
- function add_prefix($text, $sep = null, $once = true) {
452
- $args = func_get_args();
453
- return call_user_func_array($this->util->m($this->util, 'add_prefix'), $args);
454
- }
455
-
456
- /**
457
- * Prepend uppercased plugin prefix to some text
458
- * @param string $text Text to add to prefix
459
- * @param string $sep (optional) Text used to separate prefix and text
460
- * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
461
- * @return string Text with prefix prepended
462
- */
463
- function add_prefix_uc($text, $sep = null, $once = true) {
464
- $args = func_get_args();
465
- return call_user_func_array($this->util->m($this->util, 'add_prefix_uc'), $args);
466
- }
467
-
468
- /**
469
- * Add prefix to variable reference
470
- * Updates actual variable rather than return value
471
- * @uses SLB_Utilities::add_prefix_ref();
472
- * @param string $var Variable to add prefix to
473
- * @param string $sep (optional) Separator text
474
- * @param bool $once (optional) Add prefix only once
475
- * @return void
476
- */
477
- function add_prefix_ref(&$var, $sep = null, $once = true) {
478
- $args = func_get_args();
479
- $args[0] =& $var;
480
- call_user_func_array($this->util->m($this->util, 'add_prefix_ref'), $args);
481
- }
482
-
483
- /**
484
- * Remove prefix from specified string
485
- * @param string $text String to remove prefix from
486
- * @param string $sep (optional) Separator used with prefix
487
- */
488
- function remove_prefix($text, $sep = null) {
489
- $args = func_get_args();
490
- return call_user_func_array($this->util->m($this->util, 'remove_prefix'), $args);
491
- }
492
-
493
- /*-** Capabilities **-*/
494
-
495
- protected function can($cap) {
496
- if ( is_null($this->caps) ) {
497
- // Build capabilities based on instance properties
498
- $this->caps = array(
499
- 'init' => ( 'object' != $this->mode ) ? true : false,
500
- 'singleton' => ( !!$this->model ) ? true : false,
501
- 'control' => ( 'sub' == $this->mode || 'object' == $this->mode ) ? false : true,
502
- );
503
- }
504
- return ( isset($this->caps[$cap]) ) ? $this->caps[$cap] : false;
505
- }
506
-
507
- /*-** Globals **-*/
508
-
509
- /**
510
- * Get/Set (internal) global variables
511
- * @uses $globals to get/set global variables
512
- * @param string $name Variable name - If no name is specified, entire globals array is returned
513
- * @param mixed $val (optional) Set the value of a variable (Returns variable value if omitted)
514
- * @return mixed Variable value
515
- */
516
- private function gvar($name = null, $val = null) {
517
- $g =& self::$globals;
518
- if ( !is_array($g) ) {
519
- $g = array();
520
- }
521
- if ( !is_string($name) || empty($name) ) {
522
- return $g;
523
- }
524
- $ret = $val;
525
- if ( null !== $val ) {
526
- // Set Value
527
- $g[$name] = $val;
528
- } elseif ( isset($g[$name]) ) {
529
- // Retrieve variable
530
- $ret = $g[$name];
531
- }
532
- return $ret;
533
- }
534
-
535
- private function shares($name) {
536
- return ( !empty($this->shared) && in_array($name, $this->shared) ) ? true : false;
537
- }
538
-
539
- /*-** Options **-*/
540
-
541
- /**
542
- * Checks if options are valid
543
- * @param array $data Data to be used on options
544
- * @return bool TRUE if options are valid, FALSE otherwise
545
- */
546
- function is_options_valid($data, $check_var = true) {
547
- $class = $this->util->get_class('Options');
548
- $ret = ( empty($data) || !is_array($data) || !class_exists($class) ) ? false : true;
549
- if ( $ret && $check_var && !($this->options instanceof $class) )
550
- $ret = false;
551
- return $ret;
552
- }
553
- }
554
-
555
- ?>
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package Simple Lightbox
5
+ * @subpackage Base
6
+ * @author Archetyped
7
+ *
8
+ */
9
+ class SLB_Base {
10
+ /* Configuration */
11
+
12
+ /**
13
+ * Class type
14
+ * Controls initialization, etc.
15
+ * > full - Fully-functional class
16
+ * > sub - Sub-class (attached to an instance)
17
+ * > object - Simple object class (no hooks, etc.)
18
+ * @var string
19
+ */
20
+ protected $mode = 'full';
21
+
22
+ /**
23
+ * Indicates that instance is model (main controller)
24
+ * @var bool
25
+ */
26
+ protected $model = false;
27
+
28
+ /* Properties */
29
+
30
+ /**
31
+ * Variable name of base object in global scope
32
+ * @var string
33
+ */
34
+ protected $base = 'slb';
35
+
36
+ /**
37
+ * Prefix for plugin-related data (attributes, DB tables, etc.)
38
+ * @var string
39
+ */
40
+ public $prefix = 'slb';
41
+
42
+ /**
43
+ * Prefix to be added when creating internal hook (action/filter) tags
44
+ * Used by Utilities
45
+ * @var string
46
+ */
47
+ public $hook_prefix = '';
48
+
49
+ /**
50
+ * Global data
51
+ * Facilitates sharing between decoupled objects
52
+ * @var array
53
+ */
54
+ private static $globals = array();
55
+
56
+ protected $shared = array( 'options', 'admin' );
57
+
58
+ /**
59
+ * Capabilities
60
+ * @var array
61
+ */
62
+ protected $caps = null;
63
+
64
+ protected $_init = false;
65
+
66
+ private static $_init_passed = false;
67
+
68
+ /* Client */
69
+
70
+ /**
71
+ * Client files
72
+ * @var array
73
+ * Structure
74
+ * > Key: unique file ID
75
+ * > Properties
76
+ * > file (string) File path (Relative to plugin base)
77
+ * > deps (array) Script dependencies
78
+ * > Internal dependencies are wrapped in square brackets ([])
79
+ * > context (string|array)
80
+ * > Context in which the script should be included
81
+ * > in_footer (bool) optional [Default: FALSE]
82
+ * > If TRUE, file will be included in footer of page, otherwise it will be included in the header
83
+ *
84
+ * Array is processed and converted to an object on init
85
+ */
86
+ private $client_files = array(
87
+ 'scripts' => array(),
88
+ 'styles' => array(),
89
+ );
90
+
91
+ /*-** Instances **-*/
92
+
93
+ /**
94
+ * Utilities
95
+ * @var SLB_Utilities
96
+ */
97
+ public $util = null;
98
+
99
+ /**
100
+ * Options
101
+ * @var SLB_Options
102
+ */
103
+ protected $options = null;
104
+
105
+ /**
106
+ * Admin
107
+ * @var SLB_Admin
108
+ */
109
+ public $admin = null;
110
+
111
+ /*-** Initialization **-*/
112
+
113
+ /**
114
+ * Constructor
115
+ */
116
+ function __construct() {
117
+ $this->util = new SLB_Utilities( $this );
118
+ if ( $this->can( 'init' ) ) {
119
+ $hook = 'init';
120
+ if ( did_action( $hook ) || self::$_init_passed ) {
121
+ $this->_init();
122
+ } else {
123
+ add_action( $hook, $this->m( '_init' ), 1 );
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Default initialization method
130
+ * @uses _init_passed
131
+ * @uses _env()
132
+ * @uses _options()
133
+ * @uses _admin()
134
+ * @uses _hooks()
135
+ * @uses _client_files()
136
+ */
137
+ public function _init() {
138
+ self::$_init_passed = true;
139
+ if ( $this->_init || ! isset( $this ) || ! $this->can( 'init' ) ) {
140
+ return false;
141
+ }
142
+ $this->_init = true;
143
+ // Environment
144
+ $this->_env();
145
+
146
+ if ( $this->can( 'control' ) ) {
147
+ // Options
148
+ $this->_options();
149
+
150
+ // Admin
151
+ if ( is_admin() ) {
152
+ $this->_admin();
153
+ }
154
+ }
155
+
156
+ // Hooks
157
+ $this->_hooks();
158
+
159
+ // Client files
160
+ $this->_client_files();
161
+ }
162
+
163
+ /**
164
+ * Initialize environment (Localization, etc.)
165
+ */
166
+ private function _env() {
167
+ if ( ! $this->can( 'singleton' ) ) {
168
+ return false;
169
+ }
170
+ // Localization
171
+ $ldir = 'l10n';
172
+ $lpath = $this->util->get_plugin_file_path( $ldir, array( false, false ) );
173
+ $lpath_abs = $this->util->get_file_path( $ldir );
174
+ if ( is_dir( $lpath_abs ) ) {
175
+ load_plugin_textdomain( 'simple-lightbox', false, $lpath );
176
+ }
177
+
178
+ // Context
179
+ add_action( ( is_admin() ) ? 'admin_print_footer_scripts' : 'wp_footer', $this->util->m( 'set_client_context' ), $this->util->priority( 'client_footer_output' ) );
180
+ }
181
+
182
+ /**
183
+ * Initialize options
184
+ * To be implemented in child classes
185
+ */
186
+ protected function _options() {}
187
+
188
+ /**
189
+ * Initialize options
190
+ * To be called by child class
191
+ */
192
+ protected function _set_options( $options_config = null ) {
193
+ $class = $this->util->get_class( 'Options' );
194
+ $key = 'options';
195
+ if ( $this->shares( $key ) ) {
196
+ $opts = $this->gvar( $key );
197
+ // Setup options instance
198
+ if ( ! ( $opts instanceof $class ) ) {
199
+ $opts = $this->gvar( $key, new $class() );
200
+ }
201
+ } else {
202
+ $opts = new $class();
203
+ }
204
+ // Load options
205
+ if ( $this->is_options_valid( $options_config, false ) ) {
206
+ $opts->load( $options_config );
207
+ }
208
+ // Set instance property
209
+ $this->options = $opts;
210
+ }
211
+
212
+ /**
213
+ * Initialize admin
214
+ * To be called by child class
215
+ */
216
+ private function _admin() {
217
+ if ( ! is_admin() ) {
218
+ return false;
219
+ }
220
+ $class = $this->util->get_class( 'Admin' );
221
+ $key = 'admin';
222
+ if ( $this->shares( $key ) ) {
223
+ $adm = $this->gvar( $key );
224
+ // Setup options instance
225
+ if ( ! ( $adm instanceof $class ) ) {
226
+ $adm = $this->gvar( $key, new $class( $this ) );
227
+ }
228
+ } else {
229
+ $adm = new $class( $this );
230
+ }
231
+ // Set instance property
232
+ $this->admin = $adm;
233
+ }
234
+
235
+ /**
236
+ * Register default hooks
237
+ */
238
+ protected function _hooks() {
239
+ $base = $this->util->get_plugin_base_file();
240
+ // Activation
241
+ $func_activate = '_activate';
242
+ if ( method_exists( $this, $func_activate ) ) {
243
+ register_activation_hook( $base, $this->m( $func_activate ) );
244
+ }
245
+
246
+ // Deactivation
247
+ $func_deactivate = '_deactivate';
248
+ if ( method_exists( $this, $func_deactivate ) ) {
249
+ register_deactivation_hook( $base, $this->m( $func_deactivate ) );
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Initialize client files
255
+ */
256
+ protected function _client_files( $files = null ) {
257
+ // Validation
258
+ if ( ! is_array( $files ) || empty( $files ) ) {
259
+ return false;
260
+ }
261
+ foreach ( $this->client_files as $key => $val ) {
262
+ if ( isset( $files[ $key ] ) && is_array( $files[ $key ] ) || ! empty( $files[ $key ] ) ) {
263
+ $this->client_files[ $key ] = $this->util->parse_client_files( $files[ $key ], $key );
264
+ }
265
+ // Remove empty file groups
266
+ if ( empty( $this->client_files[ $key ] ) ) {
267
+ unset( $this->client_files[ $key ] );
268
+ }
269
+ }
270
+
271
+ // Stop if no files are set for registration
272
+ if ( empty( $this->client_files ) ) {
273
+ return false;
274
+ }
275
+
276
+ // Register
277
+ add_action( 'init', $this->m( 'register_client_files' ) );
278
+
279
+ // Enqueue
280
+ $hk_prfx = ( ( is_admin() ) ? 'admin' : 'wp' );
281
+ $hk_enqueue = $hk_prfx . '_enqueue_scripts';
282
+ $hk_enqueue_ft = $hk_prfx . '_footer';
283
+ add_action( $hk_enqueue, $this->m( 'enqueue_client_files' ), 10, 0 );
284
+ add_action( $hk_enqueue_ft, $this->m( 'enqueue_client_files_footer' ), 1 );
285
+ }
286
+
287
+ /**
288
+ * Register client files
289
+ * @see enqueue_client_files() for actual loading of files based on context
290
+ * @uses `init` Action hook for execution
291
+ * @return void
292
+ */
293
+ public function register_client_files() {
294
+ $v = $this->util->get_plugin_version();
295
+ foreach ( $this->client_files as $type => $files ) {
296
+ $func = $this->get_client_files_handler( $type, 'register' );
297
+ if ( ! $func ) {
298
+ continue;
299
+ }
300
+ foreach ( $files as $f ) {
301
+ // Get file URI
302
+ $f->file = ( ! $this->util->is_file( $f->file ) && is_callable( $f->file ) ) ? call_user_func( $f->file ) : $this->util->get_file_url( $f->file, true );
303
+ $params = array( $f->id, $f->file, $f->deps, $v );
304
+ // Set additional parameters based on file type (script, style, etc.)
305
+ switch ( $type ) {
306
+ case 'scripts':
307
+ $params[] = $f->in_footer;
308
+ break;
309
+ case 'styles':
310
+ $params[] = $f->media;
311
+ break;
312
+ }
313
+ // Register file
314
+ call_user_func_array( $func, $params );
315
+ }
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Enqueues files for client output (scripts/styles) based on context
321
+ * @uses `admin_enqueue_scripts` Action hook depending on context
322
+ * @uses `wp_enqueue_scripts` Action hook depending on context
323
+ * @param bool $footer (optional) Whether to enqueue footer files (Default: No)
324
+ * @return void
325
+ */
326
+ function enqueue_client_files( $footer = false ) {
327
+ // Validate
328
+ if ( ! is_bool( $footer ) ) {
329
+ $footer = false;
330
+ }
331
+ // Enqueue files
332
+ foreach ( $this->client_files as $type => $files ) {
333
+ $func = $this->get_client_files_handler( $type, 'enqueue' );
334
+ if ( ! $func ) {
335
+ continue;
336
+ }
337
+ foreach ( $files as $fkey => $f ) {
338
+ // Skip previously-enqueued files and shadow files
339
+ if ( $f->enqueued || ! $f->enqueue ) {
340
+ continue;
341
+ }
342
+ // Enqueue files only for current location (header/footer)
343
+ if ( isset( $f->in_footer ) ) {
344
+ if ( $f->in_footer !== $footer ) {
345
+ continue;
346
+ }
347
+ } elseif ( $footer ) {
348
+ continue;
349
+ }
350
+ $load = true;
351
+ // Global Callback
352
+ if ( is_callable( $f->callback ) && ! call_user_func( $f->callback ) ) {
353
+ $load = false;
354
+ }
355
+ // Context
356
+ if ( $load && ! empty( $f->context ) ) {
357
+ // Reset $load before evaluating context
358
+ $load = false;
359
+ // Iterate through contexts
360
+ foreach ( $f->context as $ctx ) {
361
+ // Context + Callback
362
+ if ( is_array( $ctx ) ) {
363
+ // Stop checking context if callback is invalid
364
+ if ( ! is_callable( $ctx[1] ) || ! call_user_func( $ctx[1] ) ) {
365
+ continue;
366
+ }
367
+ $ctx = $ctx[0];
368
+ }
369
+ // Stop checking context if valid context found
370
+ if ( $this->util->is_context( $ctx ) ) {
371
+ $load = true;
372
+ break;
373
+ }
374
+ }
375
+ }
376
+ // Load valid file
377
+ if ( $load ) {
378
+ // Mark file as enqueued
379
+ $this->client_files[ $type ]->{$fkey}->enqueued = true;
380
+ $func( $f->id );
381
+ }
382
+ }
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Enqueue client files in the footer
388
+ */
389
+ public function enqueue_client_files_footer() {
390
+ $this->enqueue_client_files( true );
391
+ }
392
+
393
+ /**
394
+ * Build function name for handling client operations
395
+ */
396
+ function get_client_files_handler( $type, $action ) {
397
+ $func = 'wp_' . $action . '_' . substr( $type, 0, -1 );
398
+ if ( ! function_exists( $func ) ) {
399
+ $func = false;
400
+ }
401
+ return $func;
402
+ }
403
+
404
+ /*-** Reflection **-*/
405
+
406
+ /**
407
+ * Retrieve base object
408
+ * @return object|bool Base object (FALSE if object does not exist)
409
+ */
410
+ function &get_base() {
411
+ $base = false;
412
+ if ( isset( $GLOBALS[ $this->base ] ) ) {
413
+ $base =& $GLOBALS[ $this->base ];
414
+ }
415
+ return $base;
416
+ }
417
+
418
+ /*-** Method/Function calling **-*/
419
+
420
+ /**
421
+ * Returns callback to instance method
422
+ * @param string $method Method name
423
+ * @return array Callback array
424
+ */
425
+ function m( $method ) {
426
+ return $this->util->m( $this, $method );
427
+ }
428
+
429
+ /*-** Prefix **-*/
430
+
431
+ /**
432
+ * Retrieve class prefix (with separator if set)
433
+ * @param bool|string $sep Separator to append to class prefix (Default: no separator)
434
+ * @return string Class prefix
435
+ */
436
+ function get_prefix( $sep = null ) {
437
+ $args = func_get_args();
438
+ return call_user_func_array( $this->util->m( $this->util, 'get_prefix' ), $args );
439
+ }
440
+
441
+ /**
442
+ * Check if a string is prefixed
443
+ * @param string $text Text to check for prefix
444
+ * @param string $sep (optional) Separator used
445
+ */
446
+ function has_prefix( $text, $sep = null ) {
447
+ $args = func_get_args();
448
+ return call_user_func_array( $this->util->m( $this->util, 'has_prefix' ), $args );
449
+ }
450
+
451
+ /**
452
+ * Prepend plugin prefix to some text
453
+ * @param string $text Text to add to prefix
454
+ * @param string $sep (optional) Text used to separate prefix and text
455
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
456
+ * @return string Text with prefix prepended
457
+ */
458
+ function add_prefix( $text, $sep = null, $once = true ) {
459
+ $args = func_get_args();
460
+ return call_user_func_array( $this->util->m( $this->util, 'add_prefix' ), $args );
461
+ }
462
+
463
+ /**
464
+ * Prepend uppercased plugin prefix to some text
465
+ * @param string $text Text to add to prefix
466
+ * @param string $sep (optional) Text used to separate prefix and text
467
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
468
+ * @return string Text with prefix prepended
469
+ */
470
+ function add_prefix_uc( $text, $sep = null, $once = true ) {
471
+ $args = func_get_args();
472
+ return call_user_func_array( $this->util->m( $this->util, 'add_prefix_uc' ), $args );
473
+ }
474
+
475
+ /**
476
+ * Add prefix to variable reference
477
+ * Updates actual variable rather than return value
478
+ * @uses SLB_Utilities::add_prefix_ref();
479
+ * @param string $var Variable to add prefix to
480
+ * @param string $sep (optional) Separator text
481
+ * @param bool $once (optional) Add prefix only once
482
+ * @return void
483
+ */
484
+ function add_prefix_ref( &$var, $sep = null, $once = true ) {
485
+ $args = func_get_args();
486
+ $args[0] =& $var;
487
+ call_user_func_array( $this->util->m( $this->util, 'add_prefix_ref' ), $args );
488
+ }
489
+
490
+ /**
491
+ * Remove prefix from specified string
492
+ * @param string $text String to remove prefix from
493
+ * @param string $sep (optional) Separator used with prefix
494
+ */
495
+ function remove_prefix( $text, $sep = null ) {
496
+ $args = func_get_args();
497
+ return call_user_func_array( $this->util->m( $this->util, 'remove_prefix' ), $args );
498
+ }
499
+
500
+ /*-** Capabilities **-*/
501
+
502
+ protected function can( $cap ) {
503
+ if ( is_null( $this->caps ) ) {
504
+ // Build capabilities based on instance properties
505
+ $this->caps = array(
506
+ 'init' => ( 'object' !== $this->mode ) ? true : false,
507
+ 'singleton' => ( ! ! $this->model ) ? true : false,
508
+ 'control' => ( 'sub' === $this->mode || 'object' === $this->mode ) ? false : true,
509
+ );
510
+ }
511
+ return ( isset( $this->caps[ $cap ] ) ) ? $this->caps[ $cap ] : false;
512
+ }
513
+
514
+ /*-** Globals **-*/
515
+
516
+ /**
517
+ * Get/Set (internal) global variables
518
+ * @uses $globals to get/set global variables
519
+ * @param string $name Variable name - If no name is specified, entire globals array is returned
520
+ * @param mixed $val (optional) Set the value of a variable (Returns variable value if omitted)
521
+ * @return mixed Variable value
522
+ */
523
+ private function gvar( $name = null, $val = null ) {
524
+ $g =& self::$globals;
525
+ if ( ! is_array( $g ) ) {
526
+ $g = array();
527
+ }
528
+ if ( ! is_string( $name ) || empty( $name ) ) {
529
+ return $g;
530
+ }
531
+ $ret = $val;
532
+ if ( null !== $val ) {
533
+ // Set Value
534
+ $g[ $name ] = $val;
535
+ } elseif ( isset( $g[ $name ] ) ) {
536
+ // Retrieve variable
537
+ $ret = $g[ $name ];
538
+ }
539
+ return $ret;
540
+ }
541
+
542
+ private function shares( $name ) {
543
+ return ( ! empty( $this->shared ) && in_array( $name, $this->shared, true ) ) ? true : false;
544
+ }
545
+
546
+ /*-** Options **-*/
547
+
548
+ /**
549
+ * Checks if options are valid
550
+ * @param array $data Data to be used on options
551
+ * @return bool TRUE if options are valid, FALSE otherwise
552
+ */
553
+ function is_options_valid( $data, $check_var = true ) {
554
+ $class = $this->util->get_class( 'Options' );
555
+ $ret = ( empty( $data ) || ! is_array( $data ) || ! class_exists( $class ) ) ? false : true;
556
+ if ( $ret && $check_var && ! ( $this->options instanceof $class ) ) {
557
+ $ret = false;
558
+ }
559
+ return $ret;
560
+ }
561
+ }
includes/class.base_collection.php CHANGED
@@ -1,369 +1,369 @@
1
- <?php
2
-
3
- /**
4
- * Managed collection
5
- * @package Simple Lightbox
6
- * @subpackage Base
7
- * @author Archetyped
8
- */
9
- class SLB_Base_Collection extends SLB_Base {
10
- /* Configuration */
11
-
12
- /**
13
- * Set object mode
14
- * @var string
15
- */
16
- protected $mode = 'object';
17
-
18
- /**
19
- * Item type
20
- * @var string
21
- */
22
- protected $item_type = null;
23
-
24
- /**
25
- * Property to use for item key
26
- * Example: A property or method of the item
27
- * @var string
28
- */
29
- protected $key_prop = null;
30
-
31
- /**
32
- * Should $key_prop be called or retrieved?
33
- * Default: Retrieved (FALSE)
34
- * @var bool
35
- */
36
- protected $key_call = false;
37
-
38
- /**
39
- * Items in collection unique?
40
- * Default: FALSE
41
- * @var bool
42
- */
43
- protected $unique = false;
44
-
45
- /* Properties */
46
-
47
- /**
48
- * Indexed array of items in collection
49
- * @var array
50
- */
51
- protected $items = null;
52
-
53
- /**
54
- * Item metadata
55
- * Indexed by item key
56
- * @var array
57
- */
58
- protected $items_meta = array();
59
-
60
- /* Item Management */
61
-
62
- /**
63
- * Initialize collections
64
- * Calls `init` action if collection has a hook prefix
65
- */
66
- private function init() {
67
- // Initialize
68
- if ( is_null($this->items) ) {
69
- $this->items = array();
70
- if ( !empty($this->hook_prefix) ) {
71
- $this->util->do_action('init', $this);
72
- }
73
- }
74
- }
75
-
76
- /**
77
- * Normalize/Validate item(s)
78
- * TODO: If no items are specified, then collection is normalized
79
- * Single items are wrapped in an array
80
- * @param array|object $items Item(s) to validate
81
- * @return array Validated items
82
- */
83
- protected function normalize($items) {
84
- if ( !is_array($items) ) {
85
- $items = array($items);
86
- }
87
- // Validate item type
88
- if ( !is_null($this->item_type) ) {
89
- foreach ( $items as $idx => $item ) {
90
- // Remove invalid items
91
- if ( !( $item instanceof $this->item_type ) ) {
92
- unset($items[$idx]);
93
- }
94
- }
95
- }
96
- if ( !empty($items) ) {
97
- $items = array_values($items);
98
- }
99
- return $items;
100
- }
101
-
102
- protected function item_valid($item) {
103
- // Validate item type
104
- return ( empty($this->item_type) || ( $item instanceof $this->item_type ) ) ? true : false;
105
- }
106
-
107
- /**
108
- * Validate item key
109
- * Checks collection for existence of key as well
110
- * @param string|int $key Key to check collection for
111
- * @return bool TRUE if key is valid
112
- */
113
- protected function key_valid($key) {
114
- $this->init();
115
- return ( ( ( is_string($key) && !empty($key) ) || is_int($key) ) && isset($this->items[$key]) ) ? true : false;
116
- }
117
-
118
- /**
119
- * Generate key for item (for storing in collection, etc.)
120
- * @param mixed $item Item to generate key for
121
- * @return string|null Item key (NULL if no key generated)
122
- */
123
- protected function get_key($item, $check_existing = false) {
124
- $ret = null;
125
- if ( $this->unique || !!$check_existing ) {
126
- // Check for item in collection
127
- if ( $this->has($item) ) {
128
- $ret = array_search($item, $this->items);
129
- } elseif ( !!$this->key_prop && ( is_object($item) || is_array($item) ) ) {
130
- if ( !!$this->key_call ) {
131
- $cb = $this->util->m($item, $this->key_prop);
132
- if ( is_callable($cb) ) {
133
- $ret = call_user_func($cb);
134
- }
135
- } elseif ( is_array($item) && isset($item[$this->key_prop]) ) {
136
- $ret = $item[$this->key_prop];
137
- } elseif ( is_object($item) && isset($item->{$this->key_prop}) ) {
138
- $ret = $item->{$this->key_prop};
139
- }
140
- }
141
- }
142
- return $ret;
143
- }
144
-
145
- /**
146
- * Add item to collection
147
- * @param mixed $item Item to add to collection
148
- * @param array $meta (optional) Item metadata
149
- * @return Current instance
150
- */
151
- public function add($item, $meta = null) {
152
- $this->init();
153
- // Validate
154
- if ( $this->item_valid($item) ) {
155
- // Add item to collection
156
- $key = $this->get_key($item);
157
- if ( !$key ) {
158
- $this->items[] = $item;
159
- $key = key($this->items);
160
- } else {
161
- $this->items[$key] = $item;
162
- }
163
- // Add metadata
164
- if ( !!$key && is_array($meta) ) {
165
- $this->add_meta($key, $meta);
166
- }
167
- }
168
- return $this;
169
- }
170
-
171
- /**
172
- * Remove item from collection
173
- * @param int|string $item Key of item to remove
174
- * @return Current instance
175
- */
176
- public function remove($item) {
177
- if ( $this->key_valid($item) ) {
178
- unset($this->items[$item]);
179
- }
180
- return $this;
181
- }
182
-
183
- /**
184
- * Clear collection
185
- * @return Current instance
186
- */
187
- public function clear() {
188
- $this->items = array();
189
- return $this;
190
- }
191
-
192
- /**
193
- * Checks if item exists in the collection
194
- * @param mixed $item Item(s) to check for
195
- * @return bool TRUE if item(s) in collection
196
- */
197
- public function has($items) {
198
- // Attempt to locate item
199
- return false;
200
- }
201
-
202
- /**
203
- * Retrieve item(s) from collection
204
- * If no items specified, entire collection returned
205
- * @param array $args (optional) Query arguments
206
- * @return object|array Specified item(s)
207
- */
208
- public function get($args = null) {
209
- $this->init();
210
- // Parse args
211
- $args_default = array(
212
- 'orderby' => null,
213
- 'order' => 'DESC',
214
- 'include' => array(),
215
- 'exclude' => array(),
216
- );
217
- $r = wp_parse_args($args, $args_default);
218
-
219
- $items = $this->items;
220
-
221
- /* Sort */
222
- if ( !is_null($r['orderby']) ) {
223
- // Validate
224
- if ( !is_array($r['orderby']) ) {
225
- $r['orderby'] = array('item' => $r['orderby']);
226
- }
227
- // Prep
228
- $metas = ( isset($r['orderby']['meta']) ) ? $this->items_meta : array();
229
- // Sort
230
- foreach ( $r['orderby'] as $stype => $sval ) {
231
- /* Meta sorting */
232
- if ( 'meta' == $stype ) {
233
- // Build sorting buckets
234
- $buckets = array();
235
- foreach ( $metas as $item => $meta ) {
236
- if ( !isset($meta[$sval]) ) {
237
- continue;
238
- }
239
- // Create bucket
240
- $idx = $meta[$sval];
241
- if ( !isset($buckets[ $idx ]) ) {
242
- $buckets[ $idx ] = array();
243
- }
244
- // Add item to bucket
245
- $buckets[ $idx ][] = $item;
246
- }
247
- // Sort buckets
248
- ksort($buckets, SORT_NUMERIC);
249
- // Merge buckets
250
- $pool = array();
251
- foreach ( $buckets as $bucket ) {
252
- $pool = array_merge($pool, $bucket);
253
- }
254
- // Fill with items
255
- $items = array_merge( array_fill_keys($pool, null), $items);
256
- }
257
- }
258
- // Clear workers
259
- unset($stype, $sval, $buckets, $pool, $item, $metas, $meta, $idx);
260
- }
261
- return $items;
262
- }
263
-
264
- /* Metadata */
265
-
266
- /**
267
- * Add metadata for item
268
- * @param string|int $item Item key
269
- * @param string|array $meta_key Meta key to set (or array of metadata)
270
- * @param mixed $meta_value (optional) Metadata value (if key set)
271
- * @param bool $reset (optional) Whether to remove existing metadata first (Default: FALSE)
272
- * @return object Current instance
273
- */
274
- protected function add_meta($item, $meta_key, $meta_value = null, $reset = false) {
275
- // Validate
276
- if ( $this->key_valid($item) && ( is_array($meta_key) || is_string($meta_key) ) ) {
277
- // Prepare metadata
278
- $meta = ( is_string($meta_key) ) ? array($meta_key => $meta_value) : $meta_key;
279
- // Reset existing meta (if necessary)
280
- if ( is_array($meta_key) && func_num_args() > 2) {
281
- $reset = func_get_arg(2);
282
- }
283
- if ( !!$reset ) {
284
- unset($this->items_meta[$item]);
285
- }
286
- // Add metadata
287
- if ( !isset($this->items_meta[$item]) ) {
288
- $this->items_meta[$item] = array();
289
- }
290
- $this->items_meta[$item] = array_merge($this->items_meta[$item], $meta);
291
- }
292
- return $this;
293
- }
294
-
295
- /**
296
- * Remove item metadata
297
- * @param string $item Item key
298
- * @return object Current instance
299
- */
300
- protected function remove_meta($item, $meta_key = null) {
301
- if ( $this->key_valid($item) && isset($this->items_meta[$item]) ) {
302
- if ( is_string($meta_key) ) {
303
- // Remove specific meta value
304
- unset($this->items_meta[$item][$meta_key]);
305
- } else {
306
- // Remove all metadata
307
- unset($this->items_meta[$item]);
308
- }
309
- }
310
- return $this;
311
- }
312
-
313
- /**
314
- * Retrieve metadata
315
- * @param string $item Item key
316
- * @param string $meta_key (optional) Meta key (All metadata retrieved if no key specified)
317
- * @return mixed|null Metadata value
318
- */
319
- protected function get_meta($item, $meta_key = null) {
320
- $ret = null;
321
- if ( $this->key_valid($item) && isset($this->items_meta[$item]) ) {
322
- if ( is_null($meta_key) ) {
323
- $ret = $this->items_meta[$item];
324
- } elseif ( is_string($meta_key) && isset($this->items_meta[$item][$meta_key]) ) {
325
- $ret = $this->items_meta[$item][$meta_key];
326
- }
327
- }
328
- return $ret;
329
- }
330
-
331
- /* Collection */
332
-
333
- /**
334
- * Build entire collection of items
335
- * Prints output
336
- */
337
- function build($build_vars = array()) {
338
- // Parse vars
339
- $this->parse_build_vars($build_vars);
340
- $this->util->do_action_ref_array('build_init', array($this));
341
- // Pre-build output
342
- $this->util->do_action_ref_array('build_pre', array($this));
343
- // Build groups
344
- $this->build_groups();
345
- // Post-build output
346
- $this->util->do_action_ref_array('build_post', array($this));
347
- }
348
-
349
- /**
350
- * Parses build variables prior to use
351
- * @uses this->reset_build_vars() to reset build variables for each request
352
- * @param array $build_vars Variables to use for current request
353
- */
354
- function parse_build_vars($build_vars = array()) {
355
- $this->reset_build_vars();
356
- $this->build_vars = $this->util->apply_filters('parse_build_vars', wp_parse_args($build_vars, $this->build_vars), $this);
357
- }
358
-
359
- /**
360
- * Reset build variables to defaults
361
- * Default Variables
362
- * > groups - array - Names of groups to build
363
- * > context - string - Context of current request
364
- * > layout - string - Name of default layout to use
365
- */
366
- function reset_build_vars() {
367
- $this->build_vars = wp_parse_args($this->build_vars, $this->build_vars_default);
368
- }
369
- }
1
+ <?php
2
+
3
+ /**
4
+ * Managed collection
5
+ * @package Simple Lightbox
6
+ * @subpackage Base
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Base_Collection extends SLB_Base {
10
+ /* Configuration */
11
+
12
+ /**
13
+ * Set object mode
14
+ * @var string
15
+ */
16
+ protected $mode = 'object';
17
+
18
+ /**
19
+ * Item type
20
+ * @var string
21
+ */
22
+ protected $item_type = null;
23
+
24
+ /**
25
+ * Property to use for item key
26
+ * Example: A property or method of the item
27
+ * @var string
28
+ */
29
+ protected $key_prop = null;
30
+
31
+ /**
32
+ * Should $key_prop be called or retrieved?
33
+ * Default: Retrieved (FALSE)
34
+ * @var bool
35
+ */
36
+ protected $key_call = false;
37
+
38
+ /**
39
+ * Items in collection unique?
40
+ * Default: FALSE
41
+ * @var bool
42
+ */
43
+ protected $unique = false;
44
+
45
+ /* Properties */
46
+
47
+ /**
48
+ * Indexed array of items in collection
49
+ * @var array
50
+ */
51
+ protected $items = null;
52
+
53
+ /**
54
+ * Item metadata
55
+ * Indexed by item key
56
+ * @var array
57
+ */
58
+ protected $items_meta = array();
59
+
60
+ /* Item Management */
61
+
62
+ /**
63
+ * Initialize collections
64
+ * Calls `init` action if collection has a hook prefix
65
+ */
66
+ private function init() {
67
+ // Initialize
68
+ if ( is_null( $this->items ) ) {
69
+ $this->items = array();
70
+ if ( ! empty( $this->hook_prefix ) ) {
71
+ $this->util->do_action( 'init', $this );
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Normalize/Validate item(s)
78
+ * TODO: If no items are specified, then collection is normalized
79
+ * Single items are wrapped in an array
80
+ * @param array|object $items Item(s) to validate
81
+ * @return array Validated items
82
+ */
83
+ protected function normalize( $items ) {
84
+ if ( ! is_array( $items ) ) {
85
+ $items = array( $items );
86
+ }
87
+ // Validate item type
88
+ if ( ! is_null( $this->item_type ) ) {
89
+ foreach ( $items as $idx => $item ) {
90
+ // Remove invalid items
91
+ if ( ! ( $item instanceof $this->item_type ) ) {
92
+ unset( $items[ $idx ] );
93
+ }
94
+ }
95
+ }
96
+ if ( ! empty( $items ) ) {
97
+ $items = array_values( $items );
98
+ }
99
+ return $items;
100
+ }
101
+
102
+ protected function item_valid( $item ) {
103
+ // Validate item type
104
+ return ( empty( $this->item_type ) || ( $item instanceof $this->item_type ) ) ? true : false;
105
+ }
106
+
107
+ /**
108
+ * Validate item key
109
+ * Checks collection for existence of key as well
110
+ * @param string|int $key Key to check collection for
111
+ * @return bool TRUE if key is valid
112
+ */
113
+ protected function key_valid( $key ) {
114
+ $this->init();
115
+ return ( ( ( is_string( $key ) && ! empty( $key ) ) || is_int( $key ) ) && isset( $this->items[ $key ] ) ) ? true : false;
116
+ }
117
+
118
+ /**
119
+ * Generate key for item (for storing in collection, etc.)
120
+ * @param mixed $item Item to generate key for
121
+ * @return string|null Item key (NULL if no key generated)
122
+ */
123
+ protected function get_key( $item, $check_existing = false ) {
124
+ $ret = null;
125
+ if ( $this->unique || ! ! $check_existing ) {
126
+ // Check for item in collection
127
+ if ( $this->has( $item ) ) {
128
+ $ret = array_search( $item, $this->items, true );
129
+ } elseif ( ! ! $this->key_prop && ( is_object( $item ) || is_array( $item ) ) ) {
130
+ if ( ! ! $this->key_call ) {
131
+ $cb = $this->util->m( $item, $this->key_prop );
132
+ if ( is_callable( $cb ) ) {
133
+ $ret = call_user_func( $cb );
134
+ }
135
+ } elseif ( is_array( $item ) && isset( $item[ $this->key_prop ] ) ) {
136
+ $ret = $item[ $this->key_prop ];
137
+ } elseif ( is_object( $item ) && isset( $item->{$this->key_prop} ) ) {
138
+ $ret = $item->{$this->key_prop};
139
+ }
140
+ }
141
+ }
142
+ return $ret;
143
+ }
144
+
145
+ /**
146
+ * Add item to collection
147
+ * @param mixed $item Item to add to collection
148
+ * @param array $meta (optional) Item metadata
149
+ * @return Current instance
150
+ */
151
+ public function add( $item, $meta = null ) {
152
+ $this->init();
153
+ // Validate
154
+ if ( $this->item_valid( $item ) ) {
155
+ // Add item to collection
156
+ $key = $this->get_key( $item );
157
+ if ( ! $key ) {
158
+ $this->items[] = $item;
159
+ $key = key( $this->items );
160
+ } else {
161
+ $this->items[ $key ] = $item;
162
+ }
163
+ // Add metadata
164
+ if ( ! ! $key && is_array( $meta ) ) {
165
+ $this->add_meta( $key, $meta );
166
+ }
167
+ }
168
+ return $this;
169
+ }
170
+
171
+ /**
172
+ * Remove item from collection
173
+ * @param int|string $item Key of item to remove
174
+ * @return Current instance
175
+ */
176
+ public function remove( $item ) {
177
+ if ( $this->key_valid( $item ) ) {
178
+ unset( $this->items[ $item ] );
179
+ }
180
+ return $this;
181
+ }
182
+
183
+ /**
184
+ * Clear collection
185
+ * @return Current instance
186
+ */
187
+ public function clear() {
188
+ $this->items = array();
189
+ return $this;
190
+ }
191
+
192
+ /**
193
+ * Checks if item exists in the collection
194
+ * @param mixed $item Item(s) to check for
195
+ * @return bool TRUE if item(s) in collection
196
+ */
197
+ public function has( $items ) {
198
+ // Attempt to locate item
199
+ return false;
200
+ }
201
+
202
+ /**
203
+ * Retrieve item(s) from collection
204
+ * If no items specified, entire collection returned
205
+ * @param array $args (optional) Query arguments
206
+ * @return object|array Specified item(s)
207
+ */
208
+ public function get( $args = null ) {
209
+ $this->init();
210
+ // Parse args
211
+ $args_default = array(
212
+ 'orderby' => null,
213
+ 'order' => 'DESC',
214
+ 'include' => array(),
215
+ 'exclude' => array(),
216
+ );
217
+ $r = wp_parse_args( $args, $args_default );
218
+
219
+ $items = $this->items;
220
+
221
+ /* Sort */
222
+ if ( ! is_null( $r['orderby'] ) ) {
223
+ // Validate
224
+ if ( ! is_array( $r['orderby'] ) ) {
225
+ $r['orderby'] = array( 'item' => $r['orderby'] );
226
+ }
227
+ // Prep
228
+ $metas = ( isset( $r['orderby']['meta'] ) ) ? $this->items_meta : array();
229
+ // Sort
230
+ foreach ( $r['orderby'] as $stype => $sval ) {
231
+ /* Meta sorting */
232
+ if ( 'meta' === $stype ) {
233
+ // Build sorting buckets
234
+ $buckets = array();
235
+ foreach ( $metas as $item => $meta ) {
236
+ if ( ! isset( $meta[ $sval ] ) ) {
237
+ continue;
238
+ }
239
+ // Create bucket
240
+ $idx = $meta[ $sval ];
241
+ if ( ! isset( $buckets[ $idx ] ) ) {
242
+ $buckets[ $idx ] = array();
243
+ }
244
+ // Add item to bucket
245
+ $buckets[ $idx ][] = $item;
246
+ }
247
+ // Sort buckets
248
+ ksort( $buckets, SORT_NUMERIC );
249
+ // Merge buckets
250
+ $pool = array();
251
+ foreach ( $buckets as $bucket ) {
252
+ $pool = array_merge( $pool, $bucket );
253
+ }
254
+ // Fill with items
255
+ $items = array_merge( array_fill_keys( $pool, null ), $items );
256
+ }
257
+ }
258
+ // Clear workers
259
+ unset( $stype, $sval, $buckets, $pool, $item, $metas, $meta, $idx );
260
+ }
261
+ return $items;
262
+ }
263
+
264
+ /* Metadata */
265
+
266
+ /**
267
+ * Add metadata for item
268
+ * @param string|int $item Item key
269
+ * @param string|array $meta_key Meta key to set (or array of metadata)
270
+ * @param mixed $meta_value (optional) Metadata value (if key set)
271
+ * @param bool $reset (optional) Whether to remove existing metadata first (Default: FALSE)
272
+ * @return object Current instance
273
+ */
274
+ protected function add_meta( $item, $meta_key, $meta_value = null, $reset = false ) {
275
+ // Validate
276
+ if ( $this->key_valid( $item ) && ( is_array( $meta_key ) || is_string( $meta_key ) ) ) {
277
+ // Prepare metadata
278
+ $meta = ( is_string( $meta_key ) ) ? array( $meta_key => $meta_value ) : $meta_key;
279
+ // Reset existing meta (if necessary)
280
+ if ( is_array( $meta_key ) && func_num_args() > 2 ) {
281
+ $reset = func_get_arg( 2 );
282
+ }
283
+ if ( ! ! $reset ) {
284
+ unset( $this->items_meta[ $item ] );
285
+ }
286
+ // Add metadata
287
+ if ( ! isset( $this->items_meta[ $item ] ) ) {
288
+ $this->items_meta[ $item ] = array();
289
+ }
290
+ $this->items_meta[ $item ] = array_merge( $this->items_meta[ $item ], $meta );
291
+ }
292
+ return $this;
293
+ }
294
+
295
+ /**
296
+ * Remove item metadata
297
+ * @param string $item Item key
298
+ * @return object Current instance
299
+ */
300
+ protected function remove_meta( $item, $meta_key = null ) {
301
+ if ( $this->key_valid( $item ) && isset( $this->items_meta[ $item ] ) ) {
302
+ if ( is_string( $meta_key ) ) {
303
+ // Remove specific meta value
304
+ unset( $this->items_meta[ $item ][ $meta_key ] );
305
+ } else {
306
+ // Remove all metadata
307
+ unset( $this->items_meta[ $item ] );
308
+ }
309
+ }
310
+ return $this;
311
+ }
312
+
313
+ /**
314
+ * Retrieve metadata
315
+ * @param string $item Item key
316
+ * @param string $meta_key (optional) Meta key (All metadata retrieved if no key specified)
317
+ * @return mixed|null Metadata value
318
+ */
319
+ protected function get_meta( $item, $meta_key = null ) {
320
+ $ret = null;
321
+ if ( $this->key_valid( $item ) && isset( $this->items_meta[ $item ] ) ) {
322
+ if ( is_null( $meta_key ) ) {
323
+ $ret = $this->items_meta[ $item ];
324
+ } elseif ( is_string( $meta_key ) && isset( $this->items_meta[ $item ][ $meta_key ] ) ) {
325
+ $ret = $this->items_meta[ $item ][ $meta_key ];
326
+ }
327
+ }
328
+ return $ret;
329
+ }
330
+
331
+ /* Collection */
332
+
333
+ /**
334
+ * Build entire collection of items
335
+ * Prints output
336
+ */
337
+ function build( $build_vars = array() ) {
338
+ // Parse vars
339
+ $this->parse_build_vars( $build_vars );
340
+ $this->util->do_action_ref_array( 'build_init', array( $this ) );
341
+ // Pre-build output
342
+ $this->util->do_action_ref_array( 'build_pre', array( $this ) );
343
+ // Build groups
344
+ $this->build_groups();
345
+ // Post-build output
346
+ $this->util->do_action_ref_array( 'build_post', array( $this ) );
347
+ }
348
+
349
+ /**
350
+ * Parses build variables prior to use
351
+ * @uses this->reset_build_vars() to reset build variables for each request
352
+ * @param array $build_vars Variables to use for current request
353
+ */
354
+ function parse_build_vars( $build_vars = array() ) {
355
+ $this->reset_build_vars();
356
+ $this->build_vars = $this->util->apply_filters( 'parse_build_vars', wp_parse_args( $build_vars, $this->build_vars ), $this );
357
+ }
358
+
359
+ /**
360
+ * Reset build variables to defaults
361
+ * Default Variables
362
+ * > groups - array - Names of groups to build
363
+ * > context - string - Context of current request
364
+ * > layout - string - Name of default layout to use
365
+ */
366
+ function reset_build_vars() {
367
+ $this->build_vars = wp_parse_args( $this->build_vars, $this->build_vars_default );
368
+ }
369
+ }
includes/class.base_object.php CHANGED
@@ -1,337 +1,343 @@
1
- <?php
2
-
3
- /**
4
- * Base Object
5
- * @package Simple Lightbox
6
- * @subpackage Base
7
- * @author Archetyped
8
- */
9
- class SLB_Base_Object extends SLB_Base {
10
- /* Configuration */
11
-
12
- /**
13
- * @var string
14
- * @see Base::$mode
15
- */
16
- protected $mode = 'object';
17
-
18
- /*-** Properties **-*/
19
-
20
- /**
21
- * Unique ID
22
- * @var string
23
- */
24
- protected $id = '';
25
-
26
- /**
27
- * Parent object
28
- * @var Base_Object
29
- */
30
- protected $parent = null;
31
-
32
- /**
33
- * Attached files
34
- * @var array
35
- * > scripts array JS scripts
36
- * > styles array Stylesheets
37
- */
38
- protected $files = array(
39
- 'scripts' => array(),
40
- 'styles' => array()
41
- );
42
-
43
- /**
44
- * Properties that can be inherited from parent
45
- * @var array
46
- */
47
- protected $parent_props = array();
48
-
49
- /*-** Methods **-*/
50
-
51
- /**
52
- * Constructor
53
- * @param string $id Unique ID for content type
54
- * @param array $props (optional) Type properties (optional because props can be set post-init)
55
- */
56
- public function __construct($id, $props = null) {
57
- parent::__construct();
58
- $this
59
- ->set_id($id)
60
- ->set_props($props);
61
- }
62
-
63
- /**
64
- * Checks if object is valid
65
- * To be overriden by child classes
66
- */
67
- public function is_valid() {
68
- return true;
69
- }
70
-
71
- /*-** Getters/Setters **-*/
72
-
73
- /**
74
- * Get ID
75
- * @return string ID
76
- */
77
- public function get_id() {
78
- return $this->id;
79
- }
80
-
81
- /**
82
- * Set ID
83
- * @param string $id ID
84
- * @return object Current instance
85
- */
86
- public function set_id($id) {
87
- $id = ( is_string($id) ) ? trim($id) : '';
88
- if ( !empty($id) ) {
89
- $this->id = $id;
90
- }
91
- return $this;
92
- }
93
-
94
- /**
95
- * Set type properties
96
- * @param array $props Type properties to set
97
- */
98
- protected function set_props($props) {
99
- if ( is_array($props) && !empty($props) ) {
100
- foreach ( $props as $key => $val ) {
101
- // Check for setter method
102
- $m = 'set_' . $key;
103
- if ( method_exists($this, $m) ) {
104
- $this->{$m}($val);
105
- }
106
- }
107
- }
108
- return $this;
109
- }
110
-
111
- /**
112
- * Get parent
113
- * @return object|null Parent
114
- */
115
- public function get_parent() {
116
- return $this->parent;
117
- }
118
-
119
- /**
120
- * Set parent
121
- * @param object $parent Parent object
122
- * @return object Current instance
123
- */
124
- public function set_parent($parent) {
125
- $this->parent = ( $parent instanceof $this ) ? $parent : null;
126
- return $this;
127
- }
128
-
129
- /**
130
- * Check if parent is set
131
- * @return bool TRUE if parent is set
132
- */
133
- public function has_parent() {
134
- return ( is_null($this->parent) ) ? false : true;
135
- }
136
-
137
- /**
138
- * Retrieve all ancestors
139
- * @return array Ancestors
140
- */
141
- public function get_ancestors() {
142
- $ret = array();
143
- $curr = $this;
144
- while ( $curr->has_parent() ) {
145
- // Add ancestor
146
- $ret[] = $par = $curr->get_parent();
147
- // Get next ancestor
148
- $curr = $par;
149
- }
150
- return $ret;
151
- }
152
-
153
- /* Files */
154
-
155
- /**
156
- * Add file
157
- * @param string $type Group to add file to
158
- * @param string $handle Name for resource
159
- * @param string $src File URI
160
- * @return object Current instance
161
- */
162
- protected function add_file($type, $handle, $src, $deps = array()) {
163
- if ( is_string($type) && is_string($handle) && is_string($src) ) {
164
- // Validate dependencies
165
- if ( !is_array($deps) ) {
166
- $deps = array();
167
- }
168
- // Init file group
169
- if ( !isset($this->files[$type]) || !is_array($this->files[$type]) ) {
170
- $this->files[$type] = array();
171
- }
172
- // Add file to group
173
- $this->files[$type][$handle] = array('handle' => $handle, 'uri' => $src, 'deps' => $deps);
174
- }
175
- return $this;
176
- }
177
-
178
- /**
179
- * Add multiple files
180
- * @param string $type Group to add files to
181
- * @param array $files Files to add
182
- * @see add_file() for file parameters
183
- * @return object Current instance
184
- */
185
- protected function add_files($type, $files) {
186
- if ( !is_array($files) || empty($files) )
187
- return false;
188
- $m = $this->m('add_file');
189
- foreach ( $files as $file ) {
190
- if ( !is_array($file) || empty($file) ) {
191
- continue;
192
- }
193
- array_unshift($file, $type);
194
- call_user_func_array($m, $file);
195
- }
196
- return $this;
197
- }
198
-
199
- /**
200
- * Retrieve files
201
- * All files or a specific group of files can be retrieved
202
- * @param string $type (optional) File group to retrieve
203
- * @return array Files
204
- */
205
- protected function get_files($type = null) {
206
- $ret = $this->files;
207
- if ( is_string($type) ) {
208
- $ret = ( isset($ret[$type]) ) ? $ret[$type] : array();
209
- }
210
- if ( !is_array($ret) ) {
211
- $ret = array();
212
- }
213
- return $ret;
214
- }
215
-
216
- /**
217
- * Retrieve file
218
- * @param string $type Group to retrieve file from
219
- * @param string $handle
220
- * @param string $format (optional) Format of return value (Default: array)
221
- * @return array|null File properties (Default: NULL)
222
- */
223
- protected function get_file($type, $handle, $format = null) {
224
- // Get files
225
- $files = $this->get_files($type);
226
- // Get specified file
227
- $ret = ( is_string($type) && isset($files[$handle]) ) ? $files[$handle] : null;
228
- // Format return value
229
- if ( !empty($ret) && !!$format ) {
230
- switch ( $format ) {
231
- case 'uri':
232
- $ret = $ret['uri'];
233
- // Normalize URI
234
- if ( !$this->util->is_uri($ret) ) {
235
- $ret = $this->util->normalize_path(site_url(), $ret);
236
- }
237
- break;
238
- case 'path':
239
- $ret = $ret['uri'];
240
- // Normalize path
241
- if ( !$this->util->is_uri($ret) ) {
242
- $ret = $this->util->get_relative_path($ret);
243
- $ret = $this->util->normalize_path(ABSPATH, $ret);
244
- }
245
- break;
246
- case 'object':
247
- $ret = (object) $ret;
248
- break;
249
- case 'contents':
250
- $ret = $ret['uri'];
251
- if ( !$this->util->is_uri($ret) ) {
252
- $ret = $this->util->normalize_path(site_url(), $ret);
253
- }
254
- $get = wp_safe_remote_get($ret);
255
- $ret = ( !is_wp_error($get) && 200 == $get['response']['code'] ) ? $get['body'] : '';
256
- break;
257
- }
258
- }
259
- return $ret;
260
- }
261
-
262
- /**
263
- * Add stylesheet
264
- * @param string $handle Name of the stylesheet
265
- * @param string $src Stylesheet URI
266
- * @return object Current instance
267
- */
268
- public function add_style($handle, $src, $deps = array()) {
269
- return $this->add_file('styles', $handle, $src, $deps);
270
- }
271
-
272
- /**
273
- * Retrieve stylesheet files
274
- * @return array Stylesheet files
275
- */
276
- public function get_styles($opts = null) {
277
- $files = $this->get_files('styles');
278
- if ( is_array($opts) ) {
279
- $opts = (object) $opts;
280
- }
281
- if ( is_object($opts) && !empty($opts) ) {
282
- // Parse options
283
- // URI Format
284
- if ( isset($opts->uri_format) ) {
285
- foreach ( $files as $hdl => $props ) {
286
- switch ( $opts->uri_format ) {
287
- case 'full':
288
- if ( !$this->util->is_uri($props['uri']) ) {
289
- $files[$hdl]['uri'] = $this->util->normalize_path(site_url(), $props['uri']);
290
- }
291
- break;
292
- }
293
- }
294
- }
295
- }
296
- return $files;
297
- }
298
-
299
- /**
300
- * Retrieve stylesheet file
301
- * @param string $handle Name of stylesheet
302
- * @param string $format (optional) Format of return value (@see `get_file()`)
303
- * @return array|null File properties (Default: NULL)
304
- */
305
- public function get_style($handle, $format = null) {
306
- return $this->get_file('styles', $handle, $format);
307
- }
308
-
309
- /**
310
- * Add script
311
- * @param string $handle Name of the script
312
- * @param string $src Script URI
313
- * @return object Current instance
314
- */
315
- public function add_script($handle, $src, $deps = array()) {
316
- return $this->add_file('scripts', $handle, $src, $deps);
317
- }
318
-
319
- /**
320
- * Retrieve script files
321
- * @return array Script files
322
- */
323
- public function get_scripts() {
324
- return $this->get_files('scripts');
325
- }
326
-
327
- /**
328
- * Retrieve script file
329
- * @param string $handle Name of script
330
- * @param string $format (optional) Format of return value (@see `get_file()`)
331
- * @return array|null File properties (Default: NULL)
332
- */
333
- public function get_script($handle, $format = null) {
334
- return $this->get_file('scripts', $handle, $format);
335
- }
336
-
337
- }
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Base Object
5
+ * @package Simple Lightbox
6
+ * @subpackage Base
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Base_Object extends SLB_Base {
10
+ /* Configuration */
11
+
12
+ /**
13
+ * @var string
14
+ * @see Base::$mode
15
+ */
16
+ protected $mode = 'object';
17
+
18
+ /*-** Properties **-*/
19
+
20
+ /**
21
+ * Unique ID
22
+ * @var string
23
+ */
24
+ protected $id = '';
25
+
26
+ /**
27
+ * Parent object
28
+ * @var Base_Object
29
+ */
30
+ protected $parent = null;
31
+
32
+ /**
33
+ * Attached files
34
+ * @var array
35
+ * > scripts array JS scripts
36
+ * > styles array Stylesheets
37
+ */
38
+ protected $files = array(
39
+ 'scripts' => array(),
40
+ 'styles' => array(),
41
+ );
42
+
43
+ /**
44
+ * Properties that can be inherited from parent
45
+ * @var array
46
+ */
47
+ protected $parent_props = array();
48
+
49
+ /*-** Methods **-*/
50
+
51
+ /**
52
+ * Constructor
53
+ * @param string $id Unique ID for content type
54
+ * @param array $props (optional) Type properties (optional because props can be set post-init)
55
+ */
56
+ public function __construct( $id, $props = null ) {
57
+ parent::__construct();
58
+ $this
59
+ ->set_id( $id )
60
+ ->set_props( $props );
61
+ }
62
+
63
+ /**
64
+ * Checks if object is valid
65
+ * To be overriden by child classes
66
+ */
67
+ public function is_valid() {
68
+ return true;
69
+ }
70
+
71
+ /*-** Getters/Setters **-*/
72
+
73
+ /**
74
+ * Get ID
75
+ * @return string ID
76
+ */
77
+ public function get_id() {
78
+ return $this->id;
79
+ }
80
+
81
+ /**
82
+ * Set ID
83
+ * @param string $id ID
84
+ * @return object Current instance
85
+ */
86
+ public function set_id( $id ) {
87
+ $id = ( is_string( $id ) ) ? trim( $id ) : '';
88
+ if ( ! empty( $id ) ) {
89
+ $this->id = $id;
90
+ }
91
+ return $this;
92
+ }
93
+
94
+ /**
95
+ * Set type properties
96
+ * @param array $props Type properties to set
97
+ */
98
+ protected function set_props( $props ) {
99
+ if ( is_array( $props ) && ! empty( $props ) ) {
100
+ foreach ( $props as $key => $val ) {
101
+ // Check for setter method
102
+ $m = 'set_' . $key;
103
+ if ( method_exists( $this, $m ) ) {
104
+ $this->{$m}( $val );
105
+ }
106
+ }
107
+ }
108
+ return $this;
109
+ }
110
+
111
+ /**
112
+ * Get parent
113
+ * @return object|null Parent
114
+ */
115
+ public function get_parent() {
116
+ return $this->parent;
117
+ }
118
+
119
+ /**
120
+ * Set parent
121
+ * @param object $parent Parent object
122
+ * @return object Current instance
123
+ */
124
+ public function set_parent( $parent ) {
125
+ $this->parent = ( $parent instanceof $this ) ? $parent : null;
126
+ return $this;
127
+ }
128
+
129
+ /**
130
+ * Check if parent is set
131
+ * @return bool TRUE if parent is set
132
+ */
133
+ public function has_parent() {
134
+ return ( is_null( $this->parent ) ) ? false : true;
135
+ }
136
+
137
+ /**
138
+ * Retrieve all ancestors
139
+ * @return array Ancestors
140
+ */
141
+ public function get_ancestors() {
142
+ $ret = array();
143
+ $curr = $this;
144
+ while ( $curr->has_parent() ) {
145
+ // Get next ancestor.
146
+ $curr = $curr->get_parent();
147
+ // Add ancestor.
148
+ $ret[] = $curr;
149
+ }
150
+
151
+ return $ret;
152
+ }
153
+
154
+ /* Files */
155
+
156
+ /**
157
+ * Add file
158
+ * @param string $type Group to add file to
159
+ * @param string $handle Name for resource
160
+ * @param string $src File URI
161
+ * @return object Current instance
162
+ */
163
+ protected function add_file( $type, $handle, $src, $deps = array() ) {
164
+ if ( is_string( $type ) && is_string( $handle ) && is_string( $src ) ) {
165
+ // Validate dependencies
166
+ if ( ! is_array( $deps ) ) {
167
+ $deps = array();
168
+ }
169
+ // Init file group
170
+ if ( ! isset( $this->files[ $type ] ) || ! is_array( $this->files[ $type ] ) ) {
171
+ $this->files[ $type ] = array();
172
+ }
173
+ // Add file to group
174
+ $this->files[ $type ][ $handle ] = array(
175
+ 'handle' => $handle,
176
+ 'uri' => $src,
177
+ 'deps' => $deps,
178
+ );
179
+ }
180
+ return $this;
181
+ }
182
+
183
+ /**
184
+ * Add multiple files
185
+ * @param string $type Group to add files to
186
+ * @param array $files Files to add
187
+ * @see add_file() for file parameters
188
+ * @return object Current instance
189
+ */
190
+ protected function add_files( $type, $files ) {
191
+ if ( ! is_array( $files ) || empty( $files ) ) {
192
+ return false;
193
+ }
194
+ $m = $this->m( 'add_file' );
195
+ foreach ( $files as $file ) {
196
+ if ( ! is_array( $file ) || empty( $file ) ) {
197
+ continue;
198
+ }
199
+ array_unshift( $file, $type );
200
+ call_user_func_array( $m, $file );
201
+ }
202
+ return $this;
203
+ }
204
+
205
+ /**
206
+ * Retrieve files
207
+ * All files or a specific group of files can be retrieved
208
+ * @param string $type (optional) File group to retrieve
209
+ * @return array Files
210
+ */
211
+ protected function get_files( $type = null ) {
212
+ $ret = $this->files;
213
+ if ( is_string( $type ) ) {
214
+ $ret = ( isset( $ret[ $type ] ) ) ? $ret[ $type ] : array();
215
+ }
216
+ if ( ! is_array( $ret ) ) {
217
+ $ret = array();
218
+ }
219
+ return $ret;
220
+ }
221
+
222
+ /**
223
+ * Retrieve file
224
+ * @param string $type Group to retrieve file from
225
+ * @param string $handle
226
+ * @param string $format (optional) Format of return value (Default: array)
227
+ * @return array|null File properties (Default: NULL)
228
+ */
229
+ protected function get_file( $type, $handle, $format = null ) {
230
+ // Get files
231
+ $files = $this->get_files( $type );
232
+ // Get specified file
233
+ $ret = ( is_string( $type ) && isset( $files[ $handle ] ) ) ? $files[ $handle ] : null;
234
+ // Format return value
235
+ if ( ! empty( $ret ) && ! ! $format ) {
236
+ switch ( $format ) {
237
+ case 'uri':
238
+ $ret = $ret['uri'];
239
+ // Normalize URI
240
+ if ( ! $this->util->is_uri( $ret ) ) {
241
+ $ret = $this->util->normalize_path( site_url(), $ret );
242
+ }
243
+ break;
244
+ case 'path':
245
+ $ret = $ret['uri'];
246
+ // Normalize path
247
+ if ( ! $this->util->is_uri( $ret ) ) {
248
+ $ret = $this->util->get_relative_path( $ret );
249
+ $ret = $this->util->normalize_path( ABSPATH, $ret );
250
+ }
251
+ break;
252
+ case 'object':
253
+ $ret = (object) $ret;
254
+ break;
255
+ case 'contents':
256
+ $ret = $ret['uri'];
257
+ if ( ! $this->util->is_uri( $ret ) ) {
258
+ $ret = $this->util->normalize_path( site_url(), $ret );
259
+ }
260
+ $get = wp_safe_remote_get( $ret );
261
+ $ret = ( ! is_wp_error( $get ) && 200 === $get['response']['code'] ) ? $get['body'] : '';
262
+ break;
263
+ }
264
+ }
265
+ return $ret;
266
+ }
267
+
268
+ /**
269
+ * Add stylesheet
270
+ * @param string $handle Name of the stylesheet
271
+ * @param string $src Stylesheet URI
272
+ * @return object Current instance
273
+ */
274
+ public function add_style( $handle, $src, $deps = array() ) {
275
+ return $this->add_file( 'styles', $handle, $src, $deps );
276
+ }
277
+
278
+ /**
279
+ * Retrieve stylesheet files
280
+ * @return array Stylesheet files
281
+ */
282
+ public function get_styles( $opts = null ) {
283
+ $files = $this->get_files( 'styles' );
284
+ if ( is_array( $opts ) ) {
285
+ $opts = (object) $opts;
286
+ }
287
+ if ( is_object( $opts ) && ! empty( $opts ) ) {
288
+ // Parse options
289
+ // URI Format
290
+ if ( isset( $opts->uri_format ) ) {
291
+ foreach ( $files as $hdl => $props ) {
292
+ switch ( $opts->uri_format ) {
293
+ case 'full':
294
+ if ( ! $this->util->is_uri( $props['uri'] ) ) {
295
+ $files[ $hdl ]['uri'] = $this->util->normalize_path( site_url(), $props['uri'] );
296
+ }
297
+ break;
298
+ }
299
+ }
300
+ }
301
+ }
302
+ return $files;
303
+ }
304
+
305
+ /**
306
+ * Retrieve stylesheet file
307
+ * @param string $handle Name of stylesheet
308
+ * @param string $format (optional) Format of return value (@see `get_file()`)
309
+ * @return array|null File properties (Default: NULL)
310
+ */
311
+ public function get_style( $handle, $format = null ) {
312
+ return $this->get_file( 'styles', $handle, $format );
313
+ }
314
+
315
+ /**
316
+ * Add script
317
+ * @param string $handle Name of the script
318
+ * @param string $src Script URI
319
+ * @return object Current instance
320
+ */
321
+ public function add_script( $handle, $src, $deps = array() ) {
322
+ return $this->add_file( 'scripts', $handle, $src, $deps );
323
+ }
324
+
325
+ /**
326
+ * Retrieve script files
327
+ * @return array Script files
328
+ */
329
+ public function get_scripts() {
330
+ return $this->get_files( 'scripts' );
331
+ }
332
+
333
+ /**
334
+ * Retrieve script file
335
+ * @param string $handle Name of script
336
+ * @param string $format (optional) Format of return value (@see `get_file()`)
337
+ * @return array|null File properties (Default: NULL)
338
+ */
339
+ public function get_script( $handle, $format = null ) {
340
+ return $this->get_file( 'scripts', $handle, $format );
341
+ }
342
+
343
+ }
includes/class.collection_controller.php CHANGED
@@ -1,57 +1,57 @@
1
- <?php
2
-
3
- /**
4
- * Collection Controller
5
- * @package Simple Lightbox
6
- * @subpackage Collection
7
- * @author Archetyped
8
- */
9
- class SLB_Collection_Controller extends SLB_Base_Collection {
10
- /* Configuration */
11
-
12
- protected $mode = 'full';
13
-
14
- protected $unique = true;
15
-
16
- /* Properties */
17
-
18
- protected $parent = null;
19
-
20
- /* Methods */
21
-
22
- public function __construct($parent = null) {
23
- $this->set_parent($parent);
24
- parent::__construct();
25
- }
26
-
27
- /* Initialization */
28
-
29
- /* Parent */
30
-
31
- /**
32
- * Set parent instance
33
- * @param SLB_Base $parent (optional) Parent instance
34
- * @return obj Current instance
35
- */
36
- protected function set_parent($parent = null) {
37
- $this->parent = ( $parent instanceof SLB_Base ) ? $parent : null;
38
- return $this;
39
- }
40
-
41
- /**
42
- * Check if parent set
43
- * @return bool TRUE if parent set
44
- */
45
- protected function has_parent() {
46
- return ( is_object($this->get_parent()) ) ? true : false;
47
- }
48
-
49
- /**
50
- * Retrieve parent
51
- * @uses $parent
52
- * @return null|obj Parent instance (NULL if no parent set)
53
- */
54
- protected function get_parent() {
55
- return $this->parent;
56
- }
57
- }
1
+ <?php
2
+
3
+ /**
4
+ * Collection Controller
5
+ * @package Simple Lightbox
6
+ * @subpackage Collection
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Collection_Controller extends SLB_Base_Collection {
10
+ /* Configuration */
11
+
12
+ protected $mode = 'full';
13
+
14
+ protected $unique = true;
15
+
16
+ /* Properties */
17
+
18
+ protected $parent = null;
19
+
20
+ /* Methods */
21
+
22
+ public function __construct( $parent = null ) {
23
+ $this->set_parent( $parent );
24
+ parent::__construct();
25
+ }
26
+
27
+ /* Initialization */
28
+
29
+ /* Parent */
30
+
31
+ /**
32
+ * Set parent instance
33
+ * @param SLB_Base $parent (optional) Parent instance
34
+ * @return obj Current instance
35
+ */
36
+ protected function set_parent( $parent = null ) {
37
+ $this->parent = ( $parent instanceof SLB_Base ) ? $parent : null;
38
+ return $this;
39
+ }
40
+
41
+ /**
42
+ * Check if parent set
43
+ * @return bool TRUE if parent set
44
+ */
45
+ protected function has_parent() {
46
+ return ( is_object( $this->get_parent() ) ) ? true : false;
47
+ }
48
+
49
+ /**
50
+ * Retrieve parent
51
+ * @uses $parent
52
+ * @return null|obj Parent instance (NULL if no parent set)
53
+ */
54
+ protected function get_parent() {
55
+ return $this->parent;
56
+ }
57
+ }
includes/class.component.php CHANGED
@@ -1,135 +1,135 @@
1
- <?php
2
-
3
- /**
4
- * Component
5
- * @package Simple Lightbox
6
- * @subpackage Base
7
- * @author Archetyped
8
- */
9
- class SLB_Component extends SLB_Base_Object {
10
- /* Properties */
11
-
12
- /**
13
- * Pretty name
14
- * @var string
15
- */
16
- protected $name = '';
17
-
18
- protected $props_required = array();
19
-
20
- private $props_required_base = array('id');
21
-
22
- /* Get/Set */
23
-
24
- /**
25
- * Set name
26
- * @param string $name Name
27
- * @return Current instance
28
- */
29
- public function set_name($name) {
30
- if ( is_string($name) ) {
31
- $name = trim($name);
32
- if ( !empty($name) ) {
33
- $this->name = $name;
34
- }
35
- }
36
- return $this;
37
- }
38
-
39
- public function get_name() {
40
- return $this->name;
41
- }
42
-
43
- public function set_scripts($scripts) {
44
- $this->add_files('scripts', $scripts);
45
- }
46
-
47
- public function set_styles($styles) {
48
- $this->add_files('styles', $styles);
49
- }
50
-
51
- /* Assets */
52
-
53
- /**
54
- * Get formatted handle for file
55
- * @param string $base_handle Base handle to format
56
- * @return string Formatted handle
57
- */
58
- public function get_handle($base_handle) {
59
- return $this->add_prefix( array('asset', $this->get_id(), $base_handle), '-');
60
- }
61
-
62
- /**
63
- * Enqueue files in client
64
- * @param string $type (optional) Type of file to load (singular) (Default: All client file types)
65
- */
66
- public function enqueue_client_files($type = null) {
67
- if ( empty($type) ) {
68
- $type = array ( 'script', 'style');
69
- }
70
- if ( !is_array($type) ) {
71
- $type = array ( $type );
72
- }
73
- foreach ( $type as $t ) {
74
- $m = (object) array (
75
- 'get' => $this->m('get_' . $t . 's'),
76
- 'enqueue' => 'wp_enqueue_' . $t,
77
- );
78
- $v = $this->util->get_plugin_version();
79
- $files = call_user_func($m->get);
80
- $param_final = ( 'script' == $t ) ? true : 'all';
81
- foreach ( $files as $f ) {
82
- $f = (object) $f;
83
- // Format handle
84
- $handle = $this->get_handle($f->handle);
85
-
86
- // Format dependencies
87
- $deps = array();
88
- foreach ( $f->deps as $dep ) {
89
- if ( $this->util->has_wrapper($dep) ) {
90
- $dep = $this->get_handle( $this->util->remove_wrapper($dep) );
91
- }
92
- $deps[] = $dep;
93
- }
94
- call_user_func($m->enqueue, $handle, $f->uri, $deps, $v, $param_final);
95
- }
96
- unset($files, $f, $param_final, $handle, $deps, $dep);
97
- }
98
- }
99
-
100
- /**
101
- * Enqueue scripts
102
- */
103
- public function enqueue_scripts() {
104
- $this->enqueue_client_files('script');
105
- }
106
-
107
- /**
108
- * Enqueue styles
109
- */
110
- public function enqueue_styles() {
111
- $this->enqueue_client_files('style');
112
- }
113
-
114
- /* Helpers */
115
-
116
- /**
117
- * Validate instance
118
- * @see `Base_Object::is_valid()`
119
- * @return bool Valid (TRUE) / Invalid (FALSE)
120
- */
121
- public function is_valid() {
122
- $ret = parent::is_valid();
123
- if ( $ret ) {
124
- // Check required component properties
125
- $props = array_merge($this->props_required_base, $this->props_required);
126
- foreach ( $props as $prop ) {
127
- if ( !isset($this->{$prop}) || empty($this->{$prop}) ) {
128
- $ret = false;
129
- break;
130
- }
131
- }
132
- }
133
- return $ret;
134
- }
135
- }
1
+ <?php
2
+
3
+ /**
4
+ * Component
5
+ * @package Simple Lightbox
6
+ * @subpackage Base
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Component extends SLB_Base_Object {
10
+ /* Properties */
11
+
12
+ /**
13
+ * Pretty name
14
+ * @var string
15
+ */
16
+ protected $name = '';
17
+
18
+ protected $props_required = array();
19
+
20
+ private $props_required_base = array( 'id' );
21
+
22
+ /* Get/Set */
23
+
24
+ /**
25
+ * Set name
26
+ * @param string $name Name
27
+ * @return Current instance
28
+ */
29
+ public function set_name( $name ) {
30
+ if ( is_string( $name ) ) {
31
+ $name = trim( $name );
32
+ if ( ! empty( $name ) ) {
33
+ $this->name = $name;
34
+ }
35
+ }
36
+ return $this;
37
+ }
38
+
39
+ public function get_name() {
40
+ return $this->name;
41
+ }
42
+
43
+ public function set_scripts( $scripts ) {
44
+ $this->add_files( 'scripts', $scripts );
45
+ }
46
+
47
+ public function set_styles( $styles ) {
48
+ $this->add_files( 'styles', $styles );
49
+ }
50
+
51
+ /* Assets */
52
+
53
+ /**
54
+ * Get formatted handle for file
55
+ * @param string $base_handle Base handle to format
56
+ * @return string Formatted handle
57
+ */
58
+ public function get_handle( $base_handle ) {
59
+ return $this->add_prefix( array( 'asset', $this->get_id(), $base_handle ), '-' );
60
+ }
61
+
62
+ /**
63
+ * Enqueue files in client
64
+ * @param string $type (optional) Type of file to load (singular) (Default: All client file types)
65
+ */
66
+ public function enqueue_client_files( $type = null ) {
67
+ if ( empty( $type ) ) {
68
+ $type = array( 'script', 'style' );
69
+ }
70
+ if ( ! is_array( $type ) ) {
71
+ $type = array( $type );
72
+ }
73
+ foreach ( $type as $t ) {
74
+ $m = (object) array(
75
+ 'get' => $this->m( 'get_' . $t . 's' ),
76
+ 'enqueue' => 'wp_enqueue_' . $t,
77
+ );
78
+ $v = $this->util->get_plugin_version();
79
+ $files = call_user_func( $m->get );
80
+ $param_final = ( 'script' === $t ) ? true : 'all';
81
+ foreach ( $files as $f ) {
82
+ $f = (object) $f;
83
+ // Format handle
84
+ $handle = $this->get_handle( $f->handle );
85
+
86
+ // Format dependencies
87
+ $deps = array();
88
+ foreach ( $f->deps as $dep ) {
89
+ if ( $this->util->has_wrapper( $dep ) ) {
90
+ $dep = $this->get_handle( $this->util->remove_wrapper( $dep ) );
91
+ }
92
+ $deps[] = $dep;
93
+ }
94
+ call_user_func( $m->enqueue, $handle, $f->uri, $deps, $v, $param_final );
95
+ }
96
+ unset( $files, $f, $param_final, $handle, $deps, $dep );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Enqueue scripts
102
+ */
103
+ public function enqueue_scripts() {
104
+ $this->enqueue_client_files( 'script' );
105
+ }
106
+
107
+ /**
108
+ * Enqueue styles
109
+ */
110
+ public function enqueue_styles() {
111
+ $this->enqueue_client_files( 'style' );
112
+ }
113
+
114
+ /* Helpers */
115
+
116
+ /**
117
+ * Validate instance
118
+ * @see `Base_Object::is_valid()`
119
+ * @return bool Valid (TRUE) / Invalid (FALSE)
120
+ */
121
+ public function is_valid() {
122
+ $ret = parent::is_valid();
123
+ if ( $ret ) {
124
+ // Check required component properties
125
+ $props = array_merge( $this->props_required_base, $this->props_required );
126
+ foreach ( $props as $prop ) {
127
+ if ( ! isset( $this->{$prop} ) || empty( $this->{$prop} ) ) {
128
+ $ret = false;
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ return $ret;
134
+ }
135
+ }
includes/class.content_handler.php CHANGED
@@ -1,82 +1,82 @@
1
- <?php
2
-
3
- /**
4
- * Content Handler
5
- * @package Simple Lightbox
6
- * @subpackage Content Handlers
7
- * @author Archetyped
8
- */
9
- class SLB_Content_Handler extends SLB_Component {
10
- /* Properties */
11
-
12
- /**
13
- * Match handler
14
- * @var callback
15
- */
16
- protected $match;
17
-
18
- /**
19
- * Custom attributes
20
- * @var callback
21
- */
22
- protected $attributes;
23
-
24
- /* Matching */
25
-
26
- /**
27
- * Set matching handler
28
- * @param callback $callback Handler callback
29
- * @return object Current instance
30
- */
31
- public function set_match($callback) {
32
- $this->match = ( is_callable($callback) ) ? $callback : null;
33
- return $this;
34
- }
35
-
36
- /**
37
- * Retrieve match handler
38
- * @return callback|null Match handler
39
- */
40
- protected function get_match() {
41
- return $this->match;
42
- }
43
-
44
- /**
45
- * Check if valid match set
46
- */
47
- protected function has_match() {
48
- return ( is_null($this->match) ) ? false : true;
49
- }
50
-
51
- /**
52
- * Match handler against URI
53
- * @param string $uri URI to check for match
54
- * @return bool TRUE if handler matches URI
55
- */
56
- public function match($uri, $uri_raw = null) {
57
- $ret = false;
58
- if ( !!$uri && is_string($uri) && $this->has_match() ) {
59
- $ret = call_user_func($this->get_match(), $uri, $uri_raw);
60
- }
61
- return $ret;
62
- }
63
-
64
- /* Attributes */
65
-
66
- public function set_attributes($callback) {
67
- $this->attributes = ( is_callable($callback) ) ? $callback : null;
68
- return $this;
69
- }
70
-
71
- public function get_attributes() {
72
- $ret = array();
73
- // Callback
74
- if ( !is_null($this->attributes) ) {
75
- $ret = call_user_func($this->attributes);
76
- }
77
- // Filter
78
- $hook = sprintf('content_handler_%s_attributes', $this->get_id());
79
- $ret = $this->util->apply_filters($hook, $ret);
80
- return ( is_array($ret) ) ? $ret : array();
81
- }
82
- }
1
+ <?php
2
+
3
+ /**
4
+ * Content Handler
5
+ * @package Simple Lightbox
6
+ * @subpackage Content Handlers
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Content_Handler extends SLB_Component {
10
+ /* Properties */
11
+
12
+ /**
13
+ * Match handler
14
+ * @var callback
15
+ */
16
+ protected $match;
17
+
18
+ /**
19
+ * Custom attributes
20
+ * @var callback
21
+ */
22
+ protected $attributes;
23
+
24
+ /* Matching */
25
+
26
+ /**
27
+ * Set matching handler
28
+ * @param callback $callback Handler callback
29
+ * @return object Current instance
30
+ */
31
+ public function set_match( $callback ) {
32
+ $this->match = ( is_callable( $callback ) ) ? $callback : null;
33
+ return $this;
34
+ }
35
+
36
+ /**
37
+ * Retrieve match handler
38
+ * @return callback|null Match handler
39
+ */
40
+ protected function get_match() {
41
+ return $this->match;
42
+ }
43
+
44
+ /**
45
+ * Check if valid match set
46
+ */
47
+ protected function has_match() {
48
+ return ( is_null( $this->match ) ) ? false : true;
49
+ }
50
+
51
+ /**
52
+ * Match handler against URI
53
+ * @param string $uri URI to check for match
54
+ * @return bool TRUE if handler matches URI
55
+ */
56
+ public function match( $uri, $uri_raw = null ) {
57
+ $ret = false;
58
+ if ( ! ! $uri && is_string( $uri ) && $this->has_match() ) {
59
+ $ret = call_user_func( $this->get_match(), $uri, $uri_raw );
60
+ }
61
+ return $ret;
62
+ }
63
+
64
+ /* Attributes */
65
+
66
+ public function set_attributes( $callback ) {
67
+ $this->attributes = ( is_callable( $callback ) ) ? $callback : null;
68
+ return $this;
69
+ }
70
+
71
+ public function get_attributes() {
72
+ $ret = array();
73
+ // Callback
74
+ if ( ! is_null( $this->attributes ) ) {
75
+ $ret = call_user_func( $this->attributes );
76
+ }
77
+ // Filter
78
+ $hook = sprintf( 'content_handler_%s_attributes', $this->get_id() );
79
+ $ret = $this->util->apply_filters( $hook, $ret );
80
+ return ( is_array( $ret ) ) ? $ret : array();
81
+ }
82
+ }
includes/class.content_handlers.php CHANGED
@@ -1,277 +1,280 @@
1
- <?php
2
-
3
- /**
4
- * Content Handler Collection
5
- * @package Simple Lightbox
6
- * @subpackage Content Handler
7
- * @author Archetyped
8
- */
9
- class SLB_Content_Handlers extends SLB_Collection_Controller {
10
- /* Configuration */
11
-
12
- protected $item_type = 'SLB_Content_Handler';
13
-
14
- public $hook_prefix = 'content_handlers';
15
-
16
- protected $key_prop = 'get_id';
17
-
18
- protected $key_call = true;
19
-
20
- /* Properties */
21
-
22
- protected $request_matches = array();
23
-
24
- /**
25
- * Cache properties (key, group)
26
- * @var object
27
- */
28
- protected $cache_props = null;
29
-
30
- /* Initialization */
31
-
32
- protected function _hooks() {
33
- parent::_hooks();
34
- $this->util->add_action('init', $this->m('init_defaults'), 5);
35
- $this->util->add_action('footer', $this->m('client_output'), 1, 0, false);
36
- $this->util->add_filter('footer_script', $this->m('client_output_script'), $this->util->priority('client_footer_output'), 1, false);
37
- }
38
-
39
- /* Collection Management */
40
-
41
- /**
42
- * Add content type handler
43
- * Accepts properties to create new handler OR previously-initialized handler instance
44
- * @uses clear_cache()
45
- * @see parent::add()
46
- * @param string $id Handler ID
47
- * @param array $props Handler properties
48
- * @return object Current instance
49
- */
50
- public function add($id, $props = array(), $priority = 10) {
51
- $this->clear_cache();
52
- if ( is_string($id) ) {
53
- // Initialize new handler
54
- $handler = new $this->item_type($id, $props);
55
- } else {
56
- // Remap parameters
57
- $handler = func_get_arg(0);
58
- if ( func_num_args() == 2 ) {
59
- $priority = func_get_arg(1);
60
- }
61
- }
62
- if ( !is_int($priority) ) {
63
- $priority = 10;
64
- }
65
- // Add to collection
66
- return parent::add($handler, array('priority' => $priority));
67
- }
68
-
69
- /**
70
- * Remove item
71
- * @uses clear_cache()
72
- * @see parent::remove()
73
- * @return object Current instance
74
- */
75
- public function remove($item) {
76
- $this->clear_cache();
77
- return parent::remove($item);
78
- }
79
-
80
- /**
81
- * Clear collection
82
- * @uses clear_cache()
83
- * @see parent::clear()
84
- * @return object Current instance
85
- */
86
- public function clear() {
87
- $this->clear_cache();
88
- return parent::clear();
89
- }
90
-
91
- /**
92
- * Retrieves handlers sorted by priority
93
- * @see parent::get()
94
- * @uses get_cache()
95
- * @param mixed $args Unused
96
- * @return array Handlers
97
- */
98
- public function get($args = null) {
99
- $items = $this->get_cache();
100
- if ( empty($items) ) {
101
- // Retrieve items
102
- $items = parent::get( array( 'orderby' => array('meta' => 'priority') ) );
103
- $this->update_cache($items);
104
- }
105
- return $items;
106
- }
107
-
108
- /**
109
- * Get matching handler for URI
110
- * @param string $uri URI to find match for
111
- * @return object Handler package (FALSE if no match found)
112
- * Package members
113
- * > handler (Content_Handler) Matching handler instance (Default: NULL)
114
- * > props (array) Properties returned from matching handler (May be empty depending on handler)
115
- */
116
- public function match($uri) {
117
- $ret = (object) array('handler' => null, 'props' => array());
118
- foreach ( $this->get() as $handler ) {
119
- $props = $handler->match($uri, $this);
120
- if ( !!$props ) {
121
- $ret->handler = $handler;
122
- // Add handler props
123
- if ( is_array($props) ) {
124
- $ret->props = $props;
125
- }
126
- // Save match
127
- $hid = $handler->get_id();
128
- if ( !isset($this->request_matches[$hid]) ) {
129
- $this->request_matches[$hid] = $handler;
130
- }
131
- break;
132
- }
133
- }
134
- return $ret;
135
- }
136
-
137
- /* Cache */
138
-
139
- /**
140
- * Retrieve cached items
141
- * @uses get_cache_props()
142
- * @uses wp_cache_get()
143
- * @return array Cached items (Default: empty array)
144
- */
145
- protected function get_cache() {
146
- $cprops= $this->get_cache_props();
147
- $items = wp_cache_get($cprops->key, $cprops->group);
148
- return ( is_array($items) ) ? $items : array();
149
- }
150
-
151
- /**
152
- * Update cached items
153
- * Cache is cleared if no items specified
154
- * @uses get_cache_props()
155
- * @uses wp_cache_get()
156
- * @param array $data Item data to cache
157
- */
158
- protected function update_cache($data = null) {
159
- $props = $this->get_cache_props();
160
- wp_cache_set($props->key, $data, $props->group);
161
- }
162
-
163
- /**
164
- * Clear cache
165
- * @uses update_cache()
166
- */
167
- protected function clear_cache() {
168
- $this->update_cache();
169
- }
170
-
171
- /**
172
- * Retrieve cache properites (key, group)
173
- * @return object Cache properties
174
- */
175
- protected function get_cache_props() {
176
- if ( !is_object($this->cache_props) ) {
177
- $this->cache_props = (object) array (
178
- 'key' => $this->hook_prefix . '_items',
179
- 'group' => $this->get_prefix(),
180
- );
181
- }
182
- return $this->cache_props;
183
- }
184
-
185
- /* Handlers */
186
-
187
- /**
188
- * Initialize default handlers
189
- * @param SLB_Content_Handlers $handlers Handlers controller
190
- */
191
- public function init_defaults($handlers) {
192
- $src_base = $this->util->get_file_url('content-handlers', true);
193
- $js_path = 'js/';
194
- $js_path .= ( SLB_DEV ) ? 'dev' : 'prod';
195
- $defaults = array (
196
- 'image' => array (
197
- 'match' => $this->m('match_image'),
198
- 'scripts' => array (
199
- array ( 'base', "$src_base/image/$js_path/handler.image.js" ),
200
- ),
201
- )
202
- );
203
- foreach ( $defaults as $id => $props ) {
204
- $handlers->add($id, $props);
205
- }
206
- }
207
-
208
- /**
209
- * Matches image URIs
210
- * @param string $uri URI to match
211
- * @return bool|array TRUE if URI is image (array is used if extra data needs to be sent)
212
- */
213
- public function match_image($uri, $handlers) {
214
- // Basic matching
215
- $match = ( $this->util->has_file_extension($uri, array('jpg', 'jpeg', 'jpe', 'jfif', 'jif', 'gif', 'png')) ) ? true : false;
216
-
217
- // Filter result
218
- $extra = new stdClass();
219
- $match = $this->util->apply_filters('image_match', $match, $uri, $extra);
220
-
221
- // Handle extra data passed from filters
222
- // Currently only `uri` supported
223
- if ( $match && isset($extra->uri) && is_string($extra->uri) ) {
224
- $match = array('uri' => $extra->uri);
225
- }
226
-
227
- return $match;
228
- }
229
-
230
- /* Output */
231
-
232
- /**
233
- * Build client output
234
- * Load handler files in client
235
- */
236
- public function client_output() {
237
- // Get handlers for current request
238
- foreach ( $this->request_matches as $handler ) {
239
- $handler->enqueue_scripts();
240
- }
241
- }
242
-
243
- /**
244
- * Client output script
245
- * @param array $commands Client script commands
246
- * @return array Modified script commands
247
- */
248
- public function client_output_script($commands) {
249
- $out = array('/* CHDL */');
250
- $code = array();
251
-
252
- foreach ( $this->request_matches as $handler ) {
253
- // Attributes
254
- $attrs = $handler->get_attributes();
255
- // Styles
256
- $styles = $handler->get_styles(array('uri_format'=>'full'));
257
- if ( !empty($styles) ) {
258
- $attrs['styles'] = array_values($styles);
259
- }
260
- if ( empty($attrs) ) {
261
- continue;
262
- }
263
- // Setup client parameters
264
- $params = array(
265
- sprintf("'%s'", $handler->get_id()),
266
- json_encode($attrs),
267
- );
268
- // Extend handler in client
269
- $code[] = $this->util->call_client_method('View.extend_content_handler', $params, false);
270
- }
271
- if ( !empty($code) ) {
272
- $out[] = implode('', $code);
273
- $commands[] = implode(PHP_EOL, $out);
274
- }
275
- return $commands;
276
- }
277
- }
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Content Handler Collection
5
+ * @package Simple Lightbox
6
+ * @subpackage Content Handler
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Content_Handlers extends SLB_Collection_Controller {
10
+ /* Configuration */
11
+
12
+ protected $item_type = 'SLB_Content_Handler';
13
+
14
+ public $hook_prefix = 'content_handlers';
15
+
16
+ protected $key_prop = 'get_id';
17
+
18
+ protected $key_call = true;
19
+
20
+ /* Properties */
21
+
22
+ protected $request_matches = array();
23
+
24
+ /**
25
+ * Cache properties (key, group)
26
+ * @var object
27
+ */
28
+ protected $cache_props = null;
29
+
30
+ /* Initialization */
31
+
32
+ protected function _hooks() {
33
+ parent::_hooks();
34
+ $this->util->add_action( 'init', $this->m( 'init_defaults' ), 5 );
35
+ $this->util->add_action( 'footer', $this->m( 'client_output' ), 1, 0, false );
36
+ $this->util->add_filter( 'footer_script', $this->m( 'client_output_script' ), $this->util->priority( 'client_footer_output' ), 1, false );
37
+ }
38
+
39
+ /* Collection Management */
40
+
41
+ /**
42
+ * Add content type handler
43
+ * Accepts properties to create new handler OR previously-initialized handler instance
44
+ * @uses clear_cache()
45
+ * @see parent::add()
46
+ * @param string $id Handler ID
47
+ * @param array $props Handler properties
48
+ * @return object Current instance
49
+ */
50
+ public function add( $id, $props = array(), $priority = 10 ) {
51
+ $this->clear_cache();
52
+ if ( is_string( $id ) ) {
53
+ // Initialize new handler
54
+ $handler = new $this->item_type( $id, $props );
55
+ } else {
56
+ // Remap parameters
57
+ $handler = func_get_arg( 0 );
58
+ if ( func_num_args() === 2 ) {
59
+ $priority = func_get_arg( 1 );
60
+ }
61
+ }
62
+ if ( ! is_int( $priority ) ) {
63
+ $priority = 10;
64
+ }
65
+ // Add to collection
66
+ return parent::add( $handler, array( 'priority' => $priority ) );
67
+ }
68
+
69
+ /**
70
+ * Remove item
71
+ * @uses clear_cache()
72
+ * @see parent::remove()
73
+ * @return object Current instance
74
+ */
75
+ public function remove( $item ) {
76
+ $this->clear_cache();
77
+ return parent::remove( $item );
78
+ }
79
+
80
+ /**
81
+ * Clear collection
82
+ * @uses clear_cache()
83
+ * @see parent::clear()
84
+ * @return object Current instance
85
+ */
86
+ public function clear() {
87
+ $this->clear_cache();
88
+ return parent::clear();
89
+ }
90
+
91
+ /**
92
+ * Retrieves handlers sorted by priority
93
+ * @see parent::get()
94
+ * @uses get_cache()
95
+ * @param mixed $args Unused
96
+ * @return array Handlers
97
+ */
98
+ public function get( $args = null ) {
99
+ $items = $this->get_cache();
100
+ if ( empty( $items ) ) {
101
+ // Retrieve items
102
+ $items = parent::get( array( 'orderby' => array( 'meta' => 'priority' ) ) );
103
+ $this->update_cache( $items );
104
+ }
105
+ return $items;
106
+ }
107
+
108
+ /**
109
+ * Get matching handler for URI
110
+ * @param string $uri URI to find match for
111
+ * @return object Handler package (FALSE if no match found)
112
+ * Package members
113
+ * > handler (Content_Handler) Matching handler instance (Default: NULL)
114
+ * > props (array) Properties returned from matching handler (May be empty depending on handler)
115
+ */
116
+ public function match( $uri ) {
117
+ $ret = (object) array(
118
+ 'handler' => null,
119
+ 'props' => array(),
120
+ );
121
+ foreach ( $this->get() as $handler ) {
122
+ $props = $handler->match( $uri, $this );
123
+ if ( ! ! $props ) {
124
+ $ret->handler = $handler;
125
+ // Add handler props
126
+ if ( is_array( $props ) ) {
127
+ $ret->props = $props;
128
+ }
129
+ // Save match
130
+ $hid = $handler->get_id();
131
+ if ( ! isset( $this->request_matches[ $hid ] ) ) {
132
+ $this->request_matches[ $hid ] = $handler;
133
+ }
134
+ break;
135
+ }
136
+ }
137
+ return $ret;
138
+ }
139
+
140
+ /* Cache */
141
+
142
+ /**
143
+ * Retrieve cached items
144
+ * @uses get_cache_props()
145
+ * @uses wp_cache_get()
146
+ * @return array Cached items (Default: empty array)
147
+ */
148
+ protected function get_cache() {
149
+ $cprops = $this->get_cache_props();
150
+ $items = wp_cache_get( $cprops->key, $cprops->group );
151
+ return ( is_array( $items ) ) ? $items : array();
152
+ }
153
+
154
+ /**
155
+ * Update cached items
156
+ * Cache is cleared if no items specified
157
+ * @uses get_cache_props()
158
+ * @uses wp_cache_get()
159
+ * @param array $data Item data to cache
160
+ */
161
+ protected function update_cache( $data = null ) {
162
+ $props = $this->get_cache_props();
163
+ wp_cache_set( $props->key, $data, $props->group );
164
+ }
165
+
166
+ /**
167
+ * Clear cache
168
+ * @uses update_cache()
169
+ */
170
+ protected function clear_cache() {
171
+ $this->update_cache();
172
+ }
173
+
174
+ /**
175
+ * Retrieve cache properites (key, group)
176
+ * @return object Cache properties
177
+ */
178
+ protected function get_cache_props() {
179
+ if ( ! is_object( $this->cache_props ) ) {
180
+ $this->cache_props = (object) array(
181
+ 'key' => $this->hook_prefix . '_items',
182
+ 'group' => $this->get_prefix(),
183
+ );
184
+ }
185
+ return $this->cache_props;
186
+ }
187
+
188
+ /* Handlers */
189
+
190
+ /**
191
+ * Initialize default handlers
192
+ * @param SLB_Content_Handlers $handlers Handlers controller
193
+ */
194
+ public function init_defaults( $handlers ) {
195
+ $src_base = $this->util->get_file_url( 'content-handlers', true );
196
+ $js_path = 'js/';
197
+ $js_path .= ( SLB_DEV ) ? 'dev' : 'prod';
198
+ $defaults = array(
199
+ 'image' => array(
200
+ 'match' => $this->m( 'match_image' ),
201
+ 'scripts' => array(
202
+ array( 'base', "$src_base/image/$js_path/handler.image.js" ),
203
+ ),
204
+ ),
205
+ );
206
+ foreach ( $defaults as $id => $props ) {
207
+ $handlers->add( $id, $props );
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Matches image URIs
213
+ * @param string $uri URI to match
214
+ * @return bool|array TRUE if URI is image (array is used if extra data needs to be sent)
215
+ */
216
+ public function match_image( $uri, $handlers ) {
217
+ // Basic matching
218
+ $match = ( $this->util->has_file_extension( $uri, array( 'avif', 'jpg', 'jpeg', 'jpe', 'jfif', 'jif', 'gif', 'png', 'webp' ) ) ) ? true : false;
219
+
220
+ // Filter result
221
+ $extra = new stdClass();
222
+ $match = $this->util->apply_filters( 'image_match', $match, $uri, $extra );
223
+
224
+ // Handle extra data passed from filters
225
+ // Currently only `uri` supported
226
+ if ( $match && isset( $extra->uri ) && is_string( $extra->uri ) ) {
227
+ $match = array( 'uri' => $extra->uri );
228
+ }
229
+
230
+ return $match;
231
+ }
232
+
233
+ /* Output */
234
+
235
+ /**
236
+ * Build client output
237
+ * Load handler files in client
238
+ */
239
+ public function client_output() {
240
+ // Get handlers for current request
241
+ foreach ( $this->request_matches as $handler ) {
242
+ $handler->enqueue_scripts();
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Client output script
248
+ * @param array $commands Client script commands
249
+ * @return array Modified script commands
250
+ */
251
+ public function client_output_script( $commands ) {
252
+ $out = array( '/* CHDL */' );
253
+ $code = array();
254
+
255
+ foreach ( $this->request_matches as $handler ) {
256
+ // Attributes
257
+ $attrs = $handler->get_attributes();
258
+ // Styles
259
+ $styles = $handler->get_styles( array( 'uri_format' => 'full' ) );
260
+ if ( ! empty( $styles ) ) {
261
+ $attrs['styles'] = array_values( $styles );
262
+ }
263
+ if ( empty( $attrs ) ) {
264
+ continue;
265
+ }
266
+ // Setup client parameters
267
+ $params = array(
268
+ sprintf( "'%s'", $handler->get_id() ),
269
+ wp_json_encode( $attrs ),
270
+ );
271
+ // Extend handler in client
272
+ $code[] = $this->util->call_client_method( 'View.extend_content_handler', $params, false );
273
+ }
274
+ if ( ! empty( $code ) ) {
275
+ $out[] = implode( '', $code );
276
+ $commands[] = implode( PHP_EOL, $out );
277
+ }
278
+ return $commands;
279
+ }
280
+ }
includes/class.field.php CHANGED
@@ -1,2 +1,2 @@
1
- <?php
2
- class SLB_Field extends SLB_Field_Type {}
1
+ <?php
2
+ class SLB_Field extends SLB_Field_Type {}
includes/class.field_base.php CHANGED
@@ -1,1118 +1,1202 @@
1
- <?php
2
-
3
- /**
4
- * Fields - Base class
5
- * Core properties/methods for fields
6
- * @package Simple Lightbox
7
- * @subpackage Fields
8
- * @author Archetyped
9
- */
10
- class SLB_Field_Base extends SLB_Base {
11
- /*-** Config **-*/
12
- protected $mode = 'object';
13
- protected $shared = false;
14
-
15
- /*-** Properties **-*/
16
-
17
- /**
18
- * @var string Unique name
19
- */
20
- var $id = '';
21
-
22
- /**
23
- * ID formatting options.
24
- *
25
- * @var array $id_formats
26
- */
27
- private $id_formats = [];
28
-
29
- /**
30
- * Flag for ID format initialization status.
31
- *
32
- * @var bool $id_formats_init
33
- */
34
- private $id_formats_init = false;
35
-
36
- /**
37
- * Special characters/phrases
38
- * Used for preserving special characters during formatting
39
- * Merged with $special_chars_default
40
- * Array Structure
41
- * > Key: Special character/phrase
42
- * > Value: Placeholder for special character
43
- * @var array
44
- */
45
- var $special_chars = null;
46
-
47
- var $special_chars_default = array(
48
- '{' => '%SQB_L%',
49
- '}' => '%SQB_R%',
50
- );
51
-
52
- /**
53
- * Reference to parent object that current instance inherits from
54
- * @var object
55
- */
56
- var $parent = null;
57
-
58
- /**
59
- * Title
60
- * @var string
61
- */
62
- var $title = '';
63
-
64
- /**
65
- * @var string Short description
66
- */
67
- var $description = '';
68
-
69
- /**
70
- * @var array Object Properties
71
- */
72
- var $properties = array();
73
-
74
- /**
75
- * Initialization properties
76
- * @var array
77
- */
78
- protected $properties_init = null;
79
-
80
- /**
81
- * Structure: Property names stored as keys in group
82
- * Root
83
- * -> Group Name
84
- * -> Property Name => Null
85
- * Reason: Faster searching over large arrays
86
- * @var array Groupings of Properties
87
- */
88
- var $property_groups = array();
89
-
90
- /**
91
- * Keys to filter out of properties array before setting properties
92
- * @var array
93
- */
94
- var $property_filter = array('group');
95
-
96
- /**
97
- * Define order of properties
98
- * Useful when processing order is important (e.g. one property depends on another)
99
- * @var array
100
- */
101
- var $property_priority = array();
102
-
103
- /**
104
- * Data for object
105
- * May also contain data for nested objects
106
- * @var mixed
107
- */
108
- var $data = null;
109
-
110
- /**
111
- * Whether data has been fetched or not
112
- * @var bool
113
- */
114
- protected $data_loaded = false;
115
-
116
- /**
117
- * @var array Script resources to include for object
118
- */
119
- var $scripts = array();
120
-
121
- /**
122
- * @var array CSS style resources to include for object
123
- */
124
- var $styles = array();
125
-
126
- /**
127
- * Hooks (Filters/Actions) for object
128
- * @var array
129
- */
130
- var $hooks = array();
131
-
132
- /**
133
- * Mapping of child properties to parent members
134
- * Allows more flexibility when creating new instances of child objects using property arrays
135
- * Associative array structure:
136
- * > Key: Child property to map FROM
137
- * > Val: Parent property to map TO
138
- * @var array
139
- */
140
- var $map = null;
141
-
142
- /**
143
- * Options used when building collection (callbacks, etc.)
144
- * Associative array
145
- * > Key: Option name
146
- * > Value: Option value
147
- * @var array
148
- */
149
- var $build_vars = array();
150
-
151
- var $build_vars_default = array();
152
-
153
- /**
154
- * Constructor
155
- */
156
- function __construct($id = '', $properties = null) {
157
- parent::__construct();
158
- // Normalize Properties
159
- $args = func_get_args();
160
- $defaults = $this->integrate_id($id);
161
- $properties = $this->make_properties($args, $defaults);
162
- // Save init properties
163
- $this->properties_init = $properties;
164
- // Set Properties
165
- $this->set_properties($properties);
166
- }
167
-
168
- /* Getters/Setters */
169
-
170
- /**
171
- * Checks if the specified path exists in the object
172
- * @param array $path Path to check for
173
- * @return bool TRUE if path exists in object, FALSE otherwise
174
- */
175
- function path_isset($path = '') {
176
- // Stop execution if no path is supplied
177
- if ( empty($path) )
178
- return false;
179
- $args = func_get_args();
180
- $path = $this->util->build_path($args);
181
- $item =& $this;
182
- // Iterate over path and check if each level exists before moving on to the next
183
- for ($x = 0; $x < count($path); $x++) {
184
- if ( $this->util->property_exists($item, $path[$x]) ) {
185
- // Set $item as reference to next level in path for next iteration
186
- $item =& $this->util->get_property($item, $path[$x]);
187
- // $item =& $item[ $path[$x] ];
188
- } else {
189
- return false;
190
- }
191
- }
192
- return true;
193
- }
194
-
195
- /**
196
- * Retrieves a value from object using a specified path
197
- * Checks to make sure path exists in object before retrieving value
198
- * @param array $path Path to retrieve value from. Each item in array is a deeper dimension
199
- * @return mixed Value at specified path
200
- */
201
- function &get_path_value($path = '') {
202
- $ret = '';
203
- $path = $this->util->build_path(func_get_args());
204
- if ( $this->path_isset($path) ) {
205
- $ret =& $this;
206
- for ($x = 0; $x < count($path); $x++) {
207
- if ( 0 == $x )
208
- $ret =& $ret->{ $path[$x] };
209
- else
210
- $ret =& $ret[ $path[$x] ];
211
- }
212
- }
213
- return $ret;
214
- }
215
-
216
- /**
217
- * Search for specified member value in field type ancestors
218
- * @param string $member Name of object member to search (e.g. properties, layout, etc.)
219
- * @param string $name Value to retrieve from member
220
- * @return mixed Member value if found (Default: empty string)
221
- */
222
- function get_parent_value($member, $name = '', $default = '') {
223
- $parent = $this->get_parent();
224
- return $this->get_object_value($parent, $member, $name, $default, 'parent');
225
- }
226
-
227
- /**
228
- * Retrieves specified member value
229
- * Handles inherited values
230
- * Merging corresponding parents if value is an array (e.g. for property groups)
231
- * @param string|array $member Member to search. May also contain a path to the desired member
232
- * @param string $name Value to retrieve from member
233
- * @param mixed $default Default value if no value found (Default: empty string)
234
- * @param string $dir Direction to move through hierarchy to find value
235
- * Possible Values:
236
- * parent (default) - Search through field parents
237
- * current - Do not search through connected objects
238
- * container - Search through field containers
239
- * caller - Search through field callers
240
- * @return mixed Specified member value
241
- * @todo Return reference
242
- */
243
- function &get_member_value($member, $name = '', $default = '', $dir = 'parent') {
244
- // Check if path to member is supplied
245
- $path = array();
246
- if ( is_array($member) && isset($member['tag']) ) {
247
- if ( isset($member['attributes']['ref_base']) ) {
248
- if ( 'root' != $member['attributes']['ref_base'] )
249
- $path[] = $member['attributes']['ref_base'];
250
- } else {
251
- $path[] = 'properties';
252
- }
253
-
254
- $path[] = $member['tag'];
255
- } else {
256
- $path = $member;
257
- }
258
-
259
- $path = $this->util->build_path($path, $name);
260
- // Set defaults and prepare data
261
- $val = $default;
262
- $inherit = false;
263
- $inherit_tag = '{inherit}';
264
-
265
- /* Determine whether the value must be retrieved from a parent/container object
266
- * Conditions:
267
- * > Path does not exist in current field
268
- * > Path exists and is not an object, but at least one of the following is true:
269
- * > Value at path is an array (e.g. properties, elements, etc. array)
270
- * > Parent/container values should be merged with retrieved array
271
- * > Value at path is a string that inherits from another field
272
- * > Value from other field will be retrieved and will replace inheritance placeholder in retrieved value
273
- */
274
-
275
- $deeper = false;
276
-
277
- if ( !$this->path_isset($path) )
278
- $deeper = true;
279
- else {
280
- $val = $this->get_path_value($path);
281
- if ( !is_object($val) && ( is_array($val) || ($inherit = strpos($val, $inherit_tag)) !== false ) )
282
- $deeper = true;
283
- else
284
- $deeper = false;
285
- }
286
- if ( $deeper && 'current' != $dir ) {
287
- $ex_val = '';
288
- // Get Parent value (recursive)
289
- if ( 'parent' == $dir )
290
- $ex_val = $this->get_parent_value($member, $name, $default);
291
- elseif ( method_exists($this, 'get_container_value') )
292
- $ex_val = $this->get_container_value($member, $name, $default);
293
- // Handle inheritance
294
- if ( is_array($val) ) {
295
- // Combine Arrays
296
- if ( is_array($ex_val) )
297
- $val = array_merge($ex_val, $val);
298
- } elseif ( $inherit !== false ) {
299
- // Replace placeholder with inherited string
300
- $val = str_replace($inherit_tag, $ex_val, $val);
301
- } else {
302
- // Default: Set parent value as value
303
- $val = $ex_val;
304
- }
305
- }
306
-
307
- return $val;
308
- }
309
-
310
- /**
311
- * Search for specified member value in an object
312
- * @param object $object Reference to object to retrieve value from
313
- * @param string $member Name of object member to search (e.g. properties, layout, etc.)
314
- * @param string $name (optional) Value to retrieve from member
315
- * @param mixed $default (optional) Default value to use if no value found (Default: empty string)
316
- * @param string $dir Direction to move through hierarchy to find value @see SLB_Field_Type::get_member_value() for possible values
317
- * @return mixed Member value if found (Default: $default)
318
- */
319
- function get_object_value(&$object, $member, $name = '', $default = '', $dir = 'parent') {
320
- $ret = $default;
321
- if ( is_object($object) && method_exists($object, 'get_member_value') )
322
- $ret = $object->get_member_value($member, $name, $default, $dir);
323
- return $ret;
324
- }
325
-
326
- /**
327
- * Set item ID
328
- * @param string $id Unique item ID
329
- */
330
- function set_id($id) {
331
- if ( empty($id) || !is_string($id) )
332
- return false;
333
- $this->id = trim($id);
334
- }
335
-
336
- /**
337
- * Retrieves field ID
338
- * @param array|string $options (optional) Options or ID of format to use
339
- * @return string item ID
340
- */
341
- function get_id($options = array()) {
342
- $item_id = trim($this->id);
343
- $formats = $this->get_id_formats();
344
- // Setup options
345
- $wrap_default = array('open' => '', 'close' => '', 'segment_open' => '', 'segment_close' => '');
346
-
347
- $options_default = array(
348
- 'format' => null,
349
- 'wrap' => array(),
350
- 'segments_pre' => null,
351
- 'prefix' => '',
352
- 'recursive' => false
353
- );
354
-
355
- // Load options based on format
356
- if ( !is_array($options) )
357
- $options = array('format' => $options);
358
- if ( isset($options['format']) && is_string($options['format']) && isset($formats[$options['format']]) )
359
- $options_default = wp_parse_args($formats[$options['format']], $options_default);
360
- else
361
- unset($options['format']);
362
- $options = wp_parse_args($options, $options_default);
363
- // Import options into function
364
- extract($options);
365
-
366
- // Validate options
367
- $wrap = wp_parse_args($wrap, $wrap_default);
368
-
369
- if ( !is_array($segments_pre) )
370
- $segments_pre = array($segments_pre);
371
- $segments_pre = array_reverse($segments_pre);
372
-
373
- // Format ID based on options
374
- $item_id = array($item_id);
375
-
376
- // Add parent objects to ID
377
- if ( !!$recursive ) {
378
- // Create array of ID components
379
- $m = 'get_caller';
380
- $c = ( method_exists($this, $m) ) ? $this->{$m}() : null;
381
- while ( !!$c ) {
382
- // Add ID of current caller to array
383
- if ( method_exists($c, 'get_id') && ( $itemp = $c->get_id() ) && !empty($itemp) )
384
- $item_id = $itemp;
385
- // Get parent object
386
- $c = ( method_exists($c, $m) ) ? $c->{$m}() : null;
387
- $itemp = '';
388
- }
389
- unset($c);
390
- }
391
-
392
- // Additional segments (Pre)
393
- foreach ( $segments_pre as $seg ) {
394
- if ( is_null($seg) )
395
- continue;
396
- if ( is_object($seg) )
397
- $seg = (array)$seg;
398
- if ( is_array($seg) )
399
- $item_id = array_merge($item_id, array_reverse($seg));
400
- elseif ( '' != strval($seg) )
401
- $item_id[] = strval($seg);
402
- }
403
-
404
- // Prefix
405
- if ( is_array($prefix) ) {
406
- // Array is sequence of instance methods to call on object
407
- // Last array member can be an array of parameters to pass to methods
408
- $count = count($prefix);
409
- $args = ( $count > 1 && is_array($prefix[$count - 1]) ) ? array_pop($prefix) : array();
410
- $p = $this;
411
- $val = '';
412
- // Iterate through methods
413
- foreach ( $prefix as $m ) {
414
- if ( !method_exists($p, $m) )
415
- continue;
416
- // Build callback
417
- $m = $this->util->m($p, $m);
418
- // Call callback
419
- $val = call_user_func_array($m, $args);
420
- // Returned value may be an instance object
421
- if ( is_object($val) )
422
- $p = $val; // Use returned object in next round
423
- else
424
- array_unshift($args, $val); // Pass returned value as parameter to next method on using current object
425
- }
426
- $prefix = $val;
427
- unset($p, $val);
428
- }
429
- if ( is_numeric($prefix) )
430
- $prefix = strval($prefix);
431
- if ( empty($prefix) || !is_string($prefix) )
432
- $prefix = '';
433
-
434
- // Convert array to string
435
- $item_id = $prefix . $wrap['open'] . implode($wrap['segment_close'] . $wrap['segment_open'], array_reverse($item_id)) . $wrap['close'];
436
- return $item_id;
437
- }
438
-
439
- /**
440
- * Retrieves ID formats.
441
- *
442
- * @return array ID formats.
443
- */
444
- private function &get_id_formats() {
445
- $this->init_id_formats();
446
- return $this->id_formats;
447
- }
448
-
449
- /**
450
- * Initializes default ID formats.
451
- *
452
- * @since 2.8.0
453
- *
454
- * @return void
455
- */
456
- private function init_id_formats() {
457
- if ( ! $this->id_formats_init ) {
458
- $this->id_formats_init = true;
459
- // Initilize default formats.
460
- $this->add_id_format(
461
- 'attr_id',
462
- [
463
- 'wrap' => [ 'open' => '_', 'segment_open' => '_' ],
464
- 'prefix' => [ 'get_container', 'get_id', 'add_prefix' ],
465
- 'recursive' => true,
466
- ],
467
- true
468
- );
469
- $this->add_id_format(
470
- 'attr_name',
471
- [
472
- 'wrap' => [ 'open' => '[', 'close' => ']', 'segment_open' => '[', 'segment_close' => ']' ],
473
- 'prefix' => [ 'get_container', 'get_id', 'add_prefix' ],
474
- 'recursive' => true,
475
- ],
476
- true
477
- );
478
-
479
- }
480
- }
481
-
482
- /**
483
- * Adds custom ID format.
484
- *
485
- * @since 2.8.0
486
- *
487
- * @param string $name Format name.
488
- * @param array $wrap
489
- * @param array $prefix
490
- * @param bool $recursive Optional.
491
- * @param bool $overwrite Optional. Overwrite existing format. Default false.
492
- * @return void
493
- */
494
- protected function add_id_format( $name, array $options, $overwrite = false ) {
495
- // Init ID formats before adding new ones.
496
- $this->init_id_formats();
497
- // Validate args.
498
- $name = trim($name);
499
- // Stop if name invalid.
500
- if ( empty( $name ) ) {
501
- return;
502
- }
503
- $overwrite = (bool) $overwrite;
504
- // Do not add format if name matches existing format (when overwriting not allowed).
505
- if ( ! $overwrite && in_array( $name, array_keys( $this->id_formats ) ) ) {
506
- return;
507
- }
508
- // Normlize options.
509
- $options = wp_parse_args( $options, [ 'wrap' => [], 'prefix' => [], 'recursive' => false ] );
510
- // Add format.
511
- $this->id_formats[ $name ] = $options;
512
- }
513
-
514
- /**
515
- * Retrieve value from data member
516
- * @param string $context Context to format data for
517
- * @param bool $top (optional) Whether to traverse through the field hierarchy to get data for field (Default: TRUE)
518
- * @return mixed Value at specified path
519
- */
520
- function get_data($context = '', $top = true) {
521
- $opt_d = array('context' => '', 'top' => true);
522
- $args = func_get_args();
523
- $a = false;
524
- if ( count($args) == 1 && is_array($args[0]) && !empty($args[0]) ) {
525
- $a = true;
526
- $args = wp_parse_args($args[0], $opt_d);
527
- extract($args);
528
- }
529
-
530
- if ( is_string($top) ) {
531
- if ( 'false' == $top )
532
- $top = false;
533
- elseif ( 'true' == $top )
534
- $top = true;
535
- elseif ( is_numeric($top) )
536
- $top = intval($top);
537
- }
538
- $top = !!$top;
539
- $obj =& $this;
540
- $obj_path = array($this);
541
- $path = array();
542
- if ( $top ) {
543
- // Iterate through hiearchy to get top-most object
544
- while ( !empty($obj) ) {
545
- $new = null;
546
- // Try to get caller first
547
- if ( method_exists($obj, 'get_caller') ) {
548
- $checked = true;
549
- $new =& $obj->get_caller();
550
- }
551
- // Try to get container if no caller found
552
- if ( empty($new) && method_exists($obj, 'get_container') ) {
553
- $checked = true;
554
- $new =& $obj->get_container();
555
- // Load data
556
- if ( method_exists($new, 'load_data') ) {
557
- $new->load_data();
558
- }
559
- }
560
-
561
- $obj =& $new;
562
- unset($new);
563
- // Stop iteration
564
- if ( !empty($obj) ) {
565
- // Add object to path if it is valid
566
- $obj_path[] =& $obj;
567
- }
568
- }
569
- unset($obj);
570
- }
571
-
572
- // Check each object (starting with top-most) for matching data for current field
573
-
574
- // Reverse array
575
- $obj_path = array_reverse($obj_path);
576
- // Build path for data location
577
- foreach ( $obj_path as $obj ) {
578
- if ( method_exists($obj, 'get_id') )
579
- $path[] = $obj->get_id();
580
- }
581
- // Iterate through objects
582
- while ( !empty($obj_path) ) {
583
- // Get next object
584
- $obj = array_shift($obj_path);
585
- // Shorten path
586
- array_shift($path);
587
- // Check for value in object and stop iteration if matching data found
588
- $val = $this->get_object_value($obj, 'data', $path, null, 'current');
589
- if ( !is_null($val) ) {
590
- break;
591
- }
592
- }
593
- return $this->format($val, $context);
594
- }
595
-
596
- /**
597
- * Sets value in data member
598
- * Sets value to data member itself by default
599
- * @param mixed $value Value to set
600
- * @param string|array $name Name of value to set (Can also be path to value)
601
- */
602
- function set_data($value, $name = '') {
603
- $ref =& $this->get_path_value('data', $name);
604
- $ref = $value;
605
- }
606
-
607
- /**
608
- * Sets parent object of current instance
609
- * Parent objects must be the same object type as current instance
610
- * @uses SLB to get field type definition
611
- * @uses SLB_Fields::has() to check if field type exists
612
- * @uses SLB_Fields::get() to retrieve field type object reference
613
- * @param string|object $parent Parent ID or reference
614
- */
615
- function set_parent($parent = null) {
616
- // Stop processing if parent empty
617
- if ( empty($parent) && !is_string($this->parent) )
618
- return false;
619
- // Parent passed as object reference wrapped in array
620
- if ( is_array($parent) && isset($parent[0]) && is_object($parent[0]) )
621
- $parent = $parent[0];
622
-
623
- // No parent set but parent ID (previously) set in object
624
- if ( empty($parent) && is_string($this->parent) )
625
- $parent = $this->parent;
626
-
627
- // Retrieve reference object if ID was supplied
628
- if ( is_string($parent) ) {
629
- $parent = trim($parent);
630
- // Get parent object reference
631
- /**
632
- * @var SLB
633
- */
634
- $b = $this->get_base();
635
- if ( !!$b && isset($b->fields) && $b->fields->has($parent) ) {
636
- $parent = $b->fields->get($parent);
637
- }
638
- }
639
-
640
- // Set parent value on object
641
- if ( is_string($parent) || is_object($parent) )
642
- $this->parent = $parent;
643
- }
644
-
645
- /**
646
- * Retrieve field type parent
647
- * @return SLB_Field_Type Parent field
648
- */
649
- function get_parent() {
650
- return $this->parent;
651
- }
652
-
653
- /**
654
- * Set object title
655
- * @param string $title Title for object
656
- * @param string $plural Plural form of title
657
- */
658
- function set_title($title = '') {
659
- if ( is_scalar($title) )
660
- $this->title = strip_tags(trim($title));
661
- }
662
-
663
- /**
664
- * Retrieve object title
665
- */
666
- function get_title() {
667
- return $this->get_member_value('title', '','', 'current');
668
- }
669
-
670
- /**
671
- * Set object description
672
- * @param string $description Description for object
673
- */
674
- function set_description($description = '') {
675
- $this->description = strip_tags(trim($description));
676
- }
677
-
678
- /**
679
- * Retrieve object description
680
- * @return string Object description
681
- */
682
- function get_description() {
683
- $dir = 'current';
684
- return $this->get_member_value('description', '','', $dir);
685
- return $desc;
686
- }
687
-
688
- /**
689
- * Sets multiple properties on field type at once
690
- * @param array $properties Properties. Each element is an array containing the arguments to set a new property
691
- * @return boolean TRUE if successful, FALSE otherwise
692
- */
693
- function set_properties($properties) {
694
- if ( !is_array($properties) ) {
695
- return false;
696
- }
697
- // Normalize properties
698
- $properties = $this->remap_properties($properties);
699
- $properties = $this->sort_properties($properties);
700
- // Set Member properties
701
- foreach ( $properties as $prop => $val ) {
702
- if ( ( $m = 'set_' . $prop ) && method_exists($this, $m) ) {
703
- $this->{$m}($val);
704
- // Remove member property from array
705
- unset($properties[$prop]);
706
- }
707
- }
708
-
709
- // Filter properties
710
- $properties = $this->filter_properties($properties);
711
- // Set additional instance properties
712
- foreach ( $properties as $name => $val) {
713
- $this->set_property($name, $val);
714
- }
715
- }
716
-
717
- /**
718
- * Remap properties based on $map
719
- * @uses $map For determine how child properties should map to parent properties
720
- * @uses SLB_Utlities::array_remap() to perform array remapping
721
- * @param array $properties Associative array of properties
722
- * @return array Remapped properties
723
- */
724
- function remap_properties($properties) {
725
- // Return remapped properties
726
- return $this->util->array_remap($properties, $this->map);
727
- }
728
-
729
- /**
730
- * Sort properties based on priority
731
- * @uses this::property_priority
732
- * @return array Sorted priorities
733
- */
734
- function sort_properties($properties) {
735
- // Stop if sorting not necessary
736
- if ( empty($properties) || !is_array($properties) || empty($this->property_priority) || !is_array($this->property_priority) )
737
- return $properties;
738
- $props = array();
739
- foreach ( $this->property_priority as $prop ) {
740
- if ( !array_key_exists($prop, $properties) )
741
- continue;
742
- // Add to new array
743
- $props[$prop] = $properties[$prop];
744
- // Remove from old array
745
- unset($properties[$prop]);
746
- }
747
- // Append any remaining properties
748
- $props = array_merge($props, $properties);
749
- return $props;
750
- }
751
-
752
- /**
753
- * Build properties array
754
- * @param array $props Instance properties
755
- * @param array $signature (optional) Default properties
756
- * @return array Normalized properties
757
- */
758
- function make_properties($props, $signature = array()) {
759
- $p = array();
760
- if ( is_array($props) ) {
761
- foreach ( $props as $prop ) {
762
- if ( is_array($prop) ) {
763
- $p = array_merge($prop, $p);
764
- }
765
- }
766
- }
767
- $props = $p;
768
- if ( is_array($signature) ) {
769
- $props = array_merge($signature, $props);
770
- }
771
- return $props;
772
- }
773
-
774
- function validate_id($id) {
775
- return ( is_scalar($id) && !empty($id) ) ? true : false;
776
- }
777
-
778
- function integrate_id($id) {
779
- return ( $this->validate_id($id) ) ? array('id' => $id) : array();
780
- }
781
-
782
- /**
783
- * Filter property members
784
- * @uses $property_filter to remove define members to remove from $properties
785
- * @param array $props Properties
786
- * @return array Filtered properties
787
- */
788
- function filter_properties($props = array()) {
789
- return $this->util->array_filter_keys($props, $this->property_filter);
790
- }
791
-
792
- /**
793
- * Add/Set a property on the field definition
794
- * @param string $name Name of property
795
- * @param mixed $value Default value for property
796
- * @param string|array $group Group(s) property belongs to
797
- * @return boolean TRUE if property is successfully added to field type, FALSE otherwise
798
- */
799
- function set_property($name, $value = '', $group = null) {
800
- // Do not add if property name is not a string
801
- if ( !is_string($name) )
802
- return false;
803
- // Create property array
804
- $prop_arr = array();
805
- $prop_arr['value'] = $value;
806
- // Add to properties array
807
- $this->properties[$name] = $value;
808
- // Add property to specified groups
809
- if ( !empty($group) ) {
810
- $this->set_group_property($group, $name);
811
- }
812
- return true;
813
- }
814
-
815
- /**
816
- * Retreives property from field type
817
- * @param string $name Name of property to retrieve
818
- * @return mixed Specified Property if exists (Default: Empty string)
819
- */
820
- function get_property($name) {
821
- $val = $this->get_member_value('properties', $name);
822
- return $val;
823
- }
824
-
825
- /**
826
- * Removes a property from item
827
- * @param string $name Property ID
828
- */
829
- function remove_property($name) {
830
- // Remove property
831
- if ( isset($this->properties[$name]) )
832
- unset($this->properties[$name]);
833
- // Remove from group
834
- foreach ( array_keys($this->property_groups) as $g ) {
835
- if ( isset($this->property_groups[$g][$name]) ) {
836
- unset($this->property_groups[$g][$name]);
837
- break;
838
- }
839
- }
840
- }
841
-
842
- /**
843
- * Adds Specified Property to a Group
844
- * @param string|array $group Group(s) to add property to
845
- * @param string $property Property to add to group
846
- */
847
- function set_group_property($group, $property) {
848
- if ( is_string($group) && isset($this->property_groups[$group][$property]) )
849
- return;
850
- if ( !is_array($group) ) {
851
- $group = array($group);
852
- }
853
-
854
- foreach ($group as $g) {
855
- $g = trim($g);
856
- // Initialize group if it doesn't already exist
857
- if ( !isset($this->property_groups[$g]) )
858
- $this->property_groups[$g] = array();
859
-
860
- // Add property to group
861
- $this->property_groups[$g][$property] = null;
862
- }
863
- }
864
-
865
- /**
866
- * Retrieve property group
867
- * @param string $group Group to retrieve
868
- * @return array Array of properties in specified group
869
- */
870
- function get_group($group) {
871
- return $this->get_member_value('property_groups', $group, array());
872
- }
873
-
874
- /**
875
- * Save field data
876
- * Child classes will define their own
877
- * functionality for this method
878
- * @return bool TRUE if save was successful (FALSE otherwise)
879
- */
880
- function save() {
881
- return true;
882
- }
883
-
884
- /*-** Hooks **-*/
885
-
886
- /**
887
- * Retrieve hooks added to object
888
- * @return array Hooks
889
- */
890
- function get_hooks() {
891
- return $this->get_member_value('hooks', '', array());
892
- }
893
-
894
- /**
895
- * Add hook for object
896
- * @see add_filter() for parameter defaults
897
- * @param $tag
898
- * @param $function_to_add
899
- * @param $priority
900
- * @param $accepted_args
901
- */
902
- function add_hook($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
903
- // Create new array for tag (if not already set)
904
- if ( !isset($this->hooks[$tag]) )
905
- $this->hooks[$tag] = array();
906
- // Build Unique ID
907
- if ( is_string($function_to_add) )
908
- $id = $function_to_add;
909
- elseif ( is_array($function_to_add) && !empty($function_to_add) )
910
- $id = strval($function_to_add[count($function_to_add) - 1]);
911
- else
912
- $id = 'function_' . ( count($this->hooks[$tag]) + 1 );
913
- // Add hook
914
- $this->hooks[$tag][$id] = func_get_args();
915
- }
916
-
917
- /**
918
- * Convenience method for adding an action for object
919
- * @see add_filter() for parameter defaults
920
- * @param $tag
921
- * @param $function_to_add
922
- * @param $priority
923
- * @param $accepted_args
924
- */
925
- function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
926
- $this->add_hook($tag, $function_to_add, $priority, $accepted_args);
927
- }
928
-
929
- /**
930
- * Convenience method for adding a filter for object
931
- * @see add_filter() for parameter defaults
932
- * @param $tag
933
- * @param $function_to_add
934
- * @param $priority
935
- * @param $accepted_args
936
- */
937
- function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
938
- $this->add_hook($tag, $function_to_add, $priority, $accepted_args);
939
- }
940
-
941
- /*-** Dependencies **-*/
942
-
943
- /**
944
- * Adds dependency to object
945
- * @param string $type Type of dependency to add (script, style)
946
- * @param array|string $context When dependency will be added (@see SLB_Utilities::get_action() for possible contexts)
947
- * @see wp_enqueue_script for the following of the parameters
948
- * @param $handle
949
- * @param $src
950
- * @param $deps
951
- * @param $ver
952
- * @param $ex
953
- */
954
- function add_dependency($type, $context, $handle, $src = false, $deps = array(), $ver = false, $ex = false) {
955
- $args = func_get_args();
956
- // Remove type/context from arguments
957
- $args = array_slice($args, 2);
958
-
959
- // Set context
960
- if ( !is_array($context) ) {
961
- // Wrap single contexts in an array
962
- if ( is_string($context) )
963
- $context = array($context);
964
- else
965
- $context = array();
966
- }
967
- // Add file to instance property
968
- if ( isset($this->{$type}) && is_array($this->{$type}) )
969
- $this->{$type}[$handle] = array('context' => $context, 'params' => $args);
970
- }
971
-
972
- /**
973
- * Add script to object to be added in specified contexts
974
- * @param array|string $context Array of contexts to add script to page
975
- * @see wp_enqueue_script for the following of the parameters
976
- * @param $handle
977
- * @param $src
978
- * @param $deps
979
- * @param $ver
980
- * @param $in_footer
981
- */
982
- function add_script( $context, $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) {
983
- $args = func_get_args();
984
- // Add file type to front of arguments array
985
- array_unshift($args, 'scripts');
986
- call_user_func_array($this->m('add_dependency'), $args);
987
- }
988
-
989
- /**
990
- * Retrieve script dependencies for object
991
- * @return array Script dependencies
992
- */
993
- function get_scripts() {
994
- return $this->get_member_value('scripts', '', array());
995
- }
996
-
997
- /**
998
- * Add style to object to be added in specified contexts
999
- * @param array|string $context Array of contexts to add style to page
1000
- * @see wp_enqueue_style for the following of the parameters
1001
- * @param $handle
1002
- * @param $src
1003
- * @param $deps
1004
- * @param $ver
1005
- * @param $in_footer
1006
- */
1007
- function add_style( $handle, $src = false, $deps = array(), $ver = false, $media = false ) {
1008
- $args = func_get_args();
1009
- array_unshift($args, 'styles');
1010
- call_user_func_array($this->m('add_dependency'), $args);
1011
- }
1012
-
1013
- /**
1014
- * Retrieve Style dependencies for object
1015
- * @return array Style dependencies
1016
- */
1017
- function get_styles() {
1018
- return $this->get_member_value('styles', '', array());
1019
- }
1020
-
1021
- /* Helpers */
1022
-
1023
- /**
1024
- * Format value based on specified context
1025
- * @param mixed $value Value to format
1026
- * @param string $context Current context
1027
- * @return mixed Formatted value
1028
- */
1029
- function format($value, $context = '') {
1030
- if ( is_scalar($context) && !empty($context) ) {
1031
- $handler = 'format_' . trim(strval($context));
1032
- // Only process if context is valid and has a handler
1033
- if ( !empty($context) && method_exists($this, $handler) ) {
1034
- // Pass value to handler
1035
- $value = $this->{$handler}($value, $context);
1036
- }
1037
- }
1038
- // Return formatted value
1039
- return $value;
1040
- }
1041
-
1042
- /**
1043
- * Format value for output as an attribute.
1044
- *
1045
- * Only strings are formatted.
1046
- *
1047
- * @since 2.8.0
1048
- *
1049
- * @param mixed $value Value to format.
1050
- * @return mixed Formatted value.
1051
- */
1052
- function format_attr( $value ) {
1053
- if ( is_string( $value ) ) {
1054
- $value = esc_attr( $value );
1055
- }
1056
- return $value;
1057
- }
1058
-
1059
- /**
1060
- * Formats value for output as plain text.
1061
- *
1062
- * Escapes HTML, etc.
1063
- * Only strings are formatted.
1064
- *
1065
- * @since 2.8.0
1066
- *
1067
- * @param mixed $value Value to format.
1068
- * @return mixed Formatted value.
1069
- */
1070
- function format_text( $value ) {
1071
- if ( is_string( $value ) ) {
1072
- $value = esc_html( $value );
1073
- }
1074
- return $value;
1075
- }
1076
-
1077
- /**
1078
- * Final formatting before output
1079
- * Restores special characters, etc.
1080
- * @uses $special_chars
1081
- * @uses $special_chars_default
1082
- * @param mixed $value Pre-final field output
1083
- * @param string $context (Optional) Formatting context
1084
- * @return mixed Formatted value
1085
- */
1086
- function format_final($value, $context = '') {
1087
- if ( !is_string($value) )
1088
- return $value;
1089
-
1090
- // Restore special chars
1091
- return $this->restore_special_chars($value, $context);
1092
- }
1093
-
1094
- function preserve_special_chars($value, $context = '') {
1095
- if ( !is_string($value) )
1096
- return $value;
1097
- $specials = $this->get_special_chars();
1098
- return str_replace(array_keys($specials), $specials, $value);
1099
- }
1100
-
1101
- function restore_special_chars($value, $context = '') {
1102
- if ( !is_string($value) )
1103
- return $value;
1104
- $specials = $this->get_special_chars();
1105
- return str_replace($specials, array_keys($specials), $value);
1106
- }
1107
-
1108
- /**
1109
- * Retrieve special characters/placeholders
1110
- * Merges defaults with class-specific characters
1111
- * @uses $special_chars
1112
- * @uses $special_chars_default
1113
- * @return array Special characters/placeholders
1114
- */
1115
- function get_special_chars() {
1116
- return wp_parse_args($this->special_chars, $this->special_chars_default);
1117
- }
1118
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Fields - Base class
5
+ * Core properties/methods for fields
6
+ * @package Simple Lightbox
7
+ * @subpackage Fields
8
+ * @author Archetyped
9
+ */
10
+ class SLB_Field_Base extends SLB_Base {
11
+ /*-** Config **-*/
12
+ protected $mode = 'object';
13
+ protected $shared = false;
14
+
15
+ /*-** Properties **-*/
16
+
17
+ /**
18
+ * @var string Unique name
19
+ */
20
+ public $id = '';
21
+
22
+ /**
23
+ * ID formatting options.
24
+ *
25
+ * @var array $id_formats
26
+ */
27
+ private $id_formats = [];
28
+
29
+ /**
30
+ * Flag for ID format initialization status.
31
+ *
32
+ * @var bool $id_formats_init
33
+ */
34
+ private $id_formats_init = false;
35
+
36
+ /**
37
+ * Special characters/phrases
38
+ * Used for preserving special characters during formatting
39
+ * Merged with $special_chars_default
40
+ * Array Structure
41
+ * > Key: Special character/phrase
42
+ * > Value: Placeholder for special character
43
+ * @var array
44
+ */
45
+ public $special_chars = null;
46
+
47
+ public $special_chars_default = array(
48
+ '{' => '%SQB_L%',
49
+ '}' => '%SQB_R%',
50
+ );
51
+
52
+ /**
53
+ * Reference to parent object that current instance inherits from
54
+ * @var object
55
+ */
56
+ public $parent = null;
57
+
58
+ /**
59
+ * Title
60
+ * @var string
61
+ */
62
+ public $title = '';
63
+
64
+ /**
65
+ * @var string Short description
66
+ */
67
+ public $description = '';
68
+
69
+ /**
70
+ * @var array Object Properties
71
+ */
72
+ public $properties = array();
73
+
74
+ /**
75
+ * Initialization properties
76
+ * @var array
77
+ */
78
+ protected $properties_init = null;
79
+
80
+ /**
81
+ * Structure: Property names stored as keys in group
82
+ * Root
83
+ * -> Group Name
84
+ * -> Property Name => Null
85
+ * Reason: Faster searching over large arrays
86
+ * @var array Groupings of Properties
87
+ */
88
+ public $property_groups = array();
89
+
90
+ /**
91
+ * Keys to filter out of properties array before setting properties
92
+ * @var array
93
+ */
94
+ public $property_filter = array( 'group' );
95
+
96
+ /**
97
+ * Define order of properties
98
+ * Useful when processing order is important (e.g. one property depends on another)
99
+ * @var array
100
+ */
101
+ public $property_priority = array();
102
+
103
+ /**
104
+ * Data for object
105
+ * May also contain data for nested objects
106
+ * @var mixed
107
+ */
108
+ public $data = null;
109
+
110
+ /**
111
+ * Whether data has been fetched or not
112
+ * @var bool
113
+ */
114
+ protected $data_loaded = false;
115
+
116
+ /**
117
+ * @var array Script resources to include for object
118
+ */
119
+ public $scripts = array();
120
+
121
+ /**
122
+ * @var array CSS style resources to include for object
123
+ */
124
+ public $styles = array();
125
+
126
+ /**
127
+ * Hooks (Filters/Actions) for object
128
+ * @var array
129
+ */
130
+ public $hooks = array();
131
+
132
+ /**
133
+ * Mapping of child properties to parent members
134
+ * Allows more flexibility when creating new instances of child objects using property arrays
135
+ * Associative array structure:
136
+ * > Key: Child property to map FROM
137
+ * > Val: Parent property to map TO
138
+ * @var array
139
+ */
140
+ public $map = null;
141
+
142
+ /**
143
+ * Options used when building collection (callbacks, etc.)
144
+ * Associative array
145
+ * > Key: Option name
146
+ * > Value: Option value
147
+ * @var array
148
+ */
149
+ public $build_vars = array();
150
+
151
+ public $build_vars_default = array();
152
+
153
+ /**
154
+ * Constructor
155
+ */
156
+ function __construct( $id = '', $properties = null ) {
157
+ parent::__construct();
158
+ // Normalize Properties
159
+ $args = func_get_args();
160
+ $defaults = $this->integrate_id( $id );
161
+ $properties = $this->make_properties( $args, $defaults );
162
+ // Save init properties
163
+ $this->properties_init = $properties;
164
+ // Set Properties
165
+ $this->set_properties( $properties );
166
+ }
167
+
168
+ /* Getters/Setters */
169
+
170
+ /**
171
+ * Checks if the specified path exists in the object
172
+ * @param array $path Path to check for
173
+ * @return bool TRUE if path exists in object, FALSE otherwise
174
+ */
175
+ function path_isset( $path = '' ) {
176
+ // Stop execution if no path is supplied
177
+ if ( empty( $path ) ) {
178
+ return false;
179
+ }
180
+ $args = func_get_args();
181
+ $path = $this->util->build_path( $args );
182
+ $item =& $this;
183
+ // Iterate over path and check if each level exists before moving on to the next
184
+ $path_size = count( $path );
185
+ for ( $x = 0; $x < $path_size; $x++ ) {
186
+ if ( $this->util->property_exists( $item, $path[ $x ] ) ) {
187
+ // Set $item as reference to next level in path for next iteration
188
+ $item =& $this->util->get_property( $item, $path[ $x ] );
189
+ } else {
190
+ return false;
191
+ }
192
+ }
193
+ return true;
194
+ }
195
+
196
+ /**
197
+ * Retrieves a value from object using a specified path
198
+ * Checks to make sure path exists in object before retrieving value
199
+ * @param array $path Path to retrieve value from. Each item in array is a deeper dimension
200
+ * @return mixed Value at specified path
201
+ */
202
+ function &get_path_value( $path = '' ) {
203
+ $ret = '';
204
+ $path = $this->util->build_path( func_get_args() );
205
+ if ( $this->path_isset( $path ) ) {
206
+ $ret =& $this;
207
+
208
+ $path_size = count( $path );
209
+ for ( $x = 0; $x < $path_size; $x++ ) {
210
+ if ( 0 === $x ) {
211
+ $ret =& $ret->{ $path[ $x ] };
212
+ } else {
213
+ $ret =& $ret[ $path[ $x ] ];
214
+ }
215
+ }
216
+ }
217
+ return $ret;
218
+ }
219
+
220
+ /**
221
+ * Search for specified member value in field type ancestors
222
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
223
+ * @param string $name Value to retrieve from member
224
+ * @return mixed Member value if found (Default: empty string)
225
+ */
226
+ function get_parent_value( $member, $name = '', $default = '' ) {
227
+ $parent = $this->get_parent();
228
+ return $this->get_object_value( $parent, $member, $name, $default, 'parent' );
229
+ }
230
+
231
+ /**
232
+ * Retrieves specified member value.
233
+ *
234
+ * Handles inherited values and merges corresponding parents
235
+ * if value is an array (e.g. for property groups).
236
+ *
237
+ * @param string|array $member Member to get value from.
238
+ * @param string|array $name Optional. Element/Path to Element to retrieve from member. Default none.
239
+ * @param mixed $default Optional. Default value to return if no data retrieved. Default empty string.
240
+ * @param string $dir Optional. Direction to move through hierarchy to find value.
241
+ * Possible Values:
242
+ * * parent (default) - Search through field parents.
243
+ * * current - Do not search through connected objects.
244
+ * * container - Search through field containers.
245
+ * * caller - Search through field callers.
246
+ * @return mixed Specified member value.
247
+ * @todo Return reference.
248
+ */
249
+ function &get_member_value( $member, $name = '', $default = '', $dir = 'parent' ) {
250
+ // Check if path to member is supplied
251
+ $path = array();
252
+ if ( is_array( $member ) && isset( $member['tag'] ) ) {
253
+ if ( isset( $member['attributes']['ref_base'] ) ) {
254
+ if ( 'root' !== $member['attributes']['ref_base'] ) {
255
+ $path[] = $member['attributes']['ref_base'];
256
+ }
257
+ } else {
258
+ $path[] = 'properties';
259
+ }
260
+
261
+ $path[] = $member['tag'];
262
+ } else {
263
+ $path = $member;
264
+ }
265
+ // Prep name.
266
+ if ( is_string( $name ) ) {
267
+ $name = trim( $name );
268
+ }
269
+ $path = $this->util->build_path( $path, $name );
270
+ // Set defaults and prepare data
271
+ $val = $default;
272
+ $inherit = false;
273
+ $inherit_tag = '{inherit}';
274
+
275
+ /**
276
+ * Determines whether the value must be retrieved from a parent/container object.
277
+ *
278
+ * Conditions:
279
+ *
280
+ * 1. Path does not exist in current field.
281
+ * 2. Path exists and is not an object, but at least one of the following is true:
282
+ * * Value at path is an array (e.g. properties, elements, etc. array):
283
+ * * - Parent/container values should be merged with retrieved array.
284
+ * * Value at path is a string that inherits from another field:
285
+ * * - Value from other field will be retrieved and will replace
286
+ * inheritance placeholder in retrieved value
287
+ * @var bool
288
+ */
289
+ $deeper = false;
290
+
291
+ if ( ! $this->path_isset( $path ) ) {
292
+ $deeper = true;
293
+ } else {
294
+ $val = $this->get_path_value( $path );
295
+ if ( is_array( $val ) ) {
296
+ $deeper = true;
297
+ } elseif ( is_string( $val ) && false !== strpos( $val, $inherit_tag ) ) {
298
+ $deeper = true;
299
+ // Value inherits from another field.
300
+ $inherit = true;
301
+ }
302
+ }
303
+
304
+ if ( $deeper && 'current' !== $dir ) {
305
+ $ex_val = '';
306
+ // Get Parent value (recursive)
307
+ if ( 'parent' === $dir ) {
308
+ $ex_val = $this->get_parent_value( $member, $name, $default );
309
+ } elseif ( method_exists( $this, 'get_container_value' ) ) {
310
+ $ex_val = $this->get_container_value( $member, $name, $default );
311
+ }
312
+ // Handle inheritance
313
+ if ( is_array( $val ) ) {
314
+ // Combine Arrays
315
+ if ( is_array( $ex_val ) ) {
316
+ $val = array_merge( $ex_val, $val );
317
+ }
318
+ } elseif ( false !== $inherit ) {
319
+ // Replace placeholder with inherited string
320
+ $val = str_replace( $inherit_tag, $ex_val, $val );
321
+ } else {
322
+ // Default: Set parent value as value
323
+ $val = $ex_val;
324
+ }
325
+ }
326
+
327
+ return $val;
328
+ }
329
+
330
+ /**
331
+ * Search for specified member value in an object
332
+ * @param object $object Reference to object to retrieve value from
333
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
334
+ * @param string $name (optional) Value to retrieve from member
335
+ * @param mixed $default (optional) Default value to use if no value found (Default: empty string)
336
+ * @param string $dir Direction to move through hierarchy to find value @see SLB_Field_Type::get_member_value() for possible values
337
+ * @return mixed Member value if found (Default: $default)
338
+ */
339
+ function get_object_value( &$object, $member, $name = '', $default = '', $dir = 'parent' ) {
340
+ $ret = $default;
341
+ if ( is_object( $object ) && method_exists( $object, 'get_member_value' ) ) {
342
+ $ret = $object->get_member_value( $member, $name, $default, $dir );
343
+ }
344
+ return $ret;
345
+ }
346
+
347
+ /**
348
+ * Set item ID
349
+ * @param string $id Unique item ID
350
+ */
351
+ function set_id( $id ) {
352
+ if ( empty( $id ) || ! is_string( $id ) ) {
353
+ return false;
354
+ }
355
+ $this->id = trim( $id );
356
+ }
357
+
358
+ /**
359
+ * Retrieves field ID
360
+ * @param array|string $options (optional) Options or ID of format to use
361
+ * @return string item ID
362
+ */
363
+ function get_id( $options = array() ) {
364
+ $item_id = trim( $this->id );
365
+ $formats = $this->get_id_formats();
366
+ // Setup options
367
+ $wrap_default = array(
368
+ 'open' => '',
369
+ 'close' => '',
370
+ 'segment_open' => '',
371
+ 'segment_close' => '',
372
+ );
373
+
374
+ $options_default = array(
375
+ 'format' => null,
376
+ 'wrap' => array(),
377
+ 'segments_pre' => null,
378
+ 'prefix' => '',
379
+ 'recursive' => false,
380
+ );
381
+
382
+ // Load options based on format
383
+ if ( ! is_array( $options ) ) {
384
+ $options = array( 'format' => $options );
385
+ }
386
+ if ( isset( $options['format'] ) && is_string( $options['format'] ) && isset( $formats[ $options['format'] ] ) ) {
387
+ $options_default = wp_parse_args( $formats[ $options['format'] ], $options_default );
388
+ } else {
389
+ unset( $options['format'] );
390
+ }
391
+ $options = wp_parse_args( $options, $options_default );
392
+ // Import options into function
393
+ extract( $options );
394
+
395
+ // Validate options
396
+ $wrap = wp_parse_args( $wrap, $wrap_default );
397
+
398
+ if ( ! is_array( $segments_pre ) ) {
399
+ $segments_pre = array( $segments_pre );
400
+ }
401
+ $segments_pre = array_reverse( $segments_pre );
402
+
403
+ // Format ID based on options
404
+ $item_id = array( $item_id );
405
+
406
+ // Add parent objects to ID
407
+ if ( ! ! $recursive ) {
408
+ // Create array of ID components
409
+ $m = 'get_caller';
410
+ $c = ( method_exists( $this, $m ) ) ? $this->{$m}() : null;
411
+ while ( ! ! $c ) {
412
+ // Add ID of current caller to array
413
+ if ( method_exists( $c, 'get_id' ) && ! strlen( $c->get_id() ) > 0 ) {
414
+ $item_id = $c->get_id();
415
+ }
416
+ // Get parent object
417
+ $c = ( method_exists( $c, $m ) ) ? $c->{$m}() : null;
418
+ }
419
+ unset( $c );
420
+ }
421
+
422
+ // Additional segments (Pre)
423
+ foreach ( $segments_pre as $seg ) {
424
+ if ( is_null( $seg ) ) {
425
+ continue;
426
+ }
427
+ if ( is_object( $seg ) ) {
428
+ $seg = (array) $seg;
429
+ }
430
+ if ( is_array( $seg ) ) {
431
+ $item_id = array_merge( $item_id, array_reverse( $seg ) );
432
+ } elseif ( '' !== strval( $seg ) ) {
433
+ $item_id[] = strval( $seg );
434
+ }
435
+ }
436
+
437
+ // Prefix
438
+ if ( is_array( $prefix ) ) {
439
+ // Array is sequence of instance methods to call on object
440
+ // Last array member can be an array of parameters to pass to methods
441
+ $count = count( $prefix );
442
+ $args = ( $count > 1 && is_array( $prefix[ $count - 1 ] ) ) ? array_pop( $prefix ) : array();
443
+ $p = $this;
444
+ $val = '';
445
+ // Iterate through methods
446
+ foreach ( $prefix as $m ) {
447
+ if ( ! method_exists( $p, $m ) ) {
448
+ continue;
449
+ }
450
+ // Build callback
451
+ $m = $this->util->m( $p, $m );
452
+ // Call callback
453
+ $val = call_user_func_array( $m, $args );
454
+ // Returned value may be an instance object
455
+ if ( is_object( $val ) ) {
456
+ $p = $val; // Use returned object in next round
457
+ } else {
458
+ array_unshift( $args, $val ); // Pass returned value as parameter to next method on using current object
459
+ }
460
+ }
461
+ $prefix = $val;
462
+ unset( $p, $val );
463
+ }
464
+ if ( is_numeric( $prefix ) ) {
465
+ $prefix = strval( $prefix );
466
+ }
467
+ if ( empty( $prefix ) || ! is_string( $prefix ) ) {
468
+ $prefix = '';
469
+ }
470
+
471
+ // Convert array to string
472
+ $item_id = $prefix . $wrap['open'] . implode( $wrap['segment_close'] . $wrap['segment_open'], array_reverse( $item_id ) ) . $wrap['close'];
473
+ return $item_id;
474
+ }
475
+
476
+ /**
477
+ * Retrieves ID formats.
478
+ *
479
+ * @return array ID formats.
480
+ */
481
+ private function &get_id_formats() {
482
+ $this->init_id_formats();
483
+ return $this->id_formats;
484
+ }
485
+
486
+ /**
487
+ * Initializes default ID formats.
488
+ *
489
+ * @since 2.8.0
490
+ *
491
+ * @return void
492
+ */
493
+ private function init_id_formats() {
494
+ if ( ! $this->id_formats_init ) {
495
+ $this->id_formats_init = true;
496
+ // Initilize default formats.
497
+ $this->add_id_format(
498
+ 'attr_id',
499
+ [
500
+ 'wrap' => [
501
+ 'open' => '_',
502
+ 'segment_open' => '_',
503
+ ],
504
+ 'prefix' => [ 'get_container', 'get_id', 'add_prefix' ],
505
+ 'recursive' => true,
506
+ ],
507
+ true
508
+ );
509
+ $this->add_id_format(
510
+ 'attr_name',
511
+ [
512
+ 'wrap' => [
513
+ 'open' => '[',
514
+ 'close' => ']',
515
+ 'segment_open' => '[',
516
+ 'segment_close' => ']',
517
+ ],
518
+ 'prefix' => [ 'get_container', 'get_id', 'add_prefix' ],
519
+ 'recursive' => true,
520
+ ],
521
+ true
522
+ );
523
+
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Adds custom ID format.
529
+ *
530
+ * @since 2.8.0
531
+ *
532
+ * @param string $name Format name.
533
+ * @param array $wrap
534
+ * @param array $prefix
535
+ * @param bool $recursive Optional.
536
+ * @param bool $overwrite Optional. Overwrite existing format. Default false.
537
+ * @return void
538
+ */
539
+ protected function add_id_format( $name, array $options, $overwrite = false ) {
540
+ // Init ID formats before adding new ones.
541
+ $this->init_id_formats();
542
+ // Validate args.
543
+ $name = trim( $name );
544
+ // Stop if name invalid.
545
+ if ( empty( $name ) ) {
546
+ return;
547
+ }
548
+ $overwrite = (bool) $overwrite;
549
+ // Do not add format if name matches existing format (when overwriting not allowed).
550
+ if ( ! $overwrite && in_array( $name, array_keys( $this->id_formats ), true ) ) {
551
+ return;
552
+ }
553
+ // Normlize options.
554
+ $options = wp_parse_args(
555
+ $options,
556
+ [
557
+ 'wrap' => [],
558
+ 'prefix' => [],
559
+ 'recursive' => false,
560
+ ]
561
+ );
562
+ // Add format.
563
+ $this->id_formats[ $name ] = $options;
564
+ }
565
+
566
+ /**
567
+ * Retrieve value from data member
568
+ * @param string $context Context to format data for
569
+ * @param bool $top (optional) Whether to traverse through the field hierarchy to get data for field (Default: TRUE)
570
+ * @return mixed Value at specified path
571
+ */
572
+ function get_data( $context = '', $top = true ) {
573
+ $opt_d = array(
574
+ 'context' => '',
575
+ 'top' => true,
576
+ );
577
+ $args = func_get_args();
578
+ $a = false;
579
+ if ( count( $args ) === 1 && is_array( $args[0] ) && ! empty( $args[0] ) ) {
580
+ $a = true;
581
+ $args = wp_parse_args( $args[0], $opt_d );
582
+ extract( $args );
583
+ }
584
+
585
+ if ( is_string( $top ) ) {
586
+ if ( 'false' === $top ) {
587
+ $top = false;
588
+ } elseif ( 'true' === $top ) {
589
+ $top = true;
590
+ } elseif ( is_numeric( $top ) ) {
591
+ $top = intval( $top );
592
+ }
593
+ }
594
+ $top = ! ! $top;
595
+ $obj =& $this;
596
+ $obj_path = array( $this );
597
+ $path = array();
598
+ if ( $top ) {
599
+ // Iterate through hiearchy to get top-most object
600
+ while ( ! empty( $obj ) ) {
601
+ $new = null;
602
+ // Try to get caller first
603
+ if ( method_exists( $obj, 'get_caller' ) ) {
604
+ $checked = true;
605
+ $new =& $obj->get_caller();
606
+ }
607
+ // Try to get container if no caller found
608
+ if ( empty( $new ) && method_exists( $obj, 'get_container' ) ) {
609
+ $checked = true;
610
+ $new =& $obj->get_container();
611
+ // Load data
612
+ if ( method_exists( $new, 'load_data' ) ) {
613
+ $new->load_data();
614
+ }
615
+ }
616
+
617
+ $obj =& $new;
618
+ unset( $new );
619
+ // Stop iteration
620
+ if ( ! empty( $obj ) ) {
621
+ // Add object to path if it is valid
622
+ $obj_path[] =& $obj;
623
+ }
624
+ }
625
+ unset( $obj );
626
+ }
627
+
628
+ // Check each object (starting with top-most) for matching data for current field
629
+
630
+ // Reverse array
631
+ $obj_path = array_reverse( $obj_path );
632
+ // Build path for data location
633
+ foreach ( $obj_path as $obj ) {
634
+ if ( method_exists( $obj, 'get_id' ) ) {
635
+ $path[] = $obj->get_id();
636
+ }
637
+ }
638
+ // Iterate through objects
639
+ while ( ! empty( $obj_path ) ) {
640
+ // Get next object
641
+ $obj = array_shift( $obj_path );
642
+ // Shorten path
643
+ array_shift( $path );
644
+ // Check for value in object and stop iteration if matching data found
645
+ $val = $this->get_object_value( $obj, 'data', $path, null, 'current' );
646
+ if ( ! is_null( $val ) ) {
647
+ break;
648
+ }
649
+ }
650
+ return $this->format( $val, $context );
651
+ }
652
+
653
+ /**
654
+ * Sets value in data member
655
+ * Sets value to data member itself by default
656
+ * @param mixed $value Value to set
657
+ * @param string|array $name Name of value to set (Can also be path to value)
658
+ */
659
+ function set_data( $value, $name = '' ) {
660
+ $ref =& $this->get_path_value( 'data', $name );
661
+ $ref = $value;
662
+ }
663
+
664
+ /**
665
+ * Sets parent object of current instance
666
+ * Parent objects must be the same object type as current instance
667
+ * @uses SLB to get field type definition
668
+ * @uses SLB_Fields::has() to check if field type exists
669
+ * @uses SLB_Fields::get() to retrieve field type object reference
670
+ * @param string|object $parent Parent ID or reference
671
+ */
672
+ function set_parent( $parent = null ) {
673
+ // Stop processing if parent empty
674
+ if ( empty( $parent ) && ! is_string( $this->parent ) ) {
675
+ return false;
676
+ }
677
+ // Parent passed as object reference wrapped in array
678
+ if ( is_array( $parent ) && isset( $parent[0] ) && is_object( $parent[0] ) ) {
679
+ $parent = $parent[0];
680
+ }
681
+
682
+ // No parent set but parent ID (previously) set in object
683
+ if ( empty( $parent ) && is_string( $this->parent ) ) {
684
+ $parent = $this->parent;
685
+ }
686
+
687
+ // Retrieve reference object if ID was supplied
688
+ if ( is_string( $parent ) ) {
689
+ $parent = trim( $parent );
690
+ // Get parent object reference
691
+ /**
692
+ * @var SLB
693
+ */
694
+ $b = $this->get_base();
695
+ if ( ! ! $b && isset( $b->fields ) && $b->fields->has( $parent ) ) {
696
+ $parent = $b->fields->get( $parent );
697
+ }
698
+ }
699
+
700
+ // Set parent value on object
701
+ if ( is_string( $parent ) || is_object( $parent ) ) {
702
+ $this->parent = $parent;
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Retrieve field type parent
708
+ * @return SLB_Field_Type Parent field
709
+ */
710
+ function get_parent() {
711
+ return $this->parent;
712
+ }
713
+
714
+ /**
715
+ * Set object title
716
+ * @param string $title Title for object
717
+ * @param string $plural Plural form of title
718
+ */
719
+ function set_title( $title = '' ) {
720
+ if ( is_scalar( $title ) ) {
721
+ $this->title = wp_strip_all_tags( trim( $title ) );
722
+ }
723
+ }
724
+
725
+ /**
726
+ * Retrieve object title
727
+ */
728
+ function get_title() {
729
+ return $this->get_member_value( 'title', '', '', 'current' );
730
+ }
731
+
732
+ /**
733
+ * Set object description
734
+ * @param string $description Description for object
735
+ */
736
+ function set_description( $description = '' ) {
737
+ $this->description = wp_strip_all_tags( trim( $description ) );
738
+ }
739
+
740
+ /**
741
+ * Retrieve object description
742
+ * @return string Object description
743
+ */
744
+ function get_description() {
745
+ $dir = 'current';
746
+ return $this->get_member_value( 'description', '', '', $dir );
747
+ }
748
+
749
+ /**
750
+ * Sets multiple properties on field type at once.
751
+ *
752
+ * @param array $properties Properties to set - each element is an
753
+ * array containing the arguments to set a
754
+ * new property.
755
+ * @return void
756
+ * @todo Test refactored code.
757
+ */
758
+ function set_properties( $properties ) {
759
+ if ( ! is_array( $properties ) ) {
760
+ return;
761
+ }
762
+ // Normalize properties
763
+ $properties = $this->remap_properties( $properties );
764
+ $properties = $this->sort_properties( $properties );
765
+
766
+ // Set Member properties.
767
+ foreach ( $properties as $prop => $val ) {
768
+ $m = 'set_' . $prop;
769
+ if ( method_exists( $this, $m ) ) {
770
+ $this->{$m}( $val );
771
+ // Remove member property from array
772
+ unset( $properties[ $prop ] );
773
+ }
774
+ unset( $m );
775
+ }
776
+
777
+ // Filter properties
778
+ $properties = $this->filter_properties( $properties );
779
+ // Set additional instance properties
780
+ foreach ( $properties as $name => $val ) {
781
+ $this->set_property( $name, $val );
782
+ }
783
+ }
784
+
785
+ /**
786
+ * Remap properties based on $map
787
+ * @uses $map For determine how child properties should map to parent properties
788
+ * @uses SLB_Utlities::array_remap() to perform array remapping
789
+ * @param array $properties Associative array of properties
790
+ * @return array Remapped properties
791
+ */
792
+ function remap_properties( $properties ) {
793
+ // Return remapped properties
794
+ return $this->util->array_remap( $properties, $this->map );
795
+ }
796
+
797
+ /**
798
+ * Sort properties based on priority
799
+ * @uses this::property_priority
800
+ * @return array Sorted priorities
801
+ */
802
+ function sort_properties( $properties ) {
803
+ // Stop if sorting not necessary
804
+ if ( empty( $properties ) || ! is_array( $properties ) || empty( $this->property_priority ) || ! is_array( $this->property_priority ) ) {
805
+ return $properties;
806
+ }
807
+ $props = array();
808
+ foreach ( $this->property_priority as $prop ) {
809
+ if ( ! array_key_exists( $prop, $properties ) ) {
810
+ continue;
811
+ }
812
+ // Add to new array
813
+ $props[ $prop ] = $properties[ $prop ];
814
+ // Remove from old array
815
+ unset( $properties[ $prop ] );
816
+ }
817
+ // Append any remaining properties
818
+ $props = array_merge( $props, $properties );
819
+ return $props;
820
+ }
821
+
822
+ /**
823
+ * Build properties array
824
+ * @param array $props Instance properties
825
+ * @param array $signature (optional) Default properties
826
+ * @return array Normalized properties
827
+ */
828
+ function make_properties( $props, $signature = array() ) {
829
+ $p = array();
830
+ if ( is_array( $props ) ) {
831
+ foreach ( $props as $prop ) {
832
+ if ( is_array( $prop ) ) {
833
+ $p = array_merge( $prop, $p );
834
+ }
835
+ }
836
+ }
837
+ $props = $p;
838
+ if ( is_array( $signature ) ) {
839
+ $props = array_merge( $signature, $props );
840
+ }
841
+ return $props;
842
+ }
843
+
844
+ function validate_id( $id ) {
845
+ return ( is_scalar( $id ) && ! empty( $id ) ) ? true : false;
846
+ }
847
+
848
+ function integrate_id( $id ) {
849
+ return ( $this->validate_id( $id ) ) ? array( 'id' => $id ) : array();
850
+ }
851
+
852
+ /**
853
+ * Filter property members
854
+ * @uses $property_filter to remove define members to remove from $properties
855
+ * @param array $props Properties
856
+ * @return array Filtered properties
857
+ */
858
+ function filter_properties( $props = array() ) {
859
+ return $this->util->array_filter_keys( $props, $this->property_filter );
860
+ }
861
+
862
+ /**
863
+ * Add/Set a property on the field definition
864
+ * @param string $name Name of property
865
+ * @param mixed $value Default value for property
866
+ * @param string|array $group Group(s) property belongs to
867
+ * @return boolean TRUE if property is successfully added to field type, FALSE otherwise
868
+ */
869
+ function set_property( $name, $value = '', $group = null ) {
870
+ // Do not add if property name is not a string
871
+ if ( ! is_string( $name ) ) {
872
+ return false;
873
+ }
874
+ // Create property array
875
+ $prop_arr = array();
876
+ $prop_arr['value'] = $value;
877
+ // Add to properties array
878
+ $this->properties[ $name ] = $value;
879
+ // Add property to specified groups
880
+ if ( ! empty( $group ) ) {
881
+ $this->set_group_property( $group, $name );
882
+ }
883
+ return true;
884
+ }
885
+
886
+ /**
887
+ * Retreives property from field type
888
+ * @param string $name Name of property to retrieve
889
+ * @return mixed Specified Property if exists (Default: Empty string)
890
+ */
891
+ function get_property( $name ) {
892
+ $val = $this->get_member_value( 'properties', $name );
893
+ return $val;
894
+ }
895
+
896
+ /**
897
+ * Removes a property from item
898
+ * @param string $name Property ID
899
+ */
900
+ function remove_property( $name ) {
901
+ // Remove property
902
+ if ( isset( $this->properties[ $name ] ) ) {
903
+ unset( $this->properties[ $name ] );
904
+ }
905
+ // Remove from group
906
+ foreach ( array_keys( $this->property_groups ) as $g ) {
907
+ if ( isset( $this->property_groups[ $g ][ $name ] ) ) {
908
+ unset( $this->property_groups[ $g ][ $name ] );
909
+ break;
910
+ }
911
+ }
912
+ }
913
+
914
+ /**
915
+ * Adds Specified Property to a Group
916
+ * @param string|array $group Group(s) to add property to
917
+ * @param string $property Property to add to group
918
+ */
919
+ function set_group_property( $group, $property ) {
920
+ if ( is_string( $group ) && isset( $this->property_groups[ $group ][ $property ] ) ) {
921
+ return;
922
+ }
923
+ if ( ! is_array( $group ) ) {
924
+ $group = array( $group );
925
+ }
926
+
927
+ foreach ( $group as $g ) {
928
+ $g = trim( $g );
929
+ // Initialize group if it doesn't already exist
930
+ if ( ! isset( $this->property_groups[ $g ] ) ) {
931
+ $this->property_groups[ $g ] = array();
932
+ }
933
+
934
+ // Add property to group
935
+ $this->property_groups[ $g ][ $property ] = null;
936
+ }
937
+ }
938
+
939
+ /**
940
+ * Retrieve property group
941
+ * @param string $group Group to retrieve
942
+ * @return array Array of properties in specified group
943
+ */
944
+ function get_group( $group ) {
945
+ return $this->get_member_value( 'property_groups', $group, array() );
946
+ }
947
+
948
+ /**
949
+ * Save field data
950
+ * Child classes will define their own
951
+ * functionality for this method
952
+ * @return bool TRUE if save was successful (FALSE otherwise)
953
+ */
954
+ function save() {
955
+ return true;
956
+ }
957
+
958
+ /*-** Hooks **-*/
959
+
960
+ /**
961
+ * Retrieve hooks added to object
962
+ * @return array Hooks
963
+ */
964
+ function get_hooks() {
965
+ return $this->get_member_value( 'hooks', '', array() );
966
+ }
967
+
968
+ /**
969
+ * Add hook for object
970
+ * @see add_filter() for parameter defaults
971
+ * @param $tag
972
+ * @param $function_to_add
973
+ * @param $priority
974
+ * @param $accepted_args
975
+ */
976
+ function add_hook( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
977
+ // Create new array for tag (if not already set)
978
+ if ( ! isset( $this->hooks[ $tag ] ) ) {
979
+ $this->hooks[ $tag ] = array();
980
+ }
981
+ // Build Unique ID
982
+ if ( is_string( $function_to_add ) ) {
983
+ $id = $function_to_add;
984
+ } elseif ( is_array( $function_to_add ) && ! empty( $function_to_add ) ) {
985
+ $id = strval( $function_to_add[ count( $function_to_add ) - 1 ] );
986
+ } else {
987
+ $id = 'function_' . ( count( $this->hooks[ $tag ] ) + 1 );
988
+ }
989
+ // Add hook
990
+ $this->hooks[ $tag ][ $id ] = func_get_args();
991
+ }
992
+
993
+ /**
994
+ * Convenience method for adding an action for object
995
+ * @see add_filter() for parameter defaults
996
+ * @param $tag
997
+ * @param $function_to_add
998
+ * @param $priority
999
+ * @param $accepted_args
1000
+ */
1001
+ function add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
1002
+ $this->add_hook( $tag, $function_to_add, $priority, $accepted_args );
1003
+ }
1004
+
1005
+ /**
1006
+ * Convenience method for adding a filter for object
1007
+ * @see add_filter() for parameter defaults
1008
+ * @param $tag
1009
+ * @param $function_to_add
1010
+ * @param $priority
1011
+ * @param $accepted_args
1012
+ */
1013
+ function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
1014
+ $this->add_hook( $tag, $function_to_add, $priority, $accepted_args );
1015
+ }
1016
+
1017
+ /*-** Dependencies **-*/
1018
+
1019
+ /**
1020
+ * Adds dependency to object
1021
+ * @param string $type Type of dependency to add (script, style)
1022
+ * @param array|string $context When dependency will be added (@see SLB_Utilities::get_action() for possible contexts)
1023
+ * @see wp_enqueue_script for the following of the parameters
1024
+ * @param $handle
1025
+ * @param $src
1026
+ * @param $deps
1027
+ * @param $ver
1028
+ * @param $ex
1029
+ */
1030
+ function add_dependency( $type, $context, $handle, $src = false, $deps = array(), $ver = false, $ex = false ) {
1031
+ $args = func_get_args();
1032
+ // Remove type/context from arguments
1033
+ $args = array_slice( $args, 2 );
1034
+
1035
+ // Set context
1036
+ if ( ! is_array( $context ) ) {
1037
+ // Wrap single contexts in an array
1038
+ if ( is_string( $context ) ) {
1039
+ $context = array( $context );
1040
+ } else {
1041
+ $context = array();
1042
+ }
1043
+ }
1044
+ // Add file to instance property
1045
+ if ( isset( $this->{$type} ) && is_array( $this->{$type} ) ) {
1046
+ $this->{$type}[ $handle ] = array(
1047
+ 'context' => $context,
1048
+ 'params' => $args,
1049
+ );
1050
+ }
1051
+ }
1052
+
1053
+ /**
1054
+ * Add script to object to be added in specified contexts
1055
+ * @param array|string $context Array of contexts to add script to page
1056
+ * @see wp_enqueue_script for the following of the parameters
1057
+ * @param $handle
1058
+ * @param $src
1059
+ * @param $deps
1060
+ * @param $ver
1061
+ * @param $in_footer
1062
+ */
1063
+ function add_script( $context, $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) {
1064
+ $args = func_get_args();
1065
+ // Add file type to front of arguments array
1066
+ array_unshift( $args, 'scripts' );
1067
+ call_user_func_array( $this->m( 'add_dependency' ), $args );
1068
+ }
1069
+
1070
+ /**
1071
+ * Retrieve script dependencies for object
1072
+ * @return array Script dependencies
1073
+ */
1074
+ function get_scripts() {
1075
+ return $this->get_member_value( 'scripts', '', array() );
1076
+ }
1077
+
1078
+ /**
1079
+ * Add style to object to be added in specified contexts
1080
+ * @param array|string $context Array of contexts to add style to page
1081
+ * @see wp_enqueue_style for the following of the parameters
1082
+ * @param $handle
1083
+ * @param $src
1084
+ * @param $deps
1085
+ * @param $ver
1086
+ * @param $in_footer
1087
+ */
1088
+ function add_style( $handle, $src = false, $deps = array(), $ver = false, $media = false ) {
1089
+ $args = func_get_args();
1090
+ array_unshift( $args, 'styles' );
1091
+ call_user_func_array( $this->m( 'add_dependency' ), $args );
1092
+ }
1093
+
1094
+ /**
1095
+ * Retrieve Style dependencies for object
1096
+ * @return array Style dependencies
1097
+ */
1098
+ function get_styles() {
1099
+ return $this->get_member_value( 'styles', '', array() );
1100
+ }
1101
+
1102
+ /* Helpers */
1103
+
1104
+ /**
1105
+ * Format value based on specified context
1106
+ * @param mixed $value Value to format
1107
+ * @param string $context Current context
1108
+ * @return mixed Formatted value
1109
+ */
1110
+ function format( $value, $context = '' ) {
1111
+ if ( is_scalar( $context ) && ! empty( $context ) ) {
1112
+ $handler = 'format_' . trim( strval( $context ) );
1113
+ // Only process if context is valid and has a handler
1114
+ if ( ! empty( $context ) && method_exists( $this, $handler ) ) {
1115
+ // Pass value to handler
1116
+ $value = $this->{$handler}( $value, $context );
1117
+ }
1118
+ }
1119
+ // Return formatted value
1120
+ return $value;
1121
+ }
1122
+
1123
+ /**
1124
+ * Format value for output as an attribute.
1125
+ *
1126
+ * Only strings are formatted.
1127
+ *
1128
+ * @since 2.8.0
1129
+ *
1130
+ * @param mixed $value Value to format.
1131
+ * @return mixed Formatted value.
1132
+ */
1133
+ function format_attr( $value ) {
1134
+ if ( is_string( $value ) ) {
1135
+ $value = esc_attr( $value );
1136
+ }
1137
+ return $value;
1138
+ }
1139
+
1140
+ /**
1141
+ * Formats value for output as plain text.
1142
+ *
1143
+ * Escapes HTML, etc.
1144
+ * Only strings are formatted.
1145
+ *
1146
+ * @since 2.8.0
1147
+ *
1148
+ * @param mixed $value Value to format.
1149
+ * @return mixed Formatted value.
1150
+ */
1151
+ function format_text( $value ) {
1152
+ if ( is_string( $value ) ) {
1153
+ $value = esc_html( $value );
1154
+ }
1155
+ return $value;
1156
+ }
1157
+
1158
+ /**
1159
+ * Final formatting before output
1160
+ * Restores special characters, etc.
1161
+ * @uses $special_chars
1162
+ * @uses $special_chars_default
1163
+ * @param mixed $value Pre-final field output
1164
+ * @param string $context (Optional) Formatting context
1165
+ * @return mixed Formatted value
1166
+ */
1167
+ function format_final( $value, $context = '' ) {
1168
+ if ( ! is_string( $value ) ) {
1169
+ return $value;
1170
+ }
1171
+
1172
+ // Restore special chars
1173
+ return $this->restore_special_chars( $value, $context );
1174
+ }
1175
+
1176
+ function preserve_special_chars( $value, $context = '' ) {
1177
+ if ( ! is_string( $value ) ) {
1178
+ return $value;
1179
+ }
1180
+ $specials = $this->get_special_chars();
1181
+ return str_replace( array_keys( $specials ), $specials, $value );
1182
+ }
1183
+
1184
+ function restore_special_chars( $value, $context = '' ) {
1185
+ if ( ! is_string( $value ) ) {
1186
+ return $value;
1187
+ }
1188
+ $specials = $this->get_special_chars();
1189
+ return str_replace( $specials, array_keys( $specials ), $value );
1190
+ }
1191
+
1192
+ /**
1193
+ * Retrieve special characters/placeholders
1194
+ * Merges defaults with class-specific characters
1195
+ * @uses $special_chars
1196
+ * @uses $special_chars_default
1197
+ * @return array Special characters/placeholders
1198
+ */
1199
+ function get_special_chars() {
1200
+ return wp_parse_args( $this->special_chars, $this->special_chars_default );
1201
+ }
1202
+ }
includes/class.field_collection.php CHANGED
@@ -1,768 +1,842 @@
1
- <?php
2
- /**
3
- * Managed collection of fields
4
- * @package Simple Lightbox
5
- * @subpackage Fields
6
- * @author Archetyped
7
- */
8
- class SLB_Field_Collection extends SLB_Field_Base {
9
-
10
- /* Configuration */
11
-
12
- protected $mode = 'sub';
13
-
14
- /* Properties */
15
-
16
- /**
17
- * Item type
18
- * @var string
19
- */
20
- var $item_type = 'SLB_Field';
21
-
22
- /**
23
- * Indexed array of items in collection
24
- * @var array
25
- */
26
- var $items = array();
27
-
28
- var $build_vars_default = array (
29
- 'groups' => array(),
30
- 'context' => '',
31
- 'layout' => 'form',
32
- 'build' => true,
33
- 'build_groups' => true,
34
- );
35
-
36
- /**
37
- * Associative array of groups in collection
38
- * Key: Group ID
39
- * Value: object of group properties
40
- * > id
41
- * > title
42
- * > description string Group description
43
- * > items array Items in group
44
- * @var array
45
- */
46
- var $groups = array();
47
-
48
- protected $properties_init = null;
49
-
50
- /* Constructors */
51
-
52
- /**
53
- * Class constructor
54
- * @uses parent::__construct()
55
- * @uses self::make_properties()
56
- * @uses self::init()
57
- * @uses self::add_groups()
58
- * @uses self::add_items()
59
- * @param string $id Collection ID
60
- * @param array $properties (optional) Properties to set for collection (Default: none)
61
- */
62
- public function __construct($id, $properties = null) {
63
- $args = func_get_args();
64
- $properties = $this->make_properties($args);
65
- // Parent constructor
66
- parent::__construct($properties);
67
-
68
- // Save initial properties
69
- $this->properties_init = $properties;
70
- }
71
-
72
- public function _init() {
73
- parent::_init();
74
-
75
- // Load properties.
76
- $this->load($this->properties_init, false);
77
-
78
- // Add custom ID format(s).
79
- $this->add_id_format(
80
- 'formatted',
81
- [
82
- 'wrap' => [ 'open' => '_' ],
83
- 'prefix' => [ 'get_prefix' ],
84
- 'recursive' => false,
85
- ]
86
- );
87
- }
88
-
89
- /*-** Getters/Setters **-*/
90
-
91
- /* Setup */
92
-
93
- /**
94
- * Load collection with specified properties
95
- * Updates existing properties
96
- * @param array $properties Properties to load
97
- * @param bool $update (optional) Update (TRUE) or overwrite (FALSE) items/groups (Default: TRUE)
98
- * @return object Current instance
99
- */
100
- public function load($properties, $update = true) {
101
- $args = func_get_args();
102
- $properties = $this->make_properties($args);
103
- if ( !empty($properties) ) {
104
- // Groups
105
- if ( isset($properties['groups']) ) {
106
- $this->add_groups($properties['groups'], $update);
107
- }
108
- // Items
109
- if ( isset($properties['items']) ) {
110
- $this->add_items($properties['items'], $update);
111
- }
112
- }
113
- return $this;
114
- }
115
-
116
- /* Data */
117
-
118
- /**
119
- * Retrieve external data for items in collection
120
- * Retrieved data is saved to the collection's $data property
121
- * Uses class properties to determine how data is retrieved
122
- * Examples:
123
- * > DB
124
- * > XML
125
- * > JSON
126
- * @return void
127
- */
128
- function load_data() {
129
- $this->data_loaded = true;
130
- }
131
-
132
- /**
133
- * Set data for an item
134
- * @param mixed $item Field to set data for
135
- * > string Field ID
136
- * > object Field Reference
137
- * > array Data for multiple items (associative array [field ID => data])
138
- * @param mixed $value Data to set
139
- * @param bool $save (optional) Whether or not data should be saved to DB (Default: Yes)
140
- */
141
- function set_data($item, $value = '', $save = true, $force_set = false) {
142
- // Set data for entire collection
143
- if ( is_array($item) ) {
144
- $this->data = wp_parse_args($item, $this->data);
145
- // Update save option
146
- $args = func_get_args();
147
- if ( 2 == count($args) && is_bool($args[1]) ) {
148
- $save = $args[1];
149
- }
150
- }
151
- // Get $item's ID
152
- elseif ( is_object($item) && method_exists($item, 'get_id') )
153
- $item = $item->get_id();
154
- // Set data
155
- if ( is_string($item) && !empty($item) && ( isset($this->items[$item]) || !!$force_set ) )
156
- $this->data[$item] = $value;
157
- if ( !!$save )
158
- $this->save();
159
- }
160
-
161
- /* Item */
162
-
163
- /**
164
- * Adds item to collection
165
- * @param string|obj $id Unique name for item or item instance
166
- * @param array $properties (optional) Item properties
167
- * @param bool $update (optional) Update or overwrite existing item (Default: FALSE)
168
- * @return object Newly-added item
169
- */
170
- function add($id, $properties = array(), $update = false) {
171
- $item;
172
- $args = func_get_args();
173
- // Properties
174
- foreach ( array_reverse($args) as $arg ) {
175
- if ( is_array($arg) ) {
176
- $properties = $arg;
177
- break;
178
- }
179
- }
180
- if ( !is_array($properties) ) {
181
- $properties = array();
182
- }
183
-
184
- // Handle item instance
185
- if ( $id instanceof $this->item_type ) {
186
- $item = $id;
187
- $item->set_properties($properties);
188
- } elseif ( class_exists($this->item_type) ) {
189
- $defaults = array (
190
- 'parent' => null,
191
- 'group' => null
192
- );
193
- $properties = array_merge($defaults, $properties);
194
- if ( is_string($id) ) {
195
- $properties['id'] = $id;
196
- }
197
- if ( !!$update && $this->has($properties['id']) ) {
198
- // Update existing item
199
- $item = $this->get($properties['id']);
200
- $item->set_properties($properties);
201
- } else {
202
- // Init item
203
- $type = $this->item_type;
204
- $item = new $type($properties);
205
- }
206
- }
207
-
208
- if ( empty($item) || 0 == strlen($item->get_id()) ) {
209
- return false;
210
- }
211
-
212
- // Set container
213
- $item->set_container($this);
214
-
215
- // Add item to collection
216
- $this->items[$item->get_id()] = $item;
217
-
218
- if ( isset($properties['group']) ) {
219
- $this->add_to_group($properties['group'], $item->get_id());
220
- }
221
-
222
- return $item;
223
- }
224
-
225
- /**
226
- * Removes item from collection
227
- * @param string|object $item Object or item ID to remove
228
- * @param bool $save (optional) Whether to save the collection after removing item (Default: YES)
229
- */
230
- function remove($item, $save = true) {
231
- // Remove item
232
- if ( $this->has($item) ) {
233
- $item = $this->get($item);
234
- $item = $item->get_id();
235
- // Remove from items array
236
- unset($this->items[$item]);
237
- // Remove item from groups
238
- $this->remove_from_group($item);
239
- }
240
- // Remove item data from collection
241
- $this->remove_data($item, false);
242
-
243
- if ( !!$save )
244
- $this->save();
245
- }
246
-
247
- /**
248
- * Remove item data from collection
249
- * @param string|object $item Object or item ID to remove
250
- * @param bool $save (optional) Whether to save the collection after removing item (Default: YES)
251
- */
252
- function remove_data($item, $save = true) {
253
- // Get item ID from object
254
- if ( $this->has($item) ) {
255
- $item = $this->get($item);
256
- $item = $item->get_id();
257
- }
258
-
259
- // Remove data from data member
260
- if ( is_string($item) && is_array($this->data) ) {
261
- unset($this->data[$item]);
262
- if ( !!$save )
263
- $this->save();
264
- }
265
- }
266
-
267
- /**
268
- * Checks if item exists in the collection
269
- * @param string $item Item ID
270
- * @return bool TRUE if item exists, FALSE otherwise
271
- */
272
- function has($item) {
273
- return ( !is_string($item) || empty($item) || is_null($this->get_member_value('items', $item, null)) ) ? false : true;
274
- }
275
-
276
- /**
277
- * Retrieve specified item in collection
278
- * @param string|object $item Item object or ID to retrieve
279
- * @return SLB_Field Specified item
280
- */
281
- function get($item, $safe_mode = false) {
282
- if ( $this->has($item) ) {
283
- if ( !is_object($item) || !($item instanceof $this->item_type) ) {
284
- if ( is_string($item) ) {
285
- $item = trim($item);
286
- $item =& $this->items[$item];
287
- }
288
- else {
289
- $item = false;
290
- }
291
- }
292
- } else {
293
- $item = false;
294
- }
295
-
296
- if ( !!$safe_mode && !is_object($item) ) {
297
- // Fallback: Return empty item if no item exists
298
- $type = $this->item_type;
299
- $item = new $type('');
300
- }
301
- return $item;
302
- }
303
-
304
- /**
305
- * Retrieve item data
306
- * @param $item Item to get data for
307
- * @param $context (optional) Context
308
- * @param $top (optional) Iterate through ancestors to get data (Default: Yes)
309
- * @return mixed Item data
310
- */
311
- function get_data($item = null, $context = '', $top = true) {
312
- $this->load_data();
313
- $ret = null;
314
- if ( $this->has($item) ) {
315
- $item = $this->get($item);
316
- $ret = $item->get_data($context, $top);
317
- } else {
318
- $ret = parent::get_data($context, $top);
319
- }
320
-
321
- if ( is_string($item) && is_array($ret) && isset($ret[$item]) )
322
- $ret = $ret[$item];
323
- return $ret;
324
- }
325
-
326
- /* Items (Collection) */
327
-
328
- /**
329
- * Add multiple items to collection
330
- * @param array $items Items to add to collection
331
- * Array Structure:
332
- * > Key (string): Item ID
333
- * > Val (array): Item properties
334
- * @return void
335
- */
336
- function add_items($items = array(), $update = false) {
337
- // Validate
338
- if ( !is_array($items) || empty($items) ) {
339
- return false;
340
- }
341
- // Add items
342
- foreach ( $items as $id => $props ) {
343
- $this->add($id, $props, $update);
344
- }
345
- }
346
-
347
- /**
348
- * Retrieve reference to items in collection
349
- * @return array Collection items (reference)
350
- */
351
- function &get_items($group = null, $sort = 'priority') {
352
- $gset = $this->group_exists($group);
353
- if ( $gset ) {
354
- $items = $this->get_group_items($group);
355
- } elseif ( !empty($group) ) {
356
- $items = array();
357
- } else {
358
- $items = $this->items;
359
- }
360
- if ( !empty($items) ) {
361
- // Sort items
362
- if ( !empty($sort) && is_string($sort) ) {
363
- if ( 'priority' == $sort ) {
364
- if ( $gset ) {
365
- // Sort by priority
366
- ksort($items, SORT_NUMERIC);
367
- }
368
- }
369
- }
370
- // Release from buckets
371
- if ( $gset ) {
372
- $items = call_user_func_array('array_merge', $items);
373
- }
374
- }
375
- return $items;
376
- }
377
-
378
- /**
379
- * Build output for items in specified group
380
- * If no group specified, all items in collection are built
381
- * @param string|object $group (optional) Group to build items for (ID or instance object)
382
- */
383
- function build_items($group = null) {
384
- // Get group items
385
- $items =& $this->get_items($group);
386
- if ( empty($items) ) {
387
- return false;
388
- }
389
-
390
- $this->util->do_action_ref_array('build_items_pre', array($this));
391
- foreach ( $items as $item ) {
392
- $item->build();
393
- }
394
- $this->util->do_action_ref_array('build_items_post', array($this));
395
- }
396
-
397
- /* Group */
398
-
399
- /**
400
- * Add groups to collection
401
- * @param array $groups Associative array of group properties
402
- * Array structure:
403
- * > Key (string): group ID
404
- * > Val (string): Group Title
405
- */
406
- function add_groups($groups = array(), $update = false) {
407
- // Validate
408
- if ( !is_array($groups) || empty($groups) ) {
409
- return false;
410
- }
411
- // Iterate
412
- foreach ( $groups as $id => $props ) {
413
- $this->add_group($id, $props, null, $update);
414
- }
415
- }
416
-
417
- /**
418
- * Adds group to collection
419
- * Groups are used to display related items in the UI
420
- * @param string $id Unique name for group
421
- * @param string $title Group title
422
- * @param string $description Short description of group's purpose
423
- * @param array $items (optional) ID's of existing items to add to group
424
- * @return object Group object
425
- */
426
- function &add_group($id, $properties = array(), $items = array(), $update = false) {
427
- // Create new group and set properties
428
- $default = array (
429
- 'title' => '',
430
- 'description' => '',
431
- 'priority' => 10
432
- );
433
- $p = ( is_array($properties) ) ? array_merge($default, $properties) : $default;
434
- if ( !is_int($p['priority']) || $p['priority'] < 0 ) {
435
- $p['priority'] = $default['priority'];
436
- }
437
- $id = trim($id);
438
- // Retrieve or init group
439
- if ( !!$update && $this->group_exists($id) ) {
440
- $grp = $this->get_group($id);
441
- $grp->title = $p['title'];
442
- $grp->description = $p['description'];
443
- $grp->priority = $p['priority'];
444
- } else {
445
- $this->groups[$id] =& $this->create_group($id, $p['title'], $p['description'], $p['priority']);
446
- }
447
- // Add items to group (if supplied)
448
- if ( !empty($items) && is_array($items) ) {
449
- $this->add_to_group($id, $items);
450
- }
451
- return $this->groups[$id];
452
- }
453
-
454
- /**
455
- * Remove specified group from collection
456
- * @param string $id Group ID to remove
457
- */
458
- function remove_group($id) {
459
- $id = trim($id);
460
- if ( $this->group_exists($id) ) {
461
- unset($this->groups[$id]);
462
- }
463
- }
464
-
465
- /**
466
- * Standardized method to create a new item group
467
- * @param string $title Group title (used in meta boxes, etc.)
468
- * @param string $description Short description of group's purpose
469
- * @param int $priority (optional) Group priority (e.g. used to sort groups during output)
470
- * @return object Group object
471
- */
472
- function &create_group($id = '', $title = '', $description = '', $priority = 10) {
473
- // Create new group object
474
- $group = new stdClass();
475
- /* Set group properties */
476
- // Set ID
477
- $id = ( is_scalar($id) ) ? trim($id) : '';
478
- $group->id = $id;
479
- // Set Title
480
- $title = ( is_scalar($title) ) ? trim($title) : '';
481
- $group->title = $title;
482
- // Set Description
483
- $description = ( is_scalar($description) ) ? trim($description) : '';
484
- $group->description = $description;
485
- // Priority
486
- $group->priority = ( is_int($priority) ) ? $priority : 10;
487
- // Create array to hold items
488
- $group->items = array();
489
- return $group;
490
- }
491
-
492
- /**
493
- * Checks if group exists in collection
494
- * @param string $id Group name
495
- * @return bool TRUE if group exists, FALSE otherwise
496
- */
497
- function group_exists($group) {
498
- $ret = false;
499
- if ( is_object($group) ) {
500
- $ret = true;
501
- } elseif ( is_string($group) && ($group = trim($group)) && strlen($group) > 0 ) {
502
- $group = trim($group);
503
- // Check if group exists
504
- $ret = !is_null($this->get_member_value('groups', $group, null));
505
- }
506
- return $ret;
507
- }
508
-
509
- /**
510
- * Adds item to a group in the collection
511
- * Group is created if it does not already exist
512
- * @param string|array $group ID of group (or group parameters if new group) to add item to
513
- * @param string|array $items Name or array of item(s) to add to group
514
- */
515
- function add_to_group($group, $items, $priority = 10) {
516
- // Validate
517
- if ( empty($items) || empty($group) || ( !is_string($group) && !is_array($group) ) ) {
518
- return false;
519
- }
520
-
521
- // Get group ID
522
- if ( is_string($group) ) {
523
- $group = array($group, $priority);
524
- }
525
- list($gid, $priority) = $group;
526
- $gid = trim(sanitize_title_with_dashes($gid));
527
- if ( empty($gid) ) {
528
- return false;
529
- }
530
- // Item priority
531
- if ( !is_int($priority) ) {
532
- $priority = 10;
533
- }
534
-
535
- // Prepare group
536
- if ( !$this->group_exists($gid) ) {
537
- // TODO Follow
538
- call_user_func($this->m('add_group'), $gid, $group);
539
- }
540
- // Prepare items
541
- if ( !is_array($items) ) {
542
- $items = array($items);
543
- }
544
- // Add Items
545
- foreach ( $items as $item ) {
546
- // Skip if not in current collection
547
- $itm_ref = $this->get($item);
548
- if ( !$itm_ref ) {
549
- continue;
550
- }
551
- $itm_id = $itm_ref->get_id();
552
- // Remove item from any other group it's in (items can only be in one group)
553
- foreach ( $this->get_groups() as $group ) {
554
- foreach ( $group->items as $tmp_pri => $tmp_items ) {
555
- if ( isset($group->items[$tmp_pri][$itm_id]) ) {
556
- unset($group->items[$tmp_pri][$itm_id]);
557
- }
558
- }
559
- }
560
- // Add reference to item in group
561
- $items =& $this->get_group($gid)->items;
562
- if ( !isset($items[$priority]) ) {
563
- $items[$priority] = array();
564
- }
565
- $items[$priority][$itm_id] = $itm_ref;
566
- }
567
- unset($itm_ref);
568
- }
569
-
570
- /**
571
- * Remove item from a group
572
- * If no group is specified, then item is removed from all groups
573
- * @param string|object $item Object or ID of item to remove from group
574
- * @param string $group (optional) Group ID to remove item from
575
- */
576
- function remove_from_group($item, $group = '') {
577
- // Get ID of item to remove or stop execution if item invalid
578
- $item = $this->get($item);
579
- $item = $item->get_id();
580
- if ( !$item )
581
- return false;
582
-
583
- // Remove item from group
584
- if ( !empty($group) ) {
585
- // Remove item from single group
586
- if ( ($group =& $this->get_group($group)) && isset($group->items[$item]) ) {
587
- unset($group->items[$item]);
588
- }
589
- } else {
590
- // Remove item from all groups
591
- foreach ( array_keys($this->groups) as $group ) {
592
- if ( ($group =& $this->get_group($group)) && isset($group->items[$item]) ) {
593
- unset($group->items[$item]);
594
- }
595
- }
596
- }
597
- }
598
-
599
- /**
600
- * Retrieve specified group
601
- * @param string $group ID of group to retrieve
602
- * @return object Reference to specified group
603
- */
604
- function &get_group($group) {
605
- if ( is_object($group) ) {
606
- return $group;
607
- }
608
- if ( is_string($group) ) {
609
- $group = trim($group);
610
- }
611
- // Create group if it doesn't already exist
612
- if ( ! $this->group_exists($group) ) {
613
- $this->add_group($group);
614
- }
615
- return $this->get_member_value('groups', $group);
616
- }
617
-
618
- /**
619
- * Retrieve a group's items
620
- * @uses SLB_Field_Collection::get_group() to retrieve group object
621
- * @param object|string $group Group object or group ID
622
- * @return array Group's items
623
- */
624
- function &get_group_items($group) {
625
- $group =& $this->get_group($group);
626
- return $group->items;
627
- }
628
-
629
- /**
630
- * Retrieve all groups in collection
631
- * @return array Reference to group objects
632
- */
633
- function &get_groups($opts = array()) {
634
- $groups =& $this->get_member_value('groups');
635
- if ( is_array($opts) && !empty($opts) ) {
636
- extract($opts, EXTR_SKIP);
637
- if ( !empty($groups) && !empty($sort) && is_string($sort) ) {
638
- if ( property_exists(current($groups), $sort) ) {
639
- // Sort groups by property
640
- $sfunc = function ( $a, $b ) use ($sort) {
641
- $ap = $a->$sort;
642
- $bp = $b->$sort;
643
- if ( $ap == $bp ) {
644
- return 0;
645
- }
646
- return ( $ap > $bp ) ? 1 : -1;
647
- };
648
- uasort($groups, $sfunc);
649
- }
650
- }
651
- }
652
- return $groups;
653
- }
654
-
655
- /**
656
- * Output groups
657
- * @uses self::build_vars to determine groups to build
658
- */
659
- function build_groups() {
660
- $this->util->do_action_ref_array('build_groups_pre', array($this));
661
-
662
- // Get groups to build
663
- $groups = ( !empty($this->build_vars['groups']) ) ? $this->build_vars['groups'] : array_keys($this->get_groups(array('sort' => 'priority')));
664
- // Check options
665
- if ( is_callable($this->build_vars['build_groups']) ) {
666
- // Pass groups to callback to build output
667
- call_user_func_array($this->build_vars['build_groups'], array($this, $groups));
668
- } elseif ( !!$this->build_vars['build_groups'] ) {
669
- // Build groups
670
- foreach ( $groups as $group ) {
671
- $this->build_group($group);
672
- }
673
- }
674
-
675
- $this->util->do_action_ref_array('build_groups_post', array($this));
676
- }
677
-
678
- /**
679
- * Build group
680
- */
681
- function build_group($group) {
682
- if ( !$this->group_exists($group) ) {
683
- return false;
684
- }
685
- $group =& $this->get_group($group);
686
- // Stop processing if group contains no items
687
- if ( !count($this->get_items($group)) ) {
688
- return false;
689
- }
690
-
691
- // Pre action
692
- $this->util->do_action_ref_array('build_group_pre', array($this, $group));
693
-
694
- // Build items
695
- $this->build_items($group);
696
-
697
- // Post action
698
- $this->util->do_action_ref_array('build_group_post', array($this, $group));
699
- }
700
-
701
- /* Collection */
702
-
703
- /**
704
- * Build entire collection of items
705
- * Prints output
706
- */
707
- function build($build_vars = array()) {
708
- // Parse vars
709
- $this->parse_build_vars($build_vars);
710
- $this->util->do_action_ref_array('build_init', array($this));
711
- // Pre-build output
712
- $this->util->do_action_ref_array('build_pre', array($this));
713
- // Build groups
714
- $this->build_groups();
715
- // Post-build output
716
- $this->util->do_action_ref_array('build_post', array($this));
717
- }
718
-
719
- /**
720
- * Set build variable
721
- * @param string $key Variable name
722
- * @param mixed $val Variable value
723
- */
724
- function set_build_var($key, $val) {
725
- $this->build_vars[$key] = $val;
726
- }
727
-
728
- /**
729
- * Retrieve build variable
730
- * @param string $key Variable name
731
- * @param mixed $default Value if variable is not set
732
- * @return mixed Variable value
733
- */
734
- function get_build_var($key, $default = null) {
735
- return ( array_key_exists($key, $this->build_vars) ) ? $this->build_vars[$key] : $default;
736
- }
737
-
738
- /**
739
- * Delete build variable
740
- * @param string $key Variable name to delete
741
- */
742
- function delete_build_var($key) {
743
- if ( array_key_exists($key, $this->build_vars) ) {
744
- unset($this->build_vars[$key]);
745
- }
746
- }
747
-
748
- /**
749
- * Parses build variables prior to use
750
- * @uses this->reset_build_vars() to reset build variables for each request
751
- * @param array $build_vars Variables to use for current request
752
- */
753
- function parse_build_vars($build_vars = array()) {
754
- $this->reset_build_vars();
755
- $this->build_vars = $this->util->apply_filters('parse_build_vars', wp_parse_args($build_vars, $this->build_vars), $this);
756
- }
757
-
758
- /**
759
- * Reset build variables to defaults
760
- * Default Variables
761
- * > groups - array - Names of groups to build
762
- * > context - string - Context of current request
763
- * > layout - string - Name of default layout to use
764
- */
765
- function reset_build_vars() {
766
- $this->build_vars = wp_parse_args($this->build_vars, $this->build_vars_default);
767
- }
768
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Managed collection of fields
4
+ * @package Simple Lightbox
5
+ * @subpackage Fields
6
+ * @author Archetyped
7
+ */
8
+ class SLB_Field_Collection extends SLB_Field_Base {
9
+
10
+ /* Configuration */
11
+
12
+ protected $mode = 'sub';
13
+
14
+ /* Properties */
15
+
16
+ /**
17
+ * Item type
18
+ * @var string
19
+ */
20
+ public $item_type = 'SLB_Field';
21
+
22
+ /**
23
+ * Indexed array of items in collection
24
+ * @var array
25
+ */
26
+ public $items = array();
27
+
28
+ public $build_vars_default = array(
29
+ 'groups' => array(),
30
+ 'context' => '',
31
+ 'layout' => 'form',
32
+ 'build' => true,
33
+ 'build_groups' => true,
34
+ );
35
+
36
+ /**
37
+ * Associative array of groups in collection
38
+ * Key: Group ID
39
+ * Value: object of group properties
40
+ * > id
41
+ * > title
42
+ * > description string Group description
43
+ * > items array Items in group
44
+ * @var array
45
+ */
46
+ public $groups = array();
47
+
48
+ protected $properties_init = null;
49
+
50
+ /* Constructors */
51
+
52
+ /**
53
+ * Class constructor
54
+ * @uses parent::__construct()
55
+ * @uses self::make_properties()
56
+ * @uses self::init()
57
+ * @uses self::add_groups()
58
+ * @uses self::add_items()
59
+ * @param string $id Collection ID
60
+ * @param array $properties (optional) Properties to set for collection (Default: none)
61
+ */
62
+ public function __construct( $id, $properties = null ) {
63
+ $args = func_get_args();
64
+ $properties = $this->make_properties( $args );
65
+ // Parent constructor
66
+ parent::__construct( $properties );
67
+
68
+ // Save initial properties
69
+ $this->properties_init = $properties;
70
+ }
71
+
72
+ public function _init() {
73
+ parent::_init();
74
+
75
+ // Load properties.
76
+ $this->load( $this->properties_init, false );
77
+
78
+ // Add custom ID format(s).
79
+ $this->add_id_format(
80
+ 'formatted',
81
+ [
82
+ 'wrap' => [ 'open' => '_' ],
83
+ 'prefix' => [ 'get_prefix' ],
84
+ 'recursive' => false,
85
+ ]
86
+ );
87
+ }
88
+
89
+ /*-** Getters/Setters **-*/
90
+
91
+ /* Setup */
92
+
93
+ /**
94
+ * Load collection with specified properties
95
+ * Updates existing properties
96
+ * @param array $properties Properties to load
97
+ * @param bool $update (optional) Update (TRUE) or overwrite (FALSE) items/groups (Default: TRUE)
98
+ * @return object Current instance
99
+ */
100
+ public function load( $properties, $update = true ) {
101
+ $args = func_get_args();
102
+ $properties = $this->make_properties( $args );
103
+ if ( ! empty( $properties ) ) {
104
+ // Groups
105
+ if ( isset( $properties['groups'] ) ) {
106
+ $this->add_groups( $properties['groups'], $update );
107
+ }
108
+ // Items
109
+ if ( isset( $properties['items'] ) ) {
110
+ $this->add_items( $properties['items'], $update );
111
+ }
112
+ }
113
+ return $this;
114
+ }
115
+
116
+ /* Data */
117
+
118
+ /**
119
+ * Retrieve external data for items in collection
120
+ * Retrieved data is saved to the collection's $data property
121
+ * Uses class properties to determine how data is retrieved
122
+ * Examples:
123
+ * > DB
124
+ * > XML
125
+ * > JSON
126
+ * @return void
127
+ */
128
+ function load_data() {
129
+ $this->data_loaded = true;
130
+ }
131
+
132
+ /**
133
+ * Set data for an item
134
+ * @param mixed $item Field to set data for
135
+ * > string Field ID
136
+ * > object Field Reference
137
+ * > array Data for multiple items (associative array [field ID => data])
138
+ * @param mixed $value Data to set
139
+ * @param bool $save (optional) Whether or not data should be saved to DB (Default: Yes)
140
+ */
141
+ function set_data( $item, $value = '', $save = true, $force_set = false ) {
142
+ // Set data for entire collection
143
+ if ( is_array( $item ) ) {
144
+ $this->data = wp_parse_args( $item, $this->data );
145
+ // Update save option
146
+ $args = func_get_args();
147
+ if ( 2 === count( $args ) && is_bool( $args[1] ) ) {
148
+ $save = $args[1];
149
+ }
150
+ } elseif ( is_object( $item ) && method_exists( $item, 'get_id' ) ) {
151
+ // Get $item's ID
152
+ $item = $item->get_id();
153
+ }
154
+ // Set data
155
+ if ( is_string( $item ) && ! empty( $item ) && ( isset( $this->items[ $item ] ) || ! ! $force_set ) ) {
156
+ $this->data[ $item ] = $value;
157
+ }
158
+ if ( ! ! $save ) {
159
+ $this->save();
160
+ }
161
+ }
162
+
163
+ /* Item */
164
+
165
+ /**
166
+ * Adds item to collection
167
+ * @param string|obj $id Unique name for item or item instance
168
+ * @param array $properties (optional) Item properties
169
+ * @param bool $update (optional) Update or overwrite existing item (Default: FALSE)
170
+ * @return object Newly-added item
171
+ */
172
+ function add( $id, $properties = array(), $update = false ) {
173
+ $item = null;
174
+ $args = func_get_args();
175
+ // Get properties.
176
+ foreach ( array_reverse( $args ) as $arg ) {
177
+ if ( is_array( $arg ) ) {
178
+ $properties = $arg;
179
+ break;
180
+ }
181
+ }
182
+ if ( ! is_array( $properties ) ) {
183
+ $properties = array();
184
+ }
185
+
186
+ // Prep item.
187
+ if ( $id instanceof $this->item_type ) {
188
+ // Use existing item.
189
+ $item = $id;
190
+ $item->set_properties( $properties );
191
+ } elseif ( class_exists( $this->item_type ) ) {
192
+ // Create new item.
193
+ $defaults = array(
194
+ 'parent' => null,
195
+ 'group' => null,
196
+ );
197
+ $properties = array_merge( $defaults, $properties );
198
+ if ( is_string( $id ) ) {
199
+ $properties['id'] = $id;
200
+ }
201
+ if ( ! ! $update && $this->has( $properties['id'] ) ) {
202
+ // Update existing item
203
+ $item = $this->get( $properties['id'] );
204
+ $item->set_properties( $properties );
205
+ } else {
206
+ // Init item
207
+ $type = $this->item_type;
208
+ $item = new $type( $properties );
209
+ }
210
+ }
211
+
212
+ if ( empty( $item ) || strlen( $item->get_id() ) === 0 ) {
213
+ return false;
214
+ }
215
+
216
+ // Set container
217
+ $item->set_container( $this );
218
+
219
+ // Add item to collection
220
+ $this->items[ $item->get_id() ] = $item;
221
+
222
+ if ( isset( $properties['group'] ) ) {
223
+ $this->add_to_group( $properties['group'], $item->get_id() );
224
+ }
225
+
226
+ return $item;
227
+ }
228
+
229
+ /**
230
+ * Removes item from collection
231
+ * @param string|object $item Object or item ID to remove
232
+ * @param bool $save (optional) Whether to save the collection after removing item (Default: YES)
233
+ */
234
+ function remove( $item, $save = true ) {
235
+ // Remove item
236
+ if ( $this->has( $item ) ) {
237
+ $item = $this->get( $item );
238
+ $item = $item->get_id();
239
+ // Remove from items array
240
+ unset( $this->items[ $item ] );
241
+ // Remove item from groups
242
+ $this->remove_from_group( $item );
243
+ }
244
+ // Remove item data from collection
245
+ $this->remove_data( $item, false );
246
+
247
+ if ( ! ! $save ) {
248
+ $this->save();
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Remove item data from collection
254
+ * @param string|object $item Object or item ID to remove
255
+ * @param bool $save (optional) Whether to save the collection after removing item (Default: YES)
256
+ */
257
+ function remove_data( $item, $save = true ) {
258
+ // Get item ID from object
259
+ if ( $this->has( $item ) ) {
260
+ $item = $this->get( $item );
261
+ $item = $item->get_id();
262
+ }
263
+
264
+ // Remove data from data member
265
+ if ( is_string( $item ) && is_array( $this->data ) ) {
266
+ unset( $this->data[ $item ] );
267
+ if ( ! ! $save ) {
268
+ $this->save();
269
+ }
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Checks if item exists in the collection.
275
+ *
276
+ * @param string|object $item Item ID or instance.
277
+ * @return bool True if item is in collection, False otherwise.
278
+ */
279
+ function has( $item ) {
280
+ // Item ID.
281
+ if ( is_string( $item ) ) {
282
+ return in_array( $item, array_keys( $this->get_items() ), true );
283
+ }
284
+ // Item instance.
285
+ if ( $item instanceof $this->item_type ) {
286
+ $items = $this->get_items( null, false );
287
+ $id = $item->get_id();
288
+ return ( isset( $items[ $id ] ) && $item === $items[ $id ] );
289
+ }
290
+ return false;
291
+ }
292
+
293
+ /**
294
+ * Retrieve specified item in collection.
295
+ *
296
+ * @param string|object $item Item ID or instance to retrieve.
297
+ * @return SLB_Field|null Specified item. Null if item is not in collection.
298
+ */
299
+ function get( $item, $safe_mode = false ) {
300
+ $invalid = null;
301
+ // Stop processing if item is not in collection.
302
+ if ( ! $this->has( $item ) ) {
303
+ return $invalid;
304
+ }
305
+ // Get item instance from ID.
306
+ if ( is_string( $item ) ) {
307
+ $items = $this->get_items( null, false );
308
+ if ( isset( $items[ $item ] ) ) {
309
+ $item = $items[ $item ];
310
+ }
311
+ }
312
+ // Return valid item instance.
313
+ if ( $item instanceof $this->item_type ) {
314
+ return $item;
315
+ }
316
+ // Invalid item.
317
+ return $invalid;
318
+ }
319
+
320
+ /**
321
+ * Retrieve item data
322
+ * @param $item Item to get data for
323
+ * @param $context (optional) Context
324
+ * @param $top (optional) Iterate through ancestors to get data (Default: Yes)
325
+ * @return mixed|null Item data. Null if no data retrieved.
326
+ */
327
+ function get_data( $item = null, $context = '', $top = true ) {
328
+ $this->load_data();
329
+ $ret = null;
330
+ if ( $this->has( $item ) ) {
331
+ $item = $this->get( $item );
332
+ $ret = $item->get_data( $context, $top );
333
+ } else {
334
+ $ret = parent::get_data( $context, $top );
335
+ }
336
+
337
+ if ( is_string( $item ) && is_array( $ret ) && isset( $ret[ $item ] ) ) {
338
+ $ret = $ret[ $item ];
339
+ }
340
+ return $ret;
341
+ }
342
+
343
+ /* Items (Collection) */
344
+
345
+ /**
346
+ * Add multiple items to collection
347
+ * @param array $items Items to add to collection
348
+ * Array Structure:
349
+ * > Key (string): Item ID
350
+ * > Val (array): Item properties
351
+ * @return void
352
+ */
353
+ function add_items( $items = array(), $update = false ) {
354
+ // Validate
355
+ if ( ! is_array( $items ) || empty( $items ) ) {
356
+ return false;
357
+ }
358
+ // Add items
359
+ foreach ( $items as $id => $props ) {
360
+ $this->add( $id, $props, $update );
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Retrieve reference to items in collection
366
+ * @return array Collection items (reference)
367
+ */
368
+ function &get_items( $group = null, $sort = 'priority' ) {
369
+ $gset = $this->group_exists( $group );
370
+ if ( $gset ) {
371
+ $items = $this->get_group_items( $group );
372
+ } elseif ( ! empty( $group ) ) {
373
+ $items = array();
374
+ } else {
375
+ $items = $this->items;
376
+ }
377
+ if ( ! empty( $items ) ) {
378
+ // Sort items
379
+ if ( ! empty( $sort ) && is_string( $sort ) ) {
380
+ if ( 'priority' === $sort ) {
381
+ if ( $gset ) {
382
+ // Sort by priority
383
+ ksort( $items, SORT_NUMERIC );
384
+ }
385
+ }
386
+ }
387
+ // Release from buckets
388
+ if ( $gset ) {
389
+ $items = call_user_func_array( 'array_merge', $items );
390
+ }
391
+ }
392
+ return $items;
393
+ }
394
+
395
+ /**
396
+ * Build output for items in specified group
397
+ * If no group specified, all items in collection are built
398
+ * @param string|object $group (optional) Group to build items for (ID or instance object)
399
+ */
400
+ function build_items( $group = null ) {
401
+ // Get group items
402
+ $items =& $this->get_items( $group );
403
+ if ( empty( $items ) ) {
404
+ return false;
405
+ }
406
+
407
+ $this->util->do_action_ref_array( 'build_items_pre', array( $this ) );
408
+ foreach ( $items as $item ) {
409
+ $item->build();
410
+ }
411
+ $this->util->do_action_ref_array( 'build_items_post', array( $this ) );
412
+ }
413
+
414
+ /* Group */
415
+
416
+ /**
417
+ * Sanitizes group ID.
418
+ *
419
+ * @param string $id Group ID.
420
+ * @param bool $fallback Optional. Use fallback group if ID is invalid. Default true.
421
+ * @return string Sanitized group ID.
422
+ */
423
+ protected function sanitize_group_id( $id, $fallback = true ) {
424
+ // Sanitize.
425
+ $id = sanitize_title_with_dashes( $id );
426
+ // Use default ID (fallback).
427
+ if ( strlen( $id ) === 0 && ! ! $fallback ) {
428
+ $id = 'default';
429
+ }
430
+ return $id;
431
+ }
432
+
433
+ /**
434
+ * Add groups to collection
435
+ * @param array $groups Associative array of group properties
436
+ * Array structure:
437
+ * > Key (string): group ID
438
+ * > Val (string): Group Title
439
+ */
440
+ function add_groups( $groups = array(), $update = false ) {
441
+ // Validate
442
+ if ( ! is_array( $groups ) || empty( $groups ) ) {
443
+ return false;
444
+ }
445
+ // Iterate
446
+ foreach ( $groups as $id => $props ) {
447
+ $this->add_group( $id, $props, null, $update );
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Adds group to collection
453
+ * Groups are used to display related items in the UI
454
+ * @param string $id Unique name for group
455
+ * @param string $title Group title
456
+ * @param string $description Short description of group's purpose
457
+ * @param array $items (optional) ID's of existing items to add to group
458
+ * @return object Group object
459
+ */
460
+ function &add_group( $id, $properties = array(), $items = array(), $update = false ) {
461
+ // Create new group and set properties
462
+ $default = array(
463
+ 'title' => '',
464
+ 'description' => '',
465
+ 'priority' => 10,
466
+ );
467
+ $p = ( is_array( $properties ) ) ? array_merge( $default, $properties ) : $default;
468
+ if ( ! is_int( $p['priority'] ) || $p['priority'] < 0 ) {
469
+ $p['priority'] = $default['priority'];
470
+ }
471
+ $id = $this->sanitize_group_id( $id );
472
+ // Retrieve or init group
473
+ if ( ! ! $update && $this->group_exists( $id ) ) {
474
+ $grp = $this->get_group( $id );
475
+ $grp->title = $p['title'];
476
+ $grp->description = $p['description'];
477
+ $grp->priority = $p['priority'];
478
+ } else {
479
+ $this->groups[ $id ] =& $this->create_group( $id, $p['title'], $p['description'], $p['priority'] );
480
+ }
481
+ // Add items to group (if supplied)
482
+ if ( ! empty( $items ) && is_array( $items ) ) {
483
+ $this->add_to_group( $id, $items );
484
+ }
485
+ return $this->groups[ $id ];
486
+ }
487
+
488
+ /**
489
+ * Remove specified group from collection
490
+ * @param string $id Group ID to remove
491
+ */
492
+ function remove_group( $id ) {
493
+ $id = trim( $id );
494
+ if ( $this->group_exists( $id ) ) {
495
+ unset( $this->groups[ $id ] );
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Creates a new item group.
501
+ *
502
+ * @param string $title Group title (used in meta boxes, etc.)
503
+ * @param string $description Short description of group's purpose
504
+ * @param int $priority (optional) Group priority (e.g. used to sort groups during output)
505
+ * @return object {
506
+ * Group object.
507
+ *
508
+ * @type string $id ID.
509
+ * @type string $title Title.
510
+ * @type string $description Description.
511
+ * @type int $priority Priority (relative to other groups in collection).
512
+ * @type array $items {
513
+ * Group items. Grouped by item priority.
514
+ * Items indexed by item ID.
515
+ * Example: `$items[10]['item_id'] = {item}
516
+ * }
517
+ * }
518
+ */
519
+ function &create_group( $id = '', $title = '', $description = '', $priority = 10 ) {
520
+ // Create new group object
521
+ $group = new stdClass();
522
+ /* Set group properties */
523
+ // Set ID
524
+ $id = ( is_scalar( $id ) ) ? trim( $id ) : '';
525
+ $group->id = $id;
526
+ // Set Title
527
+ $title = ( is_scalar( $title ) ) ? trim( $title ) : '';
528
+ $group->title = $title;
529
+ // Set Description
530
+ $description = ( is_scalar( $description ) ) ? trim( $description ) : '';
531
+ $group->description = $description;
532
+ // Priority
533
+ $group->priority = ( is_int( $priority ) ) ? $priority : 10;
534
+ // Create array to hold items
535
+ $group->items = array();
536
+ return $group;
537
+ }
538
+
539
+ /**
540
+ * Checks if group exists in collection.
541
+ *
542
+ * @param string|object $id Group name or object.
543
+ * @return bool True if group exists, False otherwise.
544
+ */
545
+ function group_exists( $group ) {
546
+ if ( is_object( $group ) ) {
547
+ return true;
548
+ }
549
+ if ( ! is_string( $group ) ) {
550
+ return false;
551
+ }
552
+ $group = $this->sanitize_group_id( $group );
553
+ if ( strlen( $group ) === 0 ) {
554
+ return false;
555
+ }
556
+ // Check if group exists
557
+ return ( ! is_null( $this->get_member_value( 'groups', $group, null ) ) );
558
+ }
559
+
560
+ /**
561
+ * Adds item to a group in the collection.
562
+ *
563
+ * Group is created if it does not already exist.
564
+ *
565
+ * @param string|array $group {
566
+ * Group ID (or parameters array) to add item to.
567
+ *
568
+ * Array structure:
569
+ *
570
+ * @type string $0 Group ID.
571
+ * @type int $1 Item priority in group.
572
+ * }
573
+ * @param string|object|array $items Item ID/Instance or array of items to add to group.
574
+ * @param int $priority Optional. Default priority to set for items in group. Default 10.
575
+ * @return bool True if item(s) successfully added to group.
576
+ * False if item(s) not added to group.
577
+ */
578
+ function add_to_group( $group, $items, $priority = 10 ) {
579
+ // Validate.
580
+ if ( empty( $items ) || empty( $group ) || ( ! is_string( $group ) && ! is_array( $group ) ) ) {
581
+ return false;
582
+ }
583
+ // Parse group properties.
584
+ if ( is_string( $group ) ) {
585
+ $gid = $group;
586
+ } elseif ( is_array( $group ) ) {
587
+ $group = array_values( $group );
588
+ if ( count( $group ) < 2 ) {
589
+ $group[] = $priority;
590
+ }
591
+ list( $gid, $priority ) = $group;
592
+ }
593
+
594
+ // Format group ID.
595
+ $gid = $this->sanitize_group_id( $gid );
596
+ if ( strlen( $gid ) === 0 ) {
597
+ return false;
598
+ }
599
+
600
+ // Item priority.
601
+ $priority = (int) $priority;
602
+
603
+ // Prepare items
604
+ if ( ! is_array( $items ) ) {
605
+ $items = [ $items ];
606
+ }
607
+
608
+ // Add Items.
609
+ $items_skipped = [];
610
+ foreach ( $items as $item ) {
611
+ // Skip if item is not in current collection.
612
+ $itm_ref = $this->get( $item );
613
+ if ( ! $itm_ref ) {
614
+ $items_skipped[] = $item;
615
+ continue;
616
+ }
617
+ // Remove item from all groups (items can only be in one group).
618
+ $this->remove_from_group( $itm_ref );
619
+ // Add reference to item in group.
620
+ $items =& $this->get_group( $gid )->items;
621
+ // Ensure priority level exists for group.
622
+ if ( ! isset( $items[ $priority ] ) ) {
623
+ $items[ $priority ] = array();
624
+ }
625
+ // Add item to group.
626
+ $items[ $priority ][ $itm_ref->get_id() ] = $itm_ref;
627
+ }
628
+ unset( $itm_ref );
629
+ return ( count( $items_skipped ) < count( $items ) );
630
+ }
631
+
632
+ /**
633
+ * Removes item from a group.
634
+ *
635
+ * If group is not specified, item is removed from all groups.
636
+ *
637
+ * @param string|object $item ID or object of item to remove from group.
638
+ * @param string|object|array $groups Optional. Group(s) to remove item from. Default null.
639
+ * - @type string Group ID.
640
+ * - @type object Group object.
641
+ * - @type array Multiple groups (ID or object).
642
+ * @return void
643
+ */
644
+ function remove_from_group( $item, $groups = null ) {
645
+ // Validate item.
646
+ $item = $this->get( $item );
647
+ if ( ! $item ) {
648
+ return;
649
+ }
650
+ // Get item ID.
651
+ $item = $item->get_id();
652
+ // Validate item ID.
653
+ if ( ! $item ) {
654
+ return;
655
+ }
656
+
657
+ // Setup groups.
658
+ if ( is_string( $groups ) || is_object( $groups ) ) {
659
+ $groups = [ $groups ];
660
+ } elseif ( ! is_array( $groups ) ) {
661
+ $groups = array_keys( $this->get_groups() );
662
+ }
663
+
664
+ // Remove item from group(s).
665
+ foreach ( $groups as $group ) {
666
+ $group = $this->get_group( $group );
667
+ foreach ( array_keys( $group->items ) as $priority ) {
668
+ unset( $group->items[ $priority ][ $item ] );
669
+ }
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Retrieves specified group.
675
+ *
676
+ * @param string|object $group Group ID or object to retrieve.
677
+ * @return object Reference to specified group.
678
+ */
679
+ function &get_group( $group ) {
680
+ if ( is_object( $group ) ) {
681
+ return $group;
682
+ }
683
+ // Create group if it doesn't already exist.
684
+ if ( ! $this->group_exists( $group ) ) {
685
+ return $this->add_group( $group );
686
+ }
687
+ // Get existing group.
688
+ $group = $this->sanitize_group_id( $group );
689
+ return $this->get_member_value( 'groups', $group );
690
+ }
691
+
692
+ /**
693
+ * Retrieve a group's items
694
+ * @uses SLB_Field_Collection::get_group() to retrieve group object
695
+ * @param object|string $group Group object or group ID
696
+ * @return array Group's items
697
+ */
698
+ function &get_group_items( $group ) {
699
+ $group =& $this->get_group( $group );
700
+ return $group->items;
701
+ }
702
+
703
+ /**
704
+ * Retrieve all groups in collection
705
+ * @return object[] Reference to group objects
706
+ */
707
+ function &get_groups( $opts = array() ) {
708
+ $groups =& $this->get_member_value( 'groups' );
709
+ if ( is_array( $opts ) && ! empty( $opts ) ) {
710
+ extract( $opts, EXTR_SKIP );
711
+ if ( ! empty( $groups ) && ! empty( $sort ) && is_string( $sort ) ) {
712
+ if ( property_exists( current( $groups ), $sort ) ) {
713
+ // Sort groups by property
714
+ $sfunc = function ( $a, $b ) use ( $sort ) {
715
+ $ap = $a->$sort;
716
+ $bp = $b->$sort;
717
+ if ( $ap === $bp ) {
718
+ return 0;
719
+ }
720
+ return ( $ap > $bp ) ? 1 : -1;
721
+ };
722
+ uasort( $groups, $sfunc );
723
+ }
724
+ }
725
+ }
726
+ return $groups;
727
+ }
728
+
729
+ /**
730
+ * Output groups
731
+ * @uses self::build_vars to determine groups to build
732
+ */
733
+ function build_groups() {
734
+ $this->util->do_action_ref_array( 'build_groups_pre', array( $this ) );
735
+
736
+ // Get groups to build
737
+ $groups = ( ! empty( $this->build_vars['groups'] ) ) ? $this->build_vars['groups'] : array_keys( $this->get_groups( array( 'sort' => 'priority' ) ) );
738
+ // Check options
739
+ if ( is_callable( $this->build_vars['build_groups'] ) ) {
740
+ // Pass groups to callback to build output
741
+ call_user_func_array( $this->build_vars['build_groups'], array( $this, $groups ) );
742
+ } elseif ( ! ! $this->build_vars['build_groups'] ) {
743
+ // Build groups
744
+ foreach ( $groups as $group ) {
745
+ $this->build_group( $group );
746
+ }
747
+ }
748
+
749
+ $this->util->do_action_ref_array( 'build_groups_post', array( $this ) );
750
+ }
751
+
752
+ /**
753
+ * Build group
754
+ */
755
+ function build_group( $group ) {
756
+ if ( ! $this->group_exists( $group ) ) {
757
+ return false;
758
+ }
759
+ $group =& $this->get_group( $group );
760
+ // Stop processing if group contains no items
761
+ if ( ! count( $this->get_items( $group ) ) ) {
762
+ return false;
763
+ }
764
+
765
+ // Pre action
766
+ $this->util->do_action_ref_array( 'build_group_pre', array( $this, $group ) );
767
+
768
+ // Build items
769
+ $this->build_items( $group );
770
+
771
+ // Post action
772
+ $this->util->do_action_ref_array( 'build_group_post', array( $this, $group ) );
773
+ }
774
+
775
+ /* Collection */
776
+
777
+ /**
778
+ * Build entire collection of items
779
+ * Prints output
780
+ */
781
+ function build( $build_vars = array() ) {
782
+ // Parse vars
783
+ $this->parse_build_vars( $build_vars );
784
+ $this->util->do_action_ref_array( 'build_init', array( $this ) );
785
+ // Pre-build output
786
+ $this->util->do_action_ref_array( 'build_pre', array( $this ) );
787
+ // Build groups
788
+ $this->build_groups();
789
+ // Post-build output
790
+ $this->util->do_action_ref_array( 'build_post', array( $this ) );
791
+ }
792
+
793
+ /**
794
+ * Set build variable
795
+ * @param string $key Variable name
796
+ * @param mixed $val Variable value
797
+ */
798
+ function set_build_var( $key, $val ) {
799
+ $this->build_vars[ $key ] = $val;
800
+ }
801
+
802
+ /**
803
+ * Retrieve build variable
804
+ * @param string $key Variable name
805
+ * @param mixed $default Value if variable is not set
806
+ * @return mixed Variable value
807
+ */
808
+ function get_build_var( $key, $default = null ) {
809
+ return ( array_key_exists( $key, $this->build_vars ) ) ? $this->build_vars[ $key ] : $default;
810
+ }
811
+
812
+ /**
813
+ * Delete build variable
814
+ * @param string $key Variable name to delete
815
+ */
816
+ function delete_build_var( $key ) {
817
+ if ( array_key_exists( $key, $this->build_vars ) ) {
818
+ unset( $this->build_vars[ $key ] );
819
+ }
820
+ }
821
+
822
+ /**
823
+ * Parses build variables prior to use
824
+ * @uses this->reset_build_vars() to reset build variables for each request
825
+ * @param array $build_vars Variables to use for current request
826
+ */
827
+ function parse_build_vars( $build_vars = array() ) {
828
+ $this->reset_build_vars();
829
+ $this->build_vars = $this->util->apply_filters( 'parse_build_vars', wp_parse_args( $build_vars, $this->build_vars ), $this );
830
+ }
831
+
832
+ /**
833
+ * Reset build variables to defaults
834
+ * Default Variables
835
+ * > groups - array - Names of groups to build
836
+ * > context - string - Context of current request
837
+ * > layout - string - Name of default layout to use
838
+ */
839
+ function reset_build_vars() {
840
+ $this->build_vars = wp_parse_args( $this->build_vars, $this->build_vars_default );
841
+ }
842
+ }
includes/class.field_type.php CHANGED
@@ -1,445 +1,462 @@
1
- <?php
2
- /**
3
- * Field Types
4
- * Stores properties for a specific field
5
- * @package Simple Lightbox
6
- * @subpackage Fields
7
- * @author Archetyped
8
- */
9
- class SLB_Field_Type extends SLB_Field_Base {
10
- /* Properties */
11
-
12
- /**
13
- * @var array Array of Field types that make up current Field type
14
- */
15
- var $elements = array();
16
-
17
- /**
18
- * @var array Field type layouts
19
- */
20
- var $layout = array();
21
-
22
- /**
23
- * @var SLB_Field_Type Parent field type (reference)
24
- */
25
- var $parent = null;
26
-
27
- /**
28
- * Object that field is in
29
- * @var SLB_Field|SLB_Field_Type|SLB_Field_Collection
30
- */
31
- var $container = null;
32
-
33
- /**
34
- * Object that called field
35
- * Used to determine field hierarchy/nesting
36
- * @var SLB_Field|SLB_Field_Type|SLB_Field_Collection
37
- */
38
- var $caller = null;
39
-
40
- function __construct($id = '', $parent = null) {
41
- $args = func_get_args();
42
- $defaults = $this->integrate_id($id);
43
- if ( !is_array($parent) )
44
- $defaults['parent'] = $parent;
45
-
46
- $props = $this->make_properties($args, $defaults);
47
- parent::__construct($props);
48
- }
49
-
50
- /* Getters/Setters */
51
-
52
- /**
53
- * Search for specified member value in field's container object (if exists)
54
- * @param string $member Name of object member to search (e.g. properties, layout, etc.)
55
- * @param string $name Value to retrieve from member
56
- * @return mixed Member value if found (Default: empty string)
57
- */
58
- function get_container_value($member, $name = '', $default = '') {
59
- $container =& $this->get_container();
60
- return $this->get_object_value($container, $member, $name, $default, 'container');
61
- }
62
-
63
- /**
64
- * Search for specified member value in field's container object (if exists)
65
- * @param string $member Name of object member to search (e.g. properties, layout, etc.)
66
- * @param string $name Value to retrieve from member
67
- * @return mixed Member value if found (Default: empty string)
68
- */
69
- function get_caller_value($member, $name = '', $default = '') {
70
- $caller =& $this->get_caller();
71
- return $this->get_object_value($caller, $member, $name, $default, 'caller');
72
- }
73
-
74
- /**
75
- * Sets reference to container object of current field
76
- * Reference is cleared if no valid object is passed to method
77
- * @param object $container
78
- */
79
- function set_container(&$container) {
80
- if ( !empty($container) && is_object($container) ) {
81
- // Set as param as container for current field
82
- $this->container =& $container;
83
- } else {
84
- // Clear container member if argument is invalid
85
- $this->clear_container();
86
- }
87
- }
88
-
89
- /**
90
- * Clears reference to container object of current field
91
- */
92
- function clear_container() {
93
- $this->container = null;
94
- }
95
-
96
- /**
97
- * Retrieves reference to container object of current field
98
- * @return object Reference to container object
99
- */
100
- function &get_container() {
101
- $ret = null;
102
- if ( $this->has_container() )
103
- $ret =& $this->container;
104
- return $ret;
105
- }
106
-
107
- /**
108
- * Checks if field has a container reference
109
- * @return bool TRUE if field is contained, FALSE otherwise
110
- */
111
- function has_container() {
112
- return !empty($this->container);
113
- }
114
-
115
- /**
116
- * Sets reference to calling object of current field
117
- * Any existing reference is cleared if no valid object is passed to method
118
- * @param object $caller Calling object
119
- */
120
- function set_caller(&$caller) {
121
- if ( !empty($caller) && is_object($caller) )
122
- $this->caller =& $caller;
123
- else
124
- $this->clear_caller();
125
- }
126
-
127
- /**
128
- * Clears reference to calling object of current field
129
- */
130
- function clear_caller() {
131
- unset($this->caller);
132
- }
133
-
134
- /**
135
- * Retrieves reference to caller object of current field
136
- * @return object Reference to caller object
137
- */
138
- function &get_caller() {
139
- $ret = null;
140
- if ( $this->has_caller() )
141
- $ret =& $this->caller;
142
- return $ret;
143
- }
144
-
145
- /**
146
- * Checks if field has a caller reference
147
- * @return bool TRUE if field is called by another field, FALSE otherwise
148
- */
149
- function has_caller() {
150
- return !empty($this->caller);
151
- }
152
-
153
- /**
154
- * Sets an element for the field type
155
- * @param string $name Name of element
156
- * @param SLB_Field_Type $type Reference of field type to use for element
157
- * @param array $properties Properties for element (passed as keyed associative array)
158
- * @param string $id_prop Name of property to set $name to (e.g. ID, etc.)
159
- */
160
- function set_element($name, $type, $properties = array(), $id_prop = 'id') {
161
- $name = trim(strval($name));
162
- if ( empty($name) )
163
- return false;
164
- // Create new field for element
165
- $el = new SLB_Field($name, $type);
166
- // Set container to current field instance
167
- $el->set_container($this);
168
- // Add properties to element
169
- $el->set_properties($properties);
170
- // Save element to current instance
171
- $this->elements[$name] =& $el;
172
- }
173
-
174
- /**
175
- * Add a layout to the field
176
- * @param string $name Name of layout
177
- * @param string $value Layout text
178
- */
179
- function set_layout($name, $value = '') {
180
- if ( !is_string($name) )
181
- return false;
182
- $name = trim($name);
183
- $this->layout[$name] = $value;
184
- return true;
185
- }
186
-
187
- /**
188
- * Retrieve specified layout
189
- * @param string $name Layout name
190
- * @param bool $parse_nested (optional) Whether nested layouts should be expanded in retreived layout or not (Default: TRUE)
191
- * @return string Specified layout text
192
- */
193
- function get_layout($name = 'form', $parse_nested = true) {
194
- // Retrieve specified layout (use $name value if no layout by that name exists)
195
- if ( empty($name) )
196
- $name = $this->get_container_value('build_vars', 'layout', 'form');
197
- $layout = $this->get_member_value('layout', $name, $name);
198
-
199
- // Find all nested layouts in current layout
200
- if ( !empty($layout) && !!$parse_nested ) {
201
- $ph = $this->get_placeholder_defaults();
202
-
203
- while ($ph->match = $this->parse_layout($layout, $ph->pattern_layout)) {
204
- // Iterate through the different types of layout placeholders
205
- foreach ($ph->match as $tag => $instances) {
206
- // Iterate through instances of a specific type of layout placeholder
207
- foreach ($instances as $instance) {
208
- // Get nested layout
209
- $nested_layout = $this->get_member_value($instance);
210
-
211
- // Replace layout placeholder with retrieved item data
212
- if ( !empty($nested_layout) )
213
- $layout = str_replace($ph->start . $instance['match'] . $ph->end, $nested_layout, $layout);
214
- }
215
- }
216
- }
217
- }
218
-
219
- return $layout;
220
- }
221
-
222
- /**
223
- * Checks if specified layout exists
224
- * Finds layout if it exists in current object or any of its parents
225
- * @param string $layout Name of layout to check for
226
- * @return bool TRUE if layout exists, FALSE otherwise
227
- */
228
- function has_layout($layout) {
229
- $ret = false;
230
- if ( is_string($layout) && ($layout = trim($layout)) && !empty($layout) ) {
231
- $layout = $this->get_member_value('layout', $layout, false);
232
- if ( $layout !== false )
233
- $ret = true;
234
- }
235
-
236
- return $ret;
237
- }
238
-
239
- /**
240
- * Checks if layout content is valid
241
- * Layouts need to have placeholders to be valid
242
- * @param string $layout_content Layout content (markup)
243
- * @return bool TRUE if layout is valid, FALSE otherwise
244
- */
245
- function is_valid_layout($layout_content) {
246
- $ph = $this->get_placeholder_defaults();
247
- return preg_match($ph->pattern_general, $layout_content);
248
- }
249
-
250
- /**
251
- * Parse field layout with a regular expression
252
- * @param string $layout Layout data
253
- * @param string $search Regular expression pattern to search layout for
254
- * @return array Associative array containing all of the regular expression matches in the layout data
255
- * Array Structure:
256
- * root => placeholder tags
257
- * => Tag instances (array)
258
- * 'tag' => (string) tag name
259
- * 'match' => (string) placeholder match
260
- * 'attributes' => (array) attributes
261
- */
262
- function parse_layout($layout, $search) {
263
- $parse_match = '';
264
- $result = [];
265
-
266
- // Find all nested layouts in layout.
267
- $match_value = preg_match_all( $search, $layout, $parse_match, PREG_PATTERN_ORDER );
268
-
269
- // Stop if no matches found.
270
- if ( ! $match_value ) {
271
- return $result;
272
- }
273
-
274
- /* Process matches */
275
-
276
- $ph_xml = '';
277
- $ph_root_tag = 'ph_root_element';
278
- $ph_start_xml = '<';
279
- $ph_end_xml = ' />';
280
- $ph_wrap_start = '<' . $ph_root_tag . '>';
281
- $ph_wrap_end = '</' . $ph_root_tag . '>';
282
- $parse_result = [];
283
-
284
- // Get all matched elements.
285
- $parse_match = $parse_match[1];
286
-
287
- // Build XML string from placeholders.
288
- foreach ( $parse_match as $ph ) {
289
- $ph_xml .= $ph_start_xml . $ph . $ph_end_xml . ' ';
290
- }
291
- $ph_xml = $ph_wrap_start . $ph_xml . $ph_wrap_end;
292
- // Parse XML data.
293
- $ph_prs = xml_parser_create();
294
- xml_parser_set_option($ph_prs, XML_OPTION_SKIP_WHITE, 1);
295
- xml_parser_set_option($ph_prs, XML_OPTION_CASE_FOLDING, 0);
296
- $ph_parsed = xml_parse_into_struct($ph_prs, $ph_xml, $parse_result['values'], $parse_result['index']);
297
- xml_parser_free($ph_prs);
298
-
299
- // Stop if placeholder parsing failed.
300
- if ( ! $ph_parsed ) {
301
- return $result;
302
- }
303
-
304
- unset( $parse_result['index'][$ph_root_tag] );
305
-
306
- // Build structured array with all parsed data.
307
- $ph_default = [
308
- 'tag' => '',
309
- 'match' => '',
310
- 'attributes' => [],
311
- ];
312
-
313
- // Build structured array.
314
- foreach ( $parse_result['index'] as $tag => $instances ) {
315
- // Create container for instances of current placeholder.
316
- $result[ $tag ] = [];
317
- // Process placeholder instances.
318
- foreach ( $instances as $instance ) {
319
- // Skip instance if it doesn't exist in parse results.
320
- if ( !isset( $parse_result['values'][ $instance ] ) ) {
321
- continue;
322
- }
323
- // Stop processing instance if a previously-saved instance with the same options already exists.
324
- foreach ( $result[ $tag ] as $tag_match ) {
325
- if ( $tag_match['match'] == $parse_match[ $instance - 1 ] ) {
326
- continue 2;
327
- }
328
- }
329
- $instance_parsed = $parse_result['values'][ $instance ];
330
- // Init instance data array.
331
- $instance_data = $ph_default;
332
-
333
- // Set tag.
334
- $instance_data['tag'] = $instance_parsed['tag'];
335
-
336
- // Set attributes.
337
- if ( isset( $instance_parsed['attributes'] ) && is_array( $instance_parsed['attributes'] ) ) {
338
- $instance_data['attributes'] = $instance_parsed['attributes'];
339
- }
340
-
341
- // Add match to array.
342
- $instance_data['match'] = $parse_match[ $instance - 1 ];
343
-
344
- // Add to result array.
345
- $result[ $tag ][] = $instance_data;
346
- }
347
- }
348
-
349
- return $result;
350
- }
351
-
352
- /**
353
- * Retrieves default properties to use when evaluating layout placeholders
354
- * @return object Object with properties for evaluating layout placeholders
355
- */
356
- function get_placeholder_defaults() {
357
- $ph = new stdClass();
358
- $ph->start = '{';
359
- $ph->end = '}';
360
- $ph->reserved = array('ref' => 'ref_base');
361
- $ph->pattern_general = '/' . $ph->start . '([a-zA-Z0-9_].*?)' . $ph->end . '/i';
362
- $ph->pattern_layout = '/' . $ph->start . '([a-zA-Z0-9].*?\s+' . $ph->reserved['ref'] . '="layout.*?".*?)' . $ph->end . '/i';
363
- return $ph;
364
- }
365
-
366
- /**
367
- * Build item output
368
- * @param string $layout (optional) Layout to build
369
- * @param string $data Data to pass to layout
370
- */
371
- function build($layout = null, $data = null) {
372
- $this->util->do_action_ref_array('build_pre', array($this));
373
- echo $this->build_layout($layout, $data);
374
- $this->util->do_action_ref_array('build_post', array($this));
375
- }
376
-
377
- /**
378
- * Builds HTML for a field based on its properties
379
- * @param string $layout (optional) Name of layout to build
380
- * @param array $data Additional data for current item
381
- */
382
- function build_layout($layout = 'form', $data = null) {
383
- $out_default = '';
384
- // Get base layout
385
- $out = $this->get_layout($layout);
386
- // Only parse valid layouts
387
- if ( $this->is_valid_layout($out) ) {
388
- $out = $this->process_placeholders( $out, $layout, $data );
389
- } else {
390
- $out = $out_default;
391
- }
392
- /* Return generated value */
393
- $out = $this->format_final($out);
394
- return $out;
395
- }
396
-
397
- /**
398
- * Processes placeholders in a string.
399
- *
400
- * Finds and replaces placeholders in a string to their full values.
401
- *
402
- * @since 2.8.0
403
- *
404
- * @param string $str String with placeholders to replace.
405
- * @param string $layout Optional. Name of layout being built.
406
- * @param array $data Optional. Additional data for current item.
407
- * @return string Original text with placeholders converted to full values.
408
- */
409
- public function process_placeholders( $str, $layout = 'form', $data = null ) {
410
- // Parse Layout.
411
- $ph = $this->get_placeholder_defaults();
412
-
413
- // Search layout for placeholders.
414
- while ( $ph->match = $this->parse_layout( $str, $ph->pattern_general ) ) {
415
- // Iterate through placeholders (tag, id, etc.)
416
- foreach ( $ph->match as $tag => $instances ) {
417
- // Iterate through instances of current placeholder
418
- foreach ( $instances as $instance ) {
419
- // Process value based on placeholder name.
420
- $target_property = $this->util->apply_filters_ref_array( "process_placeholder_${tag}", [ '', $this, &$instance, $layout, $data ], false );
421
- // Process value using default processors (if necessary).
422
- if ( '' === $target_property ) {
423
- $target_property = $this->util->apply_filters_ref_array( 'process_placeholder', [ $target_property, $this, &$instance, $layout, $data ], false );
424
- }
425
- // Format output.
426
- if ( ! is_null( $target_property ) ) {
427
- $context = ( isset( $instance['attributes']['context'] ) ) ? $instance['attributes']['context'] : '';
428
- // Handle special characters.
429
- $target_property = $this->preserve_special_chars( $target_property, $context );
430
- // Context-specific formatting.
431
- $target_property = $this->format( $target_property, $context );
432
- }
433
-
434
- // Clear value if value not a string
435
- if ( !is_scalar( $target_property ) ) {
436
- $target_property = '';
437
- }
438
- // Replace layout placeholder with retrieved item data
439
- $str = str_replace( $ph->start . $instance['match'] . $ph->end, $target_property, $str );
440
- }
441
- }
442
- }
443
- return $str;
444
- }
445
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Field Types
4
+ * Stores properties for a specific field
5
+ * @package Simple Lightbox
6
+ * @subpackage Fields
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Field_Type extends SLB_Field_Base {
10
+ /* Properties */
11
+
12
+ /**
13
+ * @var array Array of Field types that make up current Field type
14
+ */
15
+ public $elements = array();
16
+
17
+ /**
18
+ * @var array Field type layouts
19
+ */
20
+ public $layout = array();
21
+
22
+ /**
23
+ * @var SLB_Field_Type Parent field type (reference)
24
+ */
25
+ public $parent = null;
26
+
27
+ /**
28
+ * Object that field is in
29
+ * @var SLB_Field|SLB_Field_Type|SLB_Field_Collection
30
+ */
31
+ public $container = null;
32
+
33
+ /**
34
+ * Object that called field
35
+ * Used to determine field hierarchy/nesting
36
+ * @var SLB_Field|SLB_Field_Type|SLB_Field_Collection
37
+ */
38
+ public $caller = null;
39
+
40
+ function __construct( $id = '', $parent = null ) {
41
+ $args = func_get_args();
42
+ $defaults = $this->integrate_id( $id );
43
+ if ( ! is_array( $parent ) ) {
44
+ $defaults['parent'] = $parent;
45
+ }
46
+
47
+ $props = $this->make_properties( $args, $defaults );
48
+ parent::__construct( $props );
49
+ }
50
+
51
+ /* Getters/Setters */
52
+
53
+ /**
54
+ * Search for specified member value in field's container object (if exists)
55
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
56
+ * @param string $name Value to retrieve from member
57
+ * @return mixed Member value if found (Default: empty string)
58
+ */
59
+ function get_container_value( $member, $name = '', $default = '' ) {
60
+ $container =& $this->get_container();
61
+ return $this->get_object_value( $container, $member, $name, $default, 'container' );
62
+ }
63
+
64
+ /**
65
+ * Search for specified member value in field's container object (if exists)
66
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
67
+ * @param string $name Value to retrieve from member
68
+ * @return mixed Member value if found (Default: empty string)
69
+ */
70
+ function get_caller_value( $member, $name = '', $default = '' ) {
71
+ $caller =& $this->get_caller();
72
+ return $this->get_object_value( $caller, $member, $name, $default, 'caller' );
73
+ }
74
+
75
+ /**
76
+ * Sets reference to container object of current field
77
+ * Reference is cleared if no valid object is passed to method
78
+ * @param object $container
79
+ */
80
+ function set_container( &$container ) {
81
+ if ( ! empty( $container ) && is_object( $container ) ) {
82
+ // Set as param as container for current field
83
+ $this->container =& $container;
84
+ } else {
85
+ // Clear container member if argument is invalid
86
+ $this->clear_container();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Clears reference to container object of current field
92
+ */
93
+ function clear_container() {
94
+ $this->container = null;
95
+ }
96
+
97
+ /**
98
+ * Retrieves reference to container object of current field
99
+ * @return object Reference to container object
100
+ */
101
+ function &get_container() {
102
+ $ret = null;
103
+ if ( $this->has_container() ) {
104
+ $ret =& $this->container;
105
+ }
106
+ return $ret;
107
+ }
108
+
109
+ /**
110
+ * Checks if field has a container reference
111
+ * @return bool TRUE if field is contained, FALSE otherwise
112
+ */
113
+ function has_container() {
114
+ return ! empty( $this->container );
115
+ }
116
+
117
+ /**
118
+ * Sets reference to calling object of current field
119
+ * Any existing reference is cleared if no valid object is passed to method
120
+ * @param object $caller Calling object
121
+ */
122
+ function set_caller( &$caller ) {
123
+ if ( ! empty( $caller ) && is_object( $caller ) ) {
124
+ $this->caller =& $caller;
125
+ } else {
126
+ $this->clear_caller();
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Clears reference to calling object of current field
132
+ */
133
+ function clear_caller() {
134
+ unset( $this->caller );
135
+ }
136
+
137
+ /**
138
+ * Retrieves reference to caller object of current field
139
+ * @return object Reference to caller object
140
+ */
141
+ function &get_caller() {
142
+ $ret = null;
143
+ if ( $this->has_caller() ) {
144
+ $ret =& $this->caller;
145
+ }
146
+ return $ret;
147
+ }
148
+
149
+ /**
150
+ * Checks if field has a caller reference
151
+ * @return bool TRUE if field is called by another field, FALSE otherwise
152
+ */
153
+ function has_caller() {
154
+ return ! empty( $this->caller );
155
+ }
156
+
157
+ /**
158
+ * Sets an element for the field type
159
+ * @param string $name Name of element
160
+ * @param SLB_Field_Type $type Reference of field type to use for element
161
+ * @param array $properties Properties for element (passed as keyed associative array)
162
+ * @param string $id_prop Name of property to set $name to (e.g. ID, etc.)
163
+ */
164
+ function set_element( $name, $type, $properties = array(), $id_prop = 'id' ) {
165
+ $name = trim( strval( $name ) );
166
+ if ( empty( $name ) ) {
167
+ return false;
168
+ }
169
+ // Create new field for element
170
+ $el = new SLB_Field( $name, $type );
171
+ // Set container to current field instance
172
+ $el->set_container( $this );
173
+ // Add properties to element
174
+ $el->set_properties( $properties );
175
+ // Save element to current instance
176
+ $this->elements[ $name ] =& $el;
177
+ }
178
+
179
+ /**
180
+ * Add a layout to the field
181
+ * @param string $name Name of layout
182
+ * @param string $value Layout text
183
+ */
184
+ function set_layout( $name, $value = '' ) {
185
+ if ( ! is_string( $name ) ) {
186
+ return false;
187
+ }
188
+ $name = trim( $name );
189
+ $this->layout[ $name ] = $value;
190
+ return true;
191
+ }
192
+
193
+ /**
194
+ * Retrieve specified layout
195
+ * @param string $name Layout name
196
+ * @param bool $parse_nested (optional) Whether nested layouts should be expanded in retreived layout or not (Default: TRUE)
197
+ * @return string Specified layout text
198
+ */
199
+ function get_layout( $name = 'form', $parse_nested = true ) {
200
+ // Retrieve specified layout (use $name value if no layout by that name exists)
201
+ if ( empty( $name ) ) {
202
+ $name = $this->get_container_value( 'build_vars', 'layout', 'form' );
203
+ }
204
+ $layout = $this->get_member_value( 'layout', $name, $name );
205
+
206
+ // Find all nested layouts in current layout
207
+ if ( ! empty( $layout ) && ! ! $parse_nested ) {
208
+ $ph = $this->get_placeholder_defaults();
209
+ // Check layout for placeholders.
210
+ $ph->match = $this->parse_layout( $layout, $ph->pattern_layout );
211
+ while ( ! empty( $ph->match ) ) {
212
+ // Iterate through the different types of layout placeholders
213
+ foreach ( $ph->match as $tag => $instances ) {
214
+ // Iterate through instances of a specific type of layout placeholder
215
+ foreach ( $instances as $instance ) {
216
+ // Get nested layout
217
+ $nested_layout = $this->get_member_value( $instance );
218
+
219
+ if ( empty( $nested_layout ) ) {
220
+ continue;
221
+ }
222
+
223
+ // Replace layout placeholder with retrieved item data.
224
+ $layout = str_replace( $ph->start . $instance['match'] . $ph->end, $nested_layout, $layout );
225
+ }
226
+ }
227
+ // Check layout for placeholders.
228
+ $ph->match = $this->parse_layout( $layout, $ph->pattern_layout );
229
+ }
230
+ }
231
+
232
+ return $layout;
233
+ }
234
+
235
+ /**
236
+ * Checks if specified layout exists.
237
+ *
238
+ * Finds layout if it exists in current object or any of its parents.
239
+ *
240
+ * @param string $layout Name of layout to check for.
241
+ * @return bool True if layout exists, False otherwise.
242
+ */
243
+ function has_layout( $layout ) {
244
+ if ( is_string( $layout ) && ! empty( trim( $layout ) ) ) {
245
+ return false;
246
+ }
247
+ $layout = $this->get_member_value( 'layout', trim( $layout ), false );
248
+ return ( false !== $layout );
249
+ }
250
+
251
+ /**
252
+ * Checks if layout content is valid
253
+ * Layouts need to have placeholders to be valid
254
+ * @param string $layout_content Layout content (markup)
255
+ * @return bool TRUE if layout is valid, FALSE otherwise
256
+ */
257
+ function is_valid_layout( $layout_content ) {
258
+ $ph = $this->get_placeholder_defaults();
259
+ return preg_match( $ph->pattern_general, $layout_content );
260
+ }
261
+
262
+ /**
263
+ * Parse field layout with a regular expression
264
+ * @param string $layout Layout data
265
+ * @param string $search Regular expression pattern to search layout for
266
+ * @return array Associative array containing all of the regular expression matches in the layout data
267
+ * Array Structure:
268
+ * root => placeholder tags
269
+ * => Tag instances (array)
270
+ * 'tag' => (string) tag name
271
+ * 'match' => (string) placeholder match
272
+ * 'attributes' => (array) attributes
273
+ */
274
+ function parse_layout( $layout, $search ) {
275
+ $parse_match = '';
276
+ $result = [];
277
+
278
+ // Find all nested layouts in layout.
279
+ $match_value = preg_match_all( $search, $layout, $parse_match, PREG_PATTERN_ORDER );
280
+
281
+ // Stop if no matches found.
282
+ if ( ! $match_value ) {
283
+ return $result;
284
+ }
285
+
286
+ /* Process matches */
287
+
288
+ $ph_xml = '';
289
+ $ph_root_tag = 'ph_root_element';
290
+ $ph_start_xml = '<';
291
+ $ph_end_xml = ' />';
292
+ $ph_wrap_start = '<' . $ph_root_tag . '>';
293
+ $ph_wrap_end = '</' . $ph_root_tag . '>';
294
+ $parse_result = [];
295
+
296
+ // Get all matched elements.
297
+ $parse_match = $parse_match[1];
298
+
299
+ // Build XML string from placeholders.
300
+ foreach ( $parse_match as $ph ) {
301
+ $ph_xml .= $ph_start_xml . $ph . $ph_end_xml . ' ';
302
+ }
303
+ $ph_xml = $ph_wrap_start . $ph_xml . $ph_wrap_end;
304
+ // Parse XML data.
305
+ $ph_prs = xml_parser_create();
306
+ xml_parser_set_option( $ph_prs, XML_OPTION_SKIP_WHITE, 1 );
307
+ xml_parser_set_option( $ph_prs, XML_OPTION_CASE_FOLDING, 0 );
308
+ $ph_parsed = xml_parse_into_struct( $ph_prs, $ph_xml, $parse_result['values'], $parse_result['index'] );
309
+ xml_parser_free( $ph_prs );
310
+
311
+ // Stop if placeholder parsing failed.
312
+ if ( ! $ph_parsed ) {
313
+ return $result;
314
+ }
315
+
316
+ unset( $parse_result['index'][ $ph_root_tag ] );
317
+
318
+ // Build structured array with all parsed data.
319
+ $ph_default = [
320
+ 'tag' => '',
321
+ 'match' => '',
322
+ 'attributes' => [],
323
+ ];
324
+
325
+ // Build structured array.
326
+ foreach ( $parse_result['index'] as $tag => $instances ) {
327
+ // Create container for instances of current placeholder.
328
+ $result[ $tag ] = [];
329
+ // Process placeholder instances.
330
+ foreach ( $instances as $instance ) {
331
+ // Skip instance if it doesn't exist in parse results.
332
+ if ( ! isset( $parse_result['values'][ $instance ] ) ) {
333
+ continue;
334
+ }
335
+ // Stop processing instance if a previously-saved instance with the same options already exists.
336
+ foreach ( $result[ $tag ] as $tag_match ) {
337
+ if ( $tag_match['match'] === $parse_match[ $instance - 1 ] ) {
338
+ continue 2;
339
+ }
340
+ }
341
+ $instance_parsed = $parse_result['values'][ $instance ];
342
+ // Init instance data array.
343
+ $instance_data = $ph_default;
344
+
345
+ // Set tag.
346
+ $instance_data['tag'] = $instance_parsed['tag'];
347
+
348
+ // Set attributes.
349
+ if ( isset( $instance_parsed['attributes'] ) && is_array( $instance_parsed['attributes'] ) ) {
350
+ $instance_data['attributes'] = $instance_parsed['attributes'];
351
+ }
352
+
353
+ // Add match to array.
354
+ $instance_data['match'] = $parse_match[ $instance - 1 ];
355
+
356
+ // Add to result array.
357
+ $result[ $tag ][] = $instance_data;
358
+ }
359
+ }
360
+
361
+ return $result;
362
+ }
363
+
364
+ /**
365
+ * Retrieves default properties to use when evaluating layout placeholders
366
+ * @return object Object with properties for evaluating layout placeholders
367
+ */
368
+ function get_placeholder_defaults() {
369
+ $ph = new stdClass();
370
+ $ph->start = '{';
371
+ $ph->end = '}';
372
+ $ph->reserved = array( 'ref' => 'ref_base' );
373
+ $ph->pattern_general = '/' . $ph->start . '([a-zA-Z0-9_].*?)' . $ph->end . '/i';
374
+ $ph->pattern_layout = '/' . $ph->start . '([a-zA-Z0-9].*?\s+' . $ph->reserved['ref'] . '="layout.*?".*?)' . $ph->end . '/i';
375
+ return $ph;
376
+ }
377
+
378
+ /**
379
+ * Build item output
380
+ * @param string $layout (optional) Layout to build
381
+ * @param string $data Data to pass to layout
382
+ */
383
+ function build( $layout = null, $data = null ) {
384
+ $this->util->do_action_ref_array( 'build_pre', array( $this ) );
385
+ echo $this->build_layout( $layout, $data );
386
+ $this->util->do_action_ref_array( 'build_post', array( $this ) );
387
+ }
388
+
389
+ /**
390
+ * Builds HTML for a field based on its properties
391
+ * @param string $layout (optional) Name of layout to build
392
+ * @param array $data Additional data for current item
393
+ */
394
+ function build_layout( $layout = 'form', $data = null ) {
395
+ $out_default = '';
396
+ // Get base layout
397
+ $out = $this->get_layout( $layout );
398
+ // Only parse valid layouts
399
+ if ( $this->is_valid_layout( $out ) ) {
400
+ $out = $this->process_placeholders( $out, $layout, $data );
401
+ } else {
402
+ $out = $out_default;
403
+ }
404
+ /* Return generated value */
405
+ $out = $this->format_final( $out );
406
+ return $out;
407
+ }
408
+
409
+ /**
410
+ * Processes placeholders in a string.
411
+ *
412
+ * Finds and replaces placeholders in a string to their full values.
413
+ *
414
+ * @since 2.8.0
415
+ *
416
+ * @param string $str String with placeholders to replace.
417
+ * @param string $layout Optional. Name of layout being built.
418
+ * @param array $data Optional. Additional data for current item.
419
+ * @return string Original text with placeholders converted to full values.
420
+ */
421
+ public function process_placeholders( $str, $layout = 'form', $data = null ) {
422
+ // Parse Layout.
423
+ $ph = $this->get_placeholder_defaults();
424
+
425
+ // Check layout for placeholders.
426
+ $ph->match = $this->parse_layout( $str, $ph->pattern_general );
427
+
428
+ // Parse placeholders in layout.
429
+ while ( ! empty( $ph->match ) ) {
430
+ // Iterate through placeholders (tag, id, etc.)
431
+ foreach ( $ph->match as $tag => $instances ) {
432
+ // Iterate through instances of current placeholder
433
+ foreach ( $instances as $instance ) {
434
+ // Process value based on placeholder name.
435
+ $target_property = $this->util->apply_filters_ref_array( "process_placeholder_${tag}", [ '', $this, &$instance, $layout, $data ], false );
436
+ // Process value using default processors (if necessary).
437
+ if ( '' === $target_property ) {
438
+ $target_property = $this->util->apply_filters_ref_array( 'process_placeholder', [ $target_property, $this, &$instance, $layout, $data ], false );
439
+ }
440
+ // Format output.
441
+ if ( ! is_null( $target_property ) ) {
442
+ $context = ( isset( $instance['attributes']['context'] ) ) ? $instance['attributes']['context'] : '';
443
+ // Handle special characters.
444
+ $target_property = $this->preserve_special_chars( $target_property, $context );
445
+ // Context-specific formatting.
446
+ $target_property = $this->format( $target_property, $context );
447
+ }
448
+
449
+ // Clear value if value not a string
450
+ if ( ! is_scalar( $target_property ) ) {
451
+ $target_property = '';
452
+ }
453
+ // Replace layout placeholder with retrieved item data
454
+ $str = str_replace( $ph->start . $instance['match'] . $ph->end, $target_property, $str );
455
+ }
456
+ }
457
+ // Check layout for placeholders.
458
+ $ph->match = $this->parse_layout( $str, $ph->pattern_general );
459
+ }
460
+ return $str;
461
+ }
462
+ }
includes/class.fields.php CHANGED
@@ -1,446 +1,518 @@
1
- <?php
2
- /**
3
- * Collection of default system-wide fields
4
- * @package Simple Lightbox
5
- * @subpackage Fields
6
- * @author Archetyped
7
- *
8
- */
9
- class SLB_Fields extends SLB_Field_Collection {
10
-
11
- var $item_type = 'SLB_Field_Type';
12
-
13
- /**
14
- * Placeholder handlers
15
- * @var array
16
- */
17
- var $placholders = null;
18
-
19
- /* Constructor */
20
-
21
- function __construct() {
22
- parent::__construct('fields');
23
- }
24
-
25
- protected function _hooks() {
26
- parent::_hooks();
27
- // Init fields
28
- add_action('init', $this->m('register_types'));
29
- // Init placeholders
30
- add_action('init', $this->m('register_placeholders'));
31
- }
32
-
33
- /* Field Types */
34
-
35
- /**
36
- * Initialize fields
37
- */
38
- function register_types() {
39
- /* Field Types */
40
-
41
- // Base
42
- $base = new SLB_Field_Type('base');
43
- $base->set_description(__('Default Element', 'simple-lightbox'));
44
- $base->set_property('tag', 'span');
45
- $base->set_property('class', '', 'attr');
46
- $base->set_layout('form_attr', '{tag} name="{field_name}" id="{field_id}" {properties ref_base="root" group="attr"}');
47
- $base->set_layout('form', '<{form_attr ref_base="layout"} />');
48
- $base->set_layout('label', '<label for="{field_id}">{label}</label>');
49
- $base->set_layout('display', '{data context="display"}');
50
- $this->add($base);
51
-
52
- // Base closed
53
- $base_closed = new SLB_Field_Type('base_closed');
54
- $base_closed->set_parent('base');
55
- $base_closed->set_description(__('Default Element (Closed Tag)', 'simple-lightbox'));
56
- $base_closed->set_layout('form_start', '<{tag} id="{field_id}" name="{field_name}" {properties ref_base="root" group="attr"}>');
57
- $base_closed->set_layout('form_end', '</{tag}>');
58
- $base_closed->set_layout('form', '{form_start ref_base="layout"}{data}{form_end ref_base="layout"}');
59
- $this->add($base_closed);
60
-
61
- // Input
62
- $input = new SLB_Field_Type('input', 'base');
63
- $input->set_description(__('Default Input Element', 'simple-lightbox'));
64
- $input->set_property('tag', 'input');
65
- $input->set_property('type', 'text', 'attr');
66
- $input->set_property('value', '{data}', 'attr');
67
- $this->add($input);
68
-
69
- // Text input
70
- $text = new SLB_Field_Type('text', 'input');
71
- $text->set_description(__('Text Box', 'simple-lightbox'));
72
- $text->set_property('size', 15, 'attr');
73
- $text->set_property('label');
74
- $text->set_layout('form', '{label ref_base="layout"} {inherit}');
75
- $this->add($text);
76
-
77
- // Checkbox
78
- $cb = new SLB_Field_Type('checkbox', 'input');
79
- $cb->set_property('type', 'checkbox');
80
- $cb->set_property('value', null);
81
- $cb->set_layout('form_attr', '{inherit} {checked}');
82
- $cb->set_layout('form', '{label ref_base="layout"} <{form_attr ref_base="layout"} />');
83
- $this->add($cb);
84
-
85
- // Textarea
86
- $ta = new SLB_Field_Type('textarea', 'base_closed');
87
- $ta->set_property('tag', 'textarea');
88
- $ta->set_property('cols', 40, 'attr');
89
- $ta->set_property('rows', 3, 'attr');
90
- $this->add($ta);
91
-
92
- // Rich Text
93
- $rt = new SLB_Field_Type('richtext', 'textarea');
94
- $rt->set_property('class', 'theEditor {inherit}');
95
- $rt->set_layout('form', '<div class="rt_container">{inherit}</div>');
96
- $rt->add_action('admin_print_footer_scripts', 'wp_tiny_mce', 25);
97
- $this->add($rt);
98
-
99
- // Hidden
100
- $hidden = new SLB_Field_Type('hidden');
101
- $hidden->set_parent('input');
102
- $hidden->set_description(__('Hidden Field', 'simple-lightbox'));
103
- $hidden->set_property('type', 'hidden');
104
- $this->add($hidden);
105
-
106
- // Select
107
- $select = new SLB_Field_Type('select', 'base_closed');
108
- $select->set_description(__('Select tag', 'simple-lightbox'));
109
- $select->set_property('tag', 'select');
110
- $select->set_property('tag_option', 'option');
111
- $select->set_property('options', array());
112
- $select->set_layout('form', '{label ref_base="layout"} {form_start ref_base="layout"}{option_loop ref_base="layout"}{form_end ref_base="layout"}');
113
- $select->set_layout('option_loop', '{loop data="properties.options" layout="option" layout_data="option_data"}');
114
- $select->set_layout('option', '<{tag_option} value="{data_ext id="option_value" context="attr"}">{data_ext id="option_text" context="text"}</{tag_option}>');
115
- $select->set_layout('option_data', '<{tag_option} value="{data_ext id="option_value" context="attr"}" selected="selected">{data_ext id="option_text" context="text"}</{tag_option}>');
116
- $this->add($select);
117
-
118
- // Span
119
- $span = new SLB_Field_Type('span', 'base_closed');
120
- $span->set_description(__('Inline wrapper', 'simple-lightbox'));
121
- $span->set_property('tag', 'span');
122
- $span->set_property('value', 'Hello there!');
123
- $this->add($span);
124
-
125
- // Enable plugins to modify (add, remove, etc.) field types
126
- $this->util->do_action_ref_array('register_fields', array($this), false);
127
-
128
- // Signal completion of field registration
129
- $this->util->do_action_ref_array('fields_registered', array($this), false);
130
- }
131
-
132
- /* Placeholder handlers */
133
-
134
- function register_placeholders() {
135
- // Default placeholder handlers
136
- $this->register_placeholder('all', $this->m('process_placeholder_default'), 11);
137
- $this->register_placeholder('field_id', $this->m('process_placeholder_id'));
138
- $this->register_placeholder('field_name', $this->m('process_placeholder_name'));
139
- $this->register_placeholder('data', $this->m('process_placeholder_data'));
140
- $this->register_placeholder('data_ext',$this->m('process_placeholder_data_ext'));
141
- $this->register_placeholder('loop', $this->m('process_placeholder_loop'));
142
- $this->register_placeholder('label', $this->m('process_placeholder_label'));
143
- $this->register_placeholder('checked', $this->m('process_placeholder_checked'));
144
-
145
- // Allow other code to register placeholders
146
- $this->util->do_action_ref_array('register_field_placeholders', array($this), false);
147
-
148
- // Signal completion of field placeholder registration
149
- $this->util->do_action_ref_array('field_placeholders_registered', array($this), false);
150
- }
151
-
152
- /**
153
- * Register a function to handle a placeholder
154
- * Multiple handlers may be registered for a single placeholder
155
- * Adds filter hook to WP for handling specified placeholder
156
- * Placeholders are in layouts and are replaced with data at runtime
157
- * @uses add_filter()
158
- * @param string $placeholder Name of placeholder to add handler for (Using 'all' will set the function as a handler for all placeholders
159
- * @param callback $callback Function to set as a handler
160
- * @param int $priority (optional) Priority of handler
161
- * @return void
162
- */
163
- function register_placeholder($placeholder, $callback, $priority = 10) {
164
- if ( 'all' == $placeholder )
165
- $placeholder = '';
166
- else
167
- $placeholder = '_' . $placeholder;
168
- $hook = $this->add_prefix('process_placeholder' . $placeholder);
169
- add_filter($hook, $callback, $priority, 5);
170
- }
171
-
172
- /**
173
- * Default placeholder processing
174
- * To be executed when current placeholder has not been handled by another handler
175
- * @param string $output Value to be used in place of placeholder
176
- * @param SLB_Field $item Field containing placeholder
177
- * @param array $placeholder Current placeholder
178
- * @see SLB_Field::parse_layout for structure of $placeholder array
179
- * @param string $layout Layout to build
180
- * @param array $data Extended data for item
181
- * @return string Value to use in place of current placeholder
182
- */
183
- function process_placeholder_default($output, $item, $placeholder, $layout, $data) {
184
- // Validate parameters before processing
185
- if ( empty($output) && ($item instanceof SLB_Field_Type) && is_array($placeholder) ) {
186
- // Build path to replacement data
187
- $output = $item->get_member_value($placeholder);
188
-
189
- // Check if value is group (properties, etc.)
190
- // All groups must have additional attributes (beyond reserved attributes) that define how items in group are used
191
- if (is_array($output)
192
- && !empty($placeholder['attributes'])
193
- && is_array($placeholder['attributes'])
194
- && ($ph = $item->get_placeholder_defaults())
195
- && $attribs = array_diff(array_keys($placeholder['attributes']), array_values($ph->reserved))
196
- ) {
197
- /* Targeted property is an array, but the placeholder contains additional options on how property is to be used */
198
-
199
- // Find items matching criteria in $output
200
- // Check for group criteria
201
- if ( 'properties' == $placeholder['tag'] && ($prop_group = $item->get_group($placeholder['attributes']['group'])) && !empty($prop_group) ) {
202
- /* Process group */
203
- $group_out = array();
204
- // Iterate through properties in group and build string.
205
- foreach ( array_keys( $prop_group ) as $prop_key ) {
206
- $prop_val = $item->get_property( $prop_key );
207
- if ( !is_null( $prop_val ) ) {
208
- // Process placeholders.
209
- $prop_val = $item->process_placeholders( $prop_val, $layout, $data );
210
- // Add property to attribute string output.
211
- $group_out[] = esc_attr( $prop_key ) . '="' . esc_attr( $prop_val ) . '"';
212
- }
213
- }
214
- $output = implode(' ', $group_out);
215
- }
216
- } elseif ( is_object($output) && ($output instanceof $item->base_class) ) {
217
- /* Targeted property is actually a nested item */
218
- // Set caller to current item
219
- $output->set_caller($item);
220
- // Build layout for nested element
221
- $output = $output->build_layout($layout);
222
- }
223
- }
224
-
225
- return $output;
226
- }
227
-
228
- /**
229
- * Renders field ID formatted for a form field's `id` attribute.
230
- *
231
- * ID is formatted to be unique identifier for form field.
232
- * Example: `options_field_id`.
233
- * Registered as handler for `{field_id}` placeholder.
234
- *
235
- * @param string $output Placeholder's rendered value.
236
- * @param SLB_Field $item Field containing placeholder.
237
- * @param array &$placeholder Placeholder being processed.
238
- * @param string $layout Name of layout being built.
239
- * @param array $data Additional data for current field.
240
- * @return string Field's ID (formatted for a form field's `id` attribute).
241
- */
242
- function process_placeholder_id( $output, $item, &$placeholder, $layout, $data ) {
243
- // Get attributes
244
- $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_id'));
245
- $output = $item->get_id($args);
246
- // Set default placeholder context.
247
- if ( ! isset( $placeholder['attributes']['context'] ) ) {
248
- $placeholder['attributes']['context'] = 'attr';
249
- }
250
- return $output;
251
- }
252
-
253
- /**
254
- * Renders field ID formatted for a form field's `name` attribute.
255
- *
256
- * ID is formatted to be part of an associative array for processing form submission.
257
- * Example: `options[field_id]`.
258
- * Registered as handler for `{field_name}` placeholder.
259
- *
260
- * @param string $output Placeholder's rendered value.
261
- * @param SLB_Field $item Field containing placeholder.
262
- * @param array &$placeholder Placeholder being processed.
263
- * @param string $layout Name of layout being built.
264
- * @param array $data Additional data for current field.
265
- * @return string Field's ID (formatted for a form field's `name` attribute).
266
- */
267
- function process_placeholder_name($output, $item, &$placeholder, $layout, $data) {
268
- // Get attributes
269
- $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_name'));
270
- $output = $item->get_id($args);
271
- // Set default placeholder context.
272
- if ( ! isset( $placeholder['attributes']['context'] ) ) {
273
- $placeholder['attributes']['context'] = 'attr';
274
- }
275
- return $output;
276
- }
277
-
278
- /**
279
- * Build item label
280
- * @see SLB_Fields::process_placeholder_default for parameter descriptions
281
- * @return string Field label
282
- */
283
- function process_placeholder_label($output, $item, $placeholder, $layout, $data) {
284
- // Check if item has label property (e.g. sub-elements)
285
- $out = $item->get_property('label');
286
- // If property not set, use item title
287
- if ( empty($out) )
288
- $out = $item->get_title();
289
- return $out;
290
- }
291
-
292
- /**
293
- * Retrieve data for item
294
- * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
295
- * @return string Placeholder output
296
- */
297
- function process_placeholder_data($output, $item, $placeholder, $layout) {
298
- $opts = $placeholder['attributes'];
299
- // Strip context from data retrieval options (Formatting handled upstream).
300
- if ( is_array( $opts ) ) {
301
- unset( $opts['context'] );
302
- }
303
- // Get data
304
- $out = $item->get_data($opts);
305
- if ( ! is_null($out) ) {
306
- // Get specific member in value (e.g. value from a specific item element)
307
- if ( isset($opts['element']) && is_array($out) && ( $el = $opts['element'] ) && isset($out[$el]) )
308
- $out = $out[$el];
309
- }
310
-
311
- // Return data
312
- return $out;
313
- }
314
-
315
- /**
316
- * Set checked attribute on item
317
- * Evaluates item's data to see if item should be checked or not
318
- * @see SLB_Fields::process_placeholder_default for parameter descriptions
319
- * @return string Appropriate checkbox attribute
320
- */
321
- function process_placeholder_checked($output, $item, $placeholder, $layout, $data) {
322
- $out = '';
323
- $c = $item->get_container();
324
- $d = ( isset($c->data[$item->get_id()]) ) ? $c->data[$item->get_id()] : null;
325
- $item->set_property('d', true);
326
- if ( $item->get_data() )
327
- $out = 'checked="checked"';
328
- $item->set_property('d', false);
329
- return $out;
330
- }
331
-
332
- /**
333
- * Loops over data to build item output
334
- * Options:
335
- * data - Dot-delimited path in item that contains data to loop through
336
- * layout - Name of layout to use for each data item in loop
337
- * layout_data - Name of layout to use for data item that matches previously-saved item data
338
- * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
339
- * @return string Placeholder output
340
- */
341
- function process_placeholder_loop($output, $item, $placeholder, $layout, $data) {
342
- // Setup loop options
343
- $attr_defaults = array (
344
- 'layout' => '',
345
- 'layout_data' => null,
346
- 'data' => ''
347
- );
348
- $attr = wp_parse_args($placeholder['attributes'], $attr_defaults);
349
- if ( is_null($attr['layout_data']) )
350
- $attr['layout_data'] =& $attr['layout'];
351
- // Get data for loop
352
- $path = explode('.', $attr['data']);
353
- $loop_data = $item->get_member_value($path);
354
-
355
- // Check if data is callback
356
- if ( is_callable($loop_data) )
357
- $loop_data = call_user_func($loop_data);
358
-
359
- // Get item data
360
- $data = $item->get_data();
361
-
362
- // Iterate over data and build output
363
- $out = array();
364
- if ( is_array($loop_data) && !empty($loop_data) ) {
365
- foreach ( $loop_data as $value => $label ) {
366
- // Load appropriate layout based on item value
367
- $layout = ( ($data === 0 && $value === $data) xor $data == $value ) ? $attr['layout_data'] : $attr['layout'];
368
- // Stop processing if no valid layout is returned
369
- if ( empty($layout) )
370
- continue;
371
- // Prep extended item data
372
- $data_ext = array('option_value' => $value, 'option_text' => $label);
373
- $out[] = $item->build_layout($layout, $data_ext);
374
- }
375
- }
376
-
377
- // Return output
378
- return implode($out);
379
- }
380
-
381
- /**
382
- * Returns specified value from extended data array for item.
383
- *
384
- * @param string $output Value to be used in place of placeholder.
385
- * @param SLB_Field $item Field containing placeholder.
386
- * @param array $placeholder Current placeholder.
387
- * @see SLB_Field::parse_layout for structure of `$placeholder` array.
388
- * @param string $layout Name of layout being built.
389
- * @param array $data Extended data for item.
390
- *
391
- * @return string Processed value.
392
- */
393
- function process_placeholder_data_ext( $output, SLB_Field $item, array $placeholder, $layout, array $data ) {
394
- $key = ( isset( $placeholder['attributes']['id'] ) ) ? $placeholder['attributes']['id'] : false;
395
- if ( !! $key && isset( $data[ $key ] ) && is_scalar( $data[ $key ] ) ) {
396
- $output = strval( $data[ $key ] );
397
- }
398
-
399
- return $output;
400
- }
401
-
402
- /* Build */
403
-
404
- /**
405
- * Output items in a group
406
- * @param string $group ID of Group to output
407
- * @return string Group output
408
- * TODO Make compatible with parent::build_group()
409
- */
410
- function build_group($group) {
411
- $out = array();
412
- $classnames = (object) array(
413
- 'multi' => 'multi_field',
414
- 'single' => 'single_field',
415
- 'elements' => 'has_elements'
416
- );
417
-
418
- // Stop execution if group does not exist
419
- if ( $this->group_exists($group) && $group =& $this->get_group($group) ) {
420
- $group_items = ( count($group->items) > 1 ) ? $classnames->multi : $classnames->single . ( ( ( $fs = array_keys($group->items) ) && ( $f =& $group->items[$fs[0]] ) && ( $els = $f->get_member_value('elements', '', null) ) && !empty($els) ) ? '_' . $classnames->elements : '' );
421
- $classname = array($this->add_prefix('attributes_wrap'), $group_items);
422
- $out[] = '<div class="' . implode(' ', $classname) . '">'; // Wrap all items in group
423
-
424
- // Build layout for each item in group
425
- foreach ( array_keys($group->items) as $item_id ) {
426
- $item =& $group->items[$item_id];
427
- $item->set_caller($this);
428
- // Start item output
429
- $id = $this->add_prefix('field_' . $item->get_id());
430
- $out[] = '<div id="' . $id . '_wrap" class=' . $this->add_prefix('attribute_wrap') . '>';
431
- // Build item layout
432
- $out[] = $item->build_layout();
433
- // end item output
434
- $out[] = '</div>';
435
- $item->clear_caller();
436
- }
437
- $out[] = '</div>'; // Close items container
438
- // Add description if exists
439
- if ( !empty($group->description) )
440
- $out[] = '<p class=' . $this->add_prefix('group_description') . '>' . $group->description . '</p>';
441
- }
442
-
443
- // Return group output
444
- return implode($out);
445
- }
446
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Collection of default system-wide fields
4
+ * @package Simple Lightbox
5
+ * @subpackage Fields
6
+ * @author Archetyped
7
+ *
8
+ */
9
+ class SLB_Fields extends SLB_Field_Collection {
10
+
11
+ public $item_type = 'SLB_Field_Type';
12
+
13
+ /**
14
+ * Placeholder handlers
15
+ * @var array
16
+ */
17
+ public $placholders = null;
18
+
19
+ /* Constructor */
20
+
21
+ function __construct() {
22
+ parent::__construct( 'fields' );
23
+ }
24
+
25
+ protected function _hooks() {
26
+ parent::_hooks();
27
+ // Init fields
28
+ add_action( 'init', $this->m( 'register_types' ) );
29
+ // Init placeholders
30
+ add_action( 'init', $this->m( 'register_placeholders' ) );
31
+ }
32
+
33
+ /* Field Types */
34
+
35
+ /**
36
+ * Initialize fields
37
+ */
38
+ function register_types() {
39
+ /* Field Types */
40
+
41
+ // Base
42
+ $base = new SLB_Field_Type( 'base' );
43
+ $base->set_description( __( 'Default Element', 'simple-lightbox' ) );
44
+ $base->set_property( 'tag', 'span' );
45
+ $base->set_property( 'class', '', 'attr' );
46
+ $base->set_layout( 'form_attr', '{tag} name="{field_name}" id="{field_id}" {properties ref_base="root" group="attr"}' );
47
+ $base->set_layout( 'form', '<{form_attr ref_base="layout"} />' );
48
+ $base->set_layout( 'label', '<label for="{field_id}">{label}</label>' );
49
+ $base->set_layout( 'display', '{data context="display"}' );
50
+ $this->add( $base );
51
+
52
+ // Base closed
53
+ $base_closed = new SLB_Field_Type( 'base_closed' );
54
+ $base_closed->set_parent( 'base' );
55
+ $base_closed->set_description( __( 'Default Element (Closed Tag)', 'simple-lightbox' ) );
56
+ $base_closed->set_layout( 'form_start', '<{tag} id="{field_id}" name="{field_name}" {properties ref_base="root" group="attr"}>' );
57
+ $base_closed->set_layout( 'form_end', '</{tag}>' );
58
+ $base_closed->set_layout( 'form', '{form_start ref_base="layout"}{data}{form_end ref_base="layout"}' );
59
+ $this->add( $base_closed );
60
+
61
+ // Input
62
+ $input = new SLB_Field_Type( 'input', 'base' );
63
+ $input->set_description( __( 'Default Input Element', 'simple-lightbox' ) );
64
+ $input->set_property( 'tag', 'input' );
65
+ $input->set_property( 'type', 'text', 'attr' );
66
+ $input->set_property( 'value', '{data}', 'attr' );
67
+ $this->add( $input );
68
+
69
+ // Text input
70
+ $text = new SLB_Field_Type( 'text', 'input' );
71
+ $text->set_description( __( 'Text Box', 'simple-lightbox' ) );
72
+ $text->set_property( 'size', 15, 'attr' );
73
+ $text->set_property( 'label' );
74
+ $text->set_layout( 'form', '{label ref_base="layout"} {inherit}' );
75
+ $this->add( $text );
76
+
77
+ // Checkbox
78
+ $cb = new SLB_Field_Type( 'checkbox', 'input' );
79
+ $cb->set_property( 'type', 'checkbox' );
80
+ $cb->set_property( 'value', null );
81
+ $cb->set_layout( 'form_attr', '{inherit} {checked}' );
82
+ $cb->set_layout( 'form', '{label ref_base="layout"} <{form_attr ref_base="layout"} />' );
83
+ $this->add( $cb );
84
+
85
+ // Textarea
86
+ $ta = new SLB_Field_Type( 'textarea', 'base_closed' );
87
+ $ta->set_property( 'tag', 'textarea' );
88
+ $ta->set_property( 'cols', 40, 'attr' );
89
+ $ta->set_property( 'rows', 3, 'attr' );
90
+ $this->add( $ta );
91
+
92
+ // Rich Text
93
+ $rt = new SLB_Field_Type( 'richtext', 'textarea' );
94
+ $rt->set_property( 'class', 'theEditor {inherit}' );
95
+ $rt->set_layout( 'form', '<div class="rt_container">{inherit}</div>' );
96
+ $rt->add_action( 'admin_print_footer_scripts', 'wp_tiny_mce', 25 );
97
+ $this->add( $rt );
98
+
99
+ // Hidden
100
+ $hidden = new SLB_Field_Type( 'hidden' );
101
+ $hidden->set_parent( 'input' );
102
+ $hidden->set_description( __( 'Hidden Field', 'simple-lightbox' ) );
103
+ $hidden->set_property( 'type', 'hidden' );
104
+ $this->add( $hidden );
105
+
106
+ // Select
107
+ $select = new SLB_Field_Type( 'select', 'base_closed' );
108
+ $select->set_description( __( 'Select tag', 'simple-lightbox' ) );
109
+ $select->set_property( 'tag', 'select' );
110
+ $select->set_property( 'tag_option', 'option' );
111
+ $select->set_property( 'options', array() );
112
+ $select->set_layout( 'form', '{label ref_base="layout"} {form_start ref_base="layout"}{option_loop ref_base="layout"}{form_end ref_base="layout"}' );
113
+ $select->set_layout( 'option_loop', '{loop data="properties.options" layout="option" layout_data="option_data"}' );
114
+ $select->set_layout( 'option', '<{tag_option} value="{data_ext id="option_value" context="attr"}">{data_ext id="option_text" context="text"}</{tag_option}>' );
115
+ $select->set_layout( 'option_data', '<{tag_option} value="{data_ext id="option_value" context="attr"}" selected="selected">{data_ext id="option_text" context="text"}</{tag_option}>' );
116
+ $this->add( $select );
117
+
118
+ // Span
119
+ $span = new SLB_Field_Type( 'span', 'base_closed' );
120
+ $span->set_description( __( 'Inline wrapper', 'simple-lightbox' ) );
121
+ $span->set_property( 'tag', 'span' );
122
+ $span->set_property( 'value', 'Hello there!' );
123
+ $this->add( $span );
124
+
125
+ // Enable plugins to modify (add, remove, etc.) field types
126
+ $this->util->do_action_ref_array( 'register_fields', array( $this ), false );
127
+
128
+ // Signal completion of field registration
129
+ $this->util->do_action_ref_array( 'fields_registered', array( $this ), false );
130
+ }
131
+
132
+ /* Placeholder handlers */
133
+
134
+ function register_placeholders() {
135
+ // Default placeholder handlers
136
+ $this->register_placeholder( 'all', $this->m( 'process_placeholder_default' ), 11 );
137
+ $this->register_placeholder( 'field_id', $this->m( 'process_placeholder_id' ) );
138
+ $this->register_placeholder( 'field_name', $this->m( 'process_placeholder_name' ) );
139
+ $this->register_placeholder( 'data', $this->m( 'process_placeholder_data' ) );
140
+ $this->register_placeholder( 'data_ext', $this->m( 'process_placeholder_data_ext' ) );
141
+ $this->register_placeholder( 'loop', $this->m( 'process_placeholder_loop' ) );
142
+ $this->register_placeholder( 'label', $this->m( 'process_placeholder_label' ) );
143
+ $this->register_placeholder( 'checked', $this->m( 'process_placeholder_checked' ) );
144
+
145
+ // Allow other code to register placeholders
146
+ $this->util->do_action_ref_array( 'register_field_placeholders', array( $this ), false );
147
+
148
+ // Signal completion of field placeholder registration
149
+ $this->util->do_action_ref_array( 'field_placeholders_registered', array( $this ), false );
150
+ }
151
+
152
+ /**
153
+ * Register a function to handle a placeholder
154
+ * Multiple handlers may be registered for a single placeholder
155
+ * Adds filter hook to WP for handling specified placeholder
156
+ * Placeholders are in layouts and are replaced with data at runtime
157
+ * @uses add_filter()
158
+ * @param string $placeholder Name of placeholder to add handler for (Using 'all' will set the function as a handler for all placeholders
159
+ * @param callback $callback Function to set as a handler
160
+ * @param int $priority (optional) Priority of handler
161
+ * @return void
162
+ */
163
+ function register_placeholder( $placeholder, $callback, $priority = 10 ) {
164
+ if ( 'all' === $placeholder ) {
165
+ $placeholder = '';
166
+ } else {
167
+ $placeholder = '_' . $placeholder;
168
+ }
169
+ $hook = $this->add_prefix( 'process_placeholder' . $placeholder );
170
+ add_filter( $hook, $callback, $priority, 5 );
171
+ }
172
+
173
+ /**
174
+ * Handles default placeholder processing.
175
+ *
176
+ * Processes placeholders that have not been processed by another handler.
177
+ *
178
+ * @param string $output Value to be used in place of placeholder. Should be empty.
179
+ * @param SLB_Field $item Field containing placeholder.
180
+ * @param array $placeholder Current placeholder.
181
+ * @see SLB_Field::parse_layout for structure of `$placeholder` array.
182
+ * @param string $layout Layout to build.
183
+ * @param array $data Extended data for item.
184
+ * @return string Value to use in place of current placeholder.
185
+ */
186
+ function process_placeholder_default( $output, $item, $placeholder, $layout, $data ) {
187
+ // Validate parameters before processing.
188
+ if (
189
+ ! empty( $output )
190
+ || ( ! $item instanceof SLB_Field_Type )
191
+ || ! is_array( $placeholder )
192
+ ) {
193
+ return $output;
194
+ }
195
+
196
+ // Build path to replacement data.
197
+ $output = $item->get_member_value( $placeholder );
198
+
199
+ // Check if value is group (properties, etc.)
200
+ // All groups must have additional attributes (beyond reserved attributes) that define how items in group are used
201
+ if (
202
+ is_array( $output )
203
+ && ! empty( $placeholder['attributes'] )
204
+ && is_array( $placeholder['attributes'] )
205
+ && 'properties' === $placeholder['tag']
206
+ ) {
207
+ // Targeted property is an array.
208
+ // Placeholder contains additional options on how property is to be used.
209
+
210
+ // Find items matching criteria in $output
211
+ // Check for group criteria
212
+ $prop_group = $item->get_group( $placeholder['attributes']['group'] );
213
+ if ( ! empty( $prop_group ) ) {
214
+ /* Process group */
215
+ $group_out = array();
216
+ // Iterate through properties in group and build string.
217
+ foreach ( array_keys( $prop_group ) as $prop_key ) {
218
+ $prop_val = $item->get_property( $prop_key );
219
+ if ( is_null( $prop_val ) ) {
220
+ continue;
221
+ }
222
+ // Process placeholders.
223
+ $prop_val = $item->process_placeholders( $prop_val, $layout, $data );
224
+ // Add property to attribute string output.
225
+ $group_out[] = esc_attr( $prop_key ) . '="' . esc_attr( $prop_val ) . '"';
226
+ }
227
+ $output = implode( ' ', $group_out );
228
+ }
229
+ } elseif ( is_object( $output ) && ( $output instanceof $item->base_class ) ) {
230
+ /* Targeted property is actually a nested item */
231
+ // Set caller to current item
232
+ $output->set_caller( $item );
233
+ // Build layout for nested element
234
+ $output = $output->build_layout( $layout );
235
+ }
236
+
237
+ return $output;
238
+ }
239
+
240
+ /**
241
+ * Renders field ID formatted for a form field's `id` attribute.
242
+ *
243
+ * ID is formatted to be unique identifier for form field.
244
+ * Example: `options_field_id`.
245
+ * Registered as handler for `{field_id}` placeholder.
246
+ *
247
+ * @param string $output Placeholder's rendered value.
248
+ * @param SLB_Field $item Field containing placeholder.
249
+ * @param array &$placeholder Placeholder being processed.
250
+ * @param string $layout Name of layout being built.
251
+ * @param array $data Additional data for current field.
252
+ * @return string Field's ID (formatted for a form field's `id` attribute).
253
+ */
254
+ function process_placeholder_id( $output, $item, &$placeholder, $layout, $data ) {
255
+ // Get attributes
256
+ $args = wp_parse_args( $placeholder['attributes'], array( 'format' => 'attr_id' ) );
257
+ $output = $item->get_id( $args );
258
+ // Set default placeholder context.
259
+ if ( ! isset( $placeholder['attributes']['context'] ) ) {
260
+ $placeholder['attributes']['context'] = 'attr';
261
+ }
262
+ return $output;
263
+ }
264
+
265
+ /**
266
+ * Renders field ID formatted for a form field's `name` attribute.
267
+ *
268
+ * ID is formatted to be part of an associative array for processing form submission.
269
+ * Example: `options[field_id]`.
270
+ * Registered as handler for `{field_name}` placeholder.
271
+ *
272
+ * @param string $output Placeholder's rendered value.
273
+ * @param SLB_Field $item Field containing placeholder.
274
+ * @param array &$placeholder Placeholder being processed.
275
+ * @param string $layout Name of layout being built.
276
+ * @param array $data Additional data for current field.
277
+ * @return string Field's ID (formatted for a form field's `name` attribute).
278
+ */
279
+ function process_placeholder_name( $output, $item, &$placeholder, $layout, $data ) {
280
+ // Get attributes
281
+ $args = wp_parse_args( $placeholder['attributes'], array( 'format' => 'attr_name' ) );
282
+ $output = $item->get_id( $args );
283
+ // Set default placeholder context.
284
+ if ( ! isset( $placeholder['attributes']['context'] ) ) {
285
+ $placeholder['attributes']['context'] = 'attr';
286
+ }
287
+ return $output;
288
+ }
289
+
290
+ /**
291
+ * Build item label
292
+ * @see SLB_Fields::process_placeholder_default for parameter descriptions
293
+ * @return string Field label
294
+ */
295
+ function process_placeholder_label( $output, $item, $placeholder, $layout, $data ) {
296
+ // Check if item has label property (e.g. sub-elements)
297
+ $out = $item->get_property( 'label' );
298
+ // If property not set, use item title
299
+ if ( empty( $out ) ) {
300
+ $out = $item->get_title();
301
+ }
302
+ return $out;
303
+ }
304
+
305
+ /**
306
+ * Retrieve data for item
307
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
308
+ * @return string Placeholder output
309
+ */
310
+ function process_placeholder_data( $output, $item, $placeholder, $layout ) {
311
+ $opts = $placeholder['attributes'];
312
+ // Strip context from data retrieval options (Formatting handled upstream).
313
+ if ( is_array( $opts ) ) {
314
+ unset( $opts['context'] );
315
+ }
316
+ // Get data
317
+ $out = $item->get_data( $opts );
318
+ // Get specific member in value (e.g. value from a specific item element).
319
+ if (
320
+ is_array( $out )
321
+ && is_array( $opts )
322
+ && isset( $opts['element'] )
323
+ && isset( $out[ $opts['element'] ] )
324
+ ) {
325
+ $out = $out[ $opts['element'] ];
326
+ }
327
+
328
+ // Return data
329
+ return $out;
330
+ }
331
+
332
+ /**
333
+ * Set checked attribute on item
334
+ * Evaluates item's data to see if item should be checked or not
335
+ * @see SLB_Fields::process_placeholder_default for parameter descriptions
336
+ * @return string Appropriate checkbox attribute
337
+ */
338
+ function process_placeholder_checked( $output, $item, $placeholder, $layout, $data ) {
339
+ $out = '';
340
+ $c = $item->get_container();
341
+ $d = ( isset( $c->data[ $item->get_id() ] ) ) ? $c->data[ $item->get_id() ] : null;
342
+ $item->set_property( 'd', true );
343
+ if ( $item->get_data() ) {
344
+ $out = 'checked="checked"';
345
+ }
346
+ $item->set_property( 'd', false );
347
+ return $out;
348
+ }
349
+
350
+ /**
351
+ * Loops over data to build item output
352
+ * Options:
353
+ * data - Dot-delimited path in item that contains data to loop through
354
+ * layout - Name of layout to use for each data item in loop
355
+ * layout_data - Name of layout to use for data item that matches previously-saved item data
356
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
357
+ * @return string Placeholder output
358
+ */
359
+ function process_placeholder_loop( $output, $item, $placeholder, $layout, $data ) {
360
+ // Setup loop options
361
+ $attr_defaults = array(
362
+ 'layout' => '',
363
+ 'layout_data' => null,
364
+ 'data' => '',
365
+ );
366
+ $attr = wp_parse_args( $placeholder['attributes'], $attr_defaults );
367
+ if ( is_null( $attr['layout_data'] ) ) {
368
+ $attr['layout_data'] =& $attr['layout'];
369
+ }
370
+ // Get data for loop
371
+ $path = explode( '.', $attr['data'] );
372
+ $loop_data = $item->get_member_value( $path );
373
+
374
+ // Check if data is callback
375
+ if ( is_callable( $loop_data ) ) {
376
+ $loop_data = call_user_func( $loop_data );
377
+ }
378
+
379
+ // Get item data
380
+ $data = $item->get_data();
381
+
382
+ // Iterate over data and build output
383
+ $out = array();
384
+ if ( is_array( $loop_data ) && ! empty( $loop_data ) ) {
385
+ foreach ( $loop_data as $value => $label ) {
386
+ // Load appropriate layout based on item value
387
+ $layout = ( ( 0 === $data && $value === $data ) xor $data === $value ) ? $attr['layout_data'] : $attr['layout'];
388
+ // Stop processing if no valid layout is returned
389
+ if ( empty( $layout ) ) {
390
+ continue;
391
+ }
392
+ // Prep extended item data
393
+ $data_ext = array(
394
+ 'option_value' => $value,
395
+ 'option_text' => $label,
396
+ );
397
+ $out[] = $item->build_layout( $layout, $data_ext );
398
+ }
399
+ }
400
+
401
+ // Return output
402
+ return implode( $out );
403
+ }
404
+
405
+ /**
406
+ * Returns specified value from extended data array for item.
407
+ *
408
+ * @param string $output Value to be used in place of placeholder.
409
+ * @param SLB_Field $item Field containing placeholder.
410
+ * @param array $placeholder Current placeholder.
411
+ * @see SLB_Field::parse_layout for structure of `$placeholder` array.
412
+ * @param string $layout Name of layout being built.
413
+ * @param array $data Extended data for item.
414
+ *
415
+ * @return string Processed value.
416
+ */
417
+ function process_placeholder_data_ext( $output, SLB_Field $item, array $placeholder, $layout, array $data ) {
418
+ $key = ( isset( $placeholder['attributes']['id'] ) ) ? $placeholder['attributes']['id'] : false;
419
+ if ( ! ! $key && isset( $data[ $key ] ) && is_scalar( $data[ $key ] ) ) {
420
+ $output = strval( $data[ $key ] );
421
+ }
422
+
423
+ return $output;
424
+ }
425
+
426
+ /* Build */
427
+
428
+ /**
429
+ * Outputs items in a group.
430
+ *
431
+ * @param string $group ID of Group to output.
432
+ * @return string Group output.
433
+ * @todo Make compatible with parent::build_group()
434
+ */
435
+ function build_group( $group ) {
436
+ $out = array();
437
+
438
+ /**
439
+ * Renders group output as a string.
440
+ *
441
+ * @uses $out Array containing group output.
442
+ * @return string Group output.
443
+ */
444
+ $render_output = function() use ( $out ) {
445
+ // Combine output.
446
+ return implode( '', $out );
447
+ };
448
+
449
+ // Stop if group does not exist.
450
+ if ( ! $this->group_exists( $group ) ) {
451
+ return $render_output();
452
+ }
453
+
454
+ // Classnames.
455
+ $cls = (object) [
456
+ 'multi' => 'multi_field',
457
+ 'single' => 'single_field',
458
+ 'elements' => 'has_elements',
459
+ 'group_desc' => $this->add_prefix( 'group_description' ),
460
+ 'group_wrap' => $this->add_prefix( 'attributes_wrap' ),
461
+ 'item_wrap' => $this->add_prefix( 'attribute_wrap' ),
462
+ ];
463
+ // Templates.
464
+ $tpl = (object) [
465
+ 'container_start' => '<div class="%s">',
466
+ 'container_end' => '</div>',
467
+ 'item_start' => '<div id="%1$s_wrap" class="%2$s">',
468
+ 'item_end' => '</div>',
469
+ 'text_block' => '<p class="%1$s">%2$s</p>',
470
+ ];
471
+
472
+ // Process group.
473
+ $group = $this->get_group( $group );
474
+ $group_items = ( count( $group->items ) > 1 ) ? $cls->multi : $cls->single;
475
+ $fs = array_keys( $group->items );
476
+ $f =& $group->items[ $fs[0] ];
477
+ $els = $f->get_member_value( 'elements', '', null );
478
+
479
+ if ( ! empty( $els ) ) {
480
+ $group_items .= '_' . $cls->elements;
481
+ }
482
+
483
+ // Wrap items with container element.
484
+ $classname = array( $cls->group_wrap, $group_items );
485
+ $out[] = sprintf( $tpl->container_start, implode( ' ', $classname ) );
486
+
487
+ // Clear temp variables.
488
+ unset( $fs, $f, $els, $classname );
489
+
490
+ // Build layout for each item in group
491
+ foreach ( array_keys( $group->items ) as $item_id ) {
492
+ // Init item.
493
+ $item =& $group->items[ $item_id ];
494
+ $item->set_caller( $this );
495
+
496
+ // Start item output.
497
+ $id = $this->add_prefix( 'field_' . $item->get_id() );
498
+ $out[] = sprintf( $tpl->item_start, $id, $cls->item_wrap );
499
+ // Build item layout.
500
+ $out[] = $item->build_layout();
501
+ // End item output.
502
+ $out[] = $tpl->item_end;
503
+
504
+ // Cleanup.
505
+ $item->clear_caller();
506
+ unset( $item, $id );
507
+ }
508
+ // Close items container.
509
+ $out[] = $tpl->container_end;
510
+
511
+ // Add description if exists
512
+ if ( ! empty( $group->description ) ) {
513
+ $out[] = sprintf( $tpl->text_block, $cls->group_desc, $group->description );
514
+ }
515
+ // Render and return output.
516
+ return $render_output();
517
+ }
518
+ }
includes/class.option.php CHANGED
@@ -1,179 +1,185 @@
1
- <?php
2
- /**
3
- * Option object
4
- * @package Simple Lightbox
5
- * @subpackage Options
6
- * @author Archetyped
7
- */
8
- class SLB_Option extends SLB_Field {
9
-
10
- /* Properties */
11
-
12
- public $hook_prefix = 'option';
13
-
14
- /**
15
- * Determines whether option will be sent to client
16
- * @var bool
17
- */
18
- var $in_client = false;
19
-
20
- /**
21
- * Child mapping
22
- * @see SLB_Field_Base::map
23
- * @var array
24
- */
25
- var $map = array (
26
- 'default' => 'data',
27
- 'attr' => 'properties'
28
- );
29
-
30
- var $property_priority = array ('id', 'data', 'parent');
31
-
32
- /* Init */
33
-
34
- /**
35
- * @see SLB_Field::__construct()
36
- * @uses parent::__construct() to initialize instance
37
- * @param $id
38
- * @param $title
39
- * @param $default
40
- */
41
- function __construct($id, $title = '', $default = '') {
42
- // Normalize properties
43
- $args = func_get_args();
44
- $defaults = array ('title' => '', 'default' => '');
45
- $props = $this->make_properties($args, $defaults);
46
- // Validate
47
- if ( is_scalar($id) )
48
- $props['id'] = $id;
49
- if ( !is_string($props['title']) )
50
- $props['title'] = '';
51
- // Send to parent constructor
52
- parent::__construct($props);
53
- }
54
-
55
- /* Getters/Setters */
56
-
57
- /**
58
- * Retrieve default value for option
59
- * @return mixed Default option value
60
- */
61
- function get_default($context = '') {
62
- return $this->get_data($context, false);
63
- }
64
-
65
- /**
66
- * Sets parent based on default value
67
- */
68
- function set_parent($parent = null) {
69
- $p = $this->get_parent();
70
- if ( empty($parent) && empty($p) ) {
71
- $parent = 'text';
72
- $d = $this->get_default();
73
- if ( is_bool($d) )
74
- $parent = 'checkbox';
75
- $parent = 'option_' . $parent;
76
- } elseif ( !empty($p) && !is_object($p) ) {
77
- $parent =& $p;
78
- }
79
- parent::set_parent($parent);
80
- }
81
-
82
- /**
83
- * Set in_client property
84
- * @uses this::in_client
85
- * @param bool Whether or not option should be included in client output (Default: false)
86
- * @return void
87
- */
88
- function set_in_client($in_client = false) {
89
- $this->in_client = !!$in_client;
90
- }
91
-
92
- /**
93
- * Determines whether option should be included in client output
94
- * @uses this::in_client
95
- * @return bool TRUE if option is included in client output
96
- */
97
- function get_in_client() {
98
- return $this->in_client;
99
- }
100
-
101
- /* Formatting */
102
-
103
- /**
104
- * Format data as string for browser output
105
- * @see SLB_Field_Base::format()
106
- * @param mixed $value Data to format
107
- * @param string $context (optional) Current context
108
- * @return string Formatted value
109
- */
110
- function format_display($value, $context = '') {
111
- if ( !is_string($value) ) {
112
- if ( is_bool($value) ) {
113
- $value = ( $value ) ? __('Enabled', 'simple-lightbox') : __('Disabled', 'simple-lightbox');
114
- }
115
- elseif ( is_null($value) )
116
- $value = '';
117
- else
118
- $value = strval($value);
119
- } elseif ( empty($value) ) {
120
- $value = 'empty';
121
- }
122
- return htmlentities($value);
123
- }
124
-
125
- /**
126
- * Format data using same format as default value
127
- * @see SLB_Field_Base::format()
128
- * @param mixed $value Data to format
129
- * @param string $context (optional) Current context
130
- * @return mixed Formatted option value
131
- */
132
- function format_default($value, $context = '') {
133
- // Get default value
134
- $d = $this->get_default();
135
- if ( empty($d) )
136
- return $value;
137
- if ( is_bool($d) )
138
- $value = $this->format_bool($value);
139
- elseif ( is_string($d) )
140
- $value = $this->format_string($value);
141
- return $value;
142
- }
143
-
144
- /**
145
- * Format data as boolean (true/false)
146
- * @see SLB_Field_Base::format()
147
- * @param mixed $value Data to format
148
- * @param string $context (optional) Current context
149
- * @return bool Option value
150
- */
151
- function format_bool($value, $context = '') {
152
- if ( !is_bool($value) )
153
- $value = !!$value;
154
- return $value;
155
- }
156
-
157
- /**
158
- * Format data as string
159
- * @see SLB_Field_Base::format()
160
- * @param mixed $value Data to format
161
- * @param string $context (optional) Current context
162
- * @return string Option string value
163
- */
164
- function format_string($value, $context = '') {
165
- if ( is_bool($value) ) {
166
- $value = ( $value ) ? 'true' : 'false';
167
- }
168
- elseif ( is_object($value) ) {
169
- $value = get_class($value);
170
- }
171
- elseif ( is_array($value) ) {
172
- $value = implode(' ', $value);
173
- }
174
- else {
175
- $value = strval($value);
176
- }
177
- return $value;
178
- }
179
- }
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Option object
4
+ * @package Simple Lightbox
5
+ * @subpackage Options
6
+ * @author Archetyped
7
+ */
8
+ class SLB_Option extends SLB_Field {
9
+
10
+ /* Properties */
11
+
12
+ public $hook_prefix = 'option';
13
+
14
+ /**
15
+ * Determines whether option will be sent to client
16
+ * @var bool
17
+ */
18
+ public $in_client = false;
19
+
20
+ /**
21
+ * Child mapping
22
+ * @see SLB_Field_Base::map
23
+ * @var array
24
+ */
25
+ public $map = array(
26
+ 'default' => 'data',
27
+ 'attr' => 'properties',
28
+ );
29
+
30
+ public $property_priority = array( 'id', 'data', 'parent' );
31
+
32
+ /* Init */
33
+
34
+ /**
35
+ * @see SLB_Field::__construct()
36
+ * @uses parent::__construct() to initialize instance
37
+ * @param $id
38
+ * @param $title
39
+ * @param $default
40
+ */
41
+ function __construct( $id, $title = '', $default = '' ) {
42
+ // Normalize properties
43
+ $args = func_get_args();
44
+ $defaults = array(
45
+ 'title' => '',
46
+ 'default' => '',
47
+ );
48
+ $props = $this->make_properties( $args, $defaults );
49
+ // Validate
50
+ if ( is_scalar( $id ) ) {
51
+ $props['id'] = $id;
52
+ }
53
+ if ( ! is_string( $props['title'] ) ) {
54
+ $props['title'] = '';
55
+ }
56
+ // Send to parent constructor
57
+ parent::__construct( $props );
58
+ }
59
+
60
+ /* Getters/Setters */
61
+
62
+ /**
63
+ * Retrieve default value for option
64
+ * @return mixed Default option value
65
+ */
66
+ function get_default( $context = '' ) {
67
+ return $this->get_data( $context, false );
68
+ }
69
+
70
+ /**
71
+ * Sets parent based on default value
72
+ */
73
+ function set_parent( $parent = null ) {
74
+ $p = $this->get_parent();
75
+ if ( empty( $parent ) && empty( $p ) ) {
76
+ $parent = 'text';
77
+ $d = $this->get_default();
78
+ if ( is_bool( $d ) ) {
79
+ $parent = 'checkbox';
80
+ }
81
+ $parent = 'option_' . $parent;
82
+ } elseif ( ! empty( $p ) && ! is_object( $p ) ) {
83
+ $parent =& $p;
84
+ }
85
+ parent::set_parent( $parent );
86
+ }
87
+
88
+ /**
89
+ * Set in_client property
90
+ * @uses this::in_client
91
+ * @param bool Whether or not option should be included in client output (Default: false)
92
+ * @return void
93
+ */
94
+ function set_in_client( $in_client = false ) {
95
+ $this->in_client = ! ! $in_client;
96
+ }
97
+
98
+ /**
99
+ * Determines whether option should be included in client output
100
+ * @uses this::in_client
101
+ * @return bool TRUE if option is included in client output
102
+ */
103
+ function get_in_client() {
104
+ return $this->in_client;
105
+ }
106
+
107
+ /* Formatting */
108
+
109
+ /**
110
+ * Format data as string for browser output
111
+ * @see SLB_Field_Base::format()
112
+ * @param mixed $value Data to format
113
+ * @param string $context (optional) Current context
114
+ * @return string Formatted value
115
+ */
116
+ function format_display( $value, $context = '' ) {
117
+ if ( ! is_string( $value ) ) {
118
+ if ( is_bool( $value ) ) {
119
+ $value = ( $value ) ? __( 'Enabled', 'simple-lightbox' ) : __( 'Disabled', 'simple-lightbox' );
120
+ } elseif ( is_null( $value ) ) {
121
+ $value = '';
122
+ } else {
123
+ $value = strval( $value );
124
+ }
125
+ } elseif ( empty( $value ) ) {
126
+ $value = 'empty';
127
+ }
128
+ return htmlentities( $value );
129
+ }
130
+
131
+ /**
132
+ * Format data using same format as default value
133
+ * @see SLB_Field_Base::format()
134
+ * @param mixed $value Data to format
135
+ * @param string $context (optional) Current context
136
+ * @return mixed Formatted option value
137
+ */
138
+ function format_default( $value, $context = '' ) {
139
+ // Get default value
140
+ $d = $this->get_default();
141
+ if ( empty( $d ) ) {
142
+ return $value;
143
+ }
144
+ if ( is_bool( $d ) ) {
145
+ $value = $this->format_bool( $value );
146
+ } elseif ( is_string( $d ) ) {
147
+ $value = $this->format_string( $value );
148
+ }
149
+ return $value;
150
+ }
151
+
152
+ /**
153
+ * Format data as boolean (true/false)
154
+ * @see SLB_Field_Base::format()
155
+ * @param mixed $value Data to format
156
+ * @param string $context (optional) Current context
157
+ * @return bool Option value
158
+ */
159
+ function format_bool( $value, $context = '' ) {
160
+ if ( ! is_bool( $value ) ) {
161
+ $value = ! ! $value;
162
+ }
163
+ return $value;
164
+ }
165
+
166
+ /**
167
+ * Format data as string
168
+ * @see SLB_Field_Base::format()
169
+ * @param mixed $value Data to format
170
+ * @param string $context (optional) Current context
171
+ * @return string Option string value
172
+ */
173
+ function format_string( $value, $context = '' ) {
174
+ if ( is_bool( $value ) ) {
175
+ $value = ( $value ) ? 'true' : 'false';
176
+ } elseif ( is_object( $value ) ) {
177
+ $value = get_class( $value );
178
+ } elseif ( is_array( $value ) ) {
179
+ $value = implode( ' ', $value );
180
+ } else {
181
+ $value = strval( $value );
182
+ }
183
+ return $value;
184
+ }
185
+ }
includes/class.options.php CHANGED
@@ -1,679 +1,696 @@
1
- <?php
2
- /**
3
- * Options collection
4
- * @package Simple Lightbox
5
- * @subpackage Options
6
- * @author Archetyped
7
- * @uses SLB_Field_Collection
8
- */
9
- class SLB_Options extends SLB_Field_Collection {
10
-
11
- /* Properties */
12
-
13
- public $hook_prefix = 'options';
14
-
15
- var $item_type = 'SLB_Option';
16
-
17
- /**
18
- * Key for saving version to DB
19
- * @var string
20
- */
21
- private $version_key = 'version';
22
-
23
- /**
24
- * Whether version has been checked
25
- * @var bool
26
- */
27
- var $version_checked = false;
28
-
29
- var $items_migrated = false;
30
-
31
- var $build_vars = array (
32
- 'validate_pre' => false,
33
- 'validate_post' => false,
34
- 'save_pre' => false,
35
- 'save_post' => false
36
- );
37
-
38
- /* Init */
39
-
40
- function __construct($id = '', $props = array()) {
41
- // Validate arguments
42
- $args = func_get_args();
43
- // Set default ID
44
- if ( !$this->validate_id($id) ) {
45
- $id = 'options';
46
- }
47
- $defaults = $this->integrate_id($id);
48
- $props = $this->make_properties($args, $defaults);
49
- parent::__construct($props);
50
- $this->add_prefix_ref($this->version_key);
51
- }
52
-
53
- protected function _hooks() {
54
- parent::_hooks();
55
- // Register fields
56
- $this->util->add_action('register_fields', $this->m('register_fields'), 10, 1, false);
57
- // Set option parents
58
- $this->util->add_action('fields_registered', $this->m('set_parents'), 10, 1, false);
59
- // Building
60
- $this->util->add_action('build_init', $this->m('build_init'));
61
- // Admin
62
- $this->util->add_action('admin_page_render_content', $this->m('admin_page_render_content'), 10, 3, false);
63
- $this->util->add_filter('admin_action_reset', $this->m('admin_action_reset'), 10, 3, false);
64
- }
65
-
66
- /* Legacy/Migration */
67
-
68
- /**
69
- * Checks whether new version has been installed and migrates necessary settings
70
- * @uses $version_key as option name
71
- * @uses get_option() to retrieve saved version number
72
- * @uses SLB_Utilities::get_plugin_version() to retrieve current version
73
- * @return bool TRUE if version has been changed
74
- */
75
- function check_update() {
76
- if ( !$this->version_checked ) {
77
- $this->version_checked = true;
78
- $version_changed = false;
79
- // Get version from DB
80
- $vo = $this->get_version();
81
- // Get current version
82
- $vn = $this->util->get_plugin_version();
83
- // Compare versions
84
- if ( $vo != $vn ) {
85
- // Update saved version
86
- $this->set_version($vn);
87
- // Migrate old version to new version
88
- if ( strcasecmp($vo, $vn) < 0 ) {
89
- // Force full migration
90
- $version_changed = true;
91
- }
92
- }
93
- // Migrate
94
- $this->migrate($version_changed);
95
- }
96
-
97
- return $this->version_checked;
98
- }
99
-
100
- /**
101
- * Save plugin version to DB
102
- * If no version supplied, will fetch plugin data to determine version
103
- * @uses $version_key as option name
104
- * @uses update_option() to save version to options table
105
- * @param string $ver (optional) Plugin version
106
- */
107
- function set_version($ver = null) {
108
- if ( empty($ver) ) {
109
- $ver = $this->util->get_plugin_version();
110
- }
111
- return update_option($this->version_key, $ver);
112
- }
113
-
114
- /**
115
- * Retrieve saved version data
116
- * @return string Saved version
117
- */
118
- function get_version() {
119
- return get_option($this->version_key, '');
120
- }
121
-
122
- /**
123
- * Migrate options from old versions to current version
124
- * @uses self::items_migrated to determine if simple migration has been performed in current request or not
125
- * @uses self::save() to save data after migration
126
- * @param bool $full Whether to perform a full migration or not (Default: No)
127
- */
128
- function migrate($full = false) {
129
- if ( !$full && $this->items_migrated )
130
- return false;
131
-
132
- // Legacy options
133
- $d = null;
134
- $this->load_data();
135
-
136
- $items = $this->get_items();
137
-
138
- // Migrate separate options to unified option
139
- if ( $full ) {
140
- foreach ( $items as $opt => $props ) {
141
- $oid = $this->add_prefix($opt);
142
- $o = get_option($oid, $d);
143
- if ( $o !== $d ) {
144
- // Migrate value to data array
145
- $this->set_data($opt, $o, false);
146
- // Delete legacy option
147
- delete_option($oid);
148
- }
149
- }
150
- }
151
-
152
- // Migrate legacy items
153
- if ( is_array($this->properties_init) && isset($this->properties_init['legacy']) && is_array($this->properties_init['legacy']) ) {
154
- $l =& $this->properties_init['legacy'];
155
- // Normalize legacy map
156
- foreach ( $l as $opt => $dest ) {
157
- if ( !is_array($dest) ) {
158
- if ( is_string($dest) )
159
- $l[$opt] = array($dest);
160
- else
161
- unset($l[$opt]);
162
- }
163
- }
164
-
165
- /* Separate options */
166
- if ( $full ) {
167
- foreach ( $l as $opt => $dest ) {
168
- $oid = $this->add_prefix($opt);
169
- $o = get_option($oid, $d);
170
- // Only migrate valid values
171
- if ( $o !== $d ) {
172
- // Process destinations
173
- foreach ( $dest as $id ) {
174
- $this->set_data($id, $o, false, true);
175
- }
176
- }
177
- // Remove legacy option
178
- delete_option($oid);
179
- }
180
- }
181
-
182
- /* Simple Migration (Internal options only) */
183
-
184
- // Get existing items that are also legacy items
185
- $opts = array_intersect_key($this->get_data(), $l);
186
- foreach ( $opts as $opt => $val ) {
187
- $d = $this->get_data($opt);
188
- // Migrate data from old option to new option
189
- $dest = $l[$opt];
190
- // Validate new options to send data to
191
- foreach ( $dest as $id ) {
192
- $this->set_data($id, $d, false, true);
193
- }
194
- // Remove legacy option
195
- $this->remove($opt, false);
196
- }
197
- }
198
- // Save changes
199
- $this->save();
200
- // Set flag
201
- $this->items_migrated = true;
202
- }
203
-
204
- /* Option setup */
205
-
206
- /**
207
- * Get elements for creating fields
208
- * @return obj
209
- */
210
- function get_field_elements() {
211
- static $o = null;
212
- if ( empty($o) ) {
213
- $o = new stdClass();
214
- /* Layout */
215
- $layout = new stdClass();
216
- $layout->label = '<label for="{field_id}" class="title block">{label}</label>';
217
- $layout->label_ref = '{label ref_base="layout"}';
218
- $layout->field_pre = '<div class="input block">';
219
- $layout->field_post = '</div>';
220
- $layout->opt_pre = '<div class="' . $this->add_prefix('option_item') . '">';
221
- $layout->opt_post = '</div>';
222
- $layout->form = '<{form_attr ref_base="layout"} /> <span class="description">(' . __('Default', 'simple-lightbox') . ': {data context="display" top="0"})</span>';
223
- /* Combine */
224
- $o->layout =& $layout;
225
- }
226
- return $o;
227
- }
228
-
229
- /**
230
- * Register option-specific fields
231
- * @param SLB_Fields $fields Reference to global fields object
232
- * @return void
233
- */
234
- function register_fields($fields) {
235
- // Layouts
236
- $o = $this->get_field_elements();
237
- $l =& $o->layout;
238
-
239
- $form = implode('', array (
240
- $l->opt_pre,
241
- $l->label_ref,
242
- $l->field_pre,
243
- $l->form,
244
- $l->field_post,
245
- $l->opt_post
246
- ));
247
-
248
- // Text input
249
- $otxt = new SLB_Field_Type('option_text', 'text');
250
- $otxt->set_property('class', '{inherit} code');
251
- $otxt->set_property('size', null);
252
- $otxt->set_property('value', '{data}');
253
- $otxt->set_layout('label', $l->label);
254
- $otxt->set_layout('form', $form);
255
- $fields->add($otxt);
256
-
257
- // Checkbox
258
- $ocb = new SLB_Field_Type('option_checkbox', 'checkbox');
259
- $ocb->set_layout('label', $l->label);
260
- $ocb->set_layout('field_reference', sprintf( '<input type="hidden" name="%s" value="{field_name format="raw"}" />', "{$this->get_id('formatted')}_items[]" ) );
261
- $ocb->set_layout('form', '{field_reference ref_base="layout"}' . $form);
262
- $fields->add($ocb);
263
-
264
- // Select
265
- $othm = new SLB_Field_Type('option_select', 'select');
266
- $othm->set_layout('label', $l->label);
267
- $othm->set_layout('form_start', $l->field_pre . '{inherit}');
268
- $othm->set_layout('form_end', '{inherit}' . $l->field_post);
269
- $othm->set_layout('form', $l->opt_pre . '{inherit}' . $l->opt_post);
270
- $fields->add($othm);
271
- }
272
-
273
- /**
274
- * Set parent field types for options
275
- * Parent only set for Admin pages
276
- * @uses SLB_Option::set_parent() to set parent field for each option item
277
- * @uses is_admin() to determine if current request is admin page
278
- * @param object $fields Collection of default field types
279
- * @return void
280
- */
281
- function set_parents($fields) {
282
- if ( !is_admin() )
283
- return false;
284
- $items = &$this->get_items();
285
- foreach ( array_keys($items) as $opt ) {
286
- $items[$opt]->set_parent();
287
- }
288
- foreach ( $this->items as $opt ) {
289
- $p = $opt->parent;
290
- if ( is_object($p) )
291
- $p = 'o:' . $p->id;
292
- }
293
- }
294
-
295
- /* Processing */
296
-
297
- /**
298
- * Validates option data.
299
- *
300
- * Validates option values (e.g. prior to saving to DB, after form submission, etc.).
301
- * Values are formatted and sanitized according to corresponding option's data type
302
- * (e.g. boolean, string, number, etc.).
303
- *
304
- * @since 1.5.5
305
- *
306
- * @param array<string,scalar> $values Optional. Option data to validate.
307
- * Indexed by option ID.
308
- * Default form-submission data used.
309
- * @return array<string,scalar> Validated data. Indexed by option ID.
310
- */
311
- function validate( $values = null ) {
312
- /** @var array<string,scalar> $values_valid Validated option data. Indexed by option ID. */
313
- $values_valid = [];
314
- // Enforce values data type.
315
- if ( ! is_array( $values ) ) {
316
- /** @var array<string,scalar> $values */
317
- $values = [];
318
- }
319
- /**
320
- * Generates query variable using common base.
321
- *
322
- * @since 2.8.0
323
- *
324
- * @param string $text Optional. Text to append to base.
325
- *
326
- * @return string Query variable name. Format: "{base}_{text}". Default "{base}".
327
- */
328
- $qv = function ( $text = '' ) {
329
- static $base;
330
- // Get base.
331
- if ( empty( $base ) ) {
332
- $base = $this->get_id( 'formatted' );
333
- }
334
- $out = $base;
335
- // Append text to base.
336
- if ( is_string( $text ) && ! empty( $text ) ) {
337
- $out .= "_{$text}";
338
- }
339
- return $out;
340
- };
341
- // Get options form field group ID.
342
- $qvar = $qv();
343
- // Use form submission data when no values provided.
344
- if ( empty( $values ) && isset( $_POST[ $qvar ] ) && check_admin_referer( $qvar, $qv( 'nonce' ) ) ) {
345
- /** @var array<string,scalar> $values */
346
- $values = $_POST[ $qvar ];
347
- // Append non-submitted, but rendered fields (e.g. unchecked checkboxes)
348
- $qvar_items = $qv( 'items' );
349
- /** @var string[] $items_bool Boolean options rendered in submitted form. */
350
- $items_bool = ( isset( $_POST[ $qvar_items ] ) ) ? $_POST[ $qvar_items ] : null;
351
- if ( ! empty( $items_bool ) && is_array( $items_bool) ) {
352
- foreach ( $items_bool as $item_id ) {
353
- // Add missing boolean options (false == unchecked).
354
- if ( ! array_key_exists( $item_id, $values ) && $this->has( $item_id ) && is_bool( $this->get_default( $item_id ) ) ) {
355
- $values_valid[ $item_id ] = false;
356
- }
357
- }
358
- }
359
- unset( $qvar, $qvar_items, $items_bool, $item_id );
360
- }
361
- // Process values.
362
- /**
363
- * @var string $id Option ID.
364
- * @var mixed $val Option value (raw/unsanitized).
365
- */
366
- foreach ( $values as $id => $val ) {
367
- // Do not process invalid option IDs or invalid (non-scalar) data.
368
- if ( ! $this->has( $id ) || ! is_scalar( $val ) ) {
369
- continue;
370
- }
371
- // Conform to option's data type and sanitize.
372
- /** @var scalar $d Option's default data. */
373
- $d = $this->get_default( $id );
374
- // Boolean.
375
- if ( is_bool( $d ) ) {
376
- $val = !! $val;
377
- }
378
- // Numeric - do not process non-numeric values for int/float fields.
379
- elseif ( ( is_int( $d ) || is_float( $d ) ) && ! is_numeric( $val ) ) {
380
- continue;
381
- }
382
- // Integer.
383
- elseif ( is_int( $d ) ) {
384
- $val = (int) $val;
385
- }
386
- // Float.
387
- elseif ( is_float( $d ) ) {
388
- $val = (float) $val;
389
- }
390
- // Defaut: Handle as string.
391
- else {
392
- $val = sanitize_text_field( wp_unslash( $val ) );
393
- }
394
- // Add to validated data.
395
- $values_valid[ $id ] = $val;
396
- }
397
- unset( $id, $val );
398
-
399
- // Return validated values.
400
- return $values_valid;
401
- }
402
-
403
- /* Data */
404
-
405
- /**
406
- * Retrieve options from database
407
- * @uses get_option to retrieve option data
408
- * @return array Options data
409
- */
410
- function fetch_data($sanitize = true) {
411
- // Get data
412
- $data = get_option($this->get_key(), null);
413
- if ( $sanitize && is_array($data) ) {
414
- // Sanitize loaded data based on default values
415
- foreach ( $data as $id => $val ) {
416
- if ( $this->has($id) ) {
417
- $opt = $this->get($id);
418
- if ( is_bool($opt->get_default()) )
419
- $data[$id] = !!$val;
420
- }
421
- }
422
- }
423
- return $data;
424
- }
425
-
426
- /**
427
- * Retrieves option data for collection
428
- * @see SLB_Field_Collection::load_data()
429
- */
430
- function load_data() {
431
- if ( !$this->data_loaded ) {
432
- // Retrieve data
433
- $this->data = $this->fetch_data();
434
- parent::load_data();
435
- // Check update
436
- $this->check_update();
437
- }
438
- }
439
-
440
- /**
441
- * Resets option values to their default values
442
- * @param bool $hard Reset all options if TRUE (default), Reset only unset options if FALSE
443
- */
444
- function reset($hard = true) {
445
- $this->load_data();
446
- // Reset data
447
- if ( $hard ) {
448
- $this->data = null;
449
- }
450
- // Save
451
- $this->save();
452
- }
453
-
454
- /**
455
- * Save options data to database
456
- */
457
- function save() {
458
- $this->normalize_data();
459
- update_option($this->get_key(), $this->data);
460
- }
461
-
462
- /**
463
- * Normalize data
464
- * Assures that data in collection match items
465
- * @uses self::data to reset and save collection data after normalization
466
- */
467
- function normalize_data() {
468
- $data = array();
469
- foreach ( $this->get_items() as $id => $opt ) {
470
- $data[$id] = $opt->get_data();
471
- }
472
- $this->data =& $data;
473
- return $data;
474
- }
475
-
476
- /* Collection */
477
-
478
- /**
479
- * Build key for saving/retrieving data to options table
480
- * @return string Key
481
- */
482
- function get_key() {
483
- return $this->add_prefix($this->get_id());
484
- }
485
-
486
- /**
487
- * Add option to collection
488
- * @uses SLB_Field_Collection::add() to add item
489
- * @param string $id Unique item ID
490
- * @param array $properties Item properties
491
- * @param bool $update (optional) Should item be updated or overwritten (Default: FALSE)
492
- * @return SLB_Option Option instance
493
- */
494
- function &add($id, $properties = array(), $update = false) {
495
- // Create item
496
- $args = func_get_args();
497
- $ret = call_user_func_array(array('parent', 'add'), $args);
498
- return $ret;
499
- }
500
-
501
- /**
502
- * Retrieve option value
503
- * @uses get_data() to retrieve option data
504
- * @param string $option Option ID to retrieve value for
505
- * @param string $context (optional) Context for formatting data
506
- * @return mixed Option value
507
- */
508
- function get_value($option, $context = '') {
509
- return $this->get_data($option, $context);
510
- }
511
-
512
- /**
513
- * Retrieve option value as boolean (true/false)
514
- * @uses get_data() to retrieve option data
515
- * @param string $option Option ID to retrieve value for
516
- * @return bool Option value
517
- */
518
- function get_bool($option) {
519
- return $this->get_value($option, 'bool');
520
- }
521
-
522
- function get_string($option) {
523
- return $this->get_value($option, 'string');
524
- }
525
-
526
- /**
527
- * Retrieve option's default value
528
- * @uses get_data() to retrieve option data
529
- * @param string $option Option ID to retrieve value for
530
- * @param string $context (optional) Context for formatting data
531
- * @return mixed Option's default value
532
- */
533
- function get_default($option, $context = '') {
534
- return $this->get_data($option, $context, false);
535
- }
536
-
537
- /* Output */
538
-
539
- function build_init() {
540
- if ( $this->build_vars['validate_pre'] ) {
541
- $values = $this->validate();
542
- if ( $this->build_vars['save_pre'] ) {
543
- $this->set_data($values);
544
- }
545
- }
546
- }
547
-
548
- /**
549
- * Build array of option values for client output
550
- * @return array Associative array of options
551
- */
552
- function build_client_output() {
553
- $items = $this->get_items();
554
- $out = array();
555
- foreach ( $items as $option ) {
556
- if ( !$option->get_in_client() )
557
- continue;
558
- $out[$option->get_id()] = $option->get_data('default');
559
- }
560
- return $out;
561
- }
562
-
563
- /* Admin */
564
-
565
- /**
566
- * Handles output building for options on admin pages
567
- * @param obj|array $opts Options instance or Array of options instance and groups to build
568
- * @param obj $page Admin Page instance
569
- * @param obj $state Admin Page state properties
570
- */
571
- public function admin_page_render_content($opts, $page, $state) {
572
- $groups = null;
573
- if ( is_array($opts) && count($opts) == 2 ) {
574
- $groups = $opts[1];
575
- $opts = $opts[0];
576
- }
577
- if ( $opts === $this ) {
578
- // Set build variables and callbacks
579
- $this->set_build_var('admin_page', $page);
580
- $this->set_build_var('admin_state', $state);
581
- if ( !empty($groups) ) {
582
- $this->set_build_var('groups', $groups);
583
- }
584
- $hooks = array (
585
- 'filter' => array (
586
- 'parse_build_vars' => array( $this->m('admin_parse_build_vars'), 10, 2 )
587
- )
588
- );
589
-
590
- // Add hooks
591
- foreach ( $hooks as $type => $hook ) {
592
- $m = 'add_' . $type;
593
- foreach ( $hook as $tag => $args ) {
594
- array_unshift($args, $tag);
595
- call_user_func_array($this->util->m($m), $args);
596
- }
597
- }
598
-
599
- // Build output
600
- $this->build(array('build_groups' => $this->m('admin_build_groups')));
601
-
602
- // Remove hooks
603
- foreach ( $hooks as $type => $hook ) {
604
- $m = 'remove_' . $type;
605
- foreach ( $hook as $tag => $args ) {
606
- call_user_func($this->util->m($m), $tag, $args[0]);
607
- }
608
- }
609
- // Clear custom build vars
610
- $this->delete_build_var('admin_page');
611
- $this->delete_build_var('admin_state');
612
- }
613
- }
614
-
615
- /**
616
- * Builds option groups output
617
- */
618
- public function admin_build_groups() {
619
- $page = $this->get_build_var('admin_page');
620
- $state = $this->get_build_var('admin_state');
621
- $groups = $this->get_build_var('groups');
622
-
623
- // Get all groups
624
- $groups_all = $this->get_groups();
625
- if ( empty($groups) ) {
626
- $groups = array_keys($groups_all);
627
- }
628
- // Iterate through groups
629
- foreach ( $groups as $gid ) {
630
- // Validate
631
- if ( !isset($groups_all[$gid]) || !count($this->get_items($gid)) ) {
632
- continue;
633
- }
634
- // Add meta box for each group
635
- $g = $groups_all[$gid];
636
- add_meta_box($g->id, $g->title, $this->m('admin_build_group'), $state->screen, $state->context, $state->priority, array('group' => $g->id, 'page' => $page));
637
- }
638
- }
639
-
640
- /**
641
- * Group output handler for admin pages
642
- * @param obj $obj Object passed by `do_meta_boxes()` call (Default: NULL)
643
- * @param array $box Meta box properties
644
- */
645
- public function admin_build_group($obj, $box) {
646
- $a = $box['args'];
647
- $group = $a['group'];
648
- $this->build_group($group);
649
- }
650
-
651
- /**
652
- * Parse build vars
653
- * @uses `options_parse_build_vars` filter hook
654
- */
655
- public function admin_parse_build_vars($vars, $opts) {
656
- // Handle form submission
657
- if ( isset($_POST[$opts->get_id('formatted')]) ) {
658
- $vars['validate_pre'] = $vars['save_pre'] = true;
659
- }
660
- return $vars;
661
- }
662
-
663
- /**
664
- * Admin reset handler
665
- * @param bool $res Current result
666
- * @param obj $opts Options instance
667
- * @param obj $reset Admin Reset instance
668
- */
669
- public function admin_action_reset($res, $opts, $reset) {
670
- // Only process matching options instance
671
- if ( $opts === $this ) {
672
- // Reset options
673
- $this->reset();
674
- // Set result
675
- $res = true;
676
- }
677
- return $res;
678
- }
679
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Options collection
4
+ * @package Simple Lightbox
5
+ * @subpackage Options
6
+ * @author Archetyped
7
+ * @uses SLB_Field_Collection
8
+ */
9
+ class SLB_Options extends SLB_Field_Collection {
10
+
11
+ /* Properties */
12
+
13
+ public $hook_prefix = 'options';
14
+
15
+ public $item_type = 'SLB_Option';
16
+
17
+ /**
18
+ * Key for saving version to DB
19
+ * @var string
20
+ */
21
+ private $version_key = 'version';
22
+
23
+ /**
24
+ * Whether version has been checked
25
+ * @var bool
26
+ */
27
+ public $version_checked = false;
28
+
29
+ public $items_migrated = false;
30
+
31
+ public $build_vars = array(
32
+ 'validate_pre' => false,
33
+ 'validate_post' => false,
34
+ 'save_pre' => false,
35
+ 'save_post' => false,
36
+ );
37
+
38
+ /* Init */
39
+
40
+ function __construct( $id = '', $props = array() ) {
41
+ // Validate arguments
42
+ $args = func_get_args();
43
+ // Set default ID
44
+ if ( ! $this->validate_id( $id ) ) {
45
+ $id = 'options';
46
+ }
47
+ $defaults = $this->integrate_id( $id );
48
+ $props = $this->make_properties( $args, $defaults );
49
+ parent::__construct( $props );
50
+ $this->add_prefix_ref( $this->version_key );
51
+ }
52
+
53
+ protected function _hooks() {
54
+ parent::_hooks();
55
+ // Register fields
56
+ $this->util->add_action( 'register_fields', $this->m( 'register_fields' ), 10, 1, false );
57
+ // Set option parents
58
+ $this->util->add_action( 'fields_registered', $this->m( 'set_parents' ), 10, 1, false );
59
+ // Building
60
+ $this->util->add_action( 'build_init', $this->m( 'build_init' ) );
61
+ // Admin
62
+ $this->util->add_action( 'admin_page_render_content', $this->m( 'admin_page_render_content' ), 10, 3, false );
63
+ $this->util->add_filter( 'admin_action_reset', $this->m( 'admin_action_reset' ), 10, 3, false );
64
+ }
65
+
66
+ /* Legacy/Migration */
67
+
68
+ /**
69
+ * Checks whether new version has been installed and migrates necessary settings
70
+ * @uses $version_key as option name
71
+ * @uses get_option() to retrieve saved version number
72
+ * @uses SLB_Utilities::get_plugin_version() to retrieve current version
73
+ * @return bool TRUE if version has been changed
74
+ */
75
+ function check_update() {
76
+ if ( ! $this->version_checked ) {
77
+ $this->version_checked = true;
78
+ $version_changed = false;
79
+ // Get version from DB
80
+ $vo = $this->get_version();
81
+ // Get current version
82
+ $vn = $this->util->get_plugin_version();
83
+ // Compare versions
84
+ if ( $vo !== $vn ) {
85
+ // Update saved version
86
+ $this->set_version( $vn );
87
+ // Migrate old version to new version
88
+ if ( strcasecmp( $vo, $vn ) < 0 ) {
89
+ // Force full migration
90
+ $version_changed = true;
91
+ }
92
+ }
93
+ // Migrate
94
+ $this->migrate( $version_changed );
95
+ }
96
+
97
+ return $this->version_checked;
98
+ }
99
+
100
+ /**
101
+ * Save plugin version to DB
102
+ * If no version supplied, will fetch plugin data to determine version
103
+ * @uses $version_key as option name
104
+ * @uses update_option() to save version to options table
105
+ * @param string $ver (optional) Plugin version
106
+ */
107
+ function set_version( $ver = null ) {
108
+ if ( empty( $ver ) ) {
109
+ $ver = $this->util->get_plugin_version();
110
+ }
111
+ return update_option( $this->version_key, $ver );
112
+ }
113
+
114
+ /**
115
+ * Retrieve saved version data
116
+ * @return string Saved version
117
+ */
118
+ function get_version() {
119
+ return get_option( $this->version_key, '' );
120
+ }
121
+
122
+ /**
123
+ * Migrate options from old versions to current version
124
+ * @uses self::items_migrated to determine if simple migration has been performed in current request or not
125
+ * @uses self::save() to save data after migration
126
+ * @param bool $full Whether to perform a full migration or not (Default: No)
127
+ */
128
+ function migrate( $full = false ) {
129
+ if ( ! $full && $this->items_migrated ) {
130
+ return false;
131
+ }
132
+
133
+ // Legacy options
134
+ $d = null;
135
+ $this->load_data();
136
+
137
+ $items = $this->get_items();
138
+
139
+ // Migrate separate options to unified option
140
+ if ( $full ) {
141
+ foreach ( $items as $opt => $props ) {
142
+ $oid = $this->add_prefix( $opt );
143
+ $o = get_option( $oid, $d );
144
+ if ( $o !== $d ) {
145
+ // Migrate value to data array
146
+ $this->set_data( $opt, $o, false );
147
+ // Delete legacy option
148
+ delete_option( $oid );
149
+ }
150
+ }
151
+ }
152
+
153
+ // Migrate legacy items
154
+ if ( is_array( $this->properties_init ) && isset( $this->properties_init['legacy'] ) && is_array( $this->properties_init['legacy'] ) ) {
155
+ $l =& $this->properties_init['legacy'];
156
+ // Normalize legacy map
157
+ foreach ( $l as $opt => $dest ) {
158
+ if ( ! is_array( $dest ) ) {
159
+ if ( is_string( $dest ) ) {
160
+ $l[ $opt ] = array( $dest );
161
+ } else {
162
+ unset( $l[ $opt ] );
163
+ }
164
+ }
165
+ }
166
+
167
+ /* Separate options */
168
+ if ( $full ) {
169
+ foreach ( $l as $opt => $dest ) {
170
+ $oid = $this->add_prefix( $opt );
171
+ $o = get_option( $oid, $d );
172
+ // Only migrate valid values
173
+ if ( $o !== $d ) {
174
+ // Process destinations
175
+ foreach ( $dest as $id ) {
176
+ $this->set_data( $id, $o, false, true );
177
+ }
178
+ }
179
+ // Remove legacy option
180
+ delete_option( $oid );
181
+ }
182
+ }
183
+
184
+ /* Simple Migration (Internal options only) */
185
+
186
+ // Get existing items that are also legacy items
187
+ $opts = array_intersect_key( $this->get_data(), $l );
188
+ foreach ( $opts as $opt => $val ) {
189
+ $d = $this->get_data( $opt );
190
+ // Migrate data from old option to new option
191
+ $dest = $l[ $opt ];
192
+ // Validate new options to send data to
193
+ foreach ( $dest as $id ) {
194
+ $this->set_data( $id, $d, false, true );
195
+ }
196
+ // Remove legacy option
197
+ $this->remove( $opt, false );
198
+ }
199
+ }
200
+ // Save changes
201
+ $this->save();
202
+ // Set flag
203
+ $this->items_migrated = true;
204
+ }
205
+
206
+ /* Option setup */
207
+
208
+ /**
209
+ * Get elements for creating fields
210
+ * @return obj
211
+ */
212
+ function get_field_elements() {
213
+ static $o = null;
214
+ if ( empty( $o ) ) {
215
+ $o = new stdClass();
216
+ /* Layout */
217
+ $layout = new stdClass();
218
+ $layout->label = '<label for="{field_id}" class="title block">{label}</label>';
219
+ $layout->label_ref = '{label ref_base="layout"}';
220
+ $layout->field_pre = '<div class="input block">';
221
+ $layout->field_post = '</div>';
222
+ $layout->opt_pre = '<div class="' . $this->add_prefix( 'option_item' ) . '">';
223
+ $layout->opt_post = '</div>';
224
+ $layout->form = '<{form_attr ref_base="layout"} /> <span class="description">(' . __( 'Default', 'simple-lightbox' ) . ': {data context="display" top="0"})</span>';
225
+ /* Combine */
226
+ $o->layout =& $layout;
227
+ }
228
+ return $o;
229
+ }
230
+
231
+ /**
232
+ * Register option-specific fields
233
+ * @param SLB_Fields $fields Reference to global fields object
234
+ * @return void
235
+ */
236
+ function register_fields( $fields ) {
237
+ // Layouts
238
+ $o = $this->get_field_elements();
239
+ $l =& $o->layout;
240
+
241
+ $form = implode(
242
+ '',
243
+ array(
244
+ $l->opt_pre,
245
+ $l->label_ref,
246
+ $l->field_pre,
247
+ $l->form,
248
+ $l->field_post,
249
+ $l->opt_post,
250
+ )
251
+ );
252
+
253
+ // Text input
254
+ $otxt = new SLB_Field_Type( 'option_text', 'text' );
255
+ $otxt->set_property( 'class', '{inherit} code' );
256
+ $otxt->set_property( 'size', null );
257
+ $otxt->set_property( 'value', '{data}' );
258
+ $otxt->set_layout( 'label', $l->label );
259
+ $otxt->set_layout( 'form', $form );
260
+ $fields->add( $otxt );
261
+
262
+ // Checkbox
263
+ $ocb = new SLB_Field_Type( 'option_checkbox', 'checkbox' );
264
+ $ocb->set_layout( 'label', $l->label );
265
+ $ocb->set_layout( 'field_reference', sprintf( '<input type="hidden" name="%s" value="{field_name format="raw"}" />', "{$this->get_id('formatted')}_items[]" ) );
266
+ $ocb->set_layout( 'form', '{field_reference ref_base="layout"}' . $form );
267
+ $fields->add( $ocb );
268
+
269
+ // Select
270
+ $othm = new SLB_Field_Type( 'option_select', 'select' );
271
+ $othm->set_layout( 'label', $l->label );
272
+ $othm->set_layout( 'form_start', $l->field_pre . '{inherit}' );
273
+ $othm->set_layout( 'form_end', '{inherit}' . $l->field_post );
274
+ $othm->set_layout( 'form', $l->opt_pre . '{inherit}' . $l->opt_post );
275
+ $fields->add( $othm );
276
+ }
277
+
278
+ /**
279
+ * Set parent field types for options
280
+ * Parent only set for Admin pages
281
+ * @uses SLB_Option::set_parent() to set parent field for each option item
282
+ * @uses is_admin() to determine if current request is admin page
283
+ * @param object $fields Collection of default field types
284
+ * @return void
285
+ */
286
+ function set_parents( $fields ) {
287
+ if ( ! is_admin() ) {
288
+ return false;
289
+ }
290
+ $items = &$this->get_items();
291
+ foreach ( array_keys( $items ) as $opt ) {
292
+ $items[ $opt ]->set_parent();
293
+ }
294
+ foreach ( $this->items as $opt ) {
295
+ $p = $opt->parent;
296
+ if ( is_object( $p ) ) {
297
+ $p = 'o:' . $p->id;
298
+ }
299
+ }
300
+ }
301
+
302
+ /* Processing */
303
+
304
+ /**
305
+ * Validates option data.
306
+ *
307
+ * Validates option values (e.g. prior to saving to DB, after form submission, etc.).
308
+ * Values are formatted and sanitized according to corresponding option's data type
309
+ * (e.g. boolean, string, number, etc.).
310
+ *
311
+ * @since 1.5.5
312
+ *
313
+ * @param array<string,scalar> $values Optional. Option data to validate.
314
+ * Indexed by option ID.
315
+ * Default form-submission data used.
316
+ * @return array<string,scalar> Validated data. Indexed by option ID.
317
+ */
318
+ function validate( $values = null ) {
319
+ /** @var array<string,scalar> $values_valid Validated option data. Indexed by option ID. */
320
+ $values_valid = [];
321
+ // Enforce values data type.
322
+ if ( ! is_array( $values ) ) {
323
+ /** @var array<string,scalar> $values */
324
+ $values = [];
325
+ }
326
+ /**
327
+ * Generates query variable using common base.
328
+ *
329
+ * @since 2.8.0
330
+ *
331
+ * @param string $text Optional. Text to append to base.
332
+ *
333
+ * @return string Query variable name. Format: "{base}_{text}". Default "{base}".
334
+ */
335
+ $qv = function ( $text = '' ) {
336
+ static $base;
337
+ // Get base.
338
+ if ( empty( $base ) ) {
339
+ $base = $this->get_id( 'formatted' );
340
+ }
341
+ $out = $base;
342
+ // Append text to base.
343
+ if ( is_string( $text ) && ! empty( $text ) ) {
344
+ $out .= "_{$text}";
345
+ }
346
+ return $out;
347
+ };
348
+ // Get options form field group ID.
349
+ $qvar = $qv();
350
+ // Use form submission data when no values provided.
351
+ if ( empty( $values ) && isset( $_POST[ $qvar ] ) && check_admin_referer( $qvar, $qv( 'nonce' ) ) ) {
352
+ /** @var array<string,scalar> $values */
353
+ $values = $_POST[ $qvar ];
354
+ // Append non-submitted, but rendered fields (e.g. unchecked checkboxes)
355
+ $qvar_items = $qv( 'items' );
356
+ /** @var string[] $items_bool Boolean options rendered in submitted form. */
357
+ $items_bool = ( isset( $_POST[ $qvar_items ] ) ) ? $_POST[ $qvar_items ] : null;
358
+ if ( ! empty( $items_bool ) && is_array( $items_bool ) ) {
359
+ foreach ( $items_bool as $item_id ) {
360
+ // Add missing boolean options (false == unchecked).
361
+ if ( ! array_key_exists( $item_id, $values ) && $this->has( $item_id ) && is_bool( $this->get_default( $item_id ) ) ) {
362
+ $values_valid[ $item_id ] = false;
363
+ }
364
+ }
365
+ }
366
+ unset( $qvar, $qvar_items, $items_bool, $item_id );
367
+ }
368
+ // Process values.
369
+ /**
370
+ * @var string $id Option ID.
371
+ * @var mixed $val Option value (raw/unsanitized).
372
+ */
373
+ foreach ( $values as $id => $val ) {
374
+ // Do not process invalid option IDs or invalid (non-scalar) data.
375
+ if ( ! $this->has( $id ) || ! is_scalar( $val ) ) {
376
+ continue;
377
+ }
378
+ // Conform to option's data type and sanitize.
379
+ /** @var scalar $d Option's default data. */
380
+ $d = $this->get_default( $id );
381
+ if ( is_bool( $d ) ) {
382
+ // Boolean.
383
+ $val = ! ! $val;
384
+ } elseif ( ( is_int( $d ) || is_float( $d ) ) && ! is_numeric( $val ) ) {
385
+ // Numeric - do not process non-numeric values for int/float fields.
386
+ continue;
387
+ } elseif ( is_int( $d ) ) {
388
+ // Integer.
389
+ $val = (int) $val;
390
+ } elseif ( is_float( $d ) ) {
391
+ // Float.
392
+ $val = (float) $val;
393
+ } else {
394
+ // Defaut: Handle as string.
395
+ $val = sanitize_text_field( wp_unslash( $val ) );
396
+ }
397
+ // Add to validated data.
398
+ $values_valid[ $id ] = $val;
399
+ }
400
+ unset( $id, $val );
401
+
402
+ // Return validated values.
403
+ return $values_valid;
404
+ }
405
+
406
+ /* Data */
407
+
408
+ /**
409
+ * Retrieve options from database
410
+ * @uses get_option to retrieve option data
411
+ * @return array Options data
412
+ */
413
+ function fetch_data( $sanitize = true ) {
414
+ // Get data
415
+ $data = get_option( $this->get_key(), null );
416
+ if ( $sanitize && is_array( $data ) ) {
417
+ // Sanitize loaded data based on default values
418
+ foreach ( $data as $id => $val ) {
419
+ if ( $this->has( $id ) ) {
420
+ $opt = $this->get( $id );
421
+ if ( is_bool( $opt->get_default() ) ) {
422
+ $data[ $id ] = ! ! $val;
423
+ }
424
+ }
425
+ }
426
+ }
427
+ return $data;
428
+ }
429
+
430
+ /**
431
+ * Retrieves option data for collection
432
+ * @see SLB_Field_Collection::load_data()
433
+ */
434
+ function load_data() {
435
+ if ( ! $this->data_loaded ) {
436
+ // Retrieve data
437
+ $this->data = $this->fetch_data();
438
+ parent::load_data();
439
+ // Check update
440
+ $this->check_update();
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Resets option values to their default values
446
+ * @param bool $hard Reset all options if TRUE (default), Reset only unset options if FALSE
447
+ */
448
+ function reset( $hard = true ) {
449
+ $this->load_data();
450
+ // Reset data
451
+ if ( $hard ) {
452
+ $this->data = null;
453
+ }
454
+ // Save
455
+ $this->save();
456
+ }
457
+
458
+ /**
459
+ * Save options data to database
460
+ */
461
+ function save() {
462
+ $this->normalize_data();
463
+ update_option( $this->get_key(), $this->data );
464
+ }
465
+
466
+ /**
467
+ * Normalize data
468
+ * Assures that data in collection match items
469
+ * @uses self::data to reset and save collection data after normalization
470
+ */
471
+ function normalize_data() {
472
+ $data = array();
473
+ foreach ( $this->get_items() as $id => $opt ) {
474
+ $data[ $id ] = $opt->get_data();
475
+ }
476
+ $this->data =& $data;
477
+ return $data;
478
+ }
479
+
480
+ /* Collection */
481
+
482
+ /**
483
+ * Build key for saving/retrieving data to options table
484
+ * @return string Key
485
+ */
486
+ function get_key() {
487
+ return $this->add_prefix( $this->get_id() );
488
+ }
489
+
490
+ /**
491
+ * Add option to collection
492
+ * @uses SLB_Field_Collection::add() to add item
493
+ * @param string $id Unique item ID
494
+ * @param array $properties Item properties
495
+ * @param bool $update (optional) Should item be updated or overwritten (Default: FALSE)
496
+ * @return SLB_Option Option instance
497
+ */
498
+ function &add( $id, $properties = array(), $update = false ) {
499
+ // Create item
500
+ $args = func_get_args();
501
+ $ret = call_user_func_array( array( 'parent', 'add' ), $args );
502
+ return $ret;
503
+ }
504
+
505
+ /**
506
+ * Retrieve option value
507
+ * @uses get_data() to retrieve option data
508
+ * @param string $option Option ID to retrieve value for
509
+ * @param string $context (optional) Context for formatting data
510
+ * @return mixed Option value
511
+ */
512
+ function get_value( $option, $context = '' ) {
513
+ return $this->get_data( $option, $context );
514
+ }
515
+
516
+ /**
517
+ * Retrieve option value as boolean (true/false)
518
+ * @uses get_data() to retrieve option data
519
+ * @param string $option Option ID to retrieve value for
520
+ * @return bool Option value
521
+ */
522
+ function get_bool( $option ) {
523
+ return $this->get_value( $option, 'bool' );
524
+ }
525
+
526
+ function get_string( $option ) {
527
+ return $this->get_value( $option, 'string' );
528
+ }
529
+
530
+ /**
531
+ * Retrieve option's default value
532
+ * @uses get_data() to retrieve option data
533
+ * @param string $option Option ID to retrieve value for
534
+ * @param string $context (optional) Context for formatting data
535
+ * @return mixed Option's default value
536
+ */
537
+ function get_default( $option, $context = '' ) {
538
+ return $this->get_data( $option, $context, false );
539
+ }
540
+
541
+ /* Output */
542
+
543
+ function build_init() {
544
+ if ( $this->build_vars['validate_pre'] ) {
545
+ $values = $this->validate();
546
+ if ( $this->build_vars['save_pre'] ) {
547
+ $this->set_data( $values );
548
+ }
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Build array of option values for client output
554
+ * @return array Associative array of options
555
+ */
556
+ function build_client_output() {
557
+ $items = $this->get_items();
558
+ $out = array();
559
+ foreach ( $items as $option ) {
560
+ if ( ! $option->get_in_client() ) {
561
+ continue;
562
+ }
563
+ $out[ $option->get_id() ] = $option->get_data( 'default' );
564
+ }
565
+ return $out;
566
+ }
567
+
568
+ /* Admin */
569
+
570
+ /**
571
+ * Handles output building for options on admin pages
572
+ * @param obj|array $opts Options instance or Array of options instance and groups to build
573
+ * @param obj $page Admin Page instance
574
+ * @param obj $state Admin Page state properties
575
+ */
576
+ public function admin_page_render_content( $opts, $page, $state ) {
577
+ $groups = null;
578
+ if ( is_array( $opts ) && count( $opts ) === 2 ) {
579
+ $groups = $opts[1];
580
+ $opts = $opts[0];
581
+ }
582
+ if ( $opts === $this ) {
583
+ // Set build variables and callbacks
584
+ $this->set_build_var( 'admin_page', $page );
585
+ $this->set_build_var( 'admin_state', $state );
586
+ if ( ! empty( $groups ) ) {
587
+ $this->set_build_var( 'groups', $groups );
588
+ }
589
+ $hooks = array(
590
+ 'filter' => array(
591
+ 'parse_build_vars' => array( $this->m( 'admin_parse_build_vars' ), 10, 2 ),
592
+ ),
593
+ );
594
+
595
+ // Add hooks
596
+ foreach ( $hooks as $type => $hook ) {
597
+ $m = 'add_' . $type;
598
+ foreach ( $hook as $tag => $args ) {
599
+ array_unshift( $args, $tag );
600
+ call_user_func_array( $this->util->m( $m ), $args );
601
+ }
602
+ }
603
+
604
+ // Build output
605
+ $this->build( array( 'build_groups' => $this->m( 'admin_build_groups' ) ) );
606
+
607
+ // Remove hooks
608
+ foreach ( $hooks as $type => $hook ) {
609
+ $m = 'remove_' . $type;
610
+ foreach ( $hook as $tag => $args ) {
611
+ call_user_func( $this->util->m( $m ), $tag, $args[0] );
612
+ }
613
+ }
614
+ // Clear custom build vars
615
+ $this->delete_build_var( 'admin_page' );
616
+ $this->delete_build_var( 'admin_state' );
617
+ }
618
+ }
619
+
620
+ /**
621
+ * Builds option groups output
622
+ */
623
+ public function admin_build_groups() {
624
+ $page = $this->get_build_var( 'admin_page' );
625
+ $state = $this->get_build_var( 'admin_state' );
626
+ $groups = $this->get_build_var( 'groups' );
627
+
628
+ // Get all groups
629
+ $groups_all = $this->get_groups();
630
+ if ( empty( $groups ) ) {
631
+ $groups = array_keys( $groups_all );
632
+ }
633
+ // Iterate through groups
634
+ foreach ( $groups as $gid ) {
635
+ // Validate
636
+ if ( ! isset( $groups_all[ $gid ] ) || ! count( $this->get_items( $gid ) ) ) {
637
+ continue;
638
+ }
639
+ // Add meta box for each group
640
+ $g = $groups_all[ $gid ];
641
+ add_meta_box(
642
+ $g->id,
643
+ $g->title,
644
+ $this->m( 'admin_build_group' ),
645
+ $state->screen,
646
+ $state->context,
647
+ $state->priority,
648
+ array(
649
+ 'group' => $g->id,
650
+ 'page' => $page,
651
+ )
652
+ );
653
+ }
654
+ }
655
+
656
+ /**
657
+ * Group output handler for admin pages
658
+ * @param obj $obj Object passed by `do_meta_boxes()` call (Default: NULL)
659
+ * @param array $box Meta box properties
660
+ */
661
+ public function admin_build_group( $obj, $box ) {
662
+ $a = $box['args'];
663
+ $group = $a['group'];
664
+ $this->build_group( $group );
665
+ }
666
+
667
+ /**
668
+ * Parse build vars
669
+ * @uses `options_parse_build_vars` filter hook
670
+ */
671
+ public function admin_parse_build_vars( $vars, $opts ) {
672
+ // Handle form submission
673
+ if ( isset( $_POST[ $opts->get_id( 'formatted' ) ] ) ) {
674
+ $vars['save_pre'] = true;
675
+ $vars['validate_pre'] = true;
676
+ }
677
+ return $vars;
678
+ }
679
+
680
+ /**
681
+ * Admin reset handler
682
+ * @param bool $res Current result
683
+ * @param obj $opts Options instance
684
+ * @param obj $reset Admin Reset instance
685
+ */
686
+ public function admin_action_reset( $res, $opts, $reset ) {
687
+ // Only process matching options instance
688
+ if ( $opts === $this ) {
689
+ // Reset options
690
+ $this->reset();
691
+ // Set result
692
+ $res = true;
693
+ }
694
+ return $res;
695
+ }
696
+ }
includes/class.template_tag.php CHANGED
@@ -1,9 +1,9 @@
1
- <?php
2
-
3
- /**
4
- * Template Tag
5
- * @package Simple Lightbox
6
- * @subpackage Template
7
- * @author Archetyped
8
- */
9
- class SLB_Template_Tag extends SLB_Component { }
1
+ <?php
2
+
3
+ /**
4
+ * Template Tag
5
+ * @package Simple Lightbox
6
+ * @subpackage Template
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Template_Tag extends SLB_Component { }
includes/class.template_tags.php CHANGED
@@ -1,122 +1,122 @@
1
- <?php
2
-
3
- /**
4
- * Content Handler Collection
5
- * @package Simple Lightbox
6
- * @subpackage Content Handler
7
- * @author Archetyped
8
- */
9
- class SLB_Template_Tags extends SLB_Collection_Controller {
10
- /* Configuration */
11
-
12
- protected $item_type = 'SLB_Template_Tag';
13
-
14
- public $hook_prefix = 'template_tags';
15
-
16
- // Use tag ID as key
17
- protected $key_prop = 'get_id';
18
-
19
- // Call $key_prop is a method to be called
20
- protected $key_call = true;
21
-
22
- /* Properties */
23
-
24
- /**
25
- * Cache properties (key, group)
26
- * @var object
27
- */
28
- protected $cache_props = null;
29
-
30
- /* Initialization */
31
-
32
- protected function _hooks() {
33
- parent::_hooks();
34
- $this->util->add_action('init', $this->m('init_defaults'));
35
- $this->util->add_action('footer', $this->m('client_output'), 1, 0, false);
36
- $this->util->add_filter('footer_script', $this->m('client_output_script'), $this->util->priority('client_footer_output'), 1, false);
37
- }
38
-
39
- /* Collection Management */
40
-
41
- /**
42
- * Add template tag
43
- * Accepts properties to create new template tag OR previously-initialized tag instance
44
- * @see parent::add()
45
- * @param string $id Tag ID
46
- * @param array $props Tag properties
47
- * @return object Current instance
48
- */
49
- public function add($id, $props = array()) {
50
- $o = ( is_string($id) ) ? new $this->item_type($id, $props) : $id;
51
- // Add to collection
52
- return parent::add($o);
53
- }
54
-
55
- /* Defaults */
56
-
57
- /**
58
- * Initialize default template tags
59
- * @param SLB_Template_Tags $tags Tags controller
60
- */
61
- public function init_defaults($tags) {
62
- $js_path = 'js/';
63
- $js_path .= ( SLB_DEV ) ? 'dev' : 'prod';
64
- $src_base = $this->util->get_file_url('template-tags', true);
65
- $defaults = array (
66
- 'item' => array (
67
- 'scripts' => array (
68
- array ( 'base', "$src_base/item/$js_path/tag.item.js" ),
69
- )
70
- ),
71
- 'ui' => array (
72
- 'scripts' => array (
73
- array ( 'base', "$src_base/ui/$js_path/tag.ui.js" ),
74
- )
75
- ),
76
- );
77
- foreach ( $defaults as $id => $props ) {
78
- $tags->add($id, $props);
79
- }
80
- }
81
-
82
- /* Output */
83
-
84
- /**
85
- * Build client output
86
- */
87
- public function client_output() {
88
- // Load matched handlers
89
- foreach ( $this->get() as $tag ) {
90
- $tag->enqueue_scripts();
91
- }
92
- }
93
-
94
- /**
95
- * Client output script
96
- * @param array $commands Client script commands
97
- * @return array Modified script commands
98
- */
99
- public function client_output_script($commands) {
100
- $out = array('/* TPLT */');
101
- $code = array();
102
-
103
- foreach ( $this->get() as $tag ) {
104
- $styles = $tag->get_styles(array('uri_format'=>'full'));
105
- if ( empty($styles) ) {
106
- continue;
107
- }
108
- // Setup client parameters
109
- $params = array(
110
- sprintf("'%s'", $tag->get_id()),
111
- );
112
- $params[] = json_encode( array('styles' => array_values($styles)) );
113
- // Extend handler in client
114
- $code[] = $this->util->call_client_method('View.extend_template_tag_handler', $params, false);
115
- }
116
- if ( !empty($code) ) {
117
- $out[] = implode('', $code);
118
- $commands[] = implode(PHP_EOL, $out);
119
- }
120
- return $commands;
121
- }
122
- }
1
+ <?php
2
+
3
+ /**
4
+ * Content Handler Collection
5
+ * @package Simple Lightbox
6
+ * @subpackage Content Handler
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Template_Tags extends SLB_Collection_Controller {
10
+ /* Configuration */
11
+
12
+ protected $item_type = 'SLB_Template_Tag';
13
+
14
+ public $hook_prefix = 'template_tags';
15
+
16
+ // Use tag ID as key
17
+ protected $key_prop = 'get_id';
18
+
19
+ // Call $key_prop is a method to be called
20
+ protected $key_call = true;
21
+
22
+ /* Properties */
23
+
24
+ /**
25
+ * Cache properties (key, group)
26
+ * @var object
27
+ */
28
+ protected $cache_props = null;
29
+
30
+ /* Initialization */
31
+
32
+ protected function _hooks() {
33
+ parent::_hooks();
34
+ $this->util->add_action( 'init', $this->m( 'init_defaults' ) );
35
+ $this->util->add_action( 'footer', $this->m( 'client_output' ), 1, 0, false );
36
+ $this->util->add_filter( 'footer_script', $this->m( 'client_output_script' ), $this->util->priority( 'client_footer_output' ), 1, false );
37
+ }
38
+
39
+ /* Collection Management */
40
+
41
+ /**
42
+ * Add template tag
43
+ * Accepts properties to create new template tag OR previously-initialized tag instance
44
+ * @see parent::add()
45
+ * @param string $id Tag ID
46
+ * @param array $props Tag properties
47
+ * @return object Current instance
48
+ */
49
+ public function add( $id, $props = array() ) {
50
+ $o = ( is_string( $id ) ) ? new $this->item_type( $id, $props ) : $id;
51
+ // Add to collection
52
+ return parent::add( $o );
53
+ }
54
+
55
+ /* Defaults */
56
+
57
+ /**
58
+ * Initialize default template tags
59
+ * @param SLB_Template_Tags $tags Tags controller
60
+ */
61
+ public function init_defaults( $tags ) {
62
+ $js_path = 'js/';
63
+ $js_path .= ( SLB_DEV ) ? 'dev' : 'prod';
64
+ $src_base = $this->util->get_file_url( 'template-tags', true );
65
+ $defaults = array(
66
+ 'item' => array(
67
+ 'scripts' => array(
68
+ array( 'base', "$src_base/item/$js_path/tag.item.js" ),
69
+ ),
70
+ ),
71
+ 'ui' => array(
72
+ 'scripts' => array(
73
+ array( 'base', "$src_base/ui/$js_path/tag.ui.js" ),
74
+ ),
75
+ ),
76
+ );
77
+ foreach ( $defaults as $id => $props ) {
78
+ $tags->add( $id, $props );
79
+ }
80
+ }
81
+
82
+ /* Output */
83
+
84
+ /**
85
+ * Build client output
86
+ */
87
+ public function client_output() {
88
+ // Load matched handlers
89
+ foreach ( $this->get() as $tag ) {
90
+ $tag->enqueue_scripts();
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Client output script
96
+ * @param array $commands Client script commands
97
+ * @return array Modified script commands
98
+ */
99
+ public function client_output_script( $commands ) {
100
+ $out = array( '/* TPLT */' );
101
+ $code = array();
102
+
103
+ foreach ( $this->get() as $tag ) {
104
+ $styles = $tag->get_styles( array( 'uri_format' => 'full' ) );
105
+ if ( empty( $styles ) ) {
106
+ continue;
107
+ }
108
+ // Setup client parameters
109
+ $params = array(
110
+ sprintf( "'%s'", $tag->get_id() ),
111
+ );
112
+ $params[] = wp_json_encode( array( 'styles' => array_values( $styles ) ) );
113
+ // Extend handler in client
114
+ $code[] = $this->util->call_client_method( 'View.extend_template_tag_handler', $params, false );
115
+ }
116
+ if ( ! empty( $code ) ) {
117
+ $out[] = implode( '', $code );
118
+ $commands[] = implode( PHP_EOL, $out );
119
+ }
120
+ return $commands;
121
+ }
122
+ }
includes/class.theme.php CHANGED
@@ -1,109 +1,109 @@
1
- <?php
2
-
3
- /**
4
- * Theme
5
- * @package Simple Lightbox
6
- * @subpackage Themes
7
- * @author Archetyped
8
- */
9
- class SLB_Theme extends SLB_Component {
10
- /* Properties */
11
-
12
- protected $props_required = array('name');
13
-
14
- /**
15
- * Public flag
16
- * @var bool
17
- */
18
- protected $public = true;
19
-
20
- /* Get/Set */
21
-
22
- /**
23
- * Retrieve theme's ancestors
24
- * @param bool $sort_topdown (optional) Ancestor sorting (Default: Nearest to Farthest)
25
- * @return array Theme's ancestors (sorted by nearest to most distant ancestor)
26
- */
27
- public function get_ancestors($sort_topdown = false) {
28
- $ret = array();
29
- /**
30
- * @var SLB_Theme
31
- */
32
- $thm = $this;
33
- while ( $thm->has_parent() ) {
34
- $par = $thm->get_parent();
35
- // Add ancestor
36
- if ( $par->is_valid() && !in_array($par, $ret, true) ) {
37
- $ret[] = $par;
38
- }
39
- // Get next ancestor
40
- $thm = $par;
41
- }
42
- // Sorting
43
- if ( $sort_topdown ) {
44
- $ret = array_reverse($ret);
45
- }
46
- return $ret;
47
- }
48
-
49
- /**
50
- * Set public flag
51
- * @param bool $public
52
- */
53
- public function set_public($public) {
54
- $this->publ