Simple Lightbox - Version 2.0

Version Description

  • Completely rewritten lightbox code
  • Add: Automatically resize lightbox to fit window
  • Add: APIs for third-party add-ons
  • Add: Flexible theme support
  • Add: Flexible content handler support
  • Add: Mobile-optimized responsive themes (2)
  • Optimize: PHP class autoloading
  • Optimize: Improved performance and compatibility
  • Optimize: Full internationalization support
Download this release

Release Info

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

Code changes from version 1.4 to 2.0

Files changed (63) hide show
  1. CONTRIBUTING.md +28 -0
  2. COPYING +339 -339
  3. README.md +11 -0
  4. assets/screenshot-1.png +0 -0
  5. assets/screenshot-2.jpg +0 -0
  6. assets/screenshot-3.jpg +0 -0
  7. client/config.rb +24 -0
  8. client/css/admin.css +1 -0
  9. client/js/lib.admin.js +31 -0
  10. client/js/lib.core.js +591 -0
  11. client/js/lib.view.js +4649 -0
  12. client/sass/admin.scss +31 -0
  13. content-handlers/image/handler.image.js +29 -0
  14. css/admin.css +0 -3
  15. css/lightbox.css +0 -135
  16. images/blank.gif +0 -0
  17. images/closelabel.gif +0 -0
  18. images/nextlabel.gif +0 -0
  19. images/prevlabel.gif +0 -0
  20. includes/class.admin.php +1576 -0
  21. includes/class.base.php +438 -88
  22. includes/class.base_collection.php +369 -0
  23. includes/class.base_object.php +277 -0
  24. includes/class.collection_controller.php +57 -0
  25. includes/class.component.php +89 -0
  26. includes/class.content_handler.php +57 -0
  27. includes/class.content_handlers.php +228 -0
  28. includes/class.fields.php +2590 -0
  29. includes/class.options.php +673 -0
  30. includes/class.template_tag.php +9 -0
  31. includes/class.template_tags.php +101 -0
  32. includes/class.theme.php +103 -0
  33. includes/class.themes.php +225 -0
  34. includes/class.utilities.php +1346 -84
  35. js/dev/lightbox.js +0 -736
  36. js/lib.js +0 -22
  37. l10n/simple-lightbox-tr_TR.mo +0 -0
  38. l10n/simple-lightbox-tr_TR.po +163 -0
  39. l10n/simple-lightbox.pot +218 -0
  40. main.php +31 -11
  41. model.php +964 -364
  42. readme.txt +202 -80
  43. screenshot-1.gif +0 -0
  44. screenshot-2.gif +0 -0
  45. template-tags/item/tag.item.js +17 -0
  46. template-tags/ui/tag.ui.js +108 -0
  47. themes/black/config.rb +23 -0
  48. themes/black/css/style.css +1 -0
  49. themes/black/images/loading.gif +0 -0
  50. themes/black/images/nav_next.png +0 -0
  51. themes/black/images/nav_prev.png +0 -0
  52. themes/black/sass/style.scss +37 -0
  53. themes/default/client.js +202 -0
  54. themes/default/config.rb +23 -0
  55. themes/default/css/style.css +1 -0
  56. {images → themes/default/images}/loading.gif +0 -0
  57. themes/default/images/nav_next.png +0 -0
  58. themes/default/images/nav_prev.png +0 -0
  59. themes/default/images/ui_close.png +0 -0
  60. themes/default/images/ui_slideshow_pause.png +0 -0
  61. themes/default/images/ui_slideshow_play.png +0 -0
  62. themes/default/layout.html +41 -0
  63. themes/default/sass/style.scss +294 -0
CONTRIBUTING.md ADDED
@@ -0,0 +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"
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.
README.md ADDED
@@ -0,0 +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"
assets/screenshot-1.png ADDED
Binary file
assets/screenshot-2.jpg ADDED
Binary file
assets/screenshot-3.jpg ADDED
Binary file
client/config.rb ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Require any additional compass plugins here.
2
+
3
+ # Set this to the root of your project when deployed:
4
+ http_path = "/"
5
+ css_dir = "css"
6
+ sass_dir = "sass"
7
+ images_dir = "images"
8
+ javascripts_dir = "js"
9
+
10
+ # You can select your preferred output style here (can be overridden via the command line):
11
+ output_style = :compressed
12
+
13
+ # To enable relative paths to assets via compass helper functions. Uncomment:
14
+ # relative_assets = true
15
+
16
+ # To disable debugging comments that display the original location of your selectors. Uncomment:
17
+ # line_comments = false
18
+
19
+
20
+ # If you prefer the indented syntax, you might want to regenerate this
21
+ # project again passing --syntax sass, or you can uncomment this:
22
+ # preferred_syntax = :sass
23
+ # and then run:
24
+ # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
client/css/admin.css ADDED
@@ -0,0 +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}
client/js/lib.admin.js ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Admin
3
+ * @package Simple Lightbox
4
+ * @subpackage Admin
5
+ * @author Archetyped
6
+ */
7
+
8
+ (function ($) {
9
+
10
+ if ( !SLB || !SLB.attach ) {
11
+ return false;
12
+ }
13
+
14
+ var Admin = {
15
+ /**
16
+ * Initialization routines
17
+ */
18
+ init: function() {
19
+ if ( postboxes ) {
20
+ postboxes.add_postbox_toggles(pagenow);
21
+ }
22
+ },
23
+ }
24
+
25
+ SLB.attach('Admin', Admin);
26
+
27
+ $(document).ready(function() {
28
+ SLB.Admin.init();
29
+ });
30
+
31
+ })(jQuery);
client/js/lib.core.js ADDED
@@ -0,0 +1,591 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Core
3
+ * @package SLB
4
+ * @author Archetyped
5
+ */
6
+
7
+ (function($) {
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
+ Class.extend = function(members) {
18
+ var _super = this.prototype;
19
+
20
+ //Copy instance to prototype
21
+ c_init = true;
22
+ var proto = new this();
23
+ c_init = false;
24
+
25
+ var val;
26
+ //Scrub prototype objects (Decouple from super class)
27
+ for ( var name in proto ) {
28
+ if ( $.isPlainObject(proto[name]) ) {
29
+ val = $.extend({}, proto[name]);
30
+ proto[name] = val;
31
+ }
32
+ }
33
+
34
+ //Copy members
35
+ for ( var name in members ) {
36
+ //Evaluate function members (if overwriting super class method)
37
+ if ( 'function' == typeof members[name] && 'function' == typeof _super[name] ) {
38
+ proto[name] = (function(name, fn) {
39
+ return function() {
40
+ //Cache super variable
41
+ var tmp = this._super;
42
+ //Set variable to super class method
43
+ this._super = _super[name];
44
+ //Call method
45
+ var ret = fn.apply(this, arguments);
46
+ //Restore super variable
47
+ this._super = tmp;
48
+ //Return value
49
+ return ret;
50
+ }
51
+ })(name, members[name]);
52
+ } else {
53
+ val = members[name];
54
+ if ( $.isPlainObject(members[name]) ) {
55
+ val = $.extend({}, members[name]);
56
+ }
57
+ proto[name] = val;
58
+ }
59
+ }
60
+
61
+ //Constructor
62
+ function Class() {
63
+ if ( !c_init ) {
64
+ //Private init
65
+ if ( this._init ) {
66
+ this._init.apply(this, arguments);
67
+ }
68
+ //Main Constructor
69
+ if ( this._c ) {
70
+ this._c.apply(this, arguments);
71
+ }
72
+ }
73
+ }
74
+
75
+
76
+ //Populate new prototype
77
+ Class.prototype = proto;
78
+
79
+ //Set constructor
80
+ Class.prototype.constructor = Class;
81
+
82
+ Class.extend = arguments.callee;
83
+
84
+ //Return function
85
+ return Class;
86
+ };
87
+
88
+ /* Base */
89
+ var Base = {
90
+ /* Properties */
91
+
92
+ base: false,
93
+ _parent: null,
94
+ prefix: 'slb',
95
+
96
+ /* Methods */
97
+
98
+ /**
99
+ * Constructor
100
+ */
101
+ _init: function() {
102
+ this._set_parent();
103
+ },
104
+
105
+ _set_parent: function(p) {
106
+ if ( typeof p != 'undefined' )
107
+ this._parent = p;
108
+ this.util._parent = this;
109
+ },
110
+
111
+ /**
112
+ * Attach member to object
113
+ * @param string name Member name
114
+ * @param mixed Member data
115
+ * > obj: Member inherits from base object
116
+ * > other: Simple data object
117
+ */
118
+ attach: function(member, data, simple) {
119
+ simple = ( typeof simple == undefined ) ? false : !!simple;
120
+ if ( $.type(member) == 'string' && $.isPlainObject(data) ) {
121
+ //Add initial member
122
+ var obj = {};
123
+ if ( simple ) {
124
+ //Simple object
125
+ obj[member] = $.extend({}, data);
126
+ $.extend(this, obj);
127
+ } else {
128
+ //Add new instance object
129
+ data['_parent'] = this;
130
+ var c = this.Class.extend(data);
131
+ this[member] = new c();
132
+ }
133
+ }
134
+ },
135
+
136
+ /**
137
+ * Get parent object
138
+ * @return obj Parent object
139
+ */
140
+ get_parent: function() {
141
+ return this._parent;
142
+ },
143
+
144
+ /**
145
+ * Utility methods
146
+ */
147
+ util: {
148
+ /* Properties */
149
+
150
+ _base: null,
151
+ _parent: null,
152
+
153
+ /* Constants */
154
+
155
+ string: 'string',
156
+ bool: 'boolean',
157
+ array: 'array',
158
+ obj: 'object',
159
+ func: 'function',
160
+ num: 'number',
161
+
162
+ /* Methods */
163
+
164
+ get_base: function() {
165
+ if ( !this._base ) {
166
+ var p = this.get_parent();
167
+ var p_last = null;
168
+ //Iterate through parents
169
+ while ( !p.base && p_last != p && p._parent ) {
170
+ p_last = p;
171
+ p = p._parent;
172
+ }
173
+ //Set base
174
+ this._base = p;
175
+ }
176
+ return this._base;
177
+ },
178
+
179
+ get_parent: function() {
180
+ return this._parent;
181
+ },
182
+
183
+ /**
184
+ * Retrieve valid separator
185
+ * If supplied argument is not a valid separator, use default separator
186
+ * @param string (optional) sep Separator text
187
+ * @return string Separator text
188
+ */
189
+ get_sep: function(sep) {
190
+ return ( this.is_string(sep, false) ) ? sep : '_';
191
+ },
192
+
193
+ /**
194
+ * Retrieve prefix
195
+ * @return string Prefix
196
+ */
197
+ get_prefix: function() {
198
+ return ( this.is_string(this.get_parent().prefix) ) ? this.get_parent().prefix : '';
199
+ },
200
+
201
+ /**
202
+ * Check if string is prefixed
203
+ */
204
+ has_prefix: function(val, sep) {
205
+ return ( this.is_string(val) && val.indexOf(this.get_prefix() + this.get_sep(sep)) === 0 );
206
+ },
207
+
208
+ /**
209
+ * Add Prefix to a string
210
+ * @param string val Value to add prefix to
211
+ * @param string sep (optional) Separator (Default: `_`)
212
+ * @param bool (optional) once If text should only be prefixed once (Default: TRUE)
213
+ */
214
+ add_prefix: function(val, sep, once) {
215
+ //Validate
216
+ if ( !this.is_string(val) ) {
217
+ //Return prefix if value to add prefix to is empty
218
+ return this.get_prefix();
219
+ }
220
+ sep = this.get_sep(sep);
221
+ if ( !this.is_bool(once) ) {
222
+ once = true;
223
+ }
224
+
225
+ return ( once && this.has_prefix(val, sep) ) ? val : [this.get_prefix(), val].join(sep);
226
+ },
227
+
228
+ /**
229
+ * Remove Prefix from a string
230
+ * @param string val Value to add prefix to
231
+ * @param string sep (optional) Separator (Default: `_`)
232
+ * @param bool (optional) once If text should only be prefixed once (Default: true)
233
+ */
234
+ remove_prefix: function(val, sep, once) {
235
+ //Validate parameters
236
+ if ( !this.is_string(val, true) ) {
237
+ return val;
238
+ }
239
+ //Default values
240
+ sep = this.get_sep(sep);
241
+ if ( !this.is_bool(once) ) {
242
+ once = true;
243
+ }
244
+ //Check if string is prefixed
245
+ if ( this.has_prefix(val, sep) ) {
246
+ //Remove prefix
247
+ var prfx = this.get_prefix() + sep;
248
+ do {
249
+ val = val.substr(prfx.length);
250
+ } while ( !once && this.has_prefix(val, sep) );
251
+ }
252
+ return val;
253
+ },
254
+
255
+ /*
256
+ * Get attribute name
257
+ * @param string val Attribute's base name
258
+ */
259
+ get_attribute: function(val) {
260
+ //Setup
261
+ var sep = '-';
262
+ var top = 'data';
263
+ //Validate
264
+ var pre = [top, this.get_prefix()].join(sep);
265
+ if ( !this.is_string(val, false) ) {
266
+ return pre;
267
+ }
268
+ //Process
269
+ if ( val.indexOf(pre + sep) == -1 ) {
270
+ val = [pre, val].join(sep);
271
+ }
272
+ return val;
273
+ },
274
+
275
+ /* Request */
276
+
277
+ /**
278
+ * Retrieve valid context
279
+ * @return array Context
280
+ */
281
+ get_context: function() {
282
+ //Valid context
283
+ var b = this.get_base();
284
+ if ( !$.isArray(b.context) )
285
+ b.context = [];
286
+ //Return context
287
+ return b.context;
288
+ },
289
+
290
+ /**
291
+ * Check if a context exists in current request
292
+ * If multiple contexts are supplied, result will be TRUE if at least ONE context exists
293
+ *
294
+ * @param string|array ctx Context to check for
295
+ * @return bool TRUE if context exists, FALSE otherwise
296
+ */
297
+ is_context: function(ctx) {
298
+ var ret = false;
299
+ //Validate context
300
+ if ( typeof ctx == 'string' )
301
+ ctx = [ctx];
302
+ if ( $.isArray(ctx) && this.arr_intersect(this.get_context(), ctx).length ) {
303
+ ret = true;
304
+ }
305
+ return ret;
306
+ },
307
+
308
+ /* Helpers */
309
+
310
+ is_set: function(value) {
311
+ return ( $.type(value) != 'undefined' ) ? true : false;
312
+ },
313
+
314
+ is_type: function(value, type, nonempty) {
315
+ var ret = false;
316
+ if ( this.is_set(value) && null != value && this.is_set(type) ) {
317
+ switch ( $.type(type) ) {
318
+ case this.func:
319
+ ret = ( value instanceof type ) ? true : false;
320
+ break;
321
+ case this.string:
322
+ ret = ( $.type(value) == type ) ? true : false;
323
+ break;
324
+ default:
325
+ ret = false;
326
+ break;
327
+ }
328
+ }
329
+
330
+ //Validate empty values
331
+ if ( ret && ( $.type(nonempty) != this.bool || nonempty ) ) {
332
+ ret = !this.is_empty(value);
333
+ }
334
+ return ret;
335
+ },
336
+
337
+ is_string: function(value, nonempty) {
338
+ return this.is_type(value, this.string, nonempty);
339
+ },
340
+
341
+ is_array: function(value, nonempty) {
342
+ return ( this.is_type(value, this.array, nonempty) );
343
+ },
344
+
345
+ is_bool: function(value) {
346
+ return this.is_type(value, this.bool, false);
347
+ },
348
+
349
+ is_obj: function(value, nonempty) {
350
+ return this.is_type(value, this.obj, nonempty);
351
+ },
352
+
353
+ is_func: function(value) {
354
+ return this.is_type(value, this.func, false);
355
+ },
356
+
357
+ /**
358
+ * Checks if an object has a method
359
+ * @param obj Object to check
360
+ * @param string|array Names of methods to check for
361
+ * @return bool TRUE if method(s) exist, FALSE otherwise
362
+ */
363
+ is_method: function(obj, value) {
364
+ var ret = false;
365
+ if ( this.is_string(value) ) {
366
+ value = [value];
367
+ }
368
+ if ( this.in_obj(obj, value) ) {
369
+ var t = this;
370
+ $.each(value, function(idx, val) {
371
+ ret = ( t.is_func(obj[val]) ) ? true : false;
372
+ return ret;
373
+ });
374
+ }
375
+ return ret;
376
+ },
377
+
378
+ is_num: function(value, nonempty) {
379
+ return ( this.is_type(value, this.num, nonempty) && !isNaN(value) );
380
+ },
381
+
382
+ is_int: function(value, nonempty) {
383
+ return ( this.is_num(value, nonempty) && Math.floor(value) === value );
384
+ },
385
+
386
+ is_scalar: function(value, nonempty) {
387
+ return ( this.is_num(value, nonempty) || this.is_string(value, nonempty) || this.is_bool(value, nonempty) );
388
+ },
389
+
390
+ /**
391
+ * Checks if value is empty
392
+ * @param mixed value Value to check
393
+ * @param string type (optional) Data type
394
+ * @return bool TRUE if value is empty, FALSE if not empty
395
+ */
396
+ is_empty: function(value, type) {
397
+ var ret = false;
398
+ //Initial check for empty value
399
+ if ( !this.is_set(value) || null === value || false === value ) {
400
+ ret = true;
401
+ } else {
402
+ //Validate type
403
+ if ( !this.is_set(type) ) {
404
+ type = $.type(value);
405
+ }
406
+ //Type-based check
407
+ if ( this.is_type(value, type, false) ) {
408
+ switch ( type ) {
409
+ case this.string:
410
+ case this.array:
411
+ if ( value.length == 0 ) {
412
+ ret = true;
413
+ }
414
+ break;
415
+ case this.obj:
416
+ //Only evaluate literal objects
417
+ ret = ( $.isPlainObject(value) && !$.map(value, function(v, key) { return key; }).length );
418
+ break;
419
+ case this.num:
420
+ ret = ( value === 0 );
421
+ break;
422
+ }
423
+ } else {
424
+ ret = true;
425
+ }
426
+ }
427
+ return ret;
428
+ },
429
+
430
+ /**
431
+ * Check if object is a jQuery.Promise instance
432
+ * Will also match (but not guarantee) jQuery.Deferred instances
433
+ * @return bool TRUE if object is Promise/Deferred, FALSE otherwise
434
+ */
435
+ is_promise: function(obj) {
436
+ return ( this.is_obj(obj) && this.is_method(obj, ['then', 'done', 'always', 'fail', 'pipe']) )
437
+ },
438
+
439
+ /**
440
+ * Check if object is a jQuery.Deferred instance
441
+ */
442
+ is_deferred: function(obj) {
443
+ return ( this.is_promise(obj) && this.is_method(obj, ['resolve', 'reject', 'promise']));
444
+ },
445
+
446
+ /**
447
+ * Validate specified value's data type and return default value if necessary
448
+ * Data type of default value is used to determine data type
449
+ * @param mixed val Value to check
450
+ * @param mixed def Default value
451
+ * @return mixed Valid value
452
+ */
453
+ validate: function(val, def) {
454
+ return ( this.is_type(val, def, true) ) ? val : def;
455
+ },
456
+
457
+ /**
458
+ * Return formatted string
459
+ */
460
+ format: function(fmt, val) {
461
+ if ( !this.is_string(fmt) ) {
462
+ return '';
463
+ }
464
+ var params = [],
465
+ ph = '%s';
466
+ //Stop processing if no replacement values specified or format string contains no placeholders
467
+ if ( arguments.length < 2 || fmt.indexOf(ph) == -1 ) {
468
+ return fmt;
469
+ }
470
+ //Get replacement values
471
+ params = Array.prototype.slice.call(arguments, 1);
472
+
473
+ //Replace placeholders in string with parameters
474
+
475
+ //Replace all placeholders at once if single parameter set
476
+ if ( params.length == 1 ) {
477
+ fmt = fmt.replace(ph, params[0].toString());
478
+ } else {
479
+ var idx = 0,
480
+ len = params.length,
481
+ pos = 0;
482
+ while ( ( pos = fmt.indexOf(ph) ) && idx < len ) {
483
+ fmt = fmt.substr(0, pos) + params[idx].toString() + fmt.substr(pos + ph.length);
484
+ idx++;
485
+ }
486
+ //Remove any remaining placeholders
487
+ fmt = fmt.replace(ph, '');
488
+ }
489
+ return fmt;
490
+ },
491
+
492
+ /**
493
+ * Checks if key(s) exist in an object
494
+ * @param object obj Object to check
495
+ * @param string|array key Key(s) to check for in object
496
+ * @return bool TRUE if key(s) exist in object, FALSE otherwise
497
+ */
498
+ in_obj: function(obj, key, all) {
499
+ //Validate
500
+ if ( !this.is_bool(all) ) {
501
+ all = true;
502
+ }
503
+ if ( this.is_string(key) ) {
504
+ key = [key];
505
+ }
506
+ var ret = false;
507
+ if ( this.is_obj(obj) && this.is_array(key) ) {
508
+ var val;
509
+ for ( var x = 0; x < key.length; x++ ) {
510
+ val = key[x];
511
+ ret = ( this.is_string(val) && ( val in obj ) ) ? true : false;
512
+ //Stop processing if conditions have been met
513
+ if ( ( !all && ret ) || ( all && !ret ) ) {
514
+ break;
515
+ }
516
+ }
517
+ }
518
+ return ret;
519
+ },
520
+
521
+ /**
522
+ * Find common elements of 2 arrays
523
+ * @param array arr1 First array
524
+ * @param array arr2 Second array
525
+ * @return array Elements common to both arrays
526
+ */
527
+ arr_intersect: function(arr1, arr2) {
528
+ var ret = [];
529
+ if ( arr1 == arr2 ) {
530
+ return arr2;
531
+ }
532
+ if ( !$.isArray(arr2) || !arr2.length || !arr1.length ) {
533
+ return ret;
534
+ }
535
+ //Compare elements in arrays
536
+ var a1;
537
+ var a2;
538
+ var val;
539
+ if ( arr1.length < arr2.length ) {
540
+ a1 = arr1;
541
+ a2 = arr2;
542
+ } else {
543
+ a1 = arr2;
544
+ a2 = arr1;
545
+ }
546
+
547
+ for ( var x = 0; x < a1.length; x++ ) {
548
+ //Add mutual elements into intersection array
549
+ val = a1[x];
550
+ if ( a2.indexOf(val) != -1 && ret.indexOf(val) == -1 )
551
+ ret.push(val);
552
+ }
553
+
554
+ //Return intersection results
555
+ return ret;
556
+ }
557
+ }
558
+ };
559
+ var SLB_Base = Class.extend(Base);
560
+
561
+ //Init global object
562
+ var Core = {
563
+ /* Properties */
564
+
565
+ base: true,
566
+ context: [],
567
+
568
+ /**
569
+ * New object initializer
570
+ * @var obj
571
+ */
572
+ Class: SLB_Base,
573
+
574
+ /* Methods */
575
+
576
+ /**
577
+ * Setup client
578
+ * Set variables, DOM, etc.
579
+ */
580
+ setup_client: function() {
581
+ /* Quick Hide */
582
+ $('html').addClass(this.util.get_prefix());
583
+ }
584
+ };
585
+ var SLB_Core = SLB_Base.extend(Core);
586
+
587
+ this.SLB = new SLB_Core();
588
+
589
+ SLB.setup_client();
590
+
591
+ })(jQuery);
client/js/lib.view.js ADDED
@@ -0,0 +1,4649 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * View (Lightbox) functionality
3
+ * @package Simple Lightbox
4
+ * @subpackage View
5
+ * @author Archetyped
6
+ */
7
+
8
+ (function ($) {
9
+
10
+ if ( typeof SLB == 'undefined' || !SLB.attach ) {
11
+ return false;
12
+ }
13
+
14
+ /*-** Controller **-*/
15
+
16
+ var View = {
17
+
18
+ /* Properties */
19
+
20
+ /**
21
+ * Media item properties
22
+ * > Item key: Link URI
23
+ * > Base properties
24
+ * > id: WP Attachment ID
25
+ * > source: Source URI
26
+ * > title: Media title (generally WP attachment title)
27
+ * > desc: Media description (generally WP Attachment content)
28
+ * > type: Asset type (attachment, image, etc.)
29
+ */
30
+ assets: {},
31
+
32
+ /**
33
+ * Component types that can have default instances
34
+ * @var array
35
+ */
36
+ component_defaults: [],
37
+
38
+ /**
39
+ * Collection of jQuery.Deferred instances added during loading routine
40
+ * @var array
41
+ */
42
+ loading: [],
43
+
44
+ /* Component Collections */
45
+
46
+ viewers: {},
47
+ items: [],
48
+ content_handlers: {},
49
+ groups: {},
50
+ template_tags: {},
51
+
52
+ /**
53
+ * Collection/Data type mapping
54
+ * > Key: Collection name
55
+ * > Value: Data type
56
+ * @var object
57
+ */
58
+ collections: {},
59
+
60
+ /**
61
+ * Temporary component instances
62
+ * For use by controller when no component instance is available
63
+ * > Key: Component slug
64
+ * > Value: Component instance
65
+ */
66
+ component_temps: {},
67
+
68
+ /* Options */
69
+ options: {
70
+ ui_animate: true,
71
+ slideshow_enabled: true,
72
+ slideshow_autostart: false,
73
+ slideshow_duration: '6'
74
+ },
75
+
76
+ /* Methods */
77
+
78
+ /* Init */
79
+
80
+ update_refs: function() {
81
+ var c;
82
+ var r;
83
+ var ref;
84
+ for ( var p in this ) {
85
+ if ( !this.util.is_func(this[p]) || !( '_refs' in this[p].prototype ) ) {
86
+ continue;
87
+ }
88
+ //Set component
89
+ c = this[p];
90
+ if ( !this.util.is_empty(c.prototype._refs) ) {
91
+ for ( r in c.prototype._refs ) {
92
+ ref = c.prototype._refs[r];
93
+ if ( this.util.is_func(ref) ) {
94
+ continue;
95
+ }
96
+ if ( this.util.is_string(ref) && ref in this ) {
97
+ ref = c.prototype._refs[r] = this[ref];
98
+ }
99
+ if ( !this.util.is_func(ref) ) {
100
+ delete c.prototype_refs[r];
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ /* Initialize components */
107
+ this.init_components();
108
+ },
109
+
110
+ /**
111
+ * Initialization
112
+ */
113
+ init: function(options) {
114
+ var t = this;
115
+ $.when.apply($, this.loading).always(function() {
116
+ //Set options
117
+ $.extend(true, t.options, options);
118
+ //History
119
+ $(window).on('popstate', function(e) {
120
+ var state = e.originalEvent.state;
121
+ if ( t.util.in_obj(state, ['item', 'viewer']) ) {
122
+ var v = t.get_viewer(state.viewer);
123
+ v.history_handle(e);
124
+ return e.preventDefault();
125
+ }
126
+ });
127
+
128
+ /* Set defaults */
129
+
130
+ //Items
131
+ t.init_items();
132
+ });
133
+ },
134
+
135
+ init_components: function() {
136
+ this.collections = {
137
+ 'viewers': this.Viewer,
138
+ 'items': this.Content_Item,
139
+ 'content_handlers': this.Content_Handler,
140
+ 'groups': this.Group,
141
+ 'themes': this.Theme,
142
+ 'template_tags': this.Template_Tag
143
+ };
144
+
145
+ this.component_defaults = [
146
+ this.Viewer,
147
+ ];
148
+
149
+ },
150
+
151
+ /* Components */
152
+
153
+ component_make_default: function(type) {
154
+ var ret = false;
155
+ for ( var x = 0; x < this.component_defaults.length; x++ ) {
156
+ if ( type == this.component_defaults[x] ) {
157
+ ret = true;
158
+ break;
159
+ }
160
+ }
161
+ return ret;
162
+ },
163
+
164
+ /**
165
+ * Validates component type
166
+ * @param function comp Component type to check
167
+ * @return bool TRUE if param is valid component, FALSE otherwise
168
+ */
169
+ check_component: function(comp) {
170
+ //Validate component type
171
+ return ( this.util.is_func(comp) && ('_slug' in comp.prototype ) && ( comp.prototype instanceof (this.Component) ) ) ? true : false;
172
+ },
173
+
174
+ /**
175
+ * Retrieve collection of components of specified type
176
+ * @param function type Component type
177
+ * @return object|array|null Component collection (NULL if invalid)
178
+ */
179
+ get_components: function(type) {
180
+ var ret = null;
181
+ if ( this.util.is_func(type) ) {
182
+ //Determine collection
183
+ for ( var coll in this.collections ) {
184
+ if ( type == this.collections[coll] && coll in this ) {
185
+ ret = this[coll];
186
+ break;
187
+ }
188
+ }
189
+ }
190
+ return ret;
191
+ },
192
+
193
+ /**
194
+ * Retrieve component from specific collection
195
+ * @param function type Component type
196
+ * @param string id Component ID
197
+ * @return object|null Component reference (NULL if invalid)
198
+ */
199
+ get_component: function(type, id) {
200
+ var ret = null;
201
+ //Validate parameters
202
+ if ( !this.util.is_func(type) ) {
203
+ return ret;
204
+ }
205
+ //Sanitize id
206
+ if ( !this.util.is_string(id) ) {
207
+ id = null;
208
+ }
209
+
210
+ //Get component from collection
211
+ var coll = this.get_components(type);
212
+ if ( this.util.is_obj(coll) ) {
213
+ var tid = ( this.util.is_string(id) ) ? id : this.util.add_prefix('default');
214
+ if ( tid in coll ) {
215
+ ret = coll[tid];
216
+ }
217
+ }
218
+
219
+ //Default: Create default component
220
+ if ( this.util.is_empty(ret) ) {
221
+ if ( this.util.is_string(id) || this.component_make_default(type) ) {
222
+ ret = this.add_component(type, id);
223
+ }
224
+ }
225
+ //Return component
226
+ return ret;
227
+ },
228
+
229
+ /**
230
+ * Create new component instance and save to appropriate collection
231
+ * @param function type Component type to create
232
+ * @param string id ID of component
233
+ * @param object options Component initialization options (Default options used if default component is allowed)
234
+ * @return object|null New component (NULL if invalid)
235
+ */
236
+ add_component: function(type, id, options) {
237
+ //Validate type
238
+ if ( !this.util.is_func(type) ) {
239
+ return false;
240
+ }
241
+ //Validate request
242
+ if ( this.util.is_empty(id) && !this.component_make_default(type) ) {
243
+ return false;
244
+ }
245
+ //Defaults
246
+ var ret = null;
247
+ if ( this.util.is_empty(id) ) {
248
+ id = this.util.add_prefix('default');
249
+ }
250
+ if ( !this.util.is_obj(options) ) {
251
+ options = {};
252
+ }
253
+ //Check if specialized method exists for component type
254
+ var m = ( 'component' != type.prototype._slug ) ? 'add_' + type.prototype._slug : null;
255
+ if ( !this.util.is_empty(m) && ( m in this ) && this.util.is_func(this[m]) ) {
256
+ ret = this[m](id, options);
257
+ }
258
+ //Default process
259
+ else {
260
+ ret = new type(id, options);
261
+ }
262
+
263
+ //Add new component to collection
264
+ if ( this.util.is_type(ret, type) ) {
265
+ //Get collection
266
+ var coll = this.get_components(type);
267
+ //Add to collection
268
+ switch ( $.type(coll) ) {
269
+ case 'object' :
270
+ coll[id] = ret;
271
+ break;
272
+ case 'array' :
273
+ coll.push(ret);
274
+ break;
275
+ }
276
+ } else {
277
+ ret = null;
278
+ }
279
+ //Return new component
280
+ return ret;
281
+ },
282
+
283
+ /**
284
+ * Create new temporary component instance
285
+ * @param function type Component type
286
+ * @return New temporary instance
287
+ */
288
+ add_component_temp: function(type) {
289
+ var ret = null;
290
+ if ( this.check_component(type) ) {
291
+ //Create new instance
292
+ ret = new type('');
293
+ //Save to collection
294
+ this.component_temps[ret._slug] = ret;
295
+ }
296
+ return ret;
297
+ },
298
+
299
+ /**
300
+ * Retrieve temporary component instance
301
+ * Creates new temp component instance for type if not previously created
302
+ * @param function type Component type to retrieve temp instance for
303
+ * @return obj Temporary component instance
304
+ */
305
+ get_component_temp: function(type) {
306
+ return ( this.has_component_temp(type) ) ? this.component_temps[type.prototype._slug] : this.add_component_temp(type);
307
+ },
308
+
309
+ /**
310
+ * Check if temporary component instance exists
311
+ * @param function type Component type to check for
312
+ * @return bool TRUE if temp instance exists, FALSE otherwise
313
+ */
314
+ has_component_temp: function(type) {
315
+ return ( this.check_component(type) && ( type.prototype._slug in this.component_temps ) ) ? true : false;
316
+ },
317
+
318
+ /* Properties */
319
+
320
+ /**
321
+ * Retrieve specified options
322
+ * @param array opts Array of option names
323
+ * @return object Specified options (Default: empty object)
324
+ */
325
+ get_options: function(opts) {
326
+ var ret = {};
327
+ //Validate
328
+ if ( this.util.is_string(opts) ) {
329
+ opts = [opts];
330
+ }
331
+ if ( !this.util.is_array(opts) ) {
332
+ return ret;
333
+ }
334
+ //Get specified options
335
+ for ( var x = 0; x < opts.length; x++ ) {
336
+ //Skip if option not set
337
+ if ( !( opts[x] in this.options ) ) {
338
+ continue;
339
+ }
340
+ ret[ opts[x] ] = this.options[ opts[x] ];
341
+ }
342
+ return ret;
343
+ },
344
+
345
+ /**
346
+ * Retrieve option
347
+ * @uses View.options
348
+ * @param string opt Option to retrieve
349
+ * @param mixed def (optional) Default value if option does not exist (Default: NULL)
350
+ * @return mixed Option value
351
+ */
352
+ get_option: function(opt, def) {
353
+ var ret = this.get_options(opt);
354
+ if ( this.util.is_obj(ret) && ( opt in ret ) ) {
355
+ ret = ret[opt];
356
+ } else {
357
+ ret = ( this.util.is_set(def) ) ? def : null;
358
+ }
359
+ return ret;
360
+ },
361
+
362
+ /* Viewers */
363
+
364
+ /**
365
+ * Add viewer instance to collection
366
+ * @param string id Viewer ID
367
+ * @param obj options Viewer options
368
+ */
369
+ add_viewer: function(id, options) {
370
+ //Validate
371
+ if ( !this.util.is_string(id) ) {
372
+ return false;
373
+ }
374
+ if ( !this.util.is_obj(options, false) ) {
375
+ options = {};
376
+ }
377
+ //Create viewer
378
+ var v = new this.Viewer(id, options);
379
+ //Add to collection
380
+ this.viewers[v.get_id()] = v;
381
+ //Return viewer
382
+ return v;
383
+ },
384
+
385
+ /**
386
+ * Retrieve all viewer instances
387
+ * @return obj Viewer instances
388
+ */
389
+ get_viewers: function() {
390
+ return this.viewers;
391
+ },
392
+
393
+ /**
394
+ * Check if viewer exists
395
+ * @param string v Viewer ID
396
+ * @return bool TRUE if viewer exists, FALSE otherwise
397
+ */
398
+ has_viewer: function(v) {
399
+ return ( this.util.is_string(v) && v in this.get_viewers() ) ? true : false;
400
+ },
401
+
402
+ /**
403
+ * Retrieve Viewer instance
404
+ * Default viewer retrieved if specified viewer does not exist
405
+ * > Default viewer created if necessary
406
+ * @param string v Viewer ID to retrieve
407
+ * @return Viewer Viewer instance
408
+ */
409
+ get_viewer: function(v) {
410
+ //Retrieve default viewer if specified viewer not set
411
+ if ( !this.has_viewer(v) ) {
412
+ v = this.util.add_prefix('default');
413
+ //Create default viewer if necessary
414
+ if ( !this.has_viewer(v) ) {
415
+ this.add_viewer(v);
416
+ }
417
+ }
418
+ return this.get_viewers()[v];
419
+ },
420
+
421
+ /* Items */
422
+
423
+ /**
424
+ * Set event handlers
425
+ */
426
+ init_items: function() {
427
+ //Define handler
428
+ var t = this;
429
+ var handler = function() {
430
+ var ret = t.show_item(this);
431
+ if ( !t.util.is_bool(ret) ) {
432
+ ret = true;
433
+ }
434
+ return !ret;
435
+ };
436
+
437
+ //Get activated links
438
+ var sel = this.util.format('a[href][%s="%s"]', this.util.get_attribute('active'), 1);
439
+ //Add event handler
440
+ $(document).on('click', sel, handler);
441
+ },
442
+
443
+ get_items: function() {
444
+ return this.get_components(this.Content_Item);
445
+ },
446
+
447
+ /**
448
+ * Retrieve specific Content_Item instance
449
+ * @param mixed Item reference
450
+ * > Content_Item: Item instance (returned immediately)
451
+ * > DOM element: DOM element to get item for
452
+ * > int: Index of cached item
453
+ * @return Content_Item Item instance for DOM node
454
+ */
455
+ get_item: function(ref) {
456
+ //Evaluate reference type
457
+
458
+ //Content Item instance
459
+ if ( this.util.is_type(ref, this.Content_Item) ) {
460
+ return ref;
461
+ }
462
+ //Retrieve item instance
463
+ var item = null;
464
+
465
+ //DOM element
466
+ if ( this.util.in_obj(ref, 'nodeType') ) {
467
+ //Check if item instance attached to element
468
+ var key = this.get_component_temp(this.Content_Item).get_data_key();
469
+ item = $(ref).data(key);
470
+ }
471
+ //Cached item (index)
472
+ else if ( this.util.is_int(ref, false) ) {
473
+ var items = this.get_items();
474
+ if ( items.length > ref ) {
475
+ item = items[ref];
476
+ }
477
+ }
478
+ //Create default item instance
479
+ if ( !this.util.is_type(item, this.Content_Item) ) {
480
+ item = this.add_item(ref);
481
+ }
482
+ return item;
483
+ },
484
+
485
+ /**
486
+ * Create new item instance
487
+ * @param obj el DOM element representing item
488
+ * @return Content_Item New item instance
489
+ */
490
+ add_item: function(el) {
491
+ var item = new this.Content_Item(el);
492
+ return item;
493
+ },
494
+
495
+ /**
496
+ * Display item in viewer
497
+ * @param obj el DOM element representing item
498
+ */
499
+ show_item: function(el) {
500
+ var ret = this.get_item(el).show();
501
+ return ret;
502
+ },
503
+
504
+ /**
505
+ * Cache item instance
506
+ * @uses this.items to store cached items
507
+ * @param Content_Item item Item to cache
508
+ * @return int Index of item in cache
509
+ */
510
+ save_item: function(item) {
511
+ var ret = -1;
512
+ if ( !this.util.is_type(item, this.Content_Item) ) {
513
+ return ret;
514
+ }
515
+ var prop = 'items';
516
+ var items = this.get_items();
517
+ //Check if item exists in collection
518
+ ret = $.inArray(item, items);
519
+ //Cache item
520
+ if ( -1 == ret ) {
521
+ ret = items.push(item) - 1;
522
+ }
523
+ //Return item index in cache
524
+ return ret;
525
+ },
526
+
527
+ /* Content Handler */
528
+
529
+ get_content_handlers: function() {
530
+ return this.get_components(this.Content_Handler);
531
+ },
532
+
533
+ /**
534
+ * Find matching content handler for item
535
+ * @param Content_Item|string item Item to find handler for (or ID of Handler)
536
+ * @return Content_Handler|null Matching content handler (NULL if no matching handler found)
537
+ */
538
+ get_content_handler: function(item) {
539
+ //Determine handler to retrieve
540
+ var type = ( this.util.is_type(item, this.Content_Item) ) ? item.get_attribute('type', '') : item.toString();
541
+ //Retrieve handler
542
+ var types = this.get_content_handlers();
543
+ return ( type in types ) ? types[type] : null;
544
+ },
545
+
546
+ /**
547
+ * Add Content Handler
548
+ * @param string id Handler ID
549
+ * @param obj attributes (optional) Handler properties/methods
550
+ */
551
+ add_content_handler: function(id, attributes) {
552
+ //Validate
553
+ if ( !this.util.is_string(id) ) {
554
+ return false;
555
+ }
556
+ var dfr = $.Deferred();
557
+ var t = this;
558
+
559
+ if ( !this.util.is_obj(attributes, false) ) {
560
+ //Check for URI (external loading)
561
+ if ( this.util.is_string(attributes) ) {
562
+ $.get(attributes).always(function(data, textStatus) {
563
+ var r = null;
564
+ try {
565
+ eval('r = ' + data);
566
+ } catch (e) {}
567
+ if ( !t.util.is_obj(r) ) {
568
+ r = {};
569
+ }
570
+ dfr.resolve(r);
571
+ });
572
+ } else {
573
+ dfr.resolve({});
574
+ }
575
+ } else {
576
+ dfr.resolve(attributes);
577
+ }
578
+
579
+ dfr.done(function(o) {
580
+ //Save
581
+ var types = t.get_components(t.Content_Handler);
582
+ if ( !t.util.is_obj(types, false) ) {
583
+ types = {};
584
+ }
585
+ types[id] = new t.Content_Handler(id, o);
586
+ });
587
+ },
588
+
589
+ /**
590
+ * Update content handler
591
+ * @param string id Handler to update
592
+ * @param obj attr Variable number of attribute objects to add handlera
593
+ */
594
+ update_content_handler: function(id, attr) {
595
+ if ( !this.util.is_string(id) || !this.util.is_obj(attr) ) {
596
+ return false;
597
+ }
598
+ //Get existing handler
599
+ var h = this.get_content_handler(id);
600
+ if ( null == h ) {
601
+ return false;
602
+ }
603
+ //Add additional attributes
604
+ h.set_attributes(attr);
605
+ },
606
+
607
+ /* Group */
608
+
609
+ /**
610
+ * Add new group
611
+ * @param string g Group ID
612
+ * > If group with same ID already set, new group replaces existing one
613
+ * @param object attrs (optional) Group attributes
614
+ */
615
+ add_group: function(g, attrs) {
616
+ //Create new group
617
+ g = new this.Group(g, attrs);
618
+ //Add group to collection
619
+ if ( this.util.is_string(g.get_id()) ) {
620
+ this.groups[g.get_id()] = g;
621
+ }
622
+ },
623
+
624
+ /**
625
+ * Retrieve groups
626
+ * @uses groups property
627
+ * @return object Registered groups
628
+ */
629
+ get_groups: function() {
630
+ return this.groups;
631
+ },
632
+
633
+ /**
634
+ * Retrieve specified group
635
+ * @param string g Group ID
636
+ * @return object|null Group instance (NULL if group does not exist)
637
+ */
638
+ get_group: function(g) {
639
+ if ( this.util.is_string(g) ) {
640
+ if ( !this.has_group(g) ) {
641
+ //Add new group (if necessary)
642
+ this.add_group(g);
643
+ }
644
+ //Retrieve group
645
+ g = this.get_groups()[g];
646
+ }
647
+ return ( this.util.is_type(g, this.Group) ) ? g : null;
648
+ },
649
+
650
+ /**
651
+ * Checks if group is registered
652
+ * @uses get_groups() to retrieve registered groups
653
+ * @return bool TRUE if group exists, FALSE otherwise
654
+ */
655
+ has_group: function(g) {
656
+ return ( this.util.is_string(g) && ( g in this.get_groups() ) ) ? true : false;
657
+ },
658
+
659
+ /* Theme */
660
+
661
+ /**
662
+ * Add theme
663
+ * @param string name Theme name
664
+ * @param obj attr Theme options
665
+ * > Multiple attribute parameters are merged
666
+ * @return obj Theme model
667
+ */
668
+ add_theme: function(id, attr) {
669
+ var t = this;
670
+ //Validate
671
+ if ( !this.util.is_string(id) ) {
672
+ return false;
673
+ }
674
+ var dfr = $.Deferred();
675
+ this.loading.push(dfr);
676
+
677
+ //Build attributes
678
+ var attrs = [{'parent': null}];
679
+ if ( arguments.length >= 2 ) {
680
+ var args = Array.prototype.slice.call(arguments, 1);
681
+ var t = this;
682
+ $.each(args, function(idx, arg) {
683
+ if ( t.util.is_obj(arg) ) {
684
+ attrs.push(arg)
685
+ }
686
+ });
687
+ }
688
+
689
+ //Set ID
690
+ attrs.push({'id': id});
691
+
692
+ //Create theme model
693
+ var model = $.extend.apply(null, attrs);
694
+
695
+ //Connect to parent model
696
+ if ( this.util.is_string(model.parent) ) {
697
+ model.parent = this.get_theme_model(model.parent);
698
+ }
699
+
700
+ /* Process attributes */
701
+
702
+ //Layout
703
+ var prop_layout = 'layout_uri';
704
+ var dfr_layout = $.Deferred();
705
+ if ( prop_layout in model && this.util.is_string(model[prop_layout]) ) {
706
+ $.get(model[prop_layout]).always(function(data) {
707
+ //Set layout (raw) attribute
708
+ if ( t.util.is_string(data) ) {
709
+ model['layout_raw'] = data;
710
+ }
711
+ dfr_layout.resolve();
712
+ });
713
+ } else {
714
+ dfr_layout.resolve();
715
+ }
716
+
717
+ //Attributes (external)
718
+ var prop_script = 'client_script';
719
+ var dfr_script = $.Deferred();
720
+ if ( prop_script in model && this.util.is_string(model[prop_script]) ) {
721
+ //Retrieve client script
722
+ $.get(model[prop_script]).always(function(data) {
723
+ var r = null;
724
+ try {
725
+ eval('r = ' + data);
726
+ } catch (e) {}
727
+ if ( t.util.is_obj(r) ) {
728
+ //Add attributes to model
729
+ $.extend(model, r);
730
+ }
731
+ dfr_script.resolve();
732
+ });
733
+ } else {
734
+ dfr_script.resolve();
735
+ }
736
+
737
+ //Styles
738
+ var prop_style = 'client_style';
739
+ if ( prop_style in model && this.util.is_string(model[prop_style]) ) {
740
+ //Add stylesheet to document
741
+ $('<link />', {
742
+ 'id': 'theme_style_' + model.id,
743
+ 'href': model[prop_style],
744
+ 'type': 'text/css',
745
+ 'rel': 'stylesheet'
746
+ }).appendTo('body');
747
+ }
748
+
749
+ //Complete loading when all components loaded
750
+ $.when(dfr_layout, dfr_script).always(function() {
751
+ dfr.resolve();
752
+ });
753
+ //Add theme model
754
+ this.Theme.prototype._models[id] = model;
755
+ return model;
756
+ },
757
+
758
+ /**
759
+ * Update theme model
760
+ * @param string id Theme to update
761
+ * @param obj attr Variable number of attribute objects to add to model
762
+ */
763
+ update_theme: function(id, attr) {
764
+ var model = this.get_theme_model(id);
765
+ var args = Array.prototype.slice.call(arguments);
766
+ if ( this.util.is_empty(model) ) {
767
+ model = this.add_model.apply(this, args);
768
+ } else {
769
+ //Process attributes
770
+ args.shift();
771
+ var attrs = [];
772
+ var t = this;
773
+ $.each(args, function(idx, arg) {
774
+ if ( t.util.is_obj(arg) ) {
775
+ attrs.push(arg);
776
+ }
777
+ });
778
+ //Merge attributes into model
779
+ attrs.unshift(model);
780
+ $.extend.apply($, attrs);
781
+ }
782
+ return model;
783
+ },
784
+
785
+ /**
786
+ * Retrieve theme models
787
+ * @return obj Theme models
788
+ */
789
+ get_theme_models: function() {
790
+ //Retrieve matching theme model
791
+ return this.Theme.prototype._models;
792
+ },
793
+
794
+ /**
795
+ * Retrieve theme model
796
+ * @param string id Theme to retrieve
797
+ * @return obj Theme model (Default: empty object)
798
+ */
799
+ get_theme_model: function(id) {
800
+ var ms = this.get_theme_models();
801
+ return ( this.util.in_obj(ms, id) ) ? ms[id] : {};
802
+ },
803
+
804
+ /**
805
+ * Add Theme Tag Handler to Theme prototype
806
+ * @param string id Unique ID
807
+ * @param obj attrs (optional) Default tag attributes/values (or URI to attributes definition)
808
+ */
809
+ add_template_tag_handler: function(id, attributes) {
810
+ //Validate
811
+ if ( !this.util.is_string(id) ) {
812
+ return false;
813
+ }
814
+ var dfr = $.Deferred();
815
+ var t = this;
816
+
817
+ if ( !this.util.is_obj(attributes, false) ) {
818
+ //Check for URI (external loading)
819
+ if ( this.util.is_string(attributes) ) {
820
+ $.get(attributes).always(function(data) {
821
+ var r = null;
822
+ try {
823
+ eval('r = ' + data);
824
+ } catch (e) {}
825
+ if ( !t.util.is_obj(r) ) {
826
+ r = {};
827
+ }
828
+ dfr.resolve(r);
829
+ });
830
+ } else {
831
+ dfr.resolve({});
832
+ }
833
+ } else {
834
+ dfr.resolve(attributes);
835
+ }
836
+
837
+ dfr.done(function(o) {
838
+ //Add handler
839
+ t.get_template_tag_handlers()[id] = new t.Template_Tag_Handler(id, o);
840
+ });
841
+ },
842
+
843
+ /**
844
+ * Retrieve Template Tag Handler collection
845
+ * @return obj Template_Tag_Handler objects
846
+ */
847
+ get_template_tag_handlers: function() {
848
+ return this.Template_Tag.prototype.handlers;
849
+ },
850
+
851
+ /**
852
+ * Retrieve template tag handler
853
+ * @param string id ID of tag handler to retrieve
854
+ * @return Template_Tag_Handler Tag Handler instance (new instance for invalid ID)
855
+ */
856
+ get_template_tag_handler: function(id) {
857
+ var handlers = this.get_template_tag_handlers();
858
+ //Retrieve existing handler
859
+ if ( this.util.is_string(id) && ( id in handlers ) ) {
860
+ return handlers[id];
861
+ }
862
+ //Default: Return empty handler
863
+ return new this.Template_Tag_Handler(id, {});
864
+ }
865
+ };
866
+
867
+ /* Components */
868
+ var Component = {
869
+ /*-** Properties **-*/
870
+
871
+ /* Internal/Configuration */
872
+
873
+ /**
874
+ * Base name of component type
875
+ * @var string
876
+ */
877
+ _slug: 'component',
878
+
879
+ /**
880
+ * Valid component references for current component
881
+ * > Key (string): Property name that stores reference
882
+ * > Value (function): Data type of component
883
+ * @var object
884
+ */
885
+ _refs: {},
886
+
887
+ /**
888
+ * Components that may contain current object
889
+ * Used for retrieving data from a parent object
890
+ * Example: An Item may be contained by a Group
891
+ * > Value (strong): Property name of container component
892
+ * @var array
893
+ */
894
+ _containers: [],
895
+
896
+ /**
897
+ * Whether DOM element and component are connected in 1:1 relationship
898
+ * Some components will be assigned to different DOM elements depending on usage
899
+ * @var bool
900
+ */
901
+ _reciprocal: false,
902
+
903
+ /**
904
+ * DOM Element tied to component
905
+ * @var DOM Element
906
+ */
907
+ _dom: null,
908
+
909
+ /**
910
+ * Default attributes
911
+ * @var object
912
+ */
913
+ _attr_default: {},
914
+
915
+ /**
916
+ * Attributes passed to constructor
917
+ * @var obj
918
+ */
919
+ _attr_init: null,
920
+
921
+ /**
922
+ * Attributes to retrieve from parent (controller)
923
+ * @var array
924
+ */
925
+ _attr_parent: [],
926
+
927
+ /**
928
+ * Defines how parent properties should be remapped to component properties
929
+ * @var object
930
+ */
931
+ _attr_map: {},
932
+
933
+ /**
934
+ * Event handlers
935
+ * @var object
936
+ * > Key: string Event type
937
+ * > Value: array Handlers
938
+ */
939
+ _events: null,
940
+
941
+ /**
942
+ * Status management
943
+ * @var object
944
+ * > Key: Status ID
945
+ * > Value: Status value
946
+ */
947
+ _status: null,
948
+
949
+ /* Public */
950
+
951
+ attributes: false,
952
+
953
+ /**
954
+ * Component ID
955
+ * @var string
956
+ */
957
+ id: '',
958
+
959
+ /* Init */
960
+
961
+ _c: function(id, attributes) {
962
+ //Set ID
963
+ this.set_id(id);
964
+ //Save init attributes
965
+ this._attr_init = attributes;
966
+ this.register_hooks();
967
+ },
968
+
969
+ _set_parent: function() {
970
+ this._parent = View;
971
+ this.util._parent = this;
972
+ },
973
+
974
+ /**
975
+ * Register hooks on init
976
+ * Placeholder method to be overridden by child classes
977
+ */
978
+ register_hooks: function() {},
979
+
980
+ /* Methods */
981
+
982
+ /* Properties */
983
+
984
+ /**
985
+ * Retrieve status
986
+ * @param string id Status to retrieve
987
+ * @param bool raw (optional) Retrieve raw value (Default: FALSE)
988
+ * @return mixed Status value (Default: bool)
989
+ */
990
+ get_status: function(id, raw) {
991
+ var ret = false;
992
+ if ( this.util.in_obj(this._status, id) ) {
993
+ ret = ( !!raw ) ? this._status[id] : !!this._status[id];
994
+ }
995
+ return ret;
996
+ },
997
+
998
+ /**
999
+ * Set status
1000
+ * @param string id Status to retrieve
1001
+ * @param mixed val Status value (Default: TRUE)
1002
+ * @return mixed Status value (Default: bool)
1003
+ */
1004
+ set_status: function(id, val) {
1005
+ //Validate
1006
+ if ( this.util.is_string(id) ) {
1007
+ if ( !this.util.is_set(val) ) {
1008
+ val = true;
1009
+ }
1010
+ //Initialize property
1011
+ if ( !this.util.is_obj(this._status, false) ) {
1012
+ this._status = {};
1013
+ }
1014
+ //Set status
1015
+ this._status[id] = val;
1016
+ } else if ( !this.util.is_set(val) ) {
1017
+ val = false;
1018
+ }
1019
+ return val;
1020
+ },
1021
+
1022
+ /**
1023
+ * Retrieve instance ID
1024
+ * @uses id as ID base
1025
+ * @uses _slug to add namespace (if necessary)
1026
+ * @param bool ns (optional) Whether or not to namespace ID (Default: FALSE)
1027
+ * @return string Instance ID
1028
+ */
1029
+ get_id: function(ns) {
1030
+ //Validate
1031
+ if ( !this.check_id() ) {
1032
+ this.id = '';
1033
+ }
1034
+ var id = this.id;
1035
+ //Namespace ID
1036
+ if ( this.util.is_bool(ns) && ns ) {
1037
+ id = this.add_ns(id);
1038
+ }
1039
+
1040
+ return id;
1041
+ },
1042
+
1043
+ /**
1044
+ * Set instance ID
1045
+ * @param string id Unique ID
1046
+ */
1047
+ set_id: function(id) {
1048
+ this.id = ( this.check_id(id) ) ? id : '';
1049
+ },
1050
+
1051
+ /**
1052
+ * Validate ID value
1053
+ * @param string id (optional) ID value (Default: Component ID)
1054
+ * @param bool nonempty (optional) TRUE if it should also check for empty strings, FALSE otherwise (Default: FALSE)
1055
+ * @return bool TRUE if ID is valid, FALSE otherwise
1056
+ */
1057
+ check_id: function(id, nonempty) {
1058
+ //Validate
1059
+ if ( arguments.length == 1 && this.util.is_bool(arguments[0]) ) {
1060
+ nonempty = arguments[0];
1061
+ id = null;
1062
+ }
1063
+ if ( this.util.is_empty(id) ) {
1064
+ id = this.id;
1065
+ }
1066
+ if ( !this.util.is_bool(nonempty) ) {
1067
+ nonempty = false;
1068
+ }
1069
+ return ( this.util.is_string(id, nonempty) ) ? true : false;
1070
+ },
1071
+
1072
+ /**
1073
+ * Get namespace
1074
+ * @uses _slug for namespace segment
1075
+ * @uses Util.add_prefix() to prefix slug
1076
+ * @return string Component namespace
1077
+ */
1078
+ get_ns: function() {
1079
+ return this.util.add_prefix(this._slug);
1080
+ },
1081
+
1082
+ /**
1083
+ * Add namespace to value
1084
+ * @param string val Value to namespace
1085
+ * @return string Namespaced value (Empty string if invalid value provided)
1086
+ */
1087
+ add_ns: function(val) {
1088
+ return ( this.util.is_string(val) ) ? this.get_ns() + '_' + val : '';
1089
+ },
1090
+
1091
+ /* Components */
1092
+
1093
+ /**
1094
+ * Retrieve component containers
1095
+ * @uses _container property
1096
+ * @return array Component containers
1097
+ */
1098
+ get_containers: function() {
1099
+ //Sanitize property
1100
+ if ( !this.util.is_array(this._containers) ) {
1101
+ this._containers = [];
1102
+ }
1103
+ //Return value
1104
+ return this._containers;
1105
+ },
1106
+
1107
+ /**
1108
+ * Check if current object has potential container objects
1109
+ * @return bool TRUE if containers exist, FALSE otherwise
1110
+ */
1111
+ has_containers: function() {
1112
+ return ( this.get_containers().length > 0 );
1113
+ },
1114
+
1115
+ /**
1116
+ * Check if reference exists in object
1117
+ * @param string ref Reference ID
1118
+ * @return bool TRUE if reference exists, FALSE otherwise
1119
+ */
1120
+ has_reference: function(ref) {
1121
+ return ( this.util.is_string(ref) && ( ref in this ) && ( ref in this.get_references() ) ) ? true : false;
1122
+ },
1123
+
1124
+ /**
1125
+ * Retrieve object references
1126
+ * @uses _refs
1127
+ * @return obj References object
1128
+
1129
+ */
1130
+ get_references: function() {
1131
+ return this._refs;
1132
+ },
1133
+
1134
+ /**
1135
+ * Retrieve reference data type
1136
+ * @param string ref Reference ID
1137
+ * @return function Reference data type (NULL if invalid)
1138
+ */
1139
+ get_reference: function(ref) {
1140
+ return ( this.has_reference(ref) ) ? this._refs[ref] : null;
1141
+ },
1142
+
1143
+ /**
1144
+ * Checks if component is valid
1145
+ * @param obj|string Component instance or ID
1146
+ * > If ID is specified then it will check for component on current instance
1147
+ * @param function|string ctype Component type
1148
+ * > If component is an object, then ctype is required
1149
+ * > If component is string ID, then ctype is optional (Default: reference type)
1150
+ * > If ctype is a function, then it is compared to component directly
1151
+ * > If ctype is a string, then the component reference type is retrieved
1152
+ * @uses get_reference()
1153
+ * @return bool TRUE if component is valid, FALSE otherwise
1154
+ */
1155
+ check_component: function(comp, ctype) {
1156
+ //Validate
1157
+ if ( this.util.is_empty(comp)
1158
+ || ( this.util.is_obj(comp) && !this.util.is_func(ctype) )
1159
+ || ( this.util.is_string(comp) && !this.has_reference(comp) )
1160
+ || ( this.util.is_empty(ctype) && !this.util.is_string(comp) )
1161
+ || ( !this.util.is_obj(comp) && !this.util.is_string(comp) )
1162
+ ) {
1163
+ return false;
1164
+ }
1165
+ //Get component type
1166
+ if ( !this.util.is_func(ctype) ) {
1167
+ //Component is a string ID
1168
+ ctype = this.get_reference(comp);
1169
+ }
1170
+ //Get component instance
1171
+ if ( this.util.is_string(comp) ) {
1172
+ comp = this.get_component(comp, false);
1173
+ }
1174
+ return this.util.is_type(comp, ctype);
1175
+ },
1176
+
1177
+ /**
1178
+ * Retrieve component reference from current object
1179
+ * > Procedure:
1180
+ * > Check if property already set
1181
+ * > Check attributes
1182
+ * > Check container object(s)
1183
+ * > Check parent object (controller)
1184
+ * @uses _containers to check potential container components for references
1185
+ * @param string cname Component name
1186
+ * @param bool check_attr (optional) Whether or not to check instance attributes for component (Default: TRUE)
1187
+ * @param bool get_default (optional) Whether or not to retrieve default object from controller if none exists in current instance (Default: TRUE)
1188
+ * @param bool recursive (optional) Whether or not to check containers for specified component reference (Default: TRUE)
1189
+ * @return object|null Component reference (NULL if no component found)
1190
+ */
1191
+ get_component: function(cname, check_attr, get_default, recursive) {
1192
+ var c = null;
1193
+ //Validate request
1194
+ if ( !this.util.is_string(cname) || !( cname in this ) || !this.has_reference(cname) ) {
1195
+ return c;
1196
+ }
1197
+
1198
+ //Normalize parameters
1199
+ if ( !this.util.is_bool(check_attr) ) {
1200
+ check_attr = true;
1201
+ }
1202
+ if ( !this.util.is_bool(get_default) ) {
1203
+ get_default = true;
1204
+ }
1205
+ if ( !this.util.is_bool(recursive) ) {
1206
+ recursive = true;
1207
+ }
1208
+ var ctype = this._refs[cname];
1209
+ //Phase 1: Check if component reference previously set
1210
+ if ( this.util.is_type(this[cname], ctype) ) {
1211
+ return this[cname];
1212
+ }
1213
+ //If reference not set, iterate through component hierarchy until reference is found
1214
+ c = this[cname] = null;
1215
+
1216
+ //Phase 2: Check attributes
1217
+ if ( check_attr ) {
1218
+ c = this.get_attribute(cname);
1219
+ //Save object-specific component reference
1220
+ if ( !this.util.is_empty(c) ) {
1221
+ c = this.set_component(cname, c);
1222
+ }
1223
+ }
1224
+
1225
+ //Phase 3: Check Container(s)
1226
+ if ( recursive && this.util.is_empty(c) && this.has_containers() ) {
1227
+ var containers = this.get_containers();
1228
+ var con = null;
1229
+ for ( var i = 0; i < containers.length; i++ ) {
1230
+ con = containers[i];
1231
+ //Validate container
1232
+ if ( con == cname ) {
1233
+ continue;
1234
+ }
1235
+ //Retrieve container
1236
+ con = this.get_component(con, true, false);
1237
+ if ( this.util.is_empty(con) ) {
1238
+ continue;
1239
+ }
1240
+ //Attempt to retrieve component from container
1241
+ c = con.get_component(cname);
1242
+ //Stop iterating if valid component found
1243
+ if ( !this.util.is_empty(c) ) {
1244
+ break;
1245
+ }
1246
+ }
1247
+ }
1248
+
1249
+ //Phase 4: From controller (optional)
1250
+ if ( get_default && this.util.is_empty(c) ) {
1251
+ c = this.get_parent().get_component(ctype);
1252
+ }
1253
+ return c;
1254
+ },
1255
+
1256
+ /**
1257
+ * Sets component reference on current object
1258
+ * > Component property reset (set to NULL) if invalid component supplied
1259
+ * @param string name Name of property to set component on object
1260
+ * @param string|object ref Component or Component ID (to be retrieved from controller)
1261
+ * @param function validate (optional) Additional validation to be performed (Must return bool: TRUE (Valid)/FALSE (Invalid))
1262
+ * @return object Component (NULL if invalid)
1263
+ */
1264
+ set_component: function(name, ref, validate) {
1265
+ var clear = null;
1266
+ //Make sure component property exists
1267
+ if ( !this.has_reference(name) ) {
1268
+ return clear;
1269
+ }
1270
+ //Normalize reference
1271
+ if ( this.util.is_empty(ref) ) {
1272
+ ref = clear;
1273
+ }
1274
+ var ctype = this.get_reference(name);
1275
+
1276
+ //Get component from controller if ID supplied
1277
+ if ( this.util.is_string(ref) ) {
1278
+ ref = this.get_parent().get_component(ctype, ref);
1279
+ }
1280
+
1281
+ if ( !this.util.is_type(ref, ctype) ) {
1282
+ ref = clear;
1283
+ }
1284
+
1285
+ //Additional validation
1286
+ if ( !this.util.is_empty(ref) && this.util.is_func(validate) && !validate.call(this, ref) ) {
1287
+ ref = clear;
1288
+ }
1289
+ //Set (or clear) component reference
1290
+ this[name] = ref;
1291
+ //Return value for confirmation
1292
+ return this[name];
1293
+ },
1294
+
1295
+ /* Attributes */
1296
+
1297
+ /**
1298
+ * Initializes attributes
1299
+ */
1300
+ init_attributes: function(force) {
1301
+ if ( !this.util.is_bool(force) ) {
1302
+ force = false;
1303
+ }
1304
+ if ( force || !this.util.is_obj(this.attributes) ) {
1305
+ this.attributes = {};
1306
+ //Build attribute groups
1307
+ var attrs = [{}];
1308
+ attrs.push(this.init_default_attributes());
1309
+ if ( this.dom_has() ) {
1310
+ attrs.push(this.get_dom_attributes());
1311
+ }
1312
+ if ( this.util.is_obj(this._attr_init) ) {
1313
+ attrs.push(this._attr_init);
1314
+ }
1315
+ //Merge attributes
1316
+ this.attributes = $.extend.apply(null, attrs);
1317
+ }
1318
+ },
1319
+
1320
+ /**
1321
+ * Generate default attributes for component
1322
+ * @uses _attr_parent to determine options to retrieve from controller
1323
+ * @uses View.get_options() to get values from controller
1324
+ * @uses _attr_map to Remap controller attributes to instance attributes
1325
+ * @uses _attr_default to Store default attributes
1326
+ */
1327
+ init_default_attributes: function() {
1328
+ //Get parent options
1329
+ var opts = this.get_parent().get_options(this._attr_parent);
1330
+ if ( this.util.is_obj(opts) ) {
1331
+ //Remap
1332
+ for ( var opt in this._attr_map ) {
1333
+ if ( opt in opts ) {
1334
+ //Move value to new property
1335
+ opts[this._attr_map[opt]] = opts[opt];
1336
+ //Delete old property
1337
+ delete opts[opt];
1338
+ }
1339
+ }
1340
+ //Merge with default attributes
1341
+ $.extend(true, this._attr_default, opts);
1342
+ }
1343
+ return this._attr_default;
1344
+ },
1345
+
1346
+ /**
1347
+ * Retrieve DOM attributes
1348
+ */
1349
+ get_dom_attributes: function() {
1350
+ var attrs = {};
1351
+ var el = this.dom_get();
1352
+ if ( el.length ) {
1353
+ //Get attributes from element
1354
+ var opts = $(el).get(0).attributes;
1355
+ if ( this.util.is_obj(opts) ) {
1356
+ var attr_prefix = this.util.get_attribute();
1357
+ $.each(opts, function(idx, opt) {
1358
+ if ( opt.name.indexOf( attr_prefix ) == -1 ) {
1359
+ return true;
1360
+ }
1361
+ //Process custom attributes
1362
+ //Strip prefix
1363
+ var key = opt.name.substr(attr_prefix.length + 1);
1364
+ attrs[key] = opt.value;
1365
+ });
1366
+ }
1367
+ }
1368
+ return attrs;
1369
+ },
1370
+
1371
+ /**
1372
+ * Retrieve all instance attributes
1373
+ * @uses parse_attributes() to initialize attributes (if necessary)
1374
+ * @uses attributes
1375
+ * @return obj Attributes
1376
+ */
1377
+ get_attributes: function() {
1378
+ //Initilize attributes
1379
+ this.init_attributes();
1380
+ //Return attributes
1381
+ return this.attributes;
1382
+ },
1383
+
1384
+ /**
1385
+ * Retrieve value of specified attribute for value
1386
+ * @param string key Attribute to retrieve
1387
+ * @param mixed def (optional) Default value if attribute is not set
1388
+ * @param bool enforce_type (optional) Whether data type should match default value (Default: TRUE)
1389
+ * > If possible, attribute value will be converted to match default data type
1390
+ * > If attribute value cannot match default data type, default value will be used
1391
+ * @return mixed Attribute value (NULL if attribute is not set)
1392
+ */
1393
+ get_attribute: function(key, def, enforce_type) {
1394
+ //Validate
1395
+ if ( !this.util.is_set(def) ) {
1396
+ def = null;
1397
+ }
1398
+ if ( !this.util.is_string(key) ) {
1399
+ return def;
1400
+ }
1401
+ if ( !this.util.is_bool(enforce_type) ) {
1402
+ enforce_type = true;
1403
+ }
1404
+ //Get attribute value
1405
+ var ret = ( this.has_attribute(key) ) ? this.get_attributes()[key] : def;
1406
+ //Validate type
1407
+ if ( enforce_type && ret !== def && null !== def && !this.util.is_type(ret, $.type(def), false) ) {
1408
+ //Convert type
1409
+ //Scalar default
1410
+ if ( this.util.is_scalar(def, false) ) {
1411
+ //Non-scalar attribute
1412
+ if ( !this.util.is_scalar(ret, false) ) {
1413
+ ret = def;
1414
+ } else if ( this.util.is_string(def, false) ) {
1415
+ ret = ret.toString();
1416
+ } else if ( this.util.is_num(def, false) && !this.util.is_num(ret, false) ) {
1417
+ ret = ( this.util.is_int(def, false) ) ? parseInt(ret) : parseFloat(ret);
1418
+ if ( !this.util.is_num(ret, false) ) {
1419
+ ret = def;
1420
+ }
1421
+ } else if ( this.util.is_bool(def, false) ) {
1422
+ ret = ( this.util.is_string(ret) || ( this.util.is_num(ret) ) );
1423
+ } else {
1424
+ ret = def;
1425
+ }
1426
+ }
1427
+ //Non-scalar default
1428
+ else {
1429
+ ret = def;
1430
+ }
1431
+ }
1432
+ return ret;
1433
+ },
1434
+
1435
+ /**
1436
+ * Call attribute as method
1437
+ * @param string attr Attribute to call
1438
+ * @param arguments (optional) Additional arguments to pass to method
1439
+ */
1440
+ call_attribute: function(attr, args) {
1441
+ attr = this.get_attribute(attr);
1442
+ if ( this.util.is_func(attr) ) {
1443
+ //Get arguments
1444
+ var args = Array.prototype.slice.call(arguments, 1);
1445
+ //Pass arguments to user-defined method
1446
+ attr = attr.apply(this, args);
1447
+ }
1448
+ return attr;
1449
+ },
1450
+
1451
+ /**
1452
+ * Check if attribute exists
1453
+ * @param string key Attribute name
1454
+ * @return bool TRUE if exists, FALSE otherwise
1455
+ */
1456
+ has_attribute: function(key) {
1457
+ return ( key in this.get_attributes() );
1458
+ },
1459
+
1460
+ /**
1461
+ * Set component attributes
1462
+ * @param obj attributes Attributes to set
1463
+ * @param bool full (optional) Whether to fully replace or merge component's attributes with new values (Default: Merge)
1464
+ */
1465
+ set_attributes: function(attributes, full) {
1466
+ if ( !this.util.is_bool(full) ) {
1467
+ full = false;
1468
+ }
1469
+
1470
+ //Initialize attributes
1471
+ this.init_attributes(full);
1472
+
1473
+ //Merge new/existing attributes
1474
+ if ( this.util.is_obj(attributes) ) {
1475
+ $.extend(this.attributes, attributes);
1476
+ }
1477
+ },
1478
+
1479
+ /**
1480
+ * Set value for a component attribute
1481
+ * @uses get_attributes() to retrieve attributes
1482
+ * @param string key Attribute to set
1483
+ * @param mixed val Attribute value
1484
+ */
1485
+ set_attribute: function(key, val) {
1486
+ if ( this.util.is_string(key) && this.util.is_set(val) ) {
1487
+ this.get_attributes()[key] = val;
1488
+ }
1489
+ return val;
1490
+ },
1491
+
1492
+ /* DOM */
1493
+
1494
+ /**
1495
+ * Generate selector for retrieving child element
1496
+ * @param string element Class name of child element
1497
+ * @return string Element selector
1498
+ */
1499
+ dom_get_selector: function(element) {
1500
+ return ( this.util.is_string(element) ) ? '.' + this.add_ns(element) : '';
1501
+ },
1502
+
1503
+ dom_get_attribute: function() {
1504
+ return this.util.get_attribute(this._slug);
1505
+ },
1506
+
1507
+ /**
1508
+ * Set reference of instance on DOM element
1509
+ * @uses _reciprocal to determine if DOM element should also be attached to instance
1510
+ * @param string|obj (jQuery) el DOM element to attach instance to
1511
+ * @return jQuery DOM element set
1512
+ */
1513
+ dom_set: function(el) {
1514
+ el = $(el);
1515
+ //Save instance to DOM object
1516
+ el.data(this.get_data_key(), this);
1517
+ //Save DOM object to instance
1518
+ if ( this._reciprocal ) {
1519
+ this._dom = el;
1520
+ }
1521
+ return el;
1522
+ },
1523
+
1524
+ /**
1525
+ * Retrieve attached DOM element
1526
+ * @uses _dom to retrieve attached DOM element
1527
+ * @uses dom_put() to insert child element
1528
+ * @param string element Child element to retrieve
1529
+ * @param bool put (optional) Whether to insert element if it does not exist (Default: FALSE)
1530
+ * @param obj options (optional) Options for creating new object
1531
+ * @return obj jQuery DOM element
1532
+ */
1533
+ dom_get: function(element, put, options) {
1534
+ //Init Component DOM
1535
+ if ( !this.get_status('dom_init') ) {
1536
+ this.set_status('dom_init');
1537
+ this.dom_init();
1538
+ }
1539
+ //Check for main DOM element
1540
+ var ret = this._dom;
1541
+ if ( !!ret && this.util.is_string(element) ) {
1542
+ var ch = $(ret).find( this.dom_get_selector(element) );
1543
+ //Check for child element
1544
+ if ( ch.length ) {
1545
+ ret = ch;
1546
+ } else if ( this.util.is_bool(put) && put ) {
1547
+ //Insert child element
1548
+ ret = this.dom_put(element, options);
1549
+ }
1550
+ }
1551
+ return $(ret);
1552
+ },
1553
+
1554
+ /**
1555
+ * Initialize DOM element
1556
+ * To be overridden by child classes
1557
+ */
1558
+ dom_init: function() {},
1559
+
1560
+ /**
1561
+ * Wrap output in DOM element
1562
+ * Wrapper element created and added to main DOM element if not yet created
1563
+ * @param string element ID for DOM element (Used as class name for wrapper)
1564
+ * @param string|jQuery|obj content Content to add to DOM (Object contains element properties)
1565
+ * > tag : Element tag name
1566
+ * > content : Element content
1567
+ * @return jQuery Inserted element(s)
1568
+ */
1569
+ dom_put: function(element, content) {
1570
+ var r = null;
1571
+ //Stop processing if main DOM element not set or element is not valid
1572
+ if ( !this.dom_has() || !this.util.is_string(element) ) {
1573
+ return $(r);
1574
+ }
1575
+ //Setup options
1576
+ var strip = ['tag', 'content', 'put_success'];
1577
+ var options = {
1578
+ 'tag': 'div',
1579
+ 'content': '',
1580
+ 'class': this.add_ns(element)
1581
+ }
1582
+ //Setup content
1583
+ if ( !this.util.is_empty(content) ) {
1584
+ if ( this.util.is_type(content, jQuery, false) || this.util.is_string(content, false) ) {
1585
+ options.content = content;
1586
+ }
1587
+ else if ( this.util.is_obj(content, false) ) {
1588
+ $.extend(options, content);
1589
+ }
1590
+ }
1591
+ var attrs = $.extend({}, options);
1592
+ for ( var x = 0; x < strip.length; x++ ) {
1593
+ delete attrs[strip[x]];
1594
+ }
1595
+ //Retrieve existing element
1596
+ var d = this.dom_get();
1597
+ r = $(this.dom_get_selector(element), d);
1598
+ //Create element (if necessary)
1599
+ if ( !r.length ) {
1600
+ r = $(this.util.format('<%s />', options.tag), attrs).appendTo(d);
1601
+ if ( r.length && this.util.is_method(options, 'put_success') ) {
1602
+ options['put_success'].call(r, r);
1603
+ }
1604
+ }
1605
+ //Set content
1606
+ $(r).append(options.content);
1607
+ return $(r);
1608
+ },
1609
+
1610
+ /**
1611
+ * Check if DOM element is set for instance
1612
+ * DOM is initialized before evaluation
1613
+ * @return bool TRUE if DOM element set, FALSE otherwise
1614
+ */
1615
+ dom_has: function() {
1616
+ return ( !!this.dom_get().length );
1617
+ },
1618
+
1619
+ /* Data */
1620
+
1621
+ /**
1622
+ * Retrieve key used to store data in DOM element
1623
+ * @return string Data key
1624
+ */
1625
+ get_data_key: function() {
1626
+ return this.get_ns();
1627
+ },
1628
+
1629
+ /* Events */
1630
+
1631
+ /**
1632
+ * Register event handler for custom event
1633
+ * Structure
1634
+ * > Events (obj)
1635
+ * > Event-Name (array)
1636
+ * > Handlers (functions)
1637
+ * @param mixed event Custom event to register handler for
1638
+ * > string: Standard event handler
1639
+ * > array: Multiple events to register single handler on
1640
+ * > object: Map of events/handlers
1641
+ * @param function fn Event handler
1642
+ * @param obj options Handler registration options
1643
+ * > clear (bool) Clear existing event handlers before setting current handler (Default: FALSE)
1644
+ * @return obj Component instance (allows chaining)
1645
+ */
1646
+ on: function(event, fn, options) {
1647
+ //Handle request types
1648
+ if ( !this.util.is_string(event) || !this.util.is_func(fn) ) {
1649
+ var t = this;
1650
+ var args = Array.prototype.slice.call(arguments, 1);
1651
+ if ( this.util.is_array(event) ) {
1652
+ //Events array
1653
+ $.each(event, function(idx, val) {
1654
+ t.on.apply(t, [val].concat(args));
1655
+ });
1656
+ } else if ( this.util.is_obj(event) ) {
1657
+ //Events map
1658
+ $.each(event, function(ev, hdl) {
1659
+ t.on.apply(t, [ev, hdl].concat(args));
1660
+ });
1661
+ }
1662
+ return this;
1663
+ }
1664
+
1665
+ //Options
1666
+
1667
+ //Default options
1668
+ var options_std = {
1669
+ clear: false
1670
+ };
1671
+ if ( !this.util.is_obj(options, false) ) {
1672
+ //Reset options
1673
+ options = {};
1674
+ }
1675
+ //Build options
1676
+ options = $.extend({}, options_std, options);
1677
+ //Initialize events bucket
1678
+ if ( !this.util.is_obj(this._events, false) ) {
1679
+ this._events = {};
1680
+ }
1681
+ //Setup event
1682
+ var es = this._events;
1683
+ if ( !( event in es ) || !this.util.is_obj(es[event], false) || !!options.clear ) {
1684
+ es[event] = [];
1685
+ }
1686
+ //Add event handler
1687
+ es[event].push(fn);
1688
+ return this;
1689
+ },
1690
+
1691
+ /**
1692
+ * Trigger custom event
1693
+ * Event handlers are executed in the context of the current component instance
1694
+ * Event handlers are passed parameters
1695
+ * > ev (obj) Event object
1696
+ * > type (string) Event name
1697
+ * > data (mixed) Data to pass to handlers (if supplied)
1698
+ * > component (obj) Current component instance
1699
+ * @param string event Custom event to trigger
1700
+ * @param mixed data (optional) Data to pass to event handlers
1701
+ * @return jQuery.Promise Promise that is resolved once event handlers are resolved
1702
+ */
1703
+ trigger: function(event, data) {
1704
+ var dfr = $.Deferred();
1705
+ var dfrs = [];
1706
+ var t = this;
1707
+ //Handle array of events
1708
+ if ( this.util.is_array(event) ) {
1709
+ $.each(event, function(idx, val) {
1710
+ //Collect promises from triggered events
1711
+ dfrs.push( t.trigger(val, data) );
1712
+ });
1713
+ //Resolve trigger when all events have been resolved
1714
+ $.when.apply(t, dfrs).done(function() {
1715
+ dfr.resolve();
1716
+ });
1717
+ return dfr.promise();
1718
+ }
1719
+ //Validate
1720
+ if ( !this.util.is_string(event) || !( event in this._events ) ) {
1721
+ dfr.resolve();
1722
+ return dfr.promise();
1723
+ }
1724
+ //Create event object
1725
+ var ev = { 'type': event, 'data': null };
1726
+ //Add data to event object
1727
+ if ( this.util.is_set(data) ) {
1728
+ ev.data = data;
1729
+ }
1730
+ //Fire handlers for event
1731
+ $.each(this._events[event], function(idx, fn) {
1732
+ //Call handler (`this` set to current instance)
1733
+ //Collect promises from event handlers
1734
+ dfrs.push( fn.call(t, ev, t) );
1735
+ });
1736
+ //Resolve trigger when all handlers have been resolved
1737
+ $.when.apply(this, dfrs).done(function() {
1738
+ dfr.resolve();
1739
+ });
1740
+ return dfr.promise();
1741
+ }
1742
+ };
1743
+
1744
+ View.Component = Component = SLB.Class.extend(Component);
1745
+
1746
+ /**
1747
+ * Content viewer
1748
+ * @param obj options Init options
1749
+ */
1750
+ var Viewer = {
1751
+
1752
+ /* Configuration */
1753
+
1754
+ _slug: 'viewer',
1755
+
1756
+ _refs: {
1757
+ item: 'Content_Item',
1758
+ theme: 'Theme'
1759
+ },
1760
+
1761
+ _reciprocal: true,
1762
+
1763
+ _attr_default: {
1764
+ loop: true,
1765
+ animate: true,
1766
+ autofit: true,
1767
+ overlay_enabled: true,
1768
+ overlay_opacity: '0.8',
1769
+ container: null,
1770
+ slideshow_enabled: true,
1771
+ slideshow_autostart: true,
1772
+ slideshow_duration: 2,
1773
+ slideshow_active: false,
1774
+ slideshow_timer: null,
1775
+ labels: {
1776
+ close: 'close',
1777
+ nav_prev: '&laquo; prev',
1778
+ nav_next: 'next &raquo;',
1779
+ slideshow_start: 'start slideshow',
1780
+ slideshow_stop: 'stop slideshow',
1781
+ group_status: 'Image %current% of %total%',
1782
+ loading: 'loading'
1783
+ }
1784
+ },
1785
+
1786
+ _attr_parent: [
1787
+ 'theme',
1788
+ 'group_loop',
1789
+ 'ui_autofit', 'ui_animate', 'ui_overlay_opacity', 'ui_labels',
1790
+ 'slideshow_enabled', 'slideshow_autostart', 'slideshow_duration'],
1791
+
1792
+ _attr_map: {
1793
+ 'group_loop': 'loop',
1794
+ 'ui_autofit': 'autofit',
1795
+ 'ui_animate': 'animate',
1796
+ 'ui_overlay_opacity': 'overlay_opacity',
1797
+ 'ui_labels': 'labels'
1798
+ },
1799
+
1800
+ /* References */
1801
+
1802
+ /**
1803
+ * Item currently loaded in viewer
1804
+ * @var object Content_Item
1805
+ */
1806
+ item: null,
1807
+
1808
+ /**
1809
+ * Queued item to be loaded once viewer is available
1810
+ * @var object Content_Item
1811
+ */
1812
+ item_queued: null,
1813
+
1814
+ /**
1815
+ * Theme used by viewer
1816
+ * @var object Theme
1817
+ */
1818
+ theme: null,
1819
+
1820
+ /* Properties */
1821
+
1822
+ item_working: null,
1823
+
1824
+ active: false,
1825
+ init: false,
1826
+ open: false,
1827
+ loading: false,
1828
+
1829
+ /* Methods */
1830
+
1831
+ /* Init */
1832
+
1833
+ register_hooks: function() {
1834
+ var t = this;
1835
+ this
1836
+ .on(['item-prev', 'item-next'], function() {
1837
+ t.trigger('item-change');
1838
+ })
1839
+ .on(['close', 'item-change'], function() {
1840
+ t.unlock();
1841
+ });
1842
+ },
1843
+
1844
+ /* References */
1845
+
1846
+ /**
1847
+ * Set item reference
1848
+ * Validates item before setting
1849
+ * @param obj item Content_Item instance
1850
+ * @return bool TRUE if valid item set, FALSE otherwise
1851
+ */
1852
+ set_item: function(item) {
1853
+ //Clear existing item
1854
+ this.clear_item(false);
1855
+ var i = this.set_component('item', item, function(item) {
1856
+ return ( item.has_type() );
1857
+ });
1858
+ return ( !this.util.is_empty(i) );
1859
+ },
1860
+
1861
+ clear_item: function(full) {
1862
+ //Validate
1863
+ if ( !this.util.is_bool(full) ) {
1864
+ full = true;
1865
+ }
1866
+ var item = this.get_item();
1867
+ if ( !!item ) {
1868
+ item.reset();
1869
+ }
1870
+ if ( full ) {
1871
+ this.set_item(false);
1872
+ }
1873
+ },
1874
+
1875
+ /**
1876
+ * Retrieve item instance current attached to viewer
1877
+ * @return Content_Item|NULL Current item instance
1878
+ */
1879
+ get_item: function() {
1880
+ return this.get_component('item', true, false);
1881
+ },
1882
+
1883
+ /**
1884
+ * Retrieve theme reference
1885
+ * @return object Theme reference
1886
+ */
1887
+ get_theme: function() {
1888
+ //Get saved theme
1889
+ var ret = this.get_component('theme', false, false, false);
1890
+ if ( this.util.is_empty(ret) ) {
1891
+ //Theme needs to be initialized
1892
+ ret = this.set_component('theme', new View.Theme(this));
1893
+ }
1894
+ return ret;
1895
+ },
1896
+
1897
+ /**
1898
+ * Set viewer's theme
1899
+ * @param object theme Theme object
1900
+ */
1901
+ set_theme: function(theme) {
1902
+ this.set_component('theme', theme);
1903
+ },
1904
+
1905
+ /* Properties */
1906
+
1907
+ /**
1908
+ * Lock the viewer
1909
+ * Indicates that item is currently being processed
1910
+ * @return jQuery.Deferred Resolved when item processing is complete
1911
+ */
1912
+ lock: function() {
1913
+ return this.set_status('item_working', $.Deferred());
1914
+ },
1915
+
1916
+ /**
1917
+ * Retrieve lock
1918
+ * @param bool simple (optional) Whether to return a simple status of the locked status (Default: FALSE)
1919
+ * @param bool full (optional) Whether to return Deferred (TRUE) or Promise (FALSE) object (Default: FALSE)
1920
+ * @return jQuery.Promise Resolved when item processing is complete
1921
+ */
1922
+ get_lock: function(simple, full) {
1923
+ //Validate
1924
+ if ( !this.util.is_bool(simple) ) {
1925
+ simple = false;
1926
+ }
1927
+ if ( !this.util.is_bool(full) ) {
1928
+ full = false;
1929
+ }
1930
+ var s = 'item_working';
1931
+ //Simple status
1932
+ if ( simple ) {
1933
+ return this.get_status(s);
1934
+ }
1935
+ //Full value
1936
+ var r = this.get_status(s, true);
1937
+ if ( !this.util.is_promise(r) ) {
1938
+ //Create default
1939
+ r = this.lock();
1940
+ }
1941
+ return ( full ) ? r : r.promise();
1942
+ },
1943
+
1944
+ is_locked: function() {
1945
+ return this.get_lock(true);
1946
+ },
1947
+
1948
+ /**
1949
+ * Unlock the viewer
1950
+ * Any callbacks registered for this action will be executed
1951
+ * @return jQuery.Deferred Resolved instance
1952
+ */
1953
+ unlock: function() {
1954
+ return this.get_lock(false, true).resolve();
1955
+ },
1956
+
1957
+ /**
1958
+ * Set Viewer active status
1959
+ * @param bool mode (optional) Activate or deactivate status (Default: TRUE)
1960
+ * @return bool Active status
1961
+ */
1962
+ set_active: function(mode) {
1963
+ if ( !this.util.is_bool(mode) ) {
1964
+ mode = true;
1965
+ }
1966
+ return this.set_status('active', mode);
1967
+ },
1968
+
1969
+ /**
1970
+ * Check Viewer active status
1971
+ * @return bool Active status
1972
+ */
1973
+ is_active: function() {
1974
+ return this.get_status('active');
1975
+ },
1976
+
1977
+ /**
1978
+ * Set loading mode
1979
+ * @param bool mode (optional) Set (TRUE) or unset (FALSE) loading mode (Default: TRUE)
1980
+ * @return jQuery.Promise Promise that resolves when loading mode is set
1981
+ */
1982
+ set_loading: function(mode) {
1983
+ var dfr = $.Deferred();
1984
+ if ( !this.util.is_bool(mode) ) {
1985
+ mode = true;
1986
+ }
1987
+ this.loading = mode;
1988
+ //Pause/Resume slideshow
1989
+ if ( this.slideshow_active() ) {
1990
+ this.slideshow_pause(mode);
1991
+ }
1992
+ //Set CSS class on DOM element
1993
+ var m = ( mode ) ? 'addClass' : 'removeClass';
1994
+ $(this.dom_get())[m]('loading');
1995
+ if ( mode ) {
1996
+ //Loading transition
1997
+ this.get_theme().transition('load').always(function() {
1998
+ dfr.resolve();
1999
+ });
2000
+ } else {
2001
+ dfr.resolve();
2002
+ }
2003
+ return dfr.promise();
2004
+ },
2005
+
2006
+ /**
2007
+ * Unset loading mode
2008
+ * @see set_loading()
2009
+ * @return jQuery.Promise Promise that resovles when loading mode is set
2010
+ */
2011
+ unset_loading: function() {
2012
+ return this.set_loading(false);
2013
+ },
2014
+
2015
+ /**
2016
+ * Retrieve loading status
2017
+ * @return bool Loading status (Default: FALSE)
2018
+ */
2019
+ get_loading: function() {
2020
+ return ( this.util.is_bool(this.loading) ) ? this.loading : false;
2021
+ },
2022
+
2023
+ /**
2024
+ * Check if viewer is currently loading content
2025
+ * @return bool Loading status (Default: FALSE)
2026
+ */
2027
+ is_loading: function() {
2028
+ return this.get_loading();
2029
+ },
2030
+
2031
+ /* Display */
2032
+
2033
+ /**
2034
+ * Display content in viewer
2035
+ * @param Content_Item item Item to show
2036
+ * @param obj options (optional) Display options
2037
+ */
2038
+ show: function(item) {
2039
+ this.item_queued = item;
2040
+ var fin_set = 'show_deferred';
2041
+ //Validate theme
2042
+ var vt = 'theme_valid';
2043
+ var valid = true;
2044
+ if ( !this.has_attribute(vt)) {
2045
+ valid = this.set_attribute(vt, ( this.get_theme() && this.get_theme().get_template().get_layout(false) ) );
2046
+ } else {
2047
+ valid = this.get_attribute(vt, true);
2048
+ }
2049
+
2050
+ if ( !valid ) {
2051
+ this.close();
2052
+ return false;
2053
+ }
2054
+ var v = this;
2055
+ var fin = function() {
2056
+ //Lock viewer
2057
+ v.lock();
2058
+ //Reset callback flag (for new lock)
2059
+ v.set_status(fin_set, false);
2060
+ //Validate request
2061
+ if ( !v.set_item(v.item_queued) ) {
2062
+ v.close();
2063
+ return false;
2064
+ }
2065
+ //Add item to history stack
2066
+ v.history_add();
2067
+ //Activate
2068
+ v.set_active();
2069
+ //Display
2070
+ v.render();
2071
+ }
2072
+ if ( !this.is_locked() ) {
2073
+ fin();
2074
+ } else if ( !this.get_status(fin_set) ) {
2075
+ //Set flag to avoid duplicate callbacks
2076
+ this.set_status(fin_set);
2077
+ this.get_lock().always(function() {
2078
+ fin();
2079
+ });
2080
+ }
2081
+ },
2082
+
2083
+ /* History Management */
2084
+
2085
+ history_handle: function(e) {
2086
+ var state = e.originalEvent.state;
2087
+ //Load item
2088
+ if ( this.util.is_int(state.item, false) ) {
2089
+ this.get_parent().get_item(state.item).show({'event': e});
2090
+ this.trigger('item-change');
2091
+ } else {
2092
+ var count = this.history_get(true);
2093
+ //Reset count
2094
+ this.history_set(0);
2095
+ //Close viewer
2096
+ if ( -1 != count ) {
2097
+ this.close();
2098
+ }
2099
+ }
2100
+ },
2101
+
2102
+ history_get: function(full) {
2103
+ return this.get_status('history_count', full);
2104
+ },
2105
+ history_set: function(val) {
2106
+ return this.set_status('history_count', val);
2107
+ },
2108
+ history_add: function() {
2109
+ if ( !history.pushState ) {
2110
+ return false;
2111
+ }
2112
+ //Get display options
2113
+ var item = this.get_item();
2114
+ var opts = item.get_attribute('options_show');
2115
+ //Save history state
2116
+ var count = ( this.history_get() ) ? this.history_get(true) : 0;
2117
+ if ( !this.util.in_obj(opts, 'event') ) {
2118
+ //Create state
2119
+ var state = {
2120
+ 'viewer': this.get_id(),
2121
+ 'item': null,
2122
+ 'count': count
2123
+ };
2124
+ //Init: Save viewer state
2125
+ if ( !count ) {
2126
+ history.replaceState(state, null);
2127
+ }
2128
+ //Always: Save item state
2129
+ state.item = this.get_parent().save_item(item);
2130
+ state.count = ++count;
2131
+ history.pushState(state, '');
2132
+ } else {
2133
+ var e = opts.event.originalEvent;
2134
+ if ( this.util.in_obj(e, 'state') && this.util.in_obj(e.state, 'count') ) {
2135
+ count = e.state.count;
2136
+ }
2137
+ }
2138
+ //Save history item count
2139
+ this.history_set(count);
2140
+ },
2141
+ history_reset: function() {
2142
+ var count = this.history_get(true);
2143
+ if ( count ) {
2144
+ //Clear history status
2145
+ this.history_set(-1);
2146
+ //Restore history stack
2147
+ history.go( -1 * count );
2148
+ }
2149
+ },
2150
+
2151
+ /**
2152
+ * Check if viewer is currently open
2153
+ * Checks if node is actually visible in DOM
2154
+ * @return bool TRUE if viewer is open, FALSE otherwise
2155
+ */
2156
+ is_open: function() {
2157
+ return ( this.dom_get().css('display') == 'none' ) ? false : true;
2158
+ },
2159
+
2160
+ /**
2161
+ * Load output into DOM
2162
+ */
2163
+ render: function() {
2164
+ //Get theme output
2165
+ var v = this;
2166
+ var thm = this.get_theme();
2167
+ //Register theme event handlers
2168
+ if ( !this.get_status('render-events') ) {
2169
+ this.set_status('render-events');
2170
+ thm
2171
+ //Loading
2172
+ .on('render-loading', function(ev, thm) {
2173
+ var dfr = $.Deferred();
2174
+ if ( !v.is_active() ) {
2175
+ dfr.reject();
2176
+ return dfr.promise();
2177
+ }
2178
+ var set_pos = function() {
2179
+ //Set position
2180
+ v.dom_get().css('top', $(window).scrollTop());
2181
+ };
2182
+ var always = function() {
2183
+ //Set loading flag
2184
+ v.set_loading().always(function() {
2185
+ dfr.resolve();
2186
+ });
2187
+ };
2188
+ if ( v.is_open() ) {
2189
+ thm.transition('unload')
2190
+ .fail(function() {
2191
+ set_pos();
2192
+ thm.dom_get_tag('item', 'content').attr('style', '');
2193
+ })
2194
+ .always(always);
2195
+ } else {
2196
+ thm.transition('open')
2197
+ .always(function() {
2198
+ always();
2199
+ v.events_open();
2200
+ v.open = true;
2201
+ })
2202
+ .fail(function() {
2203
+ set_pos();
2204
+ //Fallback open
2205
+ v.get_overlay().show();
2206
+ v.dom_get().show();
2207
+ });
2208
+ }
2209
+ return dfr.promise();
2210
+ })
2211
+ //Complete
2212
+ .on('render-complete', function(ev, thm) {
2213
+ //Stop if viewer not active
2214
+ if ( !v.is_active() ) {
2215
+ return false;
2216
+ }
2217
+ //Set classes
2218
+ var d = v.dom_get();
2219
+ var classes = ['item_single', 'item_multi'];
2220
+ var ms = ['addClass', 'removeClass'];
2221
+ if ( !v.get_item().get_group().is_single() ) {
2222
+ ms.reverse();
2223
+ }
2224
+ $.each(ms, function(idx, val) {
2225
+ d[val](classes[idx]);
2226
+ });
2227
+ //Bind events
2228
+ v.events_complete();
2229
+ //Transition
2230
+ thm.transition('complete')
2231
+ .fail(function() {
2232
+ //Autofit content
2233
+ if ( v.get_attribute('autofit', true) ) {
2234
+ var dims = $.extend({'display': 'inline-block'}, thm.get_item_dimensions());
2235
+ var tag = thm.dom_get_tag('item', 'content').css(dims);
2236
+ }
2237
+ })
2238
+ .always(function() {
2239
+ //Unset loading flag
2240
+ v.unset_loading();
2241
+ //Trigger event
2242
+ v.trigger('render-complete');
2243
+ //Set viewer as initialized
2244
+ v.init = true;
2245
+ });
2246
+ });
2247
+ }
2248
+ //Render
2249
+ thm.render();
2250
+ },
2251
+
2252
+ /**
2253
+ * Retrieve container element
2254
+ * Creates default container element if not yet created
2255
+ * @return jQuery Container element
2256
+ */
2257
+ dom_get_container: function() {
2258
+ var sel = this.get_attribute('container');
2259
+ //Set default container
2260
+ if ( this.util.is_empty(sel) ) {
2261
+ sel = '#' + this.add_ns('wrap');
2262
+ }
2263
+ //Add default container to DOM if not yet present
2264
+ var c = $(sel);
2265
+ if ( !c.length ) {
2266
+ //Prepare ID
2267
+ var id = ( sel.indexOf('#') === 0 ) ? sel.substr(1) : sel;
2268
+ //Add element
2269
+ c = $('<div />', {'id': id}).appendTo('body');
2270
+ }
2271
+ return c;
2272
+ },
2273
+
2274
+ /**
2275
+ * Custom Viewer DOM initialization
2276
+ */
2277
+ dom_init: function() {
2278
+ //Create element & add to DOM
2279
+ //Save element to instance
2280
+ var d = this.dom_set($('<div/>', {
2281
+ 'id': this.get_id(true),
2282
+ 'class': this.get_ns()
2283
+ })).appendTo(this.dom_get_container()).hide();
2284
+ //Add theme classes
2285
+ var thm = this.get_theme();
2286
+ d.addClass(thm.get_classes(' '));
2287
+ //Add theme layout (basic)
2288
+ var v = this;
2289
+ if ( !this.get_status('render-init') ) {
2290
+ this.set_status('render-init');
2291
+ thm.on('render-init', function(ev) {
2292
+ //Add rendered theme layout to viewer DOM
2293
+ v.dom_put('layout', ev.data);
2294
+ });
2295
+ }
2296
+ thm.render(true);
2297
+ },
2298
+
2299
+ /**
2300
+ * Restore DOM
2301
+ * Show overlapping DOM elements, etc.
2302
+ * @TODO Build functionality
2303
+ */
2304
+ dom_restore: function() {},
2305
+
2306
+ /* Layout */
2307
+
2308
+ get_layout: function() {
2309
+ var ret = this.dom_get('layout', true, {
2310
+ 'put_success': function() {
2311
+ $(this).hide();
2312
+ }
2313
+ });
2314
+ return ret;
2315
+ },
2316
+
2317
+ /* Animation */
2318
+
2319
+ animation_enabled: function() {
2320
+ return this.get_attribute('animate', true);
2321
+ },
2322
+
2323
+ /* Overlay */
2324
+
2325
+ /**
2326
+ * Determine if overlay is enabled for viewer
2327
+ * @return bool TRUE if overlay is enabled, FALSE otherwise
2328
+ */
2329
+ overlay_enabled: function() {
2330
+ var ov = this.get_attribute('overlay_enabled');
2331
+ return ( this.util.is_bool(ov) ) ? ov : false;
2332
+ },
2333
+
2334
+ /**
2335
+ * Retrieve overlay DOM element
2336
+ * @return jQuery Overlay element (NULL if no overlay set for viewer)
2337
+ */
2338
+ get_overlay: function() {
2339
+ var o = null;
2340
+ var v = this;
2341
+ if ( this.overlay_enabled() ) {
2342
+ o = this.dom_get('overlay', true, {
2343
+ 'put_success': function() {
2344
+ $(this).hide().css('opacity', v.get_attribute('overlay_opacity'));
2345
+ }
2346
+ });
2347
+ }
2348
+ return $(o);
2349
+ },
2350
+
2351
+ unload: function() {
2352
+
2353
+ },
2354
+
2355
+ /**
2356
+ * Reset viewer
2357
+ */
2358
+ reset: function() {
2359
+ //Hide viewer
2360
+ this.dom_get().hide();
2361
+ //Restore DOM
2362
+ this.dom_restore();
2363
+ //History
2364
+ this.history_reset();
2365
+ //Item
2366
+ this.clear_item();
2367
+ //Reset properties
2368
+ this.set_active(false);
2369
+ this.set_loading(false);
2370
+ this.slideshow_stop();
2371
+ this.keys_disable();
2372
+ //Clear for next item
2373
+ this.get_status('item_working', true).resolve();
2374
+ },
2375
+
2376
+ /* Content */
2377
+
2378
+ get_labels: function() {
2379
+ return this.get_attribute('labels', {});
2380
+ },
2381
+
2382
+ get_label: function(name) {
2383
+ var lbls = this.get_labels();
2384
+ return ( name in lbls ) ? lbls[name] : '';
2385
+ },
2386
+
2387
+ /* Interactivity */
2388
+
2389
+ /**
2390
+ * Initialize event handlers upon opening lightbox
2391
+ */
2392
+ events_open: function() {
2393
+ //Keyboard bindings
2394
+ this.keys_enable();
2395
+ if ( this.open ) {
2396
+ return false;
2397
+ }
2398
+
2399
+ //Control event bubbling
2400
+ var l = this.get_layout();
2401
+ l.children().click(function(ev) {
2402
+ ev.stopPropagation();
2403
+ });
2404
+
2405
+ /* Close */
2406
+ var v = this;
2407
+ var close = function() {
2408
+ v.close();
2409
+ }
2410
+ //Layout
2411
+ l.click(close);
2412
+ //Overlay
2413
+ this.get_overlay().click(close);
2414
+ //Fire event
2415
+ this.trigger('events-open');
2416
+ },
2417
+
2418
+ /**
2419
+ * Initialize event handlers upon completing lightbox rendering
2420
+ */
2421
+ events_complete: function() {
2422
+ if ( this.init ) {
2423
+ return false;
2424
+ }
2425
+ //Fire event
2426
+ this.trigger('events-complete');
2427
+ },
2428
+
2429
+ keys_enable: function(mode) {
2430
+ if ( !this.util.is_bool(mode) ) {
2431
+ mode = true;
2432
+ }
2433
+ var e = ['keyup', this.util.get_prefix()].join('.');
2434
+ var v = this;
2435
+ var h = function(ev) {
2436
+ return v.keys_control(ev);
2437
+ }
2438
+ if ( mode ) {
2439
+ $(document).on(e, h);
2440
+ } else {
2441
+ $(document).off(e);
2442
+ }
2443
+ },
2444
+
2445
+ keys_disable: function() {
2446
+ this.keys_enable(false);
2447
+ },
2448
+
2449
+ keys_control: function(ev) {
2450
+ var handlers = {
2451
+ 27: this.close,
2452
+ 37: this.item_prev,
2453
+ 39: this.item_next
2454
+ };
2455
+ if ( ev.which in handlers ) {
2456
+ handlers[ev.which].call(this);
2457
+ return false;
2458
+ }
2459
+ },
2460
+
2461
+ /**
2462
+ * Check if slideshow functionality is enabled
2463
+ * @return bool TRUE if slideshow is enabled, FALSE otherwise
2464
+ */
2465
+ slideshow_enabled: function() {
2466
+ var o = this.get_attribute('slideshow_enabled');
2467
+ return ( this.util.is_bool(o) && o && this.get_item() && !this.get_item().get_group().is_single() ) ? true : false;
2468
+ },
2469
+
2470
+ /**
2471
+ * Checks if slideshow is currently active
2472
+ * @return bool TRUE if slideshow is active, FALSE otherwise
2473
+ */
2474
+ slideshow_active: function() {
2475
+ return ( this.slideshow_enabled() && ( this.get_attribute('slideshow_active') || ( !this.init && this.get_attribute('slideshow_autostart') ) ) ) ? true : false;
2476
+ },
2477
+
2478
+ /**
2479
+ * Clear slideshow timer
2480
+ */
2481
+ slideshow_clear_timer: function() {
2482
+ clearInterval(this.get_attribute('slideshow_timer'));
2483
+ },
2484
+
2485
+ /**
2486
+ * Start slideshow timer
2487
+ * @param function callback Callback function
2488
+ */
2489
+ slideshow_set_timer: function(callback) {
2490
+ this.set_attribute('slideshow_timer', setInterval(callback, this.get_attribute('slideshow_duration') * 1000));
2491
+ },
2492
+
2493
+ /**
2494
+ * Start Slideshow
2495
+ */
2496
+ slideshow_start: function() {
2497
+ if ( !this.slideshow_enabled() ) {
2498
+ return false;
2499
+ }
2500
+ this.set_attribute('slideshow_active', true);
2501
+ this.dom_get().addClass('slideshow_active');
2502
+ //Clear residual timers
2503
+ this.slideshow_clear_timer();
2504
+ //Start timer
2505
+ var v = this;
2506
+ this.slideshow_set_timer(function() {
2507
+ //Pause slideshow until next item fully loaded
2508
+ v.slideshow_pause();
2509
+
2510
+ //Show next item
2511
+ v.item_next();
2512
+ });
2513
+ this.trigger('slideshow-start');
2514
+ },
2515
+
2516
+ /**
2517
+ * Stop Slideshow
2518
+ * @param bool full (optional) Full stop (TRUE) or pause (FALSE) (Default: TRUE)
2519
+ */
2520
+ slideshow_stop: function(full) {
2521
+ if ( !this.util.is_bool(full) ) {
2522
+ full = true;
2523
+ }
2524
+ if ( full ) {
2525
+ this.set_attribute('slideshow_active', false);
2526
+ this.dom_get().removeClass('slideshow_active');
2527
+ }
2528
+ //Kill timers
2529
+ this.slideshow_clear_timer();
2530
+ this.trigger('slideshow-stop');
2531
+ },
2532
+
2533
+ slideshow_toggle: function() {
2534
+ if ( !this.slideshow_enabled() ) {
2535
+ return false;
2536
+ }
2537
+ if ( this.slideshow_active() ) {
2538
+ this.slideshow_stop();
2539
+ } else {
2540
+ this.slideshow_start();
2541
+ }
2542
+ this.trigger('slideshow-toggle');
2543
+ },
2544
+
2545
+ /**
2546
+ * Pause Slideshow
2547
+ * @param bool mode (optional) Pause (TRUE) or Resume (FALSE) slideshow (default: TRUE)
2548
+ */
2549
+ slideshow_pause: function(mode) {
2550
+ //Validate
2551
+ if ( !this.util.is_bool(mode) ) {
2552
+ mode = true;
2553
+ }
2554
+ //Set viewer slideshow properties
2555
+ if ( this.slideshow_active() ) {
2556
+ if ( !mode ) {
2557
+ //Slideshow resumed
2558
+ this.slideshow_start();
2559
+ } else {
2560
+ //Slideshow paused
2561
+ this.slideshow_stop(false);
2562
+ }
2563
+ }
2564
+ this.trigger('slideshow-pause');
2565
+ },
2566
+
2567
+ /**
2568
+ * Resume slideshow
2569
+ */
2570
+ slideshow_resume: function() {
2571
+ this.slideshow_pause(false);
2572
+ },
2573
+
2574
+ /**
2575
+ * Next item
2576
+ */
2577
+ item_next: function() {
2578
+ var g = this.get_item().get_group(true);
2579
+ var v = this;
2580
+ var ev = 'item-next';
2581
+ var st = ['events', 'viewer', ev].join('_');
2582
+ //Setup event handler
2583
+ if ( !g.get_status(st) ) {
2584
+ g.set_status(st);
2585
+ g.on(ev, function(e) {
2586
+ v.trigger(e.type);
2587
+ });
2588
+ }
2589
+ g.show_next();
2590
+ },
2591
+
2592
+ /**
2593
+ * Previous item
2594
+ */
2595
+ item_prev: function() {
2596
+ var g = this.get_item().get_group(true);
2597
+ var v = this;
2598
+ var ev = 'item-prev';
2599
+ var st = ['events', 'viewer', ev].join('_');
2600
+ if ( !g.get_status(st) ) {
2601
+ g.set_status(st);
2602
+ g.on(ev, function() {
2603
+ v.trigger(ev);
2604
+ });
2605
+ }
2606
+ g.show_prev();
2607
+ },
2608
+
2609
+ /**
2610
+ * Close viewer
2611
+ */
2612
+ close: function() {
2613
+ //Deactivate
2614
+ this.set_active(false);
2615
+ var v = this;
2616
+ var thm = this.get_theme();
2617
+ thm.transition('unload')
2618
+ .always(function() {
2619
+ thm.transition('close', true).always(function() {
2620
+ //End processes
2621
+ v.reset();
2622
+ v.trigger('close');
2623
+ });
2624
+ })
2625
+ .fail(function() {
2626
+ thm.dom_get_tag('item', 'content').attr('style', '');
2627
+ });
2628
+ return false;
2629
+ }
2630
+ };
2631
+
2632
+ View.Viewer = Component.extend(Viewer);
2633
+
2634
+ /**
2635
+ * Content group
2636
+ * @param obj options Init options
2637
+ */
2638
+ var Group = {
2639
+ /* Configuration */
2640
+
2641
+ _slug: 'group',
2642
+ _reciprocal: true,
2643
+ _refs: {
2644
+ 'current': 'Content_Item'
2645
+ },
2646
+
2647
+ /* References */
2648
+
2649
+ current: null,
2650
+
2651
+ /* Properties */
2652
+
2653
+ /**
2654
+ * Selector for getting group items
2655
+ * @var string
2656
+ */
2657
+ selector: null,
2658
+
2659
+ /* Methods */
2660
+
2661
+ /* Init */
2662
+
2663
+ register_hooks: function() {
2664
+ var t = this;
2665
+ this.on(['item-prev', 'item-next'], function() {
2666
+ t.trigger('item-change');
2667
+ });
2668
+ },
2669
+
2670
+ /* Properties */
2671
+
2672
+ /**
2673
+ * Retrieve selector for group items
2674
+ * @return string Group items selector
2675
+ */
2676
+ get_selector: function() {
2677
+ if ( this.util.is_empty(this.selector) ) {
2678
+ //Build selector
2679
+ this.selector = this.util.format('a[%s="%s"]', this.dom_get_attribute(), this.get_id());
2680
+ }
2681
+ return this.selector;
2682
+ },
2683
+
2684
+ /**
2685
+ * Retrieve group items
2686
+ */
2687
+ get_items: function() {
2688
+ var items = ( !this.util.is_empty(this.get_id()) ) ? $(this.get_selector()) : this.get_current().dom_get();
2689
+ return items;
2690
+ },
2691
+
2692
+ /**
2693
+ * Retrieve item at specified index
2694
+ * If no index specified, first item is returned
2695
+ * @param int idx Index of item to return
2696
+ * @return Content_Item Item
2697
+ */
2698
+ get_item: function(idx) {
2699
+ //Validation
2700
+ if ( !this.util.is_int(idx) ) {
2701
+ idx = 0;
2702
+ }
2703
+ //Retrieve all items
2704
+ var items = this.get_items();
2705
+ //Validate index
2706
+ var max = this.get_size() - 1;
2707
+ if ( idx > max ) {
2708
+ idx = max;
2709
+ }
2710
+ //Return specified item
2711
+ return items.get(idx);
2712
+ },
2713
+
2714
+ /**
2715
+ * Retrieve (zero-based) position of specified item in group
2716
+ * @param Content_Item item Item to locate in group
2717
+ * @return int Index position of item in group (-1 if item not in group)
2718
+ */
2719
+ get_pos: function(item) {
2720
+ if ( this.util.is_empty(item) ) {
2721
+ //Get current item
2722
+ item = this.get_current();
2723
+ }
2724
+ return ( this.util.is_type(item, View.Content_Item) ) ? this.get_items().index(item.dom_get()) : -1;
2725
+ },
2726
+
2727
+ /**
2728
+ * Retrieve current item
2729
+ * @return Content_Item Current item
2730
+ */
2731
+ get_current: function() {
2732
+ //Sanitize
2733
+ if ( !this.util.is_empty(this.current) && !this.util.is_type(this.current, View.Content_Item) ) {
2734
+ this.current = null;
2735
+ }
2736
+ return this.current;
2737
+ },
2738
+
2739
+ /**
2740
+ * Sets current group item
2741
+ * @param Content_Item item Item to set as current
2742
+ */
2743
+ set_current: function(item) {
2744
+ //Validate
2745
+ if ( this.util.is_type(item, View.Content_Item) ) {
2746
+ //Set current item
2747
+ this.current = item;
2748
+ }
2749
+ },
2750
+
2751
+ get_next: function(item) {
2752
+ //Validate
2753
+ if ( !this.util.is_type(item, View.Content_Item) ) {
2754
+ item = this.get_current();
2755
+ }
2756
+ if ( this.get_size() == 1 ) {
2757
+ return item;
2758
+ }
2759
+ var next = null;
2760
+ var pos = this.get_pos(item);
2761
+ if ( pos != -1 ) {
2762
+ pos = ( pos + 1 < this.get_size() ) ? pos + 1 : 0;
2763
+ if ( 0 != pos || item.get_viewer().get_attribute('loop') ) {
2764
+ next = this.get_item(pos);
2765
+ }
2766
+ }
2767
+ return next;
2768
+ },
2769
+
2770
+ get_prev: function(item) {
2771
+ //Validate
2772
+ if ( !this.util.is_type(item, View.Content_Item) ) {
2773
+ item = this.get_current();
2774
+ }
2775
+ if ( this.get_size() == 1 ) {
2776
+ return item;
2777
+ }
2778
+ var prev = null;
2779
+ var pos = this.get_pos(item);
2780
+ if ( pos != -1 && ( 0 != pos || item.get_viewer().get_attribute('loop') ) ) {
2781
+ if ( pos == 0 ) {
2782
+ pos = this.get_size();
2783
+ }
2784
+ pos -= 1;
2785
+ prev = this.get_item(pos);
2786
+ }
2787
+ return prev;
2788
+ },
2789
+
2790
+ show_next: function(item) {
2791
+ if ( this.get_size() > 1 ) {
2792
+ //Retrieve item
2793
+ var next = this.get_next(item);
2794
+ if ( !next ) {
2795
+ if ( !this.util.is_type(item, View.Content_Item) ) {
2796
+ item = this.get_current();
2797
+ }
2798
+ item.get_viewer().close();
2799
+ }
2800
+ var i = this.get_parent().get_item(next);
2801
+ //Update current item
2802
+ this.set_current(i);
2803
+ //Show item
2804
+ i.show();
2805
+ //Fire event
2806
+ this.trigger('item-next');
2807
+ }
2808
+ },
2809
+
2810
+ show_prev: function(item) {
2811
+ if ( this.get_size() > 1 ) {
2812
+ //Retrieve item
2813
+ var prev = this.get_prev(item);
2814
+ if ( !prev ) {
2815
+ if ( !this.util.is_type(item, View.Content_Item) ) {
2816
+ item = this.get_current();
2817
+ }
2818
+ item.get_viewer().close();
2819
+ }
2820
+ var i = this.get_parent().get_item(prev);
2821
+ //Update current item
2822
+ this.set_current(i);
2823
+ //Show item
2824
+ i.show();
2825
+ //Fire event
2826
+ this.trigger('item-prev');
2827
+ }
2828
+ },
2829
+
2830
+ /**
2831
+ * Retrieve total number of items in group
2832
+ * @return int Number of items in group
2833
+ */
2834
+ get_size: function() {
2835
+ return this.get_items().length;
2836
+ },
2837
+
2838
+ is_single: function() {
2839
+ return ( this.get_size() == 1 );
2840
+ }
2841
+ };
2842
+
2843
+ View.Group = Component.extend(Group);
2844
+
2845
+ /**
2846
+ * Content Handler
2847
+ * @param obj options Init options
2848
+ */
2849
+ var Content_Handler = {
2850
+
2851
+ /* Configuration */
2852
+
2853
+ _slug: 'content_handler',
2854
+ _refs: {
2855
+ 'item': 'Content_Item'
2856
+ },
2857
+
2858
+ /* References */
2859
+
2860
+ item: null,
2861
+
2862
+ /* Properties */
2863
+
2864
+ /**
2865
+ * Raw layout template
2866
+ * @var string
2867
+ */
2868
+ template: '',
2869
+
2870
+ /* Methods */
2871
+
2872
+ /* Item */
2873
+
2874
+ /**
2875
+ * Check if item instance set for type
2876
+ * @uses get_item()
2877
+ * @uses clear_item() to remove invalid item values
2878
+ * @return bool TRUE if valid item set, FALSE otherwise
2879
+ */
2880
+ has_item: function() {
2881
+ return ( this.util.is_empty(this.get_item()) ) ? false : true;
2882
+ },
2883
+
2884
+ /**
2885
+ * Retrieve item instance set on type
2886
+ * @uses get_component()
2887
+ * @return mixed Content_Item if valid item set, NULL otherwise
2888
+ */
2889
+ get_item: function() {
2890
+ return this.get_component('item', true, false);
2891
+ },
2892
+
2893
+ /**
2894
+ * Set item instance for type
2895
+ * Items are only meant to be set/used while item is being processed
2896
+ * @uses set_component()
2897
+ * @param Content_Item item Item instance
2898
+ * @return obj|null Item instance if item successfully set, NULL otherwise
2899
+ */
2900
+ set_item: function(item) {
2901
+ //Set reference
2902
+ var r = this.set_component('item', item);
2903
+ return r;
2904
+ },
2905
+
2906
+ /**
2907
+ * Clear item instance from type
2908
+ * Sets value to NULL
2909
+ */
2910
+ clear_item: function() {
2911
+ this.item = null;
2912
+ },
2913
+
2914
+ /* Evaluation */
2915
+
2916
+ /**
2917
+ * Check if item matches content handler
2918
+ * @param object item Content_Item instance to check for type match
2919
+ * @return bool TRUE if type matches, FALSE otherwise
2920
+ */
2921
+ match: function(item) {
2922
+ //Validate
2923
+ var attr = 'match';
2924
+ var m = this.get_attribute(attr);
2925
+ //Stop processing types with no matching algorithm
2926
+ if ( !this.util.is_empty(m) ) {
2927
+ //Process regex patterns
2928
+
2929
+ //String-based
2930
+ if ( this.util.is_string(m) ) {
2931
+ //Create new regexp object
2932
+ m = new RegExp(m, "i");
2933
+ this.set_attribute(attr, m);
2934
+ }
2935
+ //RegExp based
2936
+ if ( this.util.is_type(m, RegExp) ) {
2937
+ return m.test(item.get_uri());
2938
+ }
2939
+ //Process function
2940
+ if ( this.util.is_func(m) ) {
2941
+ return ( m.call(this, item) ) ? true : false;
2942
+ }
2943
+ }
2944
+ //Default
2945
+ return false;
2946
+ },
2947
+
2948
+ /* Processing/Output */
2949
+
2950
+ /**
2951
+ * Render output to display item
2952
+ * @param Content_Item item Item to render output for
2953
+ * @return obj jQuery.Promise that is resolved when item is rendered
2954
+ */
2955
+ render: function(item) {
2956
+ var dfr = $.Deferred();
2957
+ //Validate
2958
+ var ret = this.call_attribute('render', item);
2959
+ if ( this.util.is_promise(ret) ) {
2960
+ ret.done(function(output) {
2961
+ dfr.resolve(output);
2962
+ });
2963
+ } else {
2964
+ //String format
2965
+ if ( this.util.is_string(ret) ) {
2966
+ ret = this.util.format(ret, item.get_uri());
2967
+ }
2968
+ //Resolve deferred immediately
2969
+ dfr.resolve(ret);
2970
+ }
2971
+ return dfr.promise();
2972
+ }
2973
+ };
2974
+
2975
+ View.Content_Handler = Component.extend(Content_Handler);
2976
+
2977
+ /**
2978
+ * Content Item
2979
+ * @param obj options Init options
2980
+ */
2981
+ var Content_Item = {
2982
+ /* Configuration */
2983
+
2984
+ _slug: 'content_item',
2985
+ _reciprocal: true,
2986
+ _refs: {
2987
+ 'viewer': 'Viewer',
2988
+ 'group': 'Group',
2989
+ 'type': 'Content_Handler'
2990
+ },
2991
+ _containers: ['group'],
2992
+
2993
+ _attr_default: {
2994
+ source: null,
2995
+ permalink: null,
2996
+ dimensions: null,
2997
+ title: '',
2998
+ group: null,
2999
+ internal: false,
3000
+ output: null
3001
+ },
3002
+
3003
+ /* References */
3004
+
3005
+ group: null,
3006
+ viewer: null,
3007
+ type: null,
3008
+
3009
+ /* Properties */
3010
+
3011
+ data: null,
3012
+
3013
+ /* Init */
3014
+
3015
+ _c: function(el) {
3016
+ //Save element to instance
3017
+ this.dom_set(el);
3018
+ //Default initialization
3019
+ this._super();
3020
+ },
3021
+
3022
+ /* Methods */
3023
+
3024
+ /*-** Attributes **-*/
3025
+
3026
+ /**
3027
+ * Build default attributes
3028
+ * Populates attributes with asset properties (attachments)
3029
+ * Overrides super class method
3030
+ * @uses Component.init_default_attributes()
3031
+ */
3032
+ init_default_attributes: function() {
3033
+ this._super();
3034
+ //Add asset properties
3035
+ var d = this.dom_get();
3036
+ var key = d.attr('href') || null;
3037
+ var assets = this.get_parent().assets || null;
3038
+ //Merge asset data with default attributes
3039
+ if ( this.util.is_string(key) ) {
3040
+ var attrs = [{}, this._attr_default, {'permalink': key}];
3041
+ if ( this.util.is_obj(assets) ) {
3042
+ var t = this;
3043
+ var get_assets = function(key, raw) {
3044
+ var ret = {};
3045
+ if ( key in assets && t.util.is_obj(assets[key]) ) {
3046
+ var ret = assets[key];
3047
+ if ( t.util.is_string(raw) ) {
3048
+ var e = '_entries';
3049
+ if ( !( e in ret ) || -1 == $.inArray(raw, ret[e]) ) {
3050
+ ret = {};
3051
+ }
3052
+ }
3053
+ }
3054
+ return ret;
3055
+ };
3056
+ var asset = get_assets(key);
3057
+ if ( this.util.is_empty(asset) && ( kpos = key.indexOf('?') ) && kpos != -1 ) {
3058
+ var key_base = key.substr(0, kpos);
3059
+ asset = get_assets(key_base, key);
3060
+ }
3061
+ if ( !this.util.is_empty(asset) ) {
3062
+ attrs.push(asset);
3063
+ }
3064
+ }
3065
+ this._attr_default = $.extend.apply(this, attrs);
3066
+ }
3067
+ return this._attr_default;
3068
+ },
3069
+
3070
+ /*-** Properties **-*/
3071
+
3072
+ /**
3073
+ * Retrieve item output
3074
+ * Output generated based on content handler if not previously generated
3075
+ * @uses get_attribute() to retrieve cached output
3076
+ * @uses set_attribute() to cache generated output
3077
+ * @uses get_type() to retrieve item type
3078
+ * @uses Content_Handler.render() to generate item output
3079
+ * @return obj jQuery.Promise that is resolved when output is retrieved
3080
+ */
3081
+ get_output: function() {
3082
+ var dfr = $.Deferred();
3083
+ //Check for cached output
3084
+ var ret = this.get_attribute('output');
3085
+ if ( this.util.is_string(ret) ) {
3086
+ dfr.resolve(ret);
3087
+ } else if ( this.has_type() ) {
3088
+ //Render output from scratch (if necessary)
3089
+ //Get item type
3090
+ var type = this.get_type();
3091
+ //Render type-based output
3092
+ var item = this;
3093
+ type.render(this).done(function(output) {
3094
+ //Cache output
3095
+ item.set_output(output);
3096
+ dfr.resolve(output);
3097
+ });
3098
+ } else {
3099
+ dfr.resolve('');
3100
+ }
3101
+ return dfr.promise();
3102
+ },
3103
+
3104
+ /**
3105
+ * Cache output for future retrieval
3106
+ * @uses set_attribute() to cache output
3107
+ */
3108
+ set_output: function(out) {
3109
+ if ( this.util.is_string(out, false) ) {
3110
+ this.set_attribute('output', out);
3111
+ }
3112
+ },
3113
+
3114
+ /**
3115
+ * Retrieve item output
3116
+ * Alias for `get_output()`
3117
+ * @return jQuery.Promise Deferred that is resolved when content is retrieved
3118
+ */
3119
+ get_content: function() {
3120
+ return this.get_output();
3121
+ },
3122
+
3123
+ /**
3124
+ * Retrieve item URI
3125
+ * @param string mode (optional) Which URI should be retrieved
3126
+ * > source: Media source
3127
+ * > permalink: Item permalink
3128
+ * @return string Item URI
3129
+ */
3130
+ get_uri: function(mode) {
3131
+ //Validate
3132
+ if ( $.inArray(mode ,['source', 'permalink']) == -1 ) {
3133
+ mode = 'source';
3134
+ }
3135
+ //Retrieve URI
3136
+ var ret = this.get_attribute(mode);
3137
+ if ( !this.util.is_string(ret) ) {
3138
+ ret = ( 'source' == mode ) ? this.get_attribute('permalink') : '';
3139
+ }
3140
+ return ret;
3141
+ },
3142
+
3143
+ /**
3144
+ * Retrieve item title
3145
+ */
3146
+ get_title: function() {
3147
+ var prop = 'title';
3148
+ var prop_cached = prop + '_cached';
3149
+ //Check for cached value
3150
+ if ( this.has_attribute(prop_cached) ) {
3151
+ return this.get_attribute(prop_cached, '');
3152
+ }
3153
+
3154
+ var title = '';
3155
+ var sel_cap = '.wp-caption-text';
3156
+ //Generate title from DOM values
3157
+ var dom = this.dom_get();
3158
+
3159
+ //Standalone link
3160
+ if ( dom.length && !this.in_gallery() ) {
3161
+ //Link title
3162
+ title = dom.attr(prop);
3163
+
3164
+ //Caption
3165
+ if ( !title ) {
3166
+ title = dom.siblings(sel_cap).html();
3167
+ }
3168
+ }
3169
+
3170
+ //Saved attributes
3171
+ if ( !title ) {
3172
+ var props = ['caption', 'title'];
3173
+ for ( var x = 0; x < props.length; x++ ) {
3174
+ title = this.get_attribute(props[x], '');
3175
+ if ( !this.util.is_empty(title) ) {
3176
+ break;
3177
+ }
3178
+ }
3179
+ }
3180
+
3181
+ //Fallbacks
3182
+ if ( !title && dom.length ) {
3183
+ //Alt attribute
3184
+ title = dom.find('img').first().attr('alt');
3185
+
3186
+ //Element text
3187
+ if ( !title ) {
3188
+ title = dom.text();
3189
+ }
3190
+ }
3191
+
3192
+ //Validate
3193
+ if ( !this.util.is_string(title, false) ) {
3194
+ title = '';
3195
+ }
3196
+
3197
+ //Cache retrieved value
3198
+ this.set_attribute(prop_cached, title);
3199
+ //Return value
3200
+ return title;
3201
+ },
3202
+
3203
+ /**
3204
+ * Retrieve item dimensions
3205
+ * @return obj Item `width` and `height` properties (px)
3206
+ */
3207
+ get_dimensions: function() {
3208
+ return $.extend({'width': 0, 'height': 0}, this.get_attribute('dimensions'), {});
3209
+ },
3210
+
3211
+ /**
3212
+ * Save item data to instance
3213
+ * Item data is saved when rendered
3214
+ * @param mixed data Item data (property cleared if NULL)
3215
+ */
3216
+ set_data: function(data) {
3217
+ this.data = data;
3218
+ },
3219
+
3220
+ /**
3221
+ * Determine gallery type
3222
+ * @return string|null Gallery type ID (NULL if item not in gallery)
3223
+ */
3224
+ gallery_type: function() {
3225
+ var ret = null;
3226
+ var types = {
3227
+ 'wp': '.gallery-icon',
3228
+ 'ngg': '.ngg-gallery-thumbnail'
3229
+ };
3230
+
3231
+ var dom = this.dom_get();
3232
+ for ( var type in types ) {
3233
+ if ( dom.parent(types[type]).length > 0 ) {
3234
+ ret = type;
3235
+ break;
3236
+ }
3237
+ }
3238
+ return ret;
3239
+ },
3240
+
3241
+ /**
3242
+ * Check if current link is part of a gallery
3243
+ * @param string gType (optional) Gallery type to check for
3244
+ * @return bool TRUE if link is part of (specified) gallery (FALSE otherwise)
3245
+ */
3246
+ in_gallery: function(gType) {
3247
+ var type = this.gallery_type();
3248
+ //No gallery
3249
+ if ( null == type ) {
3250
+ return false;
3251
+ }
3252
+ //Boolean check
3253
+ if ( !this.util.is_string(gType) ) {
3254
+ return true;
3255
+ }
3256
+ //Check for specific gallery type
3257
+ return ( gType == type ) ? true : false;
3258
+ },
3259
+
3260
+ /*-** Component References **-*/
3261
+
3262
+ /* Viewer */
3263
+
3264
+ get_viewer: function() {
3265
+ return this.get_component('viewer');
3266
+ },
3267
+
3268
+ /**
3269
+ * Sets item's viewer property
3270
+ * @uses View.get_viewer() to retrieve global viewer
3271
+ * @uses this.viewer to save item's viewer
3272
+ * @param string|View.Viewer v Viewer to set for item
3273
+ * > Item's viewer is reset if invalid viewer provided
3274
+ */
3275
+ set_viewer: function(v) {
3276
+ return this.set_component('viewer', v);
3277
+ },
3278
+
3279
+ /* Group */
3280
+
3281
+ /**
3282
+ * Retrieve item's group
3283
+ * @param bool set_current (optional) Sets item as current item in group (Default: FALSE)
3284
+ * @return View.Group|bool Group reference item belongs to (FALSE if no group)
3285
+ */
3286
+ get_group: function(set_current) {
3287
+ var prop = 'group';
3288
+ //Check if group reference already set
3289
+ var g = this.get_component(prop, true, false, false);
3290
+ if ( g ) {
3291
+ } else {
3292
+ //Set empty group if no group exists
3293
+ g = this.set_component(prop, new View.Group());
3294
+ set_current = true;
3295
+ }
3296
+ if ( !!set_current ) {
3297
+ g.set_current(this);
3298
+ }
3299
+ return g;
3300
+ },
3301
+
3302
+ /**
3303
+ * Sets item's group property
3304
+ * @uses View.get_group() to retrieve global group
3305
+ * @uses this.group to set item's group
3306
+ * @param string|View.Group g Group to set for item
3307
+ * > Item's group is reset if invalid group provided
3308
+ */
3309
+ set_group: function(g) {
3310
+ //If group ID set, get object reference
3311
+ if ( this.util.is_string(g) ) {
3312
+ g = this.get_parent().get_group(g);
3313
+ }
3314
+
3315
+ //Set (or clear) group property
3316
+ this.group = ( this.util.is_type(g, View.Group) ) ? g : false;
3317
+ },
3318
+
3319
+ /* Content Handler */
3320
+
3321
+ /**
3322
+ * Retrieve item type
3323
+ * @uses get_component() to retrieve saved reference to Content_Handler instance
3324
+ * @uses View.get_content_handler() to determine item content handler (if necessary)
3325
+ * @return Content_Handler|null Content Handler of item (NULL no valid type exists)
3326
+ */
3327
+ get_type: function() {
3328
+ var t = this.get_component('type', false, false, false);
3329
+ if ( !t ) {
3330
+ t = this.set_type(this.get_parent().get_content_handler(this));
3331
+ }
3332
+ return t;
3333
+ },
3334
+
3335
+ /**
3336
+ * Save content handler reference
3337
+ * @uses set_component() to save type reference
3338
+ * @return Content_Handler|null Saved content handler (NULL if invalid)
3339
+ */
3340
+ set_type: function(type) {
3341
+ return this.set_component('type', type);
3342
+ },
3343
+
3344
+ /**
3345
+ * Check if content handler exists for item
3346
+ * @return bool TRUE if content handler exists, FALSE otherwise
3347
+ */
3348
+ has_type: function() {
3349
+ var ret = !this.util.is_empty(this.get_type());
3350
+ return ret;
3351
+ },
3352
+
3353
+ /* Actions */
3354
+
3355
+ /**
3356
+ * Display item in viewer
3357
+ * @uses get_viewer() to retrieve viewer instance for item
3358
+ * @uses Viewer.show() to display item in viewer
3359
+ * @param obj options (optional) Options
3360
+ */
3361
+ show: function(options) {
3362
+ //Validate content handler
3363
+ if ( !this.has_type() ) {
3364
+ return false;
3365
+ }
3366
+ //Set display options
3367
+ this.set_attribute('options_show', options);
3368
+ //Retrieve viewer
3369
+ var v = this.get_viewer();
3370
+ //Load item
3371
+ var ret = v.show(this);
3372
+ return ret;
3373
+ },
3374
+
3375
+ reset: function() {
3376
+ this.set_attribute('options_show', null);
3377
+ }
3378
+ };
3379
+
3380
+ View.Content_Item = Component.extend(Content_Item);
3381
+
3382
+ /**
3383
+ * Modeled Component
3384
+ */
3385
+ var Modeled_Component = {
3386
+
3387
+ _slug: 'modeled_component',
3388
+
3389
+ /* Methods */
3390
+
3391
+ /* Attributes */
3392
+
3393
+ /**
3394
+ * Retrieve attribute
3395
+ * Gives priority to model values
3396
+ * @see Component.get_attribute()
3397
+ * @param string key Attribute to retrieve
3398
+ * @param mixed def (optional) Default value (Default: NULL)
3399
+ * @param bool check_model (optional) Check model for value (Default: TRUE)
3400
+ * @param bool enforce_type (optional) Return value data type should match default value data type (Default: TRUE)
3401
+ * @return mixed Attribute value
3402
+ */
3403
+ get_attribute: function(key, def, check_model, enforce_type) {
3404
+ //Validate
3405
+ if ( !this.util.is_string(key) ) {
3406
+ //Invalid requests sent straight to super method
3407
+ return this._super(key, def, enforce_type);
3408
+ }
3409
+ if ( !this.util.is_bool(check_model) ) {
3410
+ check_model = true;
3411
+ }
3412
+ var ret = null;
3413
+ //Check model for attribute
3414
+ if ( check_model ) {
3415
+ var m = this.get_ancestor(key, false);
3416
+ if ( this.util.in_obj(m, key) ) {
3417
+ ret = m[key];
3418
+ }
3419
+ }
3420
+ //Check standard attributes as fallback
3421
+ if ( null == ret ) {
3422
+ ret = this._super(key, def, enforce_type);
3423
+ }
3424
+ return ret;
3425
+ },
3426
+
3427
+ /**
3428
+ * Set attribute value
3429
+ * Gives priority to model values
3430
+ * @see Component.set_attribute()
3431
+ * @param string key Attribute to set
3432
+ * @param mixed val Value to set for attribute
3433
+ * @param bool|obj use_model (optional) Set the value on the model (Default: TRUE)
3434
+ * > bool: Set attribute on current model (TRUE) or as standard attribute (FALSE)
3435
+ * > obj: Model object to set attribute on
3436
+ * @return mixed Attribute value
3437
+ */
3438
+ set_attribute: function(key, val, use_model) {
3439
+ //Validate
3440
+ if ( ( !this.util.is_string(key) ) || !this.util.is_set(val) ) {
3441
+ return false;
3442
+ }
3443
+ if ( !this.util.is_bool(use_model) && !this.util.is_obj(use_model) ) {
3444
+ use_model = true;
3445
+ }
3446
+ //Determine where to set attribute
3447
+ if ( !!use_model ) {
3448
+ var model = this.util.is_obj(use_model) ? use_model : this.get_model();
3449
+
3450
+ //Set attribute in model
3451
+ model[key] = val;
3452
+ } else {
3453
+ //Set as standard attribute
3454
+ this._super(key, val);
3455
+ }
3456
+ return val;
3457
+ },
3458
+
3459
+
3460
+ /* Model */
3461
+
3462
+ /**
3463
+ * Retrieve Template model
3464
+ * @return obj Model (Default: Empty object)
3465
+ */
3466
+ get_model: function() {
3467
+ var m = this.get_attribute('model', null, false);
3468
+ if ( !this.util.is_obj(m) ) {
3469
+ //Set default value
3470
+ m = {};
3471
+ this.set_attribute('model', m, false);
3472
+ }
3473
+ return m;
3474
+ },
3475
+
3476
+
3477
+ /**
3478
+ * Check if instance has model
3479
+ * @return bool TRUE if model is set, FALSE otherwise
3480
+ */
3481
+ has_model: function() {
3482
+ return ( this.util.is_empty( this.get_model() ) ) ? false : true;
3483
+ },
3484
+
3485
+
3486
+
3487
+ /**
3488
+ * Check if specified attribute exists in model
3489
+ * @param string key Attribute to check for
3490
+ * @return bool TRUE if attribute exists, FALSE otherwise
3491
+ */
3492
+ in_model: function(key) {
3493
+ return ( this.util.in_obj(this.get_model(), key) ) ? true : false;
3494
+ },
3495
+
3496
+ /**
3497
+ * Retrieve all ancestor models
3498
+ * @param bool inc_current (optional) Include current model in list (Default: FALSE)
3499
+ * @return array Theme ancestor models (Closest parent first)
3500
+ */
3501
+ get_ancestors: function(inc_current) {
3502
+ var ret = [];
3503
+ var m = this.get_model();
3504
+ while ( this.util.is_obj(m) ) {
3505
+ ret.push(m);
3506
+ m = ( this.util.in_obj(m, 'parent') && this.util.is_obj(m.parent) ) ? m.parent : null;
3507
+ }
3508
+ //Remove current model from list
3509
+ if ( !inc_current ) {
3510
+ ret.shift();
3511
+ }
3512
+ return ret;
3513
+ },
3514
+
3515
+ /**
3516
+ * Retrieve first ancestor of current theme with specified attribute
3517
+ * > Current model is also evaluated
3518
+ * @param string attr Attribute to search ancestors for
3519
+ * @param bool safe_mode (optional) Return current model if no matching ancestor found (Default: TRUE)
3520
+ * @return obj Theme ancestor (Default: Current theme model)
3521
+ */
3522
+ get_ancestor: function(attr, safe_mode) {
3523
+ //Validate
3524
+ if ( !this.util.is_string(attr) ) {
3525
+ return false;
3526
+ }
3527
+ if ( !this.util.is_bool(safe_mode) ) {
3528
+ safe_mode = true;
3529
+ }
3530
+ var mcurr;
3531
+ var m = mcurr = this.get_model();
3532
+ var found = false;
3533
+ while ( this.util.is_obj(m) ) {
3534
+ //Check if attribute exists in model
3535
+ if ( this.util.in_obj(m, attr) && !this.util.is_empty(m[attr]) ) {
3536
+ found = true;
3537
+ break;
3538
+ }
3539
+ //Get next model
3540
+ m = ( this.util.in_obj(m, 'parent') ) ? m['parent'] : null;
3541
+ }
3542
+ if ( !found ) {
3543
+ if ( safe_mode ) {
3544
+ //Use current model as fallback
3545
+ if ( this.util.is_empty(m) ) {
3546
+ m = mcurr;
3547
+ }
3548
+ //Add attribute to object
3549
+ if ( !this.util.in_obj(m, attr) ) {
3550
+ m[attr] = null;
3551
+ }
3552
+ } else {
3553
+ m = null;
3554
+ }
3555
+ }
3556
+ return m;
3557
+ }
3558
+
3559
+ };
3560
+
3561
+ Modeled_Component = Component.extend(Modeled_Component);
3562
+
3563
+ /**
3564
+ * Theme
3565
+ */
3566
+ var Theme = {
3567
+
3568
+ /* Configuration */
3569
+
3570
+ _slug: 'theme',
3571
+ _refs: {
3572
+ 'viewer': 'Viewer',
3573
+ 'template': 'Template'
3574
+ },
3575
+ _models: {},
3576
+
3577
+ _containers: ['viewer'],
3578
+
3579
+ _attr_default: {
3580
+ template: null,
3581
+ model: null
3582
+ },
3583
+
3584
+ /* References */
3585
+
3586
+ viewer: null,
3587
+ template: null,
3588
+
3589
+ /* Methods */
3590
+
3591
+ /**
3592
+ * Custom constructor
3593
+ * @see Component._c()
3594
+ */
3595
+ _c: function(id, attributes, viewer) {
3596
+ //Validate
3597
+ if ( arguments.length == 1 && this.util.is_type(arguments[0], View.Viewer) ) {
3598
+ viewer = arguments[0];
3599
+ id = null;
3600
+ }
3601
+ //Pass parameters to parent constructor
3602
+ this._super(id, attributes);
3603
+
3604
+ //Set viewer instance
3605
+ this.set_viewer(viewer);
3606
+
3607
+ //Set theme model
3608
+ this.set_model(id);
3609
+ },
3610
+
3611
+ /* Viewer */
3612
+
3613
+ get_viewer: function() {
3614
+ return this.get_component('viewer', false, true, false);
3615
+ },
3616
+
3617
+ /**
3618
+ * Sets theme's viewer property
3619
+ * @uses View.get_viewer() to retrieve global viewer
3620
+ * @uses this.viewer to save item's viewer
3621
+ * @param string|View.Viewer v Viewer to set for item
3622
+ * > Theme's viewer is reset if invalid viewer provided
3623
+ */
3624
+ set_viewer: function(v) {
3625
+ return this.set_component('viewer', v);
3626
+ },
3627
+
3628
+ /* Template */
3629
+
3630
+ /**
3631
+ * Retrieve template instance
3632
+ * @return Template instance
3633
+ */
3634
+ get_template: function() {
3635
+ //Get saved template
3636
+ var ret = this.get_component('template', true, false, false);
3637
+ //Template needs to be initialized
3638
+ if ( this.util.is_empty(ret) ) {
3639
+ //Pass model to Template instance
3640
+ var attr = { 'theme': this, 'model': this.get_model() };
3641
+ ret = this.set_component('template', new View.Template(attr));
3642
+ }
3643
+ return ret;
3644
+ },
3645
+
3646
+ /**
3647
+ * Retrieve tags from template
3648
+ * All tags will be retrieved by default
3649
+ * Specific tag/property instances can be retrieved as well
3650
+ * @see Template.get_tags()
3651
+ * @param string name (optional) Name of tags to retrieve
3652
+ * @param string prop (optional) Specific tag property to retrieve
3653
+ * @return array Tags in template
3654
+ */
3655
+ get_tags: function(name, prop) {
3656
+ return this.get_template().get_tags(name, prop);
3657
+ },
3658
+
3659
+ /**
3660
+ * Retrieve tag DOM elements
3661
+ * @see Template.dom_get_tag()
3662
+ */
3663
+ dom_get_tag: function(tag, prop) {
3664
+ return $(this.get_template().dom_get_tag(tag, prop));
3665
+ },
3666
+
3667
+ /* Model */
3668
+
3669
+ /**
3670
+ * Retrieve theme models
3671
+ * @return obj Theme models
3672
+ */
3673
+ get_models: function() {
3674
+ return this._models;
3675
+ },
3676
+
3677
+ /**
3678
+ * Retrieve specified theme model
3679
+ * @param string id (optional) Theme model to retrieve
3680
+ * > Default model retrieved if ID is invalid/not set
3681
+ * @return obj Specified theme model
3682
+ */
3683
+ get_model: function(id) {
3684
+ var ret = null;
3685
+ //Pass request to superclass method
3686
+ if ( !this.util.is_set(id) && this.util.is_obj( this.get_attribute('model', null, false) ) ) {
3687
+ ret = this._super();
3688
+ } else {
3689
+ //Retrieve matching theme model
3690
+ var models = this.get_models();
3691
+ if ( !this.util.is_string(id) ) {
3692
+ var id = this.get_parent().get_option('theme_default');
3693
+ }
3694
+ //Select first theme model if specified model is invalid
3695
+ if ( !this.util.in_obj(models, id) ) {
3696
+ id = $.map(models, function(v, key) { return key; })[0];
3697
+ }
3698
+ ret = models[id];
3699
+ }
3700
+ return ret;
3701
+ },
3702
+
3703
+ /**
3704
+ * Set model for current theme instance
3705
+ * @param string id (optional) Theme ID (Default theme retrieved if ID invalid)
3706
+ */
3707
+ set_model: function(id) {
3708
+ this.set_attribute('model', this.get_model(id), false);
3709
+ //Set ID using model attributes (if necessary)
3710
+ if ( !this.check_id(true) ) {
3711
+ var m = this.get_model();
3712
+ if ( 'id' in m ) {
3713
+ this.set_id(m.id);
3714
+ }
3715
+ }
3716
+ },
3717
+
3718
+ /* Properties */
3719
+
3720
+ /**
3721
+ * Generate class names for DOM node
3722
+ * @param string rtype (optional) Return data type
3723
+ * > Default: array
3724
+ * > If string supplied: Joined classes delimited by parameter
3725
+ * @uses get_class() to generate class names
3726
+ * @uses Array.join() to convert class names array to string
3727
+ * @return array Class names
3728
+ */
3729
+ get_classes: function(rtype) {
3730
+ //Build array of class names
3731
+ var cls = [];
3732
+ var thm = this;
3733
+ //Include theme parent's class name
3734
+ var models = this.get_ancestors(true);
3735
+ $.each(models, function(idx, model) {
3736
+ cls.push(thm.add_ns(model.id));
3737
+ });
3738
+ //Convert class names array to string
3739
+ if ( this.util.is_string(rtype) ) {
3740
+ cls = cls.join(rtype);
3741
+ }
3742
+ //Return class names
3743
+ return cls;
3744
+ },
3745
+
3746
+ /**
3747
+ * Get custom measurement
3748
+ * @param string attr Measurement to retrieve
3749
+ * @param obj def (optional) Default value
3750
+ * @return obj Attribute measurements
3751
+ */
3752
+ get_measurement: function(attr, def) {
3753
+ var meas = null;
3754
+ //Validate
3755
+ if ( !this.util.is_string(attr) ) {
3756
+ return meas;
3757
+ }
3758
+ if ( !this.util.is_obj(def, false) ) {
3759
+ def = {};
3760
+ }
3761
+ //Manage cache
3762
+ var attr_cache = this.util.format('%s_cache', attr);
3763
+ var cache = this.get_attribute(attr_cache, {}, false);
3764
+ var status = '_status';
3765
+ var item = this.get_viewer().get_item();
3766
+ var w = $(window);
3767
+ //Check cache freshness
3768
+ if ( !( status in cache ) || !this.util.is_obj(cache[status]) || cache[status].width != w.width() || cache[status].height != w.height() ) {
3769
+ cache = {};
3770
+ }
3771
+ if ( this.util.is_empty(cache) ) {
3772
+ //Set status
3773
+ cache[status] = {
3774
+ 'width': w.width(),
3775
+ 'height': w.height(),
3776
+ 'index': []
3777
+ };
3778
+ }
3779
+ //Retrieve cached values
3780
+ var pos = $.inArray(item, cache[status].index);
3781
+ if ( pos != -1 && pos in cache ) {
3782
+ meas = cache[pos];
3783
+ }
3784
+ //Generate measurement
3785
+ if ( !this.util.is_obj(meas) ) {
3786
+ //Get custom theme measurement
3787
+ meas = this.call_attribute(attr);
3788
+ if ( !this.util.is_obj(meas) ) {
3789
+ //Retrieve fallback value
3790
+ meas = this.get_measurement_default(attr);
3791
+ }
3792
+ }
3793
+ //Normalize measurement
3794
+ meas = ( this.util.is_obj(meas) ) ? $.extend({}, def, meas) : def;
3795
+ //Cache measurement
3796
+ pos = cache[status].index.push(item) - 1;
3797
+ cache[pos] = meas;
3798
+ this.set_attribute(attr_cache, cache, false);
3799
+ //Return measurement (copy)
3800
+ return $.extend({}, meas);
3801
+ },
3802
+
3803
+ /**
3804
+ * Get default measurement using attribute's default handler
3805
+ * @param string attr Measurement attribute
3806
+ * @return obj Measurement values
3807
+ */
3808
+ get_measurement_default: function(attr) {
3809
+ //Validate
3810
+ if ( !this.util.is_string(attr) ) {
3811
+ return null;
3812
+ }
3813
+ //Find default handler
3814
+ attr = this.util.format('get_%s_default', attr);
3815
+ if ( this.util.in_obj(this, attr) ) {
3816
+ attr = this[attr];
3817
+ if ( this.util.is_func(attr) ) {
3818
+ //Execute default handler
3819
+ attr = attr.call(this);
3820
+ }
3821
+ } else {
3822
+ attr = null;
3823
+ }
3824
+ return attr;
3825
+ },
3826
+
3827
+ /**
3828
+ * Retrieve theme offset
3829
+ * @return obj Theme offset with `width` & `height` properties
3830
+ */
3831
+ get_offset: function() {
3832
+ return this.get_measurement('offset', { 'width': 0, 'height': 0});
3833
+ },
3834
+
3835
+ /**
3836
+ * Generate default offset
3837
+ * @return obj Theme offsets with `width` & `height` properties
3838
+ */
3839
+ get_offset_default: function() {
3840
+ var offset = { 'width': 0, 'height': 0 };
3841
+ var v = this.get_viewer();
3842
+ var vn = v.dom_get();
3843
+ //Clone viewer
3844
+ var vc = vn
3845
+ .clone()
3846
+ .attr('id', '')
3847
+ .css({'visibility': 'hidden', 'position': 'absolute', 'top': ''})
3848
+ .removeClass('loading')
3849
+ .appendTo(vn.parent());
3850
+ //Get offset from layout node
3851
+ var l = vc.find(v.dom_get_selector('layout'));
3852
+ if ( l.length ) {
3853
+ //Clear inline styles
3854
+ l.find('*').css({
3855
+ 'width': '',
3856
+ 'height': '',
3857
+ 'display': ''
3858
+ });
3859
+ //Resize content nodes
3860
+ var tags = this.get_tags('item', 'content');
3861
+ if ( tags.length ) {
3862
+ var offset_item = v.get_item().get_dimensions();
3863
+ //Set content dimensions
3864
+ tags = $(l.find(tags[0].get_selector('full')).get(0)).css({'width': offset_item.width, 'height': offset_item.height});
3865
+ $.each(offset_item, function(key, val) {
3866
+ offset[key] = -1 * val;
3867
+ });
3868
+ }
3869
+
3870
+ //Set offset
3871
+ offset.width += l.width();
3872
+ offset.height += l.height();
3873
+ //Normalize
3874
+ $.each(offset, function(key, val) {
3875
+ if ( val < 0 ) {
3876
+ offset[key] = 0;
3877
+ }
3878
+ });
3879
+ }
3880
+ vc.empty().remove();
3881
+ return offset;
3882
+ },
3883
+
3884
+ /**
3885
+ * Retrieve theme margins
3886
+ * @return obj Theme margin with `width` & `height` properties
3887
+ */
3888
+ get_margin: function() {
3889
+ return this.get_measurement('margin', {'width': 0, 'height': 0});
3890
+ },
3891
+
3892
+ /**
3893
+ * Retrieve item dimensions
3894
+ * Dimensions are adjusted to fit window (if necessary)
3895
+ * @return obj Item dimensions with `width` & `height` properties
3896
+ */
3897
+ get_item_dimensions: function() {
3898
+ var v = this.get_viewer();
3899
+ var dims = v.get_item().get_dimensions();
3900
+ if ( v.get_attribute('autofit', false) ) {
3901
+ //Get maximum dimensions
3902
+ var margin = this.get_margin();
3903
+ var offset = this.get_offset();
3904
+ offset.height += margin.height;
3905
+ offset.width += margin.width;
3906
+ var max = {'width': $(window).width(), 'height': $(window).height() };
3907
+ if ( max.width > offset.width ) {
3908
+ max.width -= offset.width;
3909
+ }
3910
+ if ( max.height > offset.height ) {
3911
+ max.height -= offset.height;
3912
+ }
3913
+ //Get resize factor
3914
+ var factor = Math.min(max.width / dims.width, max.height / dims.height);
3915
+ //Resize dimensions
3916
+ if ( factor < 1 ) {
3917
+ $.each(dims, function(key, val) {
3918
+ dims[key] = Math.round(dims[key] * factor);
3919
+ });
3920
+ }
3921
+ }
3922
+ return $.extend({}, dims);
3923
+ },
3924
+
3925
+ /**
3926
+ * Retrieve theme dimensions
3927
+ * @return obj Theme dimensions with `width` & `height` properties
3928
+ */
3929
+ get_dimensions: function() {
3930
+ var dims = this.get_item_dimensions();
3931
+ var offset = this.get_offset();
3932
+ $.each(dims, function(key, val) {
3933
+ dims[key] += offset[key];
3934
+ });
3935
+ return dims;
3936
+ },
3937
+
3938
+ /* Output */
3939
+
3940
+ /**
3941
+ * Render Theme output
3942
+ * @param bool init (optional) Initialize theme (Default: FALSE)
3943
+ * @see Template.render()
3944
+ */
3945
+ render: function(init) {
3946
+ var thm = this;
3947
+ var tpl = this.get_template();
3948
+ var st = 'events_render';
3949
+ if ( !this.get_status(st) ) {
3950
+ this.set_status(st);
3951
+ //Register events
3952
+ tpl.on([
3953
+ 'render-init',
3954
+ 'render-loading',
3955
+ 'render-complete'
3956
+ ],
3957
+ function(ev) {
3958
+ return thm.trigger(ev.type, ev.data);
3959
+ });
3960
+ }
3961
+ //Render template
3962
+ tpl.render(init);
3963
+ },
3964
+
3965
+ transition: function(event, clear_queue) {
3966
+ var dfr = null;
3967
+ var attr = 'transition';
3968
+ var v = this.get_viewer();
3969
+ var fx_temp = null;
3970
+ var anim_on = v.animation_enabled();
3971
+ if ( v.get_attribute(attr, true) && this.util.is_string(event) ) {
3972
+ var anim_stop = function() {
3973
+ var l = v.get_layout();
3974
+ l.find('*').each(function() {
3975
+ var el = $(this);
3976
+ while ( el.queue().length ) {
3977
+ el.stop(false, true);
3978
+ }
3979
+ });
3980
+ }
3981
+ //Stop queued animations
3982
+ if ( !!clear_queue ) {
3983
+ anim_stop();
3984
+ }
3985
+ //Get transition handlers
3986
+ var attr_set = [attr, 'set'].join('_');
3987
+ var trns;
3988
+ if ( !this.get_attribute(attr_set) ) {
3989
+ var models = this.get_ancestors(true);
3990
+ trns = [];
3991
+ this.set_attribute(attr_set, true);
3992
+ var thm = this;
3993
+ $.each(models, function(idx, model) {
3994
+ if ( attr in model && thm.util.is_obj(model[attr]) ) {
3995
+ trns.push(model[attr]);
3996
+ }
3997
+ });
3998
+ //Merge transition handlers into current theme
3999
+ trns.push({});
4000
+ trns = this.set_attribute(attr, $.extend.apply($, trns.reverse()));
4001
+ } else {
4002
+ trns = this.get_attribute(attr, {});
4003
+ }
4004
+ if ( this.util.is_method(trns, event) ) {
4005
+ //Disable animations if necessary
4006
+ if ( !anim_on ) {
4007
+ fx_temp = $.fx.off;
4008
+ $.fx.off = true;
4009
+ }
4010
+ //Pass control to transition event
4011
+ dfr = trns[event].call(this, v, $.Deferred());
4012
+ }
4013
+ }
4014
+ if ( !this.util.is_promise(dfr) ) {
4015
+ dfr = $.Deferred();
4016
+ dfr.reject();
4017
+ }
4018
+ dfr.always(function() {
4019
+ //Restore animation state
4020
+ if ( null !== fx_temp ) {
4021
+ $.fx.off = fx_temp;
4022
+ }
4023
+ });
4024
+ return dfr.promise();
4025
+ }
4026
+ };
4027
+
4028
+ View.Theme = Modeled_Component.extend(Theme);
4029
+
4030
+ /**
4031
+ * Template handler
4032
+ * Parses and Builds layout from raw template
4033
+ */
4034
+ var Template = {
4035
+ /* Configuration */
4036
+
4037
+ _slug: 'template',
4038
+ _reciprocal: true,
4039
+
4040
+ _refs: {
4041
+ 'theme': 'Theme'
4042
+ },
4043
+ _containers: ['theme'],
4044
+
4045
+ _attr_default: {
4046
+ /**
4047
+ * URI to layout (raw) file
4048
+ * @var string
4049
+ */
4050
+ layout_uri: '',
4051
+
4052
+ /**
4053
+ * Raw layout template
4054
+ * @var string
4055
+ */
4056
+ layout_raw: '',
4057
+ /**
4058
+ * Parsed layout
4059
+ * Placeholders processed
4060
+ * @var string
4061
+ */
4062
+ layout_parsed: '',
4063
+ /**
4064
+ * Tags in template
4065
+ * Populated once template has been parsed
4066
+ * @var array
4067
+ */
4068
+ tags: null,
4069
+ /**
4070
+ * Model to use for properties
4071
+ * Usually reference to an object in other component
4072
+ * @var obj
4073
+ */
4074
+ model: null
4075
+ },
4076
+
4077
+ /* References */
4078
+
4079
+ theme: null,
4080
+
4081
+ /* Methods */
4082
+
4083
+ _c: function(attributes) {
4084
+ this._super('', attributes);
4085
+ },
4086
+
4087
+ get_theme: function() {
4088
+ var ret = this.get_component('theme', true, false, false);
4089
+ return ret;
4090
+ },
4091
+
4092
+ /* Output */
4093
+
4094
+ /**
4095
+ * Render output
4096
+ * @param bool init (optional) Whether to initialize layout (TRUE) or render item (FALSE) (Default: FALSE)
4097
+ * Events
4098
+ * > render-init: Initialize template
4099
+ * > render-loading: DOM elements created and item content about to be loaded
4100
+ * > render-complete: Item content loaded, ready for display
4101
+ */
4102
+ render: function(init) {
4103
+ var v = this.get_theme().get_viewer();
4104
+ if ( !this.util.is_bool(init) ) {
4105
+ init = false;
4106
+ }
4107
+ //Populate layout
4108
+ if ( !init ) {
4109
+ if ( !v.is_active() ) {
4110
+ return false;
4111
+ }
4112
+ var item = v.get_item();
4113
+ if ( !this.util.is_type(item, View.Content_Item) ) {
4114
+ v.close();
4115
+ return false;
4116
+ }
4117
+ //Iterate through tags and populate layout
4118
+ if ( v.is_active() && this.has_tags() ) {
4119
+ var loading_promise = this.trigger('render-loading');
4120
+ var tpl = this;
4121
+ var tags = this.get_tags(),
4122
+ tag_promises = [];
4123
+ //Render Tag output
4124
+ loading_promise.done(function() {
4125
+ if ( !v.is_active() ) {
4126
+ return false;
4127
+ }
4128
+ $.each(tags, function(idx, tag) {
4129
+ if ( !v.is_active() ) {
4130
+ return false;
4131
+ }
4132
+ tag_promises.push(tag.render(item).done(function(r) {
4133
+ if ( !v.is_active() ) {
4134
+ return false;
4135
+ }
4136
+ r.tag.dom_get().html(r.output);
4137
+ }));
4138
+ });
4139
+ //Fire event when all tags rendered
4140
+ if ( !v.is_active() ) {
4141
+ return false;
4142
+ }
4143
+ $.when.apply($, tag_promises).done(function() {
4144
+ tpl.trigger('render-complete');
4145
+ });
4146
+ });
4147
+ }
4148
+ } else {
4149
+ //Get Layout (basic)
4150
+ this.trigger('render-init', this.dom_get());
4151
+ }
4152
+ },
4153
+
4154
+ /*-** Layout **-*/
4155
+
4156
+ /**
4157
+ * Retrieve layout
4158
+ * @param bool parsed (optional) TRUE retrieves parsed layout, FALSE retrieves raw layout (Default: TRUE)
4159
+ * @return string Layout (HTML)
4160
+ */
4161
+ get_layout: function(parsed) {
4162
+ //Validate
4163
+ if ( !this.util.is_bool(parsed) ) {
4164
+ parsed = true;
4165
+ }
4166
+ //Determine which layout to retrieve (raw/parsed)
4167
+ var l = ( parsed ) ? this.parse_layout() : this.get_attribute('layout_raw', '');
4168
+ return l;
4169
+ },
4170
+
4171
+ /**
4172
+ * Parse layout
4173
+ * Converts template tags to HTML elements
4174
+ * > Template tag properties saved to HTML elements for future initialization
4175
+ * Returns saved layout if previously parsed
4176
+ * @return string Parsed layout
4177
+ */
4178
+ parse_layout: function() {
4179
+ //Check for previously-parsed layout
4180
+ var a = 'layout_parsed';
4181
+ var ret = this.get_attribute(a);
4182
+ //Return cached layout immediately
4183
+ if ( this.util.is_string(ret) ) {
4184
+ return ret;
4185
+ }
4186
+ //Parse raw layout
4187
+ ret = this.sanitize_layout( this.get_layout(false) );
4188
+ ret = this.parse_tags(ret);
4189
+ //Save parsed layout
4190
+ this.set_attribute(a, ret);
4191
+
4192
+ //Return parsed layout
4193
+ return ret;
4194
+ },
4195
+
4196
+ /**
4197
+ * Sanitize layout
4198
+ * @param obj|string l Layout string or jQuery object
4199
+ * @return obj|string Sanitized layout (Same data type that was passed to method)
4200
+ */
4201
+ sanitize_layout: function(l) {
4202
+ //Stop processing if invalid value
4203
+ if ( this.util.is_empty(l) ) {
4204
+ return l;
4205
+ }
4206
+ //Set return type
4207
+ var rtype = ( this.util.is_string(l) ) ? 'string' : null;
4208
+ /* Quarantine hard-coded tags */
4209
+
4210
+ //Create DOM structure from raw template
4211
+ var dom = $(l);
4212
+ //Find hard-coded tag nodes
4213
+ var tag_temp = new View.Template_Tag();
4214
+ var cls = tag_temp.get_class();
4215
+ var cls_new = ['x', cls].join('_');
4216
+ $(tag_temp.get_selector(), dom).each(function(idx) {
4217
+ //Replace matching class name with blocking class
4218
+ $(this).removeClass(cls).addClass(cls_new);
4219
+ });
4220
+ //Format return value
4221
+ switch ( rtype ) {
4222
+ case 'string' :
4223
+ dom = dom.wrap('<div />').parent().html();
4224
+ default :
4225
+ l = dom;
4226
+ }
4227
+ return l;
4228
+ },
4229
+
4230
+ /*-** Tags **-*/
4231
+
4232
+ /**
4233
+ * Extract tags from template
4234
+ * Tags are replaced with DOM element placeholders
4235
+ * Extracted tags are saved as element attribute values (for future use)
4236
+ * @param string l Raw layout to parse
4237
+ * @return string Parsed layout
4238
+ */
4239
+ parse_tags: function(l) {
4240
+ //Validate
4241
+ if ( !this.util.is_string(l) ) {
4242
+ return '';
4243
+ }
4244
+ //Parse tags in layout
4245
+ //Tag regex
4246
+ var re = /\{{2}\s*(\w.*?)\s*\}{2}/gim;
4247
+ //Tag match results
4248
+ var match;
4249
+ //Iterate through template and find tags
4250
+ while ( match = re.exec(l) ) {
4251
+ //Replace tag in layout with DOM container
4252
+ l = l.substring(0, match.index) + this.get_tag_container(match[1]) + l.substring(match.index + match[0].length);
4253
+ }
4254
+ return l;
4255
+ },
4256
+
4257
+ /**
4258
+ * Create DOM element container for tag
4259
+ * @param string Tag ID (will be prefixed)
4260
+ * @return string DOM element
4261
+ */
4262
+ get_tag_container: function(tag) {
4263
+ //Build element
4264
+ var attr = this.get_tag_attribute();
4265
+ return this.util.format('<span %s="%s"></span>', attr, escape(tag));
4266
+ },
4267
+
4268
+ get_tag_attribute: function() {
4269
+ return this.get_parent().get_component_temp(View.Template_Tag).dom_get_attribute();
4270
+ },
4271
+
4272
+ /**
4273
+ * Retrieve Template_Tag instance at specified index
4274
+ * @param int idx (optional) Index to retrieve tag from
4275
+ * @return Template_Tag Tag instance
4276
+ */
4277
+ get_tag: function(idx) {
4278
+ var ret = null;
4279
+ if ( this.has_tags() ) {
4280
+ var tags = this.get_tags();
4281
+ if ( !this.util.is_int(idx) || 0 > idx || idx >= tags.length ) {
4282
+ idx = 0;
4283
+ }
4284
+ ret = tags[idx];
4285
+ }
4286
+ return ret;
4287
+ },
4288
+
4289
+ /**
4290
+ * Retrieve tags from template
4291
+ * Subset of tags may be retrieved based on parameter values
4292
+ * Template is parsed if tags not set
4293
+ * @param string name (optional) Tag type to retrieve instances of
4294
+ * @param string prop (optional) Tag property to retrieve instances of
4295
+ * @return array Template_Tag instances
4296
+ */
4297
+ get_tags: function(name, prop) {
4298
+ var a = 'tags';
4299
+ var tags = this.get_attribute(a);
4300
+ //Initialize tags
4301
+ if ( !this.util.is_array(tags) ) {
4302
+ tags = [];
4303
+ //Retrieve layout DOM tree
4304
+ var d = this.dom_get();
4305
+ //Select tag nodes
4306
+ var attr = this.get_tag_attribute();
4307
+ var nodes = $(d).find('[' + attr + ']');
4308
+ //Build tag instances from nodes
4309
+ $(nodes).each(function(idx) {
4310
+ //Get tag placeholder
4311
+ var el = $(this);
4312
+ var tag = new View.Template_Tag(unescape(el.attr(attr)));
4313
+ //Populate valid tags
4314
+ if ( tag.has_handler() ) {
4315
+ //Add tag to array
4316
+ tags.push(tag);
4317
+ //Connect tag to DOM node
4318
+ tag.dom_set(el);
4319
+ //Set classes
4320
+ el.addClass(tag.get_classes(' '));
4321
+ }
4322
+ //Clear data attribute
4323
+ el.removeAttr(attr);
4324
+ });
4325
+ //Save tags
4326
+ this.set_attribute(a, tags, false);
4327
+ }
4328
+ tags = this.get_attribute(a, [], false);
4329
+ //Filter tags by parameters
4330
+ if ( !this.util.is_empty(tags) && this.util.is_string(name) ) {
4331
+ //Normalize
4332
+ if ( !this.util.is_string(prop) ) {
4333
+ prop = false;
4334
+ }
4335
+ var tags_filtered = [];
4336
+ var tc = null;
4337
+ for ( var x = 0; x < tags.length; x++ ) {
4338
+ tc = tags[x];
4339
+ if ( name == tc.get_name() ) {
4340
+ //Check tag property
4341
+ if ( !prop || prop == tc.get_prop() ) {
4342
+ tags_filtered.push(tc);
4343
+ }
4344
+ }
4345
+ }
4346
+ tags = tags_filtered;
4347
+ }
4348
+ return ( this.util.is_array(tags, false) ) ? tags : [];
4349
+ },
4350
+
4351
+ /**
4352
+ * Check if template contains tags
4353
+ * @return bool TRUE if tags exist, FALSE otherwise
4354
+ */
4355
+ has_tags: function() {
4356
+ return ( this.get_tags().length > 0 ) ? true : false;
4357
+ },
4358
+
4359
+ /*-** DOM **-*/
4360
+
4361
+ /**
4362
+ * Custom DOM initialization
4363
+ */
4364
+ dom_init: function() {
4365
+ //Create DOM object from parsed layout
4366
+ this.dom_set(this.get_layout());
4367
+ },
4368
+
4369
+ /**
4370
+ * Retrieve DOM element(s) for specified tag
4371
+ * @param string tag Name of tag to retrieve
4372
+ * @param string prop (optional) Specific tag property to retrieve
4373
+ * @return array DOM elements for tag
4374
+ */
4375
+ dom_get_tag: function(tag, prop) {
4376
+ var ret = $();
4377
+ var tags = this.get_tags(tag, prop);
4378
+ if ( tags.length ) {
4379
+ //Build selector
4380
+ var level = null;
4381
+ if ( this.util.is_string(tag) ) {
4382
+ level = ( this.util.is_string(prop) ) ? 'full' : 'tag';
4383
+ }
4384
+ var sel = '.' + tags[0].get_class(level);
4385
+ ret = this.dom_get().find(sel);
4386
+ }
4387
+ return ret;
4388
+ }
4389
+ };
4390
+
4391
+ View.Template = Modeled_Component.extend(Template);
4392
+
4393
+ /**
4394
+ * Template tag
4395
+ */
4396
+ var Template_Tag = {
4397
+ /* Configuration */
4398
+ _slug: 'template_tag',
4399
+ _reciprocal: true,
4400
+ /* Properties */
4401
+ _attr_default: {
4402
+ name: null,
4403
+ prop: null,
4404
+ match: null
4405
+ },
4406
+ /**
4407
+ * Tag Handlers
4408
+ * Collection of Template_Tag_Handler instances
4409
+ * @var obj
4410
+ */
4411
+ handlers: {},
4412
+ /* Methods */
4413
+
4414
+ /**
4415
+ * Constructor
4416
+ * @param
4417
+ */
4418
+ _c: function(tag_match) {
4419
+ this.parse(tag_match);
4420
+ },
4421
+
4422
+ /**
4423
+ * Set instance attributes using tag extracted from template
4424
+ * @param string tag_match Extracted tag match
4425
+ */
4426
+ parse: function(tag_match) {
4427
+ //Return default value for invalid instances
4428
+ if ( !this.util.is_string(tag_match) ) {
4429
+ return false;
4430
+ }
4431
+ //Parse instance options
4432
+ var parts = tag_match.split('|'),
4433
+ part;
4434
+ if ( !parts.length ) {
4435
+ return null;
4436
+ }
4437
+ var attrs = {
4438
+ name: null,
4439
+ prop: null,
4440
+ match: tag_match
4441
+ };
4442
+ //Get tag ID
4443
+ attrs.name = parts[0];
4444
+ //Get main property
4445
+ if ( attrs.name.indexOf('.') != -1 ) {
4446
+ attrs.name = attrs.name.split('.', 2);
4447
+ attrs.prop = attrs.name[1];
4448
+ attrs.name = attrs.name[0];
4449
+ }
4450
+ //Get other attributes
4451
+ for ( var x = 1; x < parts.length; x++ ) {
4452
+ part = parts[x].split(':', 1);
4453
+ if ( part.length > 1 && !( part[0] in attrs ) ) {
4454
+ //Add key/value pair to attributes
4455
+ attrs[part[0]] = part[1];
4456
+ }
4457
+ }
4458
+ //Save to instance
4459
+ this.set_attributes(attrs, true);
4460
+ },
4461
+
4462
+ /**
4463
+ * Render tag output
4464
+ * @param Content_Item item
4465
+ * @return obj jQuery.Promise object that is resolved when tag is rendered
4466
+ * Parameters passed to callbacks
4467
+ * > tag obj Current tag instance
4468
+ * > output string Tag output
4469
+ */
4470
+ render: function(item) {
4471
+ var tag = this;
4472
+ return tag.get_handler().render(item, tag).pipe(function(output) {
4473
+ return {'tag': tag, 'output': output};
4474
+ });
4475
+ },
4476
+
4477
+ /**
4478
+ * Retrieve tag name
4479
+ * @return string Tag name (DEFAULT: NULL)
4480
+ */
4481
+ get_name: function() {
4482
+ return this.get_attribute('name');
4483
+ },
4484
+
4485
+ /**
4486
+ * Retrieve tag property
4487
+ */
4488
+ get_prop: function() {
4489
+ return this.get_attribute('prop');
4490
+ },
4491
+
4492
+ /**
4493
+ * Retrieve tag handler
4494
+ * @return Template_Tag_Handler Handler instance (Empty instance if handler does not exist)
4495
+ */
4496
+ get_handler: function() {
4497
+ return ( this.has_handler() ) ? this.handlers[this.get_name()] : new View.Template_Tag_Handler('');
4498
+ },
4499
+
4500
+ /**
4501
+ * Check if handler exists for tag
4502
+ * @return bool TRUE if handler exists, FALSE otherwise
4503
+ */
4504
+ has_handler: function() {
4505
+ return ( this.get_name() in this.handlers );
4506
+ },
4507
+
4508
+ /**
4509
+ * Generate class names for DOM node
4510
+ * @param string rtype (optional) Return data type
4511
+ * > Default: array
4512
+ * > If string supplied: Joined classes delimited by parameter
4513
+ * @uses get_class() to generate class names
4514
+ * @uses Array.join() to convert class names array to string
4515
+ * @return array Class names
4516
+ */
4517
+ get_classes: function(rtype) {
4518
+ //Build array of class names
4519
+ var cls = [
4520
+ //General tag class
4521
+ this.get_class(),
4522
+ //Tag name
4523
+ this.get_class('tag'),
4524
+ //Tag name + property
4525
+ this.get_class('full')
4526
+ ];
4527
+ //Convert class names array to string
4528
+ if ( this.util.is_string(rtype) ) {
4529
+ cls = cls.join(rtype);
4530
+ }
4531
+ //Return class names
4532
+ return cls;
4533
+ },
4534
+
4535
+ /**
4536
+ * Generate DOM-compatible class name based with varied levels of specificity
4537
+ * @param int level (optional) Class name specificity
4538
+ * > Default: General tag class (common to all tag elements)
4539
+ * > tag: Tag Name
4540
+ * > full: Tag Name + Property
4541
+ * @return string Class name
4542
+ */
4543
+ get_class: function(level) {
4544
+ var cls = '';
4545
+ switch ( level ) {
4546
+ case 'tag' :
4547
+ //Tag name
4548
+ cls = this.add_ns(this.get_name());
4549
+ break;
4550
+ case 'full' :
4551
+ //Tag name + property
4552
+ cls = this.add_ns([this.get_name(), this.get_prop()].join('_'));
4553
+ break;
4554
+ default :
4555
+ //General
4556
+ cls = this.get_ns();
4557
+ break;
4558
+ }
4559
+ return cls;
4560
+ },
4561
+
4562
+ /**
4563
+ * Generate tag selector based on specified class name level
4564
+ * @param string level (optional) Class name specificity (@see get_class() for parameter values)
4565
+ * @return string Tag selector
4566
+ */
4567
+ get_selector: function(level) {
4568
+ return '.' + this.get_class(level);
4569
+ }
4570
+ };
4571
+
4572
+ View.Template_Tag = Component.extend(Template_Tag);
4573
+
4574
+ /**
4575
+ * Theme tag handler
4576
+ */
4577
+ var Template_Tag_Handler = {
4578
+ /* Configuration */
4579
+ _slug: 'template_tag_handler',
4580
+ /* Properties */
4581
+ _attr_default: {
4582
+ supports_modifiers: false,
4583
+ dynamic: false,
4584
+ props: {}
4585
+ },
4586
+
4587
+ /* Methods */
4588
+
4589
+ /**
4590
+ * Render tag output
4591
+ * @param Content_Item item Item currently being displayed
4592
+ * @param Template_Tag Tag instance (from template)
4593
+ * @return obj jQuery.Promise linked to rendering process
4594
+ */
4595
+ render: function(item, instance) {
4596
+ var dfr = $.Deferred();
4597
+ //Pass to attribute method
4598
+ var ret = this.call_attribute('render', item, instance);
4599
+ //Check for promise
4600
+ if ( this.util.is_promise(ret) ) {
4601
+ ret.done(function(output) {
4602
+ dfr.resolve(output);
4603
+ });
4604
+ } else {
4605
+ //Resolve non-promises immediately
4606
+ dfr.resolve(ret);
4607
+ }
4608
+ //Return promise
4609
+ return dfr.promise();
4610
+ },
4611
+
4612
+ add_prop: function(prop, fn) {
4613
+ //Get attribute
4614
+ var a = 'props';
4615
+ var props = this.get_attribute(a);
4616
+ //Validate
4617
+ if ( !this.util.is_string(prop) || !this.util.is_func(fn) ) {
4618
+ return false;
4619
+ }
4620
+ if ( !this.util.is_obj(props, false) ) {
4621
+ props = {};
4622
+ }
4623
+ //Add property
4624
+ props[prop] = fn;
4625
+ //Save attribute
4626
+ this.set_attribute(a, props);
4627
+ },
4628
+
4629
+ handle_prop: function(prop, item, instance) {
4630
+ //Locate property
4631
+ var props = this.get_attribute('props');
4632
+ var out = '';
4633
+ if ( this.util.is_obj(props) && ( prop in props ) && this.util.is_func(props[prop]) ) {
4634
+ out = props[prop].call(this, item, instance);
4635
+ } else {
4636
+ out = item.get_viewer().get_label(prop);
4637
+ }
4638
+ return out;
4639
+ }
4640
+ };
4641
+
4642
+ View.Template_Tag_Handler = Component.extend(Template_Tag_Handler);
4643
+ /* Update References */
4644
+
4645
+ //Attach to global object
4646
+ SLB.attach('View', View);
4647
+ View = SLB.View;
4648
+ View.update_refs();
4649
+ })(jQuery);
client/sass/admin.scss ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
content-handlers/image/handler.image.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+ return {
3
+ render: function(item) {
4
+ var dfr = $.Deferred();
5
+ //Create image object
6
+ var img = new Image();
7
+ var type = this;
8
+ //Set load event
9
+ var handler = function(e) {
10
+ //Save Data
11
+ item.set_data(img);
12
+ //Set attributes
13
+ var dim = {'width': img.width, 'height': img.height};
14
+ item.set_attribute('dimensions', dim);
15
+ //Build output
16
+ var out = $('<img />', {'src': item.get_uri()});
17
+ //Resolve deferred
18
+ dfr.resolve(out);
19
+ };
20
+
21
+ //Attach event handler
22
+ $(img).on('load', function(e) { handler(e); });
23
+ //Load image
24
+ img.src = item.get_uri();
25
+ //Return promise
26
+ return dfr.promise();
27
+ }
28
+ }
29
+ })(jQuery);
css/admin.css DELETED
@@ -1,3 +0,0 @@
1
- .subhead {
2
- margin-bottom: .2em;
3
- }
 
 
 
css/lightbox.css DELETED
@@ -1,135 +0,0 @@
1
- #lightbox{
2
- position: absolute;
3
- top: 20px;
4
- left: 0;
5
- width: 100%;
6
- z-index: 3100;
7
- text-align: center;
8
- line-height: 0;
9
- color:#151410;
10
- }
11
-
12
- #lightbox a, #lightbox a:hover {
13
- border-bottom:none;
14
- color:#151410;
15
- text-decoration:underline;
16
- }
17
-
18
- #lightbox a img{ border: none; }
19
-
20
- #outerImageContainer{
21
- position: relative;
22
- background-color: #fff;
23
- width: 250px;
24
- height: 250px;
25
- margin: 0 auto;
26
- }
27
-
28
- #imageContainer{
29
- padding: 10px;
30
- }
31
-
32
- #loading{
33
- position: absolute;
34
- top: 40%;
35
- left: 0%;
36
- height: 25%;
37
- width: 100%;
38
- text-align: center;
39
- line-height: 0;
40
- }
41
- #loadingLink {
42
- display:block;
43
- margin:0 auto;
44
- padding:0;
45
- width:32px;
46
- height:32px;
47
- background:url("../images/loading.gif") center center no-repeat;
48
- text-indent:-9999px;
49
- }
50
- #hoverNav{
51
- position: absolute;
52
- top: 0;
53
- left: 0;
54
- height: 100%;
55
- width: 100%;
56
- z-index: 10;
57
- }
58
- #imageContainer>#hoverNav{ left: 0;}
59
- #hoverNav a{ outline: none;}
60
-
61
- #prevLinkImg, #nextLinkImg{
62
- width: 49%;
63
- height: 100%;
64
- background: transparent url("../images/blank.gif") no-repeat; /* Trick IE into showing hover */
65
- display: block;
66
- text-indent:-9999px;
67
- }
68
- #prevLinkImg { left: 0; float: left;}
69
- #nextLinkImg { right: 0; float: right;}
70
- #prevLinkImg:hover, #prevLinkImg:visited:hover { background: url("../images/prevlabel.gif") left 15% no-repeat; }
71
- #nextLinkImg:hover, #nextLinkImg:visited:hover { background: url("../images/nextlabel.gif") right 15% no-repeat; }
72
-
73
-
74
- #imageDataContainer{
75
- font: 10px Verdana, Helvetica, sans-serif;
76
- background-color: #fff;
77
- margin: 0 auto;
78
- line-height: 1.4em;
79
- }
80
-
81
- #imageData{
82
- padding:0 10px;
83
- }
84
- #imageDetails{ width: 70%; float: left; text-align: left; }
85
- #caption{ font-weight: bold; }
86
- #numberDisplay{ display: block; clear: left; }
87
- #detailsNav{ display: block; clear: left; padding:0 0 10px 0; }
88
- #prevLinkDetails { margin:0 8px 0 0; }
89
- #nextLinkDetails { margin:0 8px 0 0; }
90
- #closeLink {
91
- display:block;
92
- margin:0;
93
- padding:0 0 10px 0;
94
- text-decoration:none;
95
- float:right;
96
- width:66px;
97
- height:28px;
98
- background:url("../images/closelabel.gif") no-repeat;
99
- text-indent:-9999px;
100
- overflow:hidden;
101
- }
102
-
103
- #overlay{
104
- position: absolute;
105
- top: 0;
106
- left: 0;
107
- z-index: 3090;
108
- width: 100%;
109
- height: 500px;
110
- background-color: #151410;
111
- filter:alpha(opacity=60);
112
- -moz-opacity: 0.6;
113
- opacity: 0.6;
114
- }
115
-
116
-
117
- .clearfix:after {
118
- content: ".";
119
- display: block;
120
- height: 0;
121
- clear: both;
122
- visibility: hidden;
123
- }
124
-
125
- * html>body .clearfix {
126
- display: inline-block;
127
- width: 100%;
128
- }
129
-
130
- * html .clearfix {
131
- /* Hides from IE-mac \*/
132
- height: 1%;
133
- /* End hide from IE-mac */
134
- }
135
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
images/blank.gif DELETED
Binary file
images/closelabel.gif DELETED
Binary file
images/nextlabel.gif DELETED
Binary file
images/prevlabel.gif DELETED
Binary file
includes/class.admin.php ADDED
@@ -0,0 +1,1576 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ /* Files */
15
+
16
+ var $scripts = array (
17
+ 'admin' => array (
18
+ 'file' => 'client/js/lib.admin.js',
19
+ 'deps' => array('[core]'),
20
+ 'context' => array( 'admin_page_slb_options' ),
21
+ 'in_footer' => true,
22
+ ),
23
+ );
24
+
25
+ var $styles = array (
26
+ 'admin' => array (
27
+ 'file' => 'client/css/admin.css',
28
+ 'context' => array( 'admin_page_slb_options', 'admin_page_plugins' )
29
+ )
30
+ );
31
+
32
+ /* Properties */
33
+
34
+ /**
35
+ * Parent object
36
+ * Set on initialization
37
+ * @var obj
38
+ */
39
+ var $parent = null;
40
+
41
+ /**
42
+ * Messages
43
+ * @var array
44
+ */
45
+ var $messages = array();
46
+
47
+ /* Views */
48
+
49
+ /**
50
+ * Custom admin top-level menus
51
+ * Associative Array
52
+ * > Key: Menu ID
53
+ * > Val: Menu properties
54
+ * @var array
55
+ */
56
+ var $menus = array();
57
+
58
+ /**
59
+ * Custom admin pages
60
+ * Associative Array
61
+ * > Key: Page ID
62
+ * > Val: Page properties
63
+ * @var array
64
+ */
65
+ var $pages = array();
66
+
67
+ /**
68
+ * Custom admin sections
69
+ * Associative Array
70
+ * > Key: Section ID
71
+ * > Val: Section properties
72
+ * @var array
73
+ */
74
+ var $sections = array();
75
+
76
+ /**
77
+ * Reset options
78
+ * Indexed Array
79
+ * @var array
80
+ */
81
+ var $resets = array();
82
+
83
+ /* Constructor */
84
+
85
+ /**
86
+ * TODO Determine if $parent needed
87
+ */
88
+ function __construct(&$parent) {
89
+ parent::__construct();
90
+ //Set parent
91
+ if ( is_object($parent) )
92
+ $this->parent =& $parent;
93
+ }
94
+
95
+ /* Init */
96
+
97
+ protected function _hooks() {
98
+ parent::_hooks();
99
+ //Init
100
+ add_action('admin_menu', $this->m('init_menus'), 11);
101
+
102
+ //Reset Settings
103
+ add_action('admin_action_' . $this->add_prefix('admin'), $this->m('handle_action'));
104
+
105
+ //Notices
106
+ add_action('admin_notices', $this->m('handle_notices'));
107
+
108
+ //Plugin listing
109
+ add_filter('plugin_action_links_' . $this->util->get_plugin_base_name(), $this->m('plugin_action_links'), 10, 4);
110
+ add_action('in_plugin_update_message-' . $this->util->get_plugin_base_name(), $this->m('plugin_update_message'), 10, 2);
111
+ add_filter('site_transient_update_plugins', $this->m('plugin_update_transient'));
112
+ }
113
+
114
+ /* Handlers */
115
+
116
+ function handle_action() {
117
+ //Parse action
118
+ $t = 'type';
119
+ $g = 'group';
120
+ $o = 'obj';
121
+ $this->add_prefix_ref($t);
122
+ $this->add_prefix_ref($g);
123
+ $this->add_prefix_ref($o);
124
+ $r =& $_REQUEST;
125
+
126
+ //Retrieve view that initiated the action
127
+ if ( isset($r[$t]) && 'view' == $r[$t] ) {
128
+ if ( isset($r[$g]) && ( $prop = $r[$g] . 's' ) && property_exists($this, $prop) && is_array($this->{$prop}) && isset($r[$o]) && isset($this->{$prop}[$r[$o]]) ) {
129
+ $view =& $this->{$prop}[$r[$o]];
130
+ //Pass request to view
131
+ $view->do_callback();
132
+ }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Display notices
138
+ * Messages are localized upon display
139
+ * @uses `admin_notices` action hook to display messages
140
+ */
141
+ function handle_notices() {
142
+ $msgs = $this->util->apply_filters('admin_messages', array());
143
+ foreach ( $msgs as $mid => $msg ) {
144
+ //Filter out empty messages
145
+ if ( empty($msg) )
146
+ continue;
147
+ //Build and display message
148
+ $mid = $this->add_prefix('msg_' . $mid);
149
+ ?>
150
+ <div id="<?php echo esc_attr($mid); ?>" class="updated fade">
151
+ <p>
152
+ <?php echo esc_html($msg);?>
153
+ </p>
154
+ </div>
155
+ <?php
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Displays notices for admin operations
161
+ */
162
+ function show_notices() {
163
+ if ( is_admin() && isset($_REQUEST[$this->add_prefix('action')]) ) {
164
+ $action = $_REQUEST[$this->add_prefix('action')];
165
+ $msg = null;
166
+ if ( $action ) {
167
+ $msg = $this->get_message($action);
168
+ if ( ! empty($msg) ) {
169
+
170
+ }
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
+ function init_menus() {
182
+ //Add top level menus (when necessary)
183
+ /**
184
+ * @var SLB_Admin_Menu
185
+ */
186
+ $menu;
187
+ foreach ( $this->menus as $menu ) {
188
+ //Register menu
189
+ $hook = add_menu_page($menu->get_label('title'), $menu->get_label('menu'), $menu->get_capability(), $menu->get_id(), $menu->get_callback());
190
+ //Add hook to menu object
191
+ $menu->set_hookname($hook);
192
+ $this->menus[$menu->get_id_raw()] =& $menu;
193
+ }
194
+
195
+ /**
196
+ * @var SLB_Admin_Page
197
+ */
198
+ $page;
199
+ //Add subpages
200
+ foreach ( $this->pages as $page ) {
201
+ //Build Arguments
202
+ $args = array ( $page->get_label('header'), $page->get_label('menu'), $page->get_capability(), $page->get_id(), $page->get_callback() );
203
+ $f = null;
204
+ //Handle pages for default WP menus
205
+ if ( $page->is_parent_wp() ) {
206
+ $f = 'add_' . $page->get_parent() . '_page';
207
+ }
208
+
209
+ //Handle pages for custom menus
210
+ if ( ! function_exists($f) ) {
211
+ array_unshift( $args, $page->get_parent() );
212
+ $f = 'add_submenu_page';
213
+ }
214
+
215
+ //Add admin page
216
+ $hook = call_user_func_array($f, $args);
217
+ //Save hook to page properties
218
+ $page->set_hookname($hook);
219
+ $this->pages[$page->get_id_raw()] =& $page;
220
+ }
221
+
222
+ //Add sections
223
+ /**
224
+ * @var SLB_Admin_Section
225
+ */
226
+ $section;
227
+ foreach ( $this->sections as $section ) {
228
+ add_settings_section($section->get_id(), $section->get_title(), $section->get_callback(), $section->get_parent());
229
+ if ( $section->is_options_valid() )
230
+ register_setting($section->get_parent(), $section->get_id(), $section->get_options()->m('validate'));
231
+ }
232
+ }
233
+
234
+
235
+ /* Methods */
236
+
237
+ /**
238
+ * Add a new view
239
+ * @param string $type View type
240
+ * @param string $id Unique view ID
241
+ * @param array $args Arguments to pass to view constructor
242
+ * @return int|bool View ID (FALSE if view was not properly initialized)
243
+ */
244
+ private function add_view($type, $id, $args) {
245
+ //Validate request
246
+ $class = $this->add_prefix('admin_' . $type);
247
+ $collection = $type . 's';
248
+ if ( !class_exists($class) || !property_exists($this, $collection) || !is_array($this->{$collection}) )
249
+ return false;
250
+ //Create new instance
251
+ $r = new ReflectionClass($class);
252
+ $view =& $r->newInstanceArgs($args);
253
+ if ( $view->is_valid() )
254
+ $this->{$collection}[$id] =& $view;
255
+ else
256
+ $id = false;
257
+ unset($view, $r);
258
+ return $id;
259
+ }
260
+
261
+ /**
262
+ * Add reset option to plugin action links
263
+ * @param string $id Unique ID
264
+ * @param array $labels Text for reset instance
265
+ * > title - Link text (also title attribute value)
266
+ * > confirm - Confirmation message
267
+ * > success - Success message
268
+ * > failure - Failure message
269
+ * @param SLB_Options|array $options Options instance (or instance + specific groups)
270
+ */
271
+ function add_reset($id, $labels, $options) {
272
+ $args = func_get_args();
273
+ return $this->add_view('reset', $id, $args);
274
+ }
275
+
276
+ /*-** Menus **-*/
277
+
278
+ /**
279
+ * Adds custom admin panel
280
+ * @param string $id Menu ID
281
+ * @param string|array $labels Text labels
282
+ * @param int $pos (optional) Menu position in navigation (index order)
283
+ * @return string Menu ID
284
+ */
285
+ function add_menu($id, $labels, $position = null) {
286
+ $args = array ( $id, $labels, null, null, null, $position );
287
+ return $this->add_view('menu', $id, $args);
288
+ }
289
+
290
+ /* Page */
291
+
292
+ /**
293
+ * Add admin page
294
+ * @uses this->pages
295
+ * @param string $id Page ID (unique)
296
+ * @param string|array $labels Text labels (Associative array for multiple labels)
297
+ * > menu: Menu title
298
+ * > header: Page header
299
+ * @param string $menu Menu ID to add page to
300
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
301
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
302
+ * @param callback $callback (optional) Callback for custom page building
303
+ * @param string $capability (optional) Custom capability for accessing page
304
+ * @return string Page ID
305
+ */
306
+ function add_page($id, $parent, $labels, $options = null, $callback = null, $capability = null) {
307
+ $args = func_get_args();
308
+ wp_enqueue_script('postbox');
309
+ return $this->add_view('page', $id, $args);
310
+ }
311
+
312
+ /* WP Pages */
313
+
314
+ /**
315
+ * Add admin page to a standard WP menu
316
+ * @uses this->add_page()
317
+ * @param string $id Page ID (unique)
318
+ * @param string|array $labels Text labels (Associative array for multiple labels)
319
+ * > menu: Menu title
320
+ * > header: Page header
321
+ * @param string $menu Name of WP menu to add page to
322
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
323
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
324
+ * @param callback $callback (optional) Callback for custom page building
325
+ * @param string $capability (optional) Custom capability for accessing page
326
+ * @return string Page ID
327
+ */
328
+ function add_wp_page($id, $parent, $labels, $options = null, $callback = null, $capability = null) {
329
+ //Add page
330
+ $pid = $this->add_page($id, $parent, $labels, $options, $callback, $capability);
331
+ //Set parent as WP
332
+ if ( $pid ) {
333
+ $this->pages[$pid]->set_parent_wp();
334
+ }
335
+ return $pid;
336
+ }
337
+
338
+ /**
339
+ * Add admin page to Dashboard menu
340
+ * @see add_dashboard_page()
341
+ * @uses this->add_wp_page()
342
+ * @param string $id Page ID (unique)
343
+ * @param string|array $labels Text labels (Associative array for multiple labels)
344
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
345
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
346
+ * @param callback $callback (optional) Callback for custom page building
347
+ * @param string $capability (optional) Custom capability for accessing page
348
+ * @return string Page ID
349
+ */
350
+ function add_dashboard_page($id, $labels, $options = null, $callback = null, $capability = null) {
351
+ $id = $this->add_wp_page($id, 'dashboard', $labels, $options, $callback, $capability);
352
+ return $id;
353
+ }
354
+
355
+ /**
356
+ * Add admin page to Comments menu
357
+ * @see add_comments_page()
358
+ * @uses this->add_wp_page()
359
+ * @param string $id Page ID (unique)
360
+ * @param string|array $labels Text labels (Associative array for multiple labels)
361
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
362
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
363
+ * @param callback $callback (optional) Callback for custom page building
364
+ * @param string $capability (optional) Custom capability for accessing page
365
+ * @return string Page ID
366
+ */
367
+ function add_comments_page($id, $labels, $options = null, $callback = null, $capability = null) {
368
+ $id = $this->add_wp_page($id, 'comments', $labels, $options, $callback, $capability);
369
+ return $id;
370
+ }
371
+
372
+ /**
373
+ * Add admin page to Links menu
374
+ * @see add_links_page()
375
+ * @uses this->add_wp_page()
376
+ * @param string $id Page ID (unique)
377
+ * @param string|array $labels Text labels (Associative array for multiple labels)
378
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
379
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
380
+ * @param callback $callback (optional) Callback for custom page building
381
+ * @param string $capability (optional) Custom capability for accessing page
382
+ * @return string Page ID
383
+ */
384
+ function add_links_page($id, $labels, $options = null, $callback = null, $capability = null) {
385
+ $id = $this->add_wp_page($id, 'links', $labels, $options, $callback, $capability);
386
+ return $id;
387
+ }
388
+
389
+
390
+ /**
391
+ * Add admin page to Posts menu
392
+ * @see add_posts_page()
393
+ * @uses this->add_wp_page()
394
+ * @param string $id Page ID (unique)
395
+ * @param string|array $labels Text labels (Associative array for multiple labels)
396
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
397
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
398
+ * @param callback $callback (optional) Callback for custom page building
399
+ * @param string $capability (optional) Custom capability for accessing page
400
+ * @return string Page ID
401
+ */
402
+ function add_posts_page($id, $labels, $options = null, $callback = null, $capability = null) {
403
+ $id = $this->add_wp_page($id, 'posts', $labels, $options, $callback, $capability);
404
+ return $id;
405
+ }
406
+
407
+ /**
408
+ * Add admin page to Pages menu
409
+ * @see add_pages_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 obj|array $options (optional) Options object (Use array to define options object & specific group(s))
414
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
415
+ * @param callback $callback (optional) Callback for custom page building
416
+ * @param string $capability (optional) Custom capability for accessing page
417
+ * @return string Page ID
418
+ */
419
+ function add_pages_page($id, $labels, $options = null, $callback = null, $capability = null) {
420
+ $id = $this->add_wp_page($id, 'pages', $labels, $options, $callback, $capability);
421
+ return $id;
422
+ }
423
+
424
+ /**
425
+ * Add admin page to Media menu
426
+ * @see add_media_page()
427
+ * @uses this->add_wp_page()
428
+ * @param string $id Page ID (unique)
429
+ * @param string|array $labels Text labels (Associative array for multiple labels)
430
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
431
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
432
+ * @param callback $callback (optional) Callback for custom page building
433
+ * @param string $capability (optional) Custom capability for accessing page
434
+ * @return string Page ID
435
+ */
436
+ function add_media_page($id, $labels, $options = null, $callback = null, $capability = null) {
437
+ $id = $this->add_wp_page($id, 'media', $labels, $options, $callback, $capability);
438
+ return $id;
439
+ }
440
+
441
+ /**
442
+ * Add admin page to Themes menu
443
+ * @see add_theme_page()
444
+ * @uses this->add_wp_page()
445
+ * @param string $id Page ID (unique)
446
+ * @param string|array $labels Text labels (Associative array for multiple labels)
447
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
448
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
449
+ * @param callback $callback (optional) Callback for custom page building
450
+ * @param string $capability (optional) Custom capability for accessing page
451
+ * @return string Page ID
452
+ */
453
+ function add_theme_page($id, $labels, $options = null, $callback = null, $capability = null) {
454
+ $id = $this->add_wp_page($id, 'theme', $labels, $options, $callback, $capability);
455
+ return $id;
456
+ }
457
+
458
+ /**
459
+ * Add admin page to Plugins menu
460
+ * @see add_plugins_page()
461
+ * @uses this->add_wp_page()
462
+ * @param string $id Page ID (unique)
463
+ * @param string|array $labels Text labels (Associative array for multiple labels)
464
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
465
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
466
+ * @param callback $callback (optional) Callback for custom page building
467
+ * @param string $capability (optional) Custom capability for accessing page
468
+ * @return string Page ID
469
+ */
470
+ function add_plugins_page($id, $labels, $options = null, $callback = null, $capability = null) {
471
+ $id = $this->add_wp_page($id, 'plugins', $labels, $options, $callback, $capability);
472
+ return $id;
473
+ }
474
+
475
+ /**
476
+ * Add admin page to Options menu
477
+ * @see add_options_page()
478
+ * @uses this->add_wp_page()
479
+ * @param string $id Page ID (unique)
480
+ * @param string|array $labels Text labels (Associative array for multiple labels)
481
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
482
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
483
+ * @param callback $callback (optional) Callback for custom page building
484
+ * @param string $capability (optional) Custom capability for accessing page
485
+ * @return string Page ID
486
+ */
487
+ function add_options_page($id, $labels, $options = null, $callback = null, $capability = null) {
488
+ $id = $this->add_wp_page($id, 'options', $labels, $options, $callback, $capability);
489
+ return $id;
490
+ }
491
+
492
+ /**
493
+ * Add admin page to Tools menu
494
+ * @see add_management_page()
495
+ * @uses this->add_wp_page()
496
+ * @param string $id Page ID (unique)
497
+ * @param string|array $labels Text labels (Associative array for multiple labels)
498
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
499
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
500
+ * @param callback $callback (optional) Callback for custom page building
501
+ * @param string $capability (optional) Custom capability for accessing page
502
+ * @return string Page ID
503
+ */
504
+ function add_management_page($id, $labels, $options = null, $callback = null, $capability = null) {
505
+ $id = $this->add_wp_page($id, 'management', $labels, $options, $callback, $capability);
506
+ return $id;
507
+ }
508
+
509
+ /**
510
+ * Add admin page to Users menu
511
+ * @uses this->add_wp_page()
512
+ * @param string $id Page ID (unique)
513
+ * @param string|array $labels Text labels (Associative array for multiple labels)
514
+ * @param obj|array $options (optional) Options object (Use array to define options object & specific group(s))
515
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
516
+ * @param callback $callback (optional) Callback for custom page building
517
+ * @param string $capability (optional) Custom capability for accessing page
518
+ * @return string Page ID
519
+ */
520
+ function add_users_page($id, $labels, $options = null, $callback = null, $capability = null) {
521
+ $id = $this->add_wp_page($id, 'users', $labels, $options, $callback, $capability);
522
+ return $id;
523
+ }
524
+
525
+ /* Section */
526
+
527
+ /**
528
+ * Add section
529
+ * @uses this->sections
530
+ * @param string $id Unique section ID
531
+ * @param string $page Page ID
532
+ * @param string $labels Label text
533
+ * @param obj|array $options Options object (Use array to define options object & specific group(s))
534
+ * > Array Example: array($options, 'group_1') or array($options, array('group_1', 'group_3'))
535
+ * @param callback $callback (optional) Callback for custom building
536
+ * @return string Section ID
537
+ */
538
+ function add_section($id, $parent, $labels, $options = null, $callback = null) {
539
+ $section = new SLB_Admin_Section($id, $parent, $labels, $options, $callback);
540
+
541
+ //Add Section
542
+ if ( $section->is_valid() )
543
+ $this->sections[$id] =& $section;
544
+ else
545
+ $id = false;
546
+ return $id;
547
+ }
548
+
549
+ /* Operations */
550
+
551
+ /**
552
+ * Adds custom links below plugin on plugin listing page
553
+ * @uses `plugin_action_links_$plugin-name` Filter hook
554
+ * @param $actions
555
+ * @param $plugin_file
556
+ * @param $plugin_data
557
+ * @param $context
558
+ */
559
+ function plugin_action_links($actions, $plugin_file, $plugin_data, $context) {
560
+ global $admin_page_hooks;
561
+ //Add link to settings (only if active)
562
+ if ( is_plugin_active($this->util->get_plugin_base_name()) ) {
563
+ /* Get Actions */
564
+
565
+ $acts = array();
566
+ $type = 'plugin_action';
567
+
568
+ /* Get view links */
569
+ foreach ( array('menus', 'pages', 'sections') as $views ) {
570
+ foreach ( $this->{$views} as $view ) {
571
+ if ( !$view->has_label($type) )
572
+ continue;
573
+ $acts[] = (object) array (
574
+ 'id' => $views . '_' . $view->get_id(),
575
+ 'label' => $view->get_label($type),
576
+ 'uri' => $view->get_uri(),
577
+ 'attributes' => array()
578
+ );
579
+ }
580
+ }
581
+
582
+ /* Get reset links */
583
+ $type = 'title';
584
+ foreach ( $this->resets as $reset ) {
585
+ if ( !$reset->has_label($type) )
586
+ continue;
587
+ $id = 'reset_' . $reset->get_id();
588
+ $acts[] = (object) array (
589
+ 'id' => $id,
590
+ 'label' => $reset->get_label($type),
591
+ 'uri' => $reset->get_uri(),
592
+ 'attributes' => $reset->get_link_attr()
593
+ );
594
+ }
595
+
596
+ //Add links
597
+ $links = array();
598
+ foreach ( $acts as $act ) {
599
+ $links[$act->id] = $this->util->build_html_link($act->uri, $act->label, $act->attributes);
600
+ }
601
+
602
+ //Add links
603
+ $actions = array_merge($links, $actions);
604
+ }
605
+ return $actions;
606
+ }
607
+
608
+ /*-** START: Refactor **-*/
609
+
610
+ /**
611
+ * Adds additional message for plugin updates
612
+ * @uses `in_plugin_update_message-$plugin-name` Action hook
613
+ * @uses this->plugin_update_get_message()
614
+ * @var array $plugin_data Current plugin data
615
+ * @var object $r Update response data
616
+ */
617
+ function plugin_update_message($plugin_data, $r) {
618
+ if ( !isset($r->new_version) )
619
+ return false;
620
+ if ( stripos($r->new_version, 'beta') !== false ) {
621
+ $cls_notice = $this->add_prefix('notice');
622
+ echo '<br />' . $this->plugin_update_get_message($r);
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Modify update plugins response data if necessary
628
+ * @uses `site_transient_update_plugins` Filter hook
629
+ * @uses this->plugin_update_get_message()
630
+ * @param obj $transient Transient data
631
+ * @return obj Modified transient data
632
+ */
633
+ function plugin_update_transient($transient) {
634
+ $n = $this->util->get_plugin_base_name();
635
+ if ( isset($transient->response) && isset($transient->response[$n]) && is_object($transient->response[$n]) && !isset($transient->response[$n]->upgrade_notice) ) {
636
+ $r =& $transient->response[$n];
637
+ $r->upgrade_notice = $this->plugin_update_get_message($r);
638
+ }
639
+ return $transient;
640
+ }
641
+
642
+ /**
643
+ * Retrieve custom update message
644
+ * @uses this->get_message()
645
+ * @param obj $r Response data from plugin update API
646
+ * @return string Message (Default: empty string)
647
+ */
648
+ function plugin_update_get_message($r) {
649
+ $msg = '';
650
+ $cls_notice = $this->add_prefix('notice');
651
+ if ( !is_object($r) || !isset($r->new_version) )
652
+ return $msg;
653
+ if ( stripos($r->new_version, 'beta') !== false ) {
654
+ $msg = sprintf($this->get_message('beta'), $cls_notice);
655
+ }
656
+ return $msg;
657
+ }
658
+
659
+ /*-** Messages **-*/
660
+
661
+ /**
662
+ * Retrieve stored messages
663
+ * @param string $msg_id Message ID
664
+ * @return string Message text
665
+ */
666
+ function get_message($msg_id) {
667
+ $msg = '';
668
+ $msgs = $this->get_messages();
669
+ if ( is_string($msg_id) && isset($msgs[$msg_id]) ) {
670
+ $msg = $msgs[$msg_id];
671
+ }
672
+ return $msg;
673
+ }
674
+
675
+ /**
676
+ * Retrieve all messages
677
+ * Initializes messages if necessary
678
+ * @uses $messages
679
+ * @return array Messages
680
+ */
681
+ function get_messages() {
682
+ if ( empty($this->messages) ) {
683
+ //Initialize messages if necessary
684
+ $this->messages = array(
685
+ 'reset' => __('The settings have been reset', 'simple-lightbox'),
686
+ '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'),
687
+ 'access_denied' => __('Access Denied', 'simple-lightbox'),
688
+ );
689
+ }
690
+ return $this->messages;
691
+ }
692
+
693
+ /**
694
+ * Set message text
695
+ * @uses this->messages
696
+ * @param string $id Message ID
697
+ * @param string $text Message text
698
+ */
699
+ function set_message($id, $text) {
700
+ $this->messages[trim($id)] = $text;
701
+ }
702
+ /*-** END: Refactor **-*/
703
+
704
+ }
705
+
706
+ /**
707
+ * Admin View Base functionality
708
+ * Core functionality for Menus/Pages/Sections
709
+ * @package Simple Lightbox
710
+ * @subpackage Admin
711
+ * @author Archetyped
712
+ */
713
+ class SLB_Admin_View extends SLB_Base {
714
+ /* Properties */
715
+
716
+ /**
717
+ * Unique ID
718
+ * @var string
719
+ */
720
+ var $id = null;
721
+
722
+ /**
723
+ * Labels
724
+ * @var array (Associative)
725
+ */
726
+ var $labels = array();
727
+
728
+ /**
729
+ * Options object to use
730
+ * @var SLB_Options
731
+ */
732
+ var $options = null;
733
+
734
+ /**
735
+ * Option groups to use
736
+ * If empty, use entire options object
737
+ * @var array
738
+ */
739
+ var $option_groups = array();
740
+
741
+ /**
742
+ * Option building arguments
743
+ * @var array
744
+ */
745
+ var $option_args = array();
746
+
747
+ /**
748
+ * Function to handle building UI
749
+ * @var callback
750
+ */
751
+ var $callback = null;
752
+
753
+ /**
754
+ * Capability for access control
755
+ * @var string
756
+ */
757
+ var $capability = 'manage_options';
758
+
759
+ /**
760
+ * Icon to use
761
+ * @var string
762
+ */
763
+ var $icon = null;
764
+
765
+ /**
766
+ * View parent ID/Slug
767
+ * @var string
768
+ */
769
+ var $parent = null;
770
+
771
+ /**
772
+ * Whether parent is a custom view or a default WP one
773
+ * @var bool
774
+ */
775
+ var $parent_custom = true;
776
+
777
+ /**
778
+ * If view requires a parent
779
+ * @var bool
780
+ */
781
+ var $parent_required = false;
782
+
783
+ /**
784
+ * WP-Generated hook name for view
785
+ * @var string
786
+ */
787
+ var $hookname = null;
788
+
789
+ /**
790
+ * Messages to be displayed
791
+ * Indexed Array
792
+ * @var array
793
+ */
794
+ var $messages = array();
795
+
796
+ /**
797
+ * Required properties
798
+ * Associative array
799
+ * > Key: Property name
800
+ * > Value: Required data type
801
+ * @var array
802
+ */
803
+ protected $required = array();
804
+
805
+ /**
806
+ * Default required properties
807
+ * Merged into $required array with this->init_required()
808
+ * @see this->required for more information
809
+ * @var array
810
+ */
811
+ protected $_required = array ( 'id' => 'string', 'labels' => 'array' );
812
+
813
+ /* Init */
814
+
815
+ function __construct($id, $labels, $options = null, $callback = null, $capability = null, $icon = null) {
816
+ parent::__construct();
817
+
818
+ $this->init_required();
819
+ $this->set_id($id);
820
+ $this->set_labels($labels);
821
+ $this->set_options($options);
822
+ $this->set_callback($callback);
823
+ $this->set_capability($capability);
824
+ $this->set_icon($icon);
825
+ }
826
+
827
+ function init_required() {
828
+ $this->required = array_merge($this->_required, $this->required);
829
+ //Check for parent requirement
830
+ if ( $this->parent_required )
831
+ $this->required['parent'] = 'string';
832
+ }
833
+
834
+ /* Property Methods */
835
+
836
+ /**
837
+ * Retrieve ID (Formatted by default)
838
+ * @param bool $formatted (optional) Whether ID should be formatted for external use or not
839
+ * @return string ID
840
+ */
841
+ function get_id($formatted = true) {
842
+ $id = $this->id;
843
+ if ( $formatted )
844
+ $this->add_prefix_ref($id);
845
+ return $id;
846
+ }
847
+
848
+ /**
849
+ * Retrieve raw ID
850
+ * @return string Raw ID
851
+ */
852
+ function get_id_raw() {
853
+ return $this->get_id(false);
854
+ }
855
+
856
+ /**
857
+ * Set ID
858
+ * @param string ID
859
+ */
860
+ function set_id($id) {
861
+ if ( is_scalar($id) ) {
862
+ $this->id = trim(strval($id));
863
+ return true;
864
+ }
865
+ return false;
866
+ }
867
+
868
+ /**
869
+ * Retrieve label
870
+ * Uses first label (or default if defined) if specified type does not exist
871
+ * @param string $type Label type to retrieve
872
+ * @param string $default (optional) Default value if label type does not exist
873
+ * @return string Label text
874
+ */
875
+ function get_label($type, $default = null) {
876
+ //Retrieve existing label type
877
+ if ( $this->has_label($type) )
878
+ return $this->labels[$type];
879
+ //Use default label if type is not set
880
+ if ( empty($default) && !empty($this->labels) ) {
881
+ reset($this->labels);
882
+ $default = current($this->labels);
883
+ }
884
+
885
+ return ( empty($default) ) ? '' : $default;
886
+ }
887
+
888
+ /**
889
+ * Set text labels
890
+ * @param array|string $labels
891
+ */
892
+ function set_labels($labels) {
893
+ if ( empty($labels) )
894
+ return false;
895
+ //Single string
896
+ if ( is_string($labels) ) {
897
+ $labels = array ( $labels );
898
+ }
899
+
900
+ //Array
901
+ if ( is_array($labels) ) {
902
+ //Merge with existing labels
903
+ if ( empty($this->labels) || !is_array($this->labels) ) {
904
+ $this->labels = array();
905
+ }
906
+ $this->labels = array_merge($this->labels, $labels);
907
+ }
908
+ }
909
+
910
+ /**
911
+ * Set single text label
912
+ * @uses this->set_labels()
913
+ * @param string $type Label type to set
914
+ * @param string $value Label value
915
+ */
916
+ function set_label($type, $value) {
917
+ if ( is_string($type) && is_string($value) ) {
918
+ $label = array( $type => $value );
919
+ $this->set_labels($label);
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Checks if specified label is set on view
925
+ * @param string $type Label type
926
+ * @return bool TRUE if label exists, FALSE otherwise
927
+ */
928
+ function has_label($type) {
929
+ return ( isset($this->labels[$type]) );
930
+ }
931
+
932
+ /**
933
+ * Retrieve instance options
934
+ * @return SLB_Options Options instance
935
+ */
936
+ function &get_options() {
937
+ return $this->options;
938
+ }
939
+
940
+ /**
941
+ * Set options object
942
+ * @param obj|array $options Options instance
943
+ * > If array, Options instance and specific groups are specified
944
+ * > 0: Options instance
945
+ * > 1: Group(s)
946
+ */
947
+ function set_options($options) {
948
+ if ( empty($options) )
949
+ return false;
950
+
951
+ $groups = null;
952
+
953
+ if ( is_array($options) ) {
954
+ $options = array_values($options);
955
+ //Set option groups
956
+ if ( isset($options[1]) ) {
957
+ $groups = $options[1];
958
+ }
959
+ //Set options object
960
+ $options =& $options[0];
961
+ }
962
+
963
+ if ( $this->util->is_a($options, 'Options') ) {
964
+ //Save options
965
+ $this->options =& $options;
966
+
967
+ //Save option groups for valid options
968
+ $this->set_option_groups($groups);
969
+ }
970
+ }
971
+
972
+ /**
973
+ * Set option groups
974
+ * @param string|array $groups Specified group(s)
975
+ */
976
+ function set_option_groups($groups) {
977
+ if ( empty($groups) )
978
+ return false;
979
+
980
+ //Validate data
981
+ if ( !is_array($groups) ) {
982
+ if ( is_scalar($groups) ) {
983
+ $groups = array(strval($groups));
984
+ }
985
+ }
986
+
987
+ if ( is_array($groups) ) {
988
+ $this->option_groups = $groups;
989
+ }
990
+ }
991
+
992
+ /**
993
+ * Retrieve view messages
994
+ * @return array Messages
995
+ */
996
+ function &get_messages() {
997
+ if ( !is_array($this->messages) )
998
+ $this->messages = array();
999
+ return $this->messages;
1000
+ }
1001
+
1002
+ /**
1003
+ * Save message
1004
+ * @param string $text Message text
1005
+ */
1006
+ function set_message($text) {
1007
+ $msgs =& $this->get_messages();
1008
+ $text = trim($text);
1009
+ if ( empty($msgs) && !empty($text) )
1010
+ $this->util->add_filter('admin_messages', $this->m('do_messages'));
1011
+ $msgs[] = $text;
1012
+ }
1013
+
1014
+ /**
1015
+ * Add messages to array
1016
+ * Called by internal `admin_messages` filter hook
1017
+ * @param array $msgs Aggregated messages
1018
+ * @return array Merged messages array
1019
+ */
1020
+ function do_messages($msgs = array()) {
1021
+ $m =& $this->get_messages();
1022
+ if ( !empty($m) )
1023
+ $msgs = array_merge($msgs, $m);
1024
+ return $msgs;
1025
+ }
1026
+
1027
+ /**
1028
+ * Retrieve view callback
1029
+ * @return callback Callback
1030
+ */
1031
+ function get_callback() {
1032
+ return ( $this->has_callback() ) ? $this->callback : $this->m('handle');
1033
+ }
1034
+
1035
+ /**
1036
+ * Set callback function for building item
1037
+ * @param callback $callback Callback function to use
1038
+ */
1039
+ function set_callback($callback) {
1040
+ if ( is_callable($callback) )
1041
+ $this->callback = $callback;
1042
+ }
1043
+
1044
+ function has_callback() {
1045
+ return ( !empty($this->callback) ) ? true : false;
1046
+ }
1047
+
1048
+ function do_callback() {
1049
+ call_user_func($this->get_callback());
1050
+ }
1051
+
1052
+ /**
1053
+ * Retrieve capability
1054
+ * @return string Capability
1055
+ */
1056
+ function get_capability() {
1057
+ return $this->capability;
1058
+ }
1059
+
1060
+ /**
1061
+ * Set capability for access control
1062
+ * @param string $capability Capability
1063
+ */
1064
+ function set_capability($capability) {
1065
+ if ( is_string($capability) && !empty($capability) )
1066
+ $this->capability = $capability;
1067
+ }
1068
+
1069
+ /**
1070
+ * Set icon
1071
+ * @param string $icon Icon URI
1072
+ */
1073
+ function set_icon($icon) {
1074
+ if ( !empty($icon) && is_string($icon) )
1075
+ $this->icon = $icon;
1076
+ }
1077
+
1078
+ function get_hookname() {
1079
+ return ( empty($this->hookname) ) ? '' : $this->hookname;
1080
+ }
1081
+
1082
+ /**
1083
+ * Set hookname
1084
+ * @param string $hookname Hookname value
1085
+ */
1086
+ function set_hookname($hookname) {
1087
+ if ( !empty($hookname) && is_string($hookname) )
1088
+ $this->hookname = $hookname;
1089
+ }
1090
+
1091
+ /**
1092
+ * Retrieve parent
1093
+ * Formats parent ID for custom parents
1094
+ * @return string Parent ID
1095
+ */
1096
+ function get_parent() {
1097
+ $parent = $this->parent;
1098
+ return ( $this->is_parent_custom() ) ? $this->add_prefix($parent) : $parent;
1099
+ }
1100
+
1101
+ /**
1102
+ * Set parent for view
1103
+ * @param string $parent Parent ID
1104
+ */
1105
+ function set_parent($parent) {
1106
+ if ( $this->parent_required ) {
1107
+ if ( !empty($parent) && is_string($parent) )
1108
+ $this->parent = $parent;
1109
+ } else {
1110
+ $this->parent = null;
1111
+ }
1112
+ }
1113
+
1114
+ /**
1115
+ * Specify whether parent is a custom view or a WP view
1116
+ * @param bool $custom (optional) TRUE if custom, FALSE if WP
1117
+ */
1118
+ function set_parent_custom($custom = true) {
1119
+ if ( $this->parent_required ) {
1120
+ $this->parent_custom = !!$custom;
1121
+ }
1122
+ }
1123
+
1124
+ /**
1125
+ * Set parent as WP view
1126
+ * @uses this->set_parent_custom()
1127
+ */
1128
+ function set_parent_wp() {
1129
+ $this->set_parent_custom(false);
1130
+ }
1131
+
1132
+ /**
1133
+ * Get view URI
1134
+ * URI Structures:
1135
+ * > Top Level Menus: admin.php?page={menu_id}
1136
+ * > Pages: [parent_page_file.php|admin.php]?page={page_id}
1137
+ * > Section: [parent_menu_uri]#{section_id}
1138
+ *
1139
+ * @uses $admin_page_hooks to determine if page is child of default WP page
1140
+ * @return string Object URI
1141
+ */
1142
+ function get_uri($file = null, $format = null) {
1143
+ static $page_hooks = null;
1144
+ $uri = '';
1145
+ if ( empty($file) )
1146
+ $file = 'admin.php';
1147
+ if ( $this->is_child() ) {
1148
+ $parent = str_replace('_page_' . $this->get_id(), '', $this->get_hookname());
1149
+ if ( is_null($page_hooks) ) {
1150
+ $page_hooks = array_flip($GLOBALS['admin_page_hooks']);
1151
+ }
1152
+ if ( isset($page_hooks[$parent]) )
1153
+ $file = $page_hooks[$parent];
1154
+ }
1155
+
1156
+ if ( empty($format) ) {
1157
+ $delim = ( strpos($file, '?') === false ) ? '?' : '&amp;';
1158
+ $format = '%1$s' . $delim . 'page=%2$s';
1159
+ }
1160
+ $uri = sprintf($format, $file, $this->get_id());
1161
+
1162
+ return $uri;
1163
+ }
1164
+
1165
+ /* Handlers */
1166
+
1167
+ /**
1168
+ * Default View handler
1169
+ * Used as callback when none set
1170
+ */
1171
+ function handle() {}
1172
+
1173
+ /* Validation */
1174
+
1175
+ /**
1176
+ * Check if instance is valid based on required properties/data types
1177
+ * @return bool TRUE if valid, FALSE if not valid
1178
+ */
1179
+ function is_valid() {
1180
+ $valid = true;
1181
+ foreach ( $this->required as $prop => $type ) {
1182
+ if ( empty($this->{$prop} )
1183
+ || ( !empty($type) && is_string($type) && ( $f = 'is_' . $type ) && function_exists($f) && !$f($this->{$prop}) ) ) {
1184
+ $valid = false;
1185
+ break;
1186
+ }
1187
+ }
1188
+ return $valid;
1189
+ }
1190
+
1191
+ function is_child() {
1192
+ return $this->parent_required;
1193
+ }
1194
+
1195
+ function is_parent_custom() {
1196
+ return ( $this->is_child() && $this->parent_custom ) ? true : false;
1197
+ }
1198
+
1199
+ function is_parent_wp() {
1200
+ return ( $this->is_child() && !$this->parent_custom ) ? true : false;
1201
+ }
1202
+
1203
+ function is_options_valid() {
1204
+ return ( is_object($this->get_options()) && $this->util->is_a($this->get_options(), $this->util->get_class('Options')) ) ? true : false;
1205
+ }
1206
+
1207
+ /* Options */
1208
+
1209
+ /**
1210
+ * Parse options build vars
1211
+ * @uses `options_parse_build_vars` filter hook
1212
+ */
1213
+ function options_parse_build_vars($vars, $opts) {
1214
+ //Handle form submission
1215
+ if ( isset($_REQUEST[$opts->get_id('formatted')]) ) {
1216
+ $vars['validate_pre'] = $vars['save_pre'] = true;
1217
+ }
1218
+ return $vars;
1219
+ }
1220
+
1221
+ function options_build_pre(&$opts) {
1222
+ //Build form output
1223
+ $form_id = $this->add_prefix('admin_form_' . $this->get_id_raw());
1224
+ ?>
1225
+ <form id="<?php esc_attr_e($form_id); ?>" name="<?php esc_attr_e($form_id); ?>" action="" method="post">
1226
+ <?php
1227
+ }
1228
+
1229
+ function options_build_post(&$opts) {
1230
+ submit_button();
1231
+ ?>
1232
+ </form>
1233
+ <?php
1234
+ }
1235
+
1236
+ /**
1237
+ * Builds option groups output
1238
+ * @param SLB_Options $options Options instance
1239
+ * @param array $groups Groups to build
1240
+ */
1241
+ function options_build_groups($options, $groups) {
1242
+ //Add meta box for each group
1243
+ $screen = get_current_screen();
1244
+ foreach ( $groups as $gid ) {
1245
+ $g = $options->get_group($gid);
1246
+ if ( !count($options->get_items($gid)) ) {
1247
+ continue;
1248
+ }
1249
+ add_meta_box($gid, $g->title, $this->m('options_build_group'), $screen, 'normal', 'default', array('options' => $options, 'group' => $gid));
1250
+ }
1251
+ //Build options
1252
+ do_meta_boxes($screen, 'normal', null);
1253
+ }
1254
+
1255
+ function options_build_group($obj, $args) {
1256
+ $args = $args['args'];
1257
+ $group = $args['group'];
1258
+ $opts = $args['options'];
1259
+ $opts->build_group($group);
1260
+ }
1261
+
1262
+ function show_options($show_submit = true) {
1263
+ //Build options output
1264
+ if ( !$this->is_options_valid() ) {
1265
+ return false;
1266
+ }
1267
+ /**
1268
+ * @var SLB_Options
1269
+ */
1270
+ $opts =& $this->get_options();
1271
+ $hooks = array (
1272
+ 'filter' => array (
1273
+ 'parse_build_vars' => array( $this->m('options_parse_build_vars'), 10, 2 )
1274
+ ),
1275
+ 'action' => array (
1276
+ 'build_pre' => array( $this->m('options_build_pre') ),
1277
+ 'build_post' => array ( $this->m('options_build_post') ),
1278
+ )
1279
+ );
1280
+ //Add hooks
1281
+ foreach ( $hooks as $type => $hook ) {
1282
+ $m = 'add_' . $type;
1283
+ foreach ( $hook as $tag => $args ) {
1284
+ array_unshift($args, $tag);
1285
+ call_user_func_array($opts->util->m($m), $args);
1286
+ }
1287
+ }
1288
+ ?>
1289
+ <div class="metabox-holder">
1290
+ <?php
1291
+ //Build output
1292
+ $opts->build(array('build_groups' => $this->m('options_build_groups')));
1293
+ ?>
1294
+ </div>
1295
+ <?php
1296
+ //Remove hooks
1297
+ foreach ( $hooks as $type => $hook ) {
1298
+ $m = 'remove_' . $type;
1299
+ foreach ( $hook as $tag => $args ) {
1300
+ call_user_func($opts->util->m($m), $tag, $args[0]);
1301
+ }
1302
+ }
1303
+ }
1304
+
1305
+ /* UI Elements */
1306
+
1307
+ /**
1308
+ * Build submit button element
1309
+ * @param string $text (optional) Button text
1310
+ * @param string $id (optional) Button ID (prefixed on output)
1311
+ * @param object $parent (optional) Page/Section object that contains button
1312
+ * @return object Button properties (id, output)
1313
+ */
1314
+ function get_button_submit($text = null, $id = null, $parent = null) {
1315
+ //Format values
1316
+ if ( !is_string($text) || empty($text) )
1317
+ $text = __('Save Changes');
1318
+ if ( is_object($parent) && isset($parent->id) )
1319
+ $parent = $parent->id . '_';
1320
+ else
1321
+ $parent = '';
1322
+ if ( !is_string($id) || empty($id) )
1323
+ $id = 'submit';
1324
+ $id = $this->add_prefix($parent . $id);
1325
+ //Build HTML
1326
+ $out = $this->util->build_html_element(array(
1327
+ 'tag' => 'input',
1328
+ 'wrap' => false,
1329
+ 'attributes' => array(
1330
+ 'type' => 'submit',
1331
+ 'class' => 'button-primary',
1332
+ 'id' => $id,
1333
+ 'name' => $id,
1334
+ 'value' => $text
1335
+ )
1336
+ ));
1337
+ $out = '<p class="submit">' . $out . '</p>';
1338
+ $ret = new stdClass;
1339
+ $ret->id = $id;
1340
+ $ret->output = $out;
1341
+ return $ret;
1342
+ }
1343
+
1344
+ /**
1345
+ * Output submit button element
1346
+ * @param string $text (optional) Button text
1347
+ * @param string $id (optional) Button ID (prefixed on output)
1348
+ * @param object $parent (optional) Page/Section object that contains button
1349
+ * @return object Button properties (id, output)
1350
+ */
1351
+ function button_submit($text = null, $id = null, $parent = null) {
1352
+ $btn = $this->get_button_submit($text, $id, $parent);
1353
+ echo $btn->output;
1354
+ return $btn;
1355
+ }
1356
+ }
1357
+
1358
+ /**
1359
+ * Admin Menu functionality
1360
+ * @package Simple Lightbox
1361
+ * @subpackage Admin
1362
+ * @author Archetyped
1363
+ */
1364
+ class SLB_Admin_Menu extends SLB_Admin_View {
1365
+ /* Properties */
1366
+
1367
+ /**
1368
+ * Menu position
1369
+ * @var int
1370
+ */
1371
+ var $position = null;
1372
+
1373
+ /* Init */
1374
+
1375
+ function __construct($id, $labels, $options = null, $callback = null, $capability = null, $icon = null, $position = null) {
1376
+ //Default
1377
+ parent::__construct($id, $labels, $options, $callback, $capability, $icon);
1378
+ //Class specific
1379
+ $this->set_position($position);
1380
+ }
1381
+
1382
+ /* Getters/Setters */
1383
+
1384
+ function set_position($position) {
1385
+ if ( is_int($position) )
1386
+ $this->position = $position;
1387
+ }
1388
+
1389
+ /* Handlers */
1390
+
1391
+ function handle() {
1392
+ if ( !current_user_can($this->get_capability()) )
1393
+ wp_die(__('Access Denied', 'simple-lightbox'));
1394
+ ?>
1395
+ <div class="wrap">
1396
+ <h2><?php esc_html_e( $this->get_label('header') ); ?></h2>
1397
+ <?php
1398
+ $this->show_options();
1399
+ ?>
1400
+ </div>
1401
+ <?php
1402
+ }
1403
+ }
1404
+
1405
+ /**
1406
+ * Admin Page functionality
1407
+ * @package Simple Lightbox
1408
+ * @subpackage Admin
1409
+ * @author Archetyped
1410
+ */
1411
+ class SLB_Admin_Page extends SLB_Admin_View {
1412
+ /* Properties */
1413
+
1414
+ var $parent_required = true;
1415
+
1416
+ /* Init */
1417
+
1418
+ function __construct($id, $parent, $labels, $options = null, $callback = null, $capability = null, $icon = null) {
1419
+ //Default
1420
+ parent::__construct($id, $labels, $options, $callback, $capability, $icon);
1421
+ //Class specific
1422
+ $this->set_parent($parent);
1423
+ }
1424
+
1425
+ /* Operations */
1426
+
1427
+ function show_icon() {
1428
+ echo screen_icon();
1429
+ }
1430
+
1431
+ /* Handlers */
1432
+
1433
+ /**
1434
+ * Default Page handler
1435
+ * Builds options form UI for page
1436
+ * @see this->init_menus() Set as callback for custom admin pages
1437
+ * @uses current_user_can() to check if user has access to current page
1438
+ * @uses wp_die() to end execution when user does not have permission to access page
1439
+ */
1440
+ function handle() {
1441
+ if ( !current_user_can($this->get_capability()) )
1442
+ wp_die(__('Access Denied', 'simple-lightbox'));
1443
+ ?>
1444
+ <div class="wrap">
1445
+ <?php $this->show_icon(); ?>
1446
+ <h2><?php esc_html_e( $this->get_label('header') ); ?></h2>
1447
+ <?php
1448
+ $this->show_options();
1449
+ ?>
1450
+ </div>
1451
+ <?php
1452
+ }
1453
+ }
1454
+
1455
+ /**
1456
+ * Admin Section functionality
1457
+ * @package Simple Lightbox
1458
+ * @subpackage Admin
1459
+ * @author Archetyped
1460
+ */
1461
+ class SLB_Admin_Section extends SLB_Admin_View {
1462
+ /* Properties */
1463
+
1464
+ var $parent_required = true;
1465
+ var $parent_custom = false;
1466
+
1467
+ /* Init */
1468
+
1469
+ function __construct($id, $parent, $labels, $options = null, $callback = null, $capability = null) {
1470
+ //Default
1471
+ parent::__construct($id, $labels, $options, $callback, $capability);
1472
+ //Class specific
1473
+ $this->set_parent($parent);
1474
+ }
1475
+
1476
+ /* Getters/Setters */
1477
+
1478
+ function get_uri() {
1479
+ $file = 'options-' . $this->get_parent() . '.php';
1480
+ return parent::get_uri($file, '%1$s#%2$s');
1481
+ }
1482
+
1483
+ /**
1484
+ * Retrieve formatted title for section
1485
+ * Wraps title text in element with anchor so that it can be linked to
1486
+ * @return string Title
1487
+ */
1488
+ function get_title() {
1489
+ return '<div id="' . $this->get_id() . '" class="' . $this->add_prefix('section_head') . '">' . $this->get_label('title') . '</div>';
1490
+ }
1491
+
1492
+ /* Handlers */
1493
+
1494
+ function handle() {
1495
+ $this->show_options(false);
1496
+ }
1497
+
1498
+ function options_parse_build_vars($vars, $opts) {
1499
+ return $vars;
1500
+ }
1501
+
1502
+ function options_build_pre() {}
1503
+
1504
+ function options_build_post() {}
1505
+ }
1506
+
1507
+ class SLB_Admin_Reset extends SLB_Admin_View {
1508
+ /* Properties */
1509
+
1510
+ var $required = array ( 'options' => 'object' );
1511
+
1512
+ var $parent_required = false;
1513
+
1514
+ /* Init */
1515
+
1516
+ function __construct($id, $labels, $options) {
1517
+ parent::__construct($id, $labels, $options);
1518
+ }
1519
+
1520
+ /* Handlers */
1521
+
1522
+ /**
1523
+ * Default handler
1524
+ * Resets plugin settings
1525
+ * @return string Status message (success, fail, etc.)
1526
+ */
1527
+ function handle() {
1528
+ //Validate user
1529
+ if ( ! current_user_can('activate_plugins') || ! check_admin_referer($this->get_id()) )
1530
+ wp_die(__('Access Denied', 'simple-lightbox'));
1531
+
1532
+ //Reset settings
1533
+ if ( $this->is_options_valid() )
1534
+ $this->get_options()->reset(true);
1535
+
1536
+ //Set Status Message
1537
+ $this->set_message($this->get_label('success'));
1538
+
1539
+ /*
1540
+ //Redirect user
1541
+ $uri = remove_query_arg(array('_wpnonce', 'action'), add_query_arg(array($this->add_prefix('action') => $action), $_SERVER['REQUEST_URI']));
1542
+ wp_redirect($uri);
1543
+ exit;
1544
+ */
1545
+ }
1546
+
1547
+ function get_uri() {
1548
+ 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());
1549
+ }
1550
+
1551
+ function get_query_args() {
1552
+ return array (
1553
+ 'action' => $this->add_prefix('admin'),
1554
+ $this->add_prefix('type') => 'view',
1555
+ $this->add_prefix('group') => 'reset',
1556
+ $this->add_prefix('obj') => $this->get_id_raw()
1557
+ );
1558
+ }
1559
+
1560
+ function get_query_args_remove() {
1561
+ $args_r = array (
1562
+ '_wpnonce',
1563
+ $this->add_prefix('action')
1564
+ );
1565
+
1566
+ return array_unique( array_merge( array_keys( $this->get_query_args() ), $args_r ) );
1567
+ }
1568
+
1569
+ function get_link_attr() {
1570
+ return array (
1571
+ 'class' => 'delete',
1572
+ 'onclick' => "return confirm('" . $this->get_label('confirm') . "')"
1573
+ );
1574
+ }
1575
+
1576
+ }
includes/class.base.php CHANGED
@@ -1,7 +1,5 @@
1
  <?php
2
 
3
- require_once 'class.utilities.php';
4
-
5
  /**
6
  * @package Simple Lightbox
7
  * @subpackage Base
@@ -9,12 +7,88 @@ require_once 'class.utilities.php';
9
  *
10
  */
11
  class SLB_Base {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
 
 
13
  /**
14
- * Prefix for Cornerstone-related data (attributes, DB tables, etc.)
15
  * @var string
16
  */
17
- var $prefix = 'slb';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  /**
20
  * Utilities
@@ -23,150 +97,426 @@ class SLB_Base {
23
  var $util = null;
24
 
25
  /**
26
- * Legacy constructor
 
27
  */
28
- function SLB_Base() {
29
- $this->__construct();
30
- }
 
 
 
 
 
 
31
 
32
  /**
33
  * Constructor
34
  */
35
  function __construct() {
36
- $this->util =& new SLB_Utilities();
 
 
 
 
 
 
 
 
37
  }
38
 
39
  /**
40
  * Default initialization method
41
- * To be overriden by child classes
 
 
 
 
 
42
  */
43
- function init() {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  /**
46
- * Returns callback to instance method
47
- * @param string $method Method name
48
- * @return array Callback array
49
  */
50
- function &m($method) {
51
- return $this->util->m($this, $method);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
53
 
54
  /**
55
- * Retrieves post metadata for internal methods
56
- * Metadata set internally is wrapped in an array so it is unwrapped before returned the retrieved value
57
- * @see get_post_meta()
58
- * @param int $post_id Post ID
59
- * @param string $key Name of metadata to retrieve
60
- * @param boolean $single Whether or not to retrieve single value or not
61
- * @return mixed Retrieved post metadata
62
  */
63
- function post_meta_get($post_id, $key, $single = false) {
64
- $meta_value = get_post_meta($post_id, $this->post_meta_get_key($key), $single);
65
- if (is_array($meta_value) && count($meta_value) == 1)
66
- $meta_value = $meta_value[0];
67
- return $meta_value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
 
70
  /**
71
- * Wraps metadata in array for storage in database
72
- * @param mixed $meta_value Value to be set as metadata
73
- * @return array Wrapped metadata value
74
  */
75
- function post_meta_prepare_value($meta_value) {
76
- return array($meta_value);
 
 
 
 
 
 
 
 
 
77
  }
78
 
79
  /**
80
- * Adds Metadata for a post to database
81
- * For internal methods
82
- * @see add_post_meta
83
- * @param $post_id
84
- * @param $meta_key
85
- * @param $meta_value
86
- * @param $unique
87
- * @return boolean Result of operation
88
  */
89
- function post_meta_add($post_id, $meta_key, $meta_value, $unique = false) {
90
- $meta_value = $this->post_meta_value_prepare($meta_value);
91
- return add_post_meta($post_id, $meta_key, $meta_value, $unique);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
 
94
  /**
95
- * Updates post metadata for internal data/methods
96
- * @see update_post_meta()
97
- * @param $post_id
98
- * @param $meta_key
99
- * @param $meta_value
100
- * @param $prev_value
101
- * @return boolean Result of operation
102
  */
103
- function post_meta_update($post_id, $meta_key, $meta_value, $prev_value = '') {
104
- $meta_value = $this->post_meta_prepare_value($meta_value);
105
- return update_post_meta($post_id, $meta_key, $meta_value, $prev_value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
107
 
108
  /**
109
- * Builds postmeta key for custom data set by plugin
110
- * @param string $key Base key name
111
- * @return string Formatted postmeta key
 
112
  */
113
- function post_meta_get_key($key) {
114
- $sep = '_';
115
- if ( strpos($key, $sep . $this->prefix) !== 0 ) {
116
- $key_base = func_get_args();
117
- if ( !empty($key_base) ) {
118
- $key = array_merge((array)$this->prefix, $key_base);
119
- return $sep . implode($sep, $key);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
121
  }
122
-
123
- return $key;
124
  }
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  /**
127
  * Retrieve class prefix (with separator if set)
128
  * @param bool|string $sep Separator to append to class prefix (Default: no separator)
129
  * @return string Class prefix
130
  */
131
- function get_prefix($sep = false) {
132
- $sep = ( is_string($sep) ) ? $sep : '';
133
- $prefix = ( !empty($this->prefix) ) ? $this->prefix . $sep : '';
134
- return $prefix;
 
 
 
 
 
 
 
 
 
135
  }
136
 
137
  /**
138
  * Prepend plugin prefix to some text
139
  * @param string $text Text to add to prefix
140
- * @param string $sep Text used to separate prefix and text
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  * @return string Text with prefix prepended
142
  */
143
- function add_prefix($text = '', $sep = '_') {
144
- return $this->get_prefix($sep) . $text;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  }
146
 
147
- function remove_prefix($text = '', $sep = '_') {
148
- if ( !empty($text) && strpos($text, ( $p = $this->get_prefix($sep) )) === 0 )
149
- $text = substr($text, strlen($p));
150
- return $text;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  }
152
 
 
 
153
  /**
154
- * Creates a meta key for storing post meta data
155
- * Prefixes standard prefixed text with underscore to hide meta data on post edit forms
156
- * @param string $text Text to use as base of meta key
157
- * @return string Formatted meta key
 
158
  */
159
- function make_meta_key($text = '') {
160
- return '_' . $this->add_prefix($text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  }
162
 
 
 
 
 
 
 
163
  /**
164
- * Returns Database prefix for Cornerstone-related DB Tables
165
- * @return string Database prefix
 
166
  */
167
- function get_db_prefix() {
168
- global $wpdb;
169
- return $wpdb->prefix . $this->get_prefix('_');
 
 
 
170
  }
171
  }
172
 
1
  <?php
2
 
 
 
3
  /**
4
  * @package Simple Lightbox
5
  * @subpackage Base
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
+ var $client_files = array(
87
+ 'scripts' => array(),
88
+ 'styles' => array()
89
+ );
90
+
91
+ /*-** Instances **-*/
92
 
93
  /**
94
  * Utilities
97
  var $util = null;
98
 
99
  /**
100
+ * Options
101
+ * @var SLB_Options
102
  */
103
+ var $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 = 'plugins_loaded';
120
+ if ( current_filter() == $hook || self::$_init_passed ) {
121
+ $this->_init();
122
+ } else {
123
+ add_action($hook, $this->m('_init'));
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('ar-series', 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 called by child class
183
+ */
184
+ protected function _options($options_config = null) {
185
+ $class = $this->util->get_class('Options');
186
+ $key = 'options';
187
+ if ( $this->shares($key) ) {
188
+ /**
189
+ * @var SLB_Options
190
+ */
191
+ $opts = $this->gvar($key);
192
+ //Setup options instance
193
+ if ( !is_a($opts, $class) ) {
194
+ $opts = $this->gvar($key, new $class());
195
+ }
196
+ } else {
197
+ $opts = new $class();
198
+ }
199
+ //Load options
200
+ if ( $this->is_options_valid($options_config, false) ) {
201
+ $opts->load($options_config);
202
+ }
203
+ //Set instance property
204
+ $this->options = $opts;
205
  }
206
 
207
  /**
208
+ * Initialize admin
209
+ * To be called by child class
 
 
 
 
 
210
  */
211
+ private function _admin() {
212
+ if ( !is_admin() ) {
213
+ return false;
214
+ }
215
+ $class = $this->util->get_class('Admin');
216
+ $key = 'admin';
217
+ if ( $this->shares($key) ) {
218
+ /**
219
+ * @var SLB_Admin
220
+ */
221
+ $adm = $this->gvar($key);
222
+ //Setup options instance
223
+ if ( !is_a($adm, $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() {
253
+ foreach ( $this->client_files as $key => $val ) {
254
+ if ( empty($val) && isset($this->{$key}) )
255
+ $this->client_files[$key] =& $this->{$key};
256
+ $g =& $this->client_files[$key];
257
+ if ( is_array($g) && !empty($g) ) {
258
+ $g = $this->util->parse_client_files($g, $key);
259
+ }
260
+ //Remove empty file groups
261
+ if ( empty($g) )
262
+ unset($this->client_files[$key]);
263
+ }
264
+
265
+ //Register
266
+ add_action('init', $this->m('register_client_files'));
267
+
268
+ //Enqueue
269
+ $hook_enqueue = ( ( is_admin() ) ? 'admin' : 'wp' ) . '_enqueue_scripts' ;
270
+ add_action($hook_enqueue, $this->m('enqueue_client_files'));
271
  }
272
 
273
  /**
274
+ * Register client files
275
+ * @see enqueue_client_files() for actual loading of files based on context
276
+ * @uses `init` Action hook for execution
277
+ * @return void
 
 
 
278
  */
279
+ public function register_client_files() {
280
+ $v = $this->util->get_plugin_version();
281
+ foreach ( $this->client_files as $type => $files ) {
282
+ $func = $this->get_client_files_handler($type, 'register');
283
+ if ( !$func )
284
+ continue;
285
+ foreach ( $files as $f ) {
286
+ //Get file URI
287
+ $f->file = ( !$this->util->is_file($f->file) && is_callable($f->file) ) ? call_user_func($f->file) : $this->util->get_file_url($f->file);
288
+ $params = array($f->id, $f->file, $f->deps, $v);
289
+ //Set additional parameters based on file type (script, style, etc.)
290
+ switch ( $type ) {
291
+ case 'scripts':
292
+ $params[] = $f->in_footer;
293
+ break;
294
+ case 'styles':
295
+ $params[] = $f->media;
296
+ break;
297
+ }
298
+ //Register file
299
+ call_user_func_array($func, $params);
300
+ }
301
+ }
302
  }
303
 
304
  /**
305
+ * Enqueues files for client output (scripts/styles) based on context
306
+ * @uses `admin_enqueue_scripts` Action hook depending on context
307
+ * @uses `wp_enqueue_scripts` Action hook depending on context
308
+ * @return void
309
  */
310
+ function enqueue_client_files() {
311
+ //Enqueue files
312
+ foreach ( $this->client_files as $type => $files ) {
313
+ $func = $this->get_client_files_handler($type, 'enqueue');
314
+ if ( !$func ) {
315
+ continue;
316
+ }
317
+ foreach ( $files as $f ) {
318
+ //Skip shadow files
319
+ if ( !$f->enqueue ) {
320
+ continue;
321
+ }
322
+ $load = true;
323
+ //Global Callback
324
+ if ( is_callable($f->callback) && !call_user_func($f->callback) ) {
325
+ $load = false;
326
+ }
327
+ //Context
328
+ if ( $load && !empty($f->context) ) {
329
+ //Reset $load before evaluating context
330
+ $load = false;
331
+ //Iterate through contexts
332
+ foreach ( $f->context as $ctx ) {
333
+ //Context + Callback
334
+ if ( is_array($ctx) ) {
335
+ //Stop checking context if callback is invalid
336
+ if ( !is_callable($ctx[1]) || !call_user_func($ctx[1]) )
337
+ continue;
338
+ $ctx = $ctx[0];
339
+ }
340
+ //Stop checking context if valid context found
341
+ if ( $this->util->is_context($ctx) ) {
342
+ $load = true;
343
+ break;
344
+ }
345
+ }
346
+ }
347
+
348
+ //Load valid file
349
+ if ( $load ) {
350
+ $func($f->id);
351
+ }
352
  }
353
  }
 
 
354
  }
355
 
356
+ /**
357
+ * Build function name for handling client operations
358
+ */
359
+ function get_client_files_handler($type, $action) {
360
+ $func = 'wp_' . $action . '_' . substr($type, 0, -1);
361
+ if ( !function_exists($func) )
362
+ $func = false;
363
+ return $func;
364
+ }
365
+
366
+ /*-** Reflection **-*/
367
+
368
+ /**
369
+ * Retrieve base object
370
+ * @return object|bool Base object (FALSE if object does not exist)
371
+ */
372
+ function &get_base() {
373
+ $base = false;
374
+ if ( isset($GLOBALS[$this->base]) )
375
+ $base =& $GLOBALS[$this->base];
376
+ return $base;
377
+ }
378
+
379
+ /*-** Method/Function calling **-*/
380
+
381
+ /**
382
+ * Returns callback to instance method
383
+ * @param string $method Method name
384
+ * @return array Callback array
385
+ */
386
+ function m($method) {
387
+ return $this->util->m($this, $method);
388
+ }
389
+
390
+ /*-** Prefix **-*/
391
+
392
  /**
393
  * Retrieve class prefix (with separator if set)
394
  * @param bool|string $sep Separator to append to class prefix (Default: no separator)
395
  * @return string Class prefix
396
  */
397
+ function get_prefix($sep = null) {
398
+ $args = func_get_args();
399
+ return call_user_func_array($this->util->m($this->util, 'get_prefix'), $args);
400
+ }
401
+
402
+ /**
403
+ * Check if a string is prefixed
404
+ * @param string $text Text to check for prefix
405
+ * @param string $sep (optional) Separator used
406
+ */
407
+ function has_prefix($text, $sep = null) {
408
+ $args = func_get_args();
409
+ return call_user_func_array($this->util->m($this->util, 'has_prefix'), $args);
410
  }
411
 
412
  /**
413
  * Prepend plugin prefix to some text
414
  * @param string $text Text to add to prefix
415
+ * @param string $sep (optional) Text used to separate prefix and text
416
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
417
+ * @return string Text with prefix prepended
418
+ */
419
+ function add_prefix($text, $sep = null, $once = true) {
420
+ $args = func_get_args();
421
+ return call_user_func_array($this->util->m($this->util, 'add_prefix'), $args);
422
+ }
423
+
424
+ /**
425
+ * Prepend uppercased plugin prefix to some text
426
+ * @param string $text Text to add to prefix
427
+ * @param string $sep (optional) Text used to separate prefix and text
428
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
429
  * @return string Text with prefix prepended
430
  */
431
+ function add_prefix_uc($text, $sep = null, $once = true) {
432
+ $args = func_get_args();
433
+ return call_user_func_array($this->util->m($this->util, 'add_prefix_uc'), $args);
434
+ }
435
+
436
+ /**
437
+ * Add prefix to variable reference
438
+ * Updates actual variable rather than return value
439
+ * @uses SLB_Utilities::add_prefix_ref();
440
+ * @param string $var Variable to add prefix to
441
+ * @param string $sep (optional) Separator text
442
+ * @param bool $once (optional) Add prefix only once
443
+ * @return void
444
+ */
445
+ function add_prefix_ref(&$var, $sep = null, $once = true) {
446
+ $args = func_get_args();
447
+ $args[0] =& $var;
448
+ call_user_func_array($this->util->m($this->util, 'add_prefix_ref'), $args);
449
  }
450
 
451
+ /**
452
+ * Remove prefix from specified string
453
+ * @param string $text String to remove prefix from
454
+ * @param string $sep (optional) Separator used with prefix
455
+ */
456
+ function remove_prefix($text, $sep = null) {
457
+ $args = func_get_args();
458
+ return call_user_func_array($this->util->m($this->util, 'remove_prefix'), $args);
459
+ }
460
+
461
+ /*-** Capabilities **-*/
462
+
463
+ protected function can($cap) {
464
+ if ( is_null($this->caps) ) {
465
+ //Build capabilities based on instance properties
466
+ $this->caps = array(
467
+ 'init' => ( 'object' != $this->mode ) ? true : false,
468
+ 'singleton' => ( !!$this->model ) ? true : false,
469
+ 'control' => ( 'sub' == $this->mode || 'object' == $this->mode ) ? false : true,
470
+ );
471
+ }
472
+ return ( isset($this->caps[$cap]) ) ? $this->caps[$cap] : false;
473
  }
474
 
475
+ /*-** Globals **-*/
476
+
477
  /**
478
+ * Get/Set (internal) global variables
479
+ * @uses $globals to get/set global variables
480
+ * @param string $name Variable name - If no name is specified, entire globals array is returned
481
+ * @param mixed $val (optional) Set the value of a variable (Returns variable value if omitted)
482
+ * @return mixed Variable value
483
  */
484
+ private function gvar($name = null, $val = null) {
485
+ $g =& self::$globals;
486
+ if ( !is_array($g) ) {
487
+ $g = array();
488
+ }
489
+ if ( !is_string($name) || empty($name) ) {
490
+ return $g;
491
+ }
492
+ $ret = $val;
493
+ if ( null !== $val ) {
494
+ //Set Value
495
+ $g[$name] = $val;
496
+ } elseif ( isset($g[$name]) ) {
497
+ //Retrieve variable
498
+ $ret = $g[$name];
499
+ }
500
+ return $ret;
501
  }
502
 
503
+ private function shares($name) {
504
+ return ( !empty($this->shared) && in_array($name, $this->shared) ) ? true : false;
505
+ }
506
+
507
+ /*-** Options **-*/
508
+
509
  /**
510
+ * Checks if options are valid
511
+ * @param array $data Data to be used on options
512
+ * @return bool TRUE if options are valid, FALSE otherwise
513
  */
514
+ function is_options_valid($data, $check_var = true) {
515
+ $class = $this->util->get_class('Options');
516
+ $ret = ( empty($data) || !is_array($data) || !class_exists($class) ) ? false : true;
517
+ if ( $ret && $check_var && !is_a($this->options, $class) )
518
+ $ret = false;
519
+ return $ret;
520
  }
521
  }
522
 
includes/class.base_collection.php ADDED
@@ -0,0 +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
+ }
includes/class.base_object.php ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * Retrieve files
180
+ * All files or a specific group of files can be retrieved
181
+ * @param string $type (optional) File group to retrieve
182
+ * @return array Files
183
+ */
184
+ protected function get_files($type = null) {
185
+ $ret = $this->files;
186
+ if ( is_string($type) ) {
187
+ $ret = ( isset($ret[$type]) ) ? $ret[$type] : array();
188
+ }
189
+ if ( !is_array($ret) ) {
190
+ $ret = array();
191
+ }
192
+ return $ret;
193
+ }
194
+
195
+ /**
196
+ * Retrieve file
197
+ * @param string $type Group to retrieve file from
198
+ * @param string $handle
199
+ * @param string $format (optional) Format of return value (Default: array)
200
+ * @return array|null File properties (Default: NULL)
201
+ */
202
+ protected function get_file($type, $handle, $format = null) {
203
+ //Get files
204
+ $files = $this->get_files($type);
205
+ //Get specified file
206
+ $ret = ( is_string($type) && isset($files[$handle]) ) ? $files[$handle] : null;
207
+ //Format return value
208
+ if ( !empty($ret) && !!$format ) {
209
+ switch ( $format ) {
210
+ case 'uri':
211
+ $ret = $ret['uri'];
212
+ break;
213
+ case 'object':
214
+ $ret = (object) $ret;
215
+ break;
216
+ }
217
+ }
218
+ return $ret;
219
+ }
220
+
221
+ /**
222
+ * Add stylesheet
223
+ * @param string $handle Name of the stylesheet
224
+ * @param string $src Stylesheet URI
225
+ * @return object Current instance
226
+ */
227
+ public function add_style($handle, $src, $deps = array()) {
228
+ return $this->add_file('styles', $handle, $src, $deps);
229
+ }
230
+
231
+ /**
232
+ * Retrieve stylesheet files
233
+ * @return array Stylesheet files
234
+ */
235
+ public function get_styles() {
236
+ return $this->get_files('styles');
237
+ }
238
+
239
+ /**
240
+ * Retrieve stylesheet file
241
+ * @param string $handle Name of stylesheet
242
+ * @param string $format (optional) Format of return value (@see `get_file()`)
243
+ * @return array|null File properties (Default: NULL)
244
+ */
245
+ public function get_style($handle, $format = null) {
246
+ return $this->get_file('styles', $handle, $format);
247
+ }
248
+
249
+ /**
250
+ * Add script
251
+ * @param string $handle Name of the script
252
+ * @param string $src Script URI
253
+ * @return object Current instance
254
+ */
255
+ public function add_script($handle, $src, $deps = array()) {
256
+ return $this->add_file('scripts', $handle, $src, $deps);
257
+ }
258
+
259
+ /**
260
+ * Retrieve script files
261
+ * @return array Script files
262
+ */
263
+ public function get_scripts() {
264
+ return $this->get_files('scripts');
265
+ }
266
+
267
+ /**
268
+ * Retrieve script file
269
+ * @param string $handle Name of script
270
+ * @param string $format (optional) Format of return value (@see `get_file()`)
271
+ * @return array|null File properties (Default: NULL)
272
+ */
273
+ public function get_script($handle, $format = null) {
274
+ return $this->get_file('scripts', $handle, $format);
275
+ }
276
+
277
+ }
includes/class.collection_controller.php ADDED
@@ -0,0 +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
+ }
includes/class.component.php ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ /* Helpers */
44
+
45
+ /**
46
+ * Validate instance
47
+ * @see `Base_Object::is_valid()`
48
+ * @return bool Valid (TRUE) / Invalid (FALSE)
49
+ */
50
+ public function is_valid() {
51
+ $ret = parent::is_valid();
52
+ if ( $ret ) {
53
+ //Check required component properties
54
+ $props = array_merge($this->props_required_base, $this->props_required);
55
+ foreach ( $props as $prop ) {
56
+ if ( !isset($this->{$prop}) || empty($this->{$prop}) ) {
57
+ $ret = false;
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ return $ret;
63
+ }
64
+
65
+ /* Client */
66
+
67
+ /**
68
+ * Set client script file
69
+ * @see Base_Object::add_script()
70
+ * @param string $src Script URI
71
+ * @param array $deps (optional) File dependencies
72
+ */
73
+ public function set_client_script($src, $deps = array()) {
74
+ if ( is_array($src) ) {
75
+ list($src, $deps) = func_get_arg(0);
76
+ }
77
+ return $this->add_script('client', $src, $deps);
78
+ }
79
+
80
+ /**
81
+ * Retrieve client script
82
+ * @see Base_Object::get_script()
83
+ * @param string $format (optional) Data format of return value
84
+ * @return mixed Client script data (formatted according to $format parameter)
85
+ */
86
+ public function get_client_script($format = null) {
87
+ return $this->get_script('client', $format);
88
+ }
89
+ }
includes/class.content_handler.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ /* Matching */
19
+
20
+ /**
21
+ * Set matching handler
22
+ * @param callback $callback Handler callback
23
+ * @return object Current instance
24
+ */
25
+ public function set_match($callback) {
26
+ $this->match = ( is_callable($callback) ) ? $callback : null;
27
+ return $this;
28
+ }
29
+
30
+ /**
31
+ * Retrieve match handler
32
+ * @return callback|null Match handler
33
+ */
34
+ protected function get_match() {
35
+ return $this->match;
36
+ }
37
+
38
+ /**
39
+ * Check if valid match set
40
+ */
41
+ protected function has_match() {
42
+ return ( is_null($this->match) ) ? false : true;
43
+ }
44
+
45
+ /**
46
+ * Match handler against URI
47
+ * @param string $uri URI to check for match
48
+ * @return bool TRUE if handler matches URI
49
+ */
50
+ public function match($uri) {
51
+ $ret = false;
52
+ if ( !!$uri && is_string($uri) && $this->has_match() ) {
53
+ $ret = call_user_func($this->get_match(), $uri);
54
+ }
55
+ return $ret;
56
+ }
57
+ }
includes/class.content_handlers.php ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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'));
35
+
36
+ add_action('wp_footer', $this->m('client_output'), $this->util->priority('client_footer_output'));
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
+ * @return array Handlers
96
+ */
97
+ public function get() {
98
+ $items = $this->get_cache();
99
+ if ( empty($items) ) {
100
+ //Retrieve items
101
+ $items = parent::get( array( 'orderby' => array('meta' => 'priority') ) );
102
+ $this->update_cache($items);
103
+ }
104
+ return $items;
105
+ }
106
+
107
+ /**
108
+ * Get matching handler for URI
109
+ * @param string $uri URI to find match for
110
+ * @return SLB_Content_Handler Matching handler (NULL if no handler matched)
111
+ */
112
+ public function match($uri) {
113
+ foreach ( $this->get() as $handler ) {
114
+ if ( $handler->match($uri) ) {
115
+ //Save match
116
+ $hid = $handler->get_id();
117
+ if ( !isset($this->request_matches[$hid]) ) {
118
+ $this->request_matches[$hid] = $handler;
119
+ }
120
+ return $handler;
121
+ }
122
+ }
123
+ return null;
124
+ }
125
+
126
+ /* Cache */
127
+
128
+ /**
129
+ * Retrieve cached items
130
+ * @uses get_cache_props()
131
+ * @uses wp_cache_get()
132
+ * @return array Cached items (Default: empty array)
133
+ */
134
+ protected function get_cache() {
135
+ $cprops= $this->get_cache_props();
136
+ $items = wp_cache_get($cprops->key, $cprops->group);
137
+ return ( is_array($items) ) ? $items : array();
138
+ }
139
+
140
+ /**
141
+ * Update cached items
142
+ * Cache is cleared if no items specified
143
+ * @uses get_cache_props()
144
+ * @uses wp_cache_get()
145
+ * @param array $data Item data to cache
146
+ */
147
+ protected function update_cache($data = null) {
148
+ $props = $this->get_cache_props();
149
+ wp_cache_set($props->key, $data, $props->group);
150
+ }
151
+
152
+ /**
153
+ * Clear cache
154
+ * @uses update_cache()
155
+ */
156
+ protected function clear_cache() {
157
+ $this->update_cache();
158
+ }
159
+
160
+ /**
161
+ * Retrieve cache properites (key, group)
162
+ * @return object Cache properties
163
+ */
164
+ protected function get_cache_props() {
165
+ if ( !is_object($this->cache_props) ) {
166
+ $this->cache_props = (object) array (
167
+ 'key' => $this->hook_prefix . '_items',
168
+ 'group' => $this->get_prefix(),
169
+ );
170
+ }
171
+ return $this->cache_props;
172
+ }
173
+
174
+ /* Handlers */
175
+
176
+ /**
177
+ * Initialize default handlers
178
+ * @param SLB_Content_Handlers $controller Handlers controller
179
+ */
180
+ public function init_defaults($controller) {
181
+ $handlers = array (
182
+ 'image' => array (
183
+ 'match' => $this->m('match_image'),
184
+ 'client_script' => $this->util->get_file_url('content-handlers/image/handler.image.js'),
185
+ ),
186
+ );
187
+ foreach ( $handlers as $id => $props ) {
188
+ $controller->add($id, $props);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Matches image URIs
194
+ * @param string $uri URI to match
195
+ * @return bool TRUE if URI is image
196
+ */
197
+ public function match_image($uri) {
198
+ return ( $this->util->has_file_extension($uri, array('jpg', 'jpeg', 'jpe', 'jfif', 'jif', 'gif', 'png')) ) ? true : false;
199
+ }
200
+
201
+ /* Output */
202
+
203
+ /**
204
+ * Client output
205
+ */
206
+ public function client_output() {
207
+ //Stop if not enabled
208
+ if ( !$this->has_parent() || !$this->get_parent()->is_enabled() ) {
209
+ return;
210
+ }
211
+ $id_fmt = 'add_handler_%s';
212
+ $out = array();
213
+ $out[] = '<!-- SLB-HDL -->' . PHP_EOL;
214
+ $code = array();
215
+ //Load matched handlers
216
+ foreach ( $this->request_matches as $handler ) {
217
+ //Define
218
+ $params = array(
219
+ sprintf("'%s'", $handler->get_id()),
220
+ sprintf("'%s'", $handler->get_client_script('uri')),
221
+ );
222
+ $code[] = $this->util->call_client_method('View.add_content_handler', $params, false);
223
+ }
224
+ $out[] = $this->util->build_script_element(implode('', $code), 'add_content_handlers', true, true);
225
+ $out[] = '<!-- /SLB-HDL -->' . PHP_EOL;
226
+ echo implode('', $out);
227
+ }
228
+ }
includes/class.fields.php ADDED
@@ -0,0 +1,2590 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * Merged with defaults during initialization
25
+ * @see $id_formats_default
26
+ * @var array
27
+ */
28
+ var $id_formats = null;
29
+
30
+ /**
31
+ * Default ID Formatting options
32
+ * Structure:
33
+ * > Key (string): Format name
34
+ * > Val (array): Options
35
+ * @var array
36
+ */
37
+ var $id_formats_default = array(
38
+ 'attr_id' => array(
39
+ 'wrap' => array('open' => '_', 'segment_open' => '_'),
40
+ 'prefix' => array('get_container', 'get_id', 'add_prefix'),
41
+ 'recursive' => true
42
+ ),
43
+ 'attr_name' => array(
44
+ 'wrap' => array('open' => '[', 'close' => ']', 'segment_open' => '[', 'segment_close' => ']'),
45
+ 'recursive' => true,
46
+ 'prefix' => array('get_container', 'get_id', 'add_prefix')
47
+ )
48
+ );
49
+
50
+ /**
51
+ * Special characters/phrases
52
+ * Used for preserving special characters during formatting
53
+ * Merged with $special_chars_default
54
+ * Array Structure
55
+ * > Key: Special character/phrase
56
+ * > Value: Placeholder for special character
57
+ * @var array
58
+ */
59
+ var $special_chars = null;
60
+
61
+ var $special_chars_default = array(
62
+ '{' => '%SQB_L%',
63
+ '}' => '%SQB_R%',
64
+ );
65
+
66
+ /**
67
+ * Reference to parent object that current instance inherits from
68
+ * @var object
69
+ */
70
+ var $parent = null;
71
+
72
+ /**
73
+ * Title
74
+ * @var string
75
+ */
76
+ var $title = '';
77
+
78
+ /**
79
+ * @var string Short description
80
+ */
81
+ var $description = '';
82
+
83
+ /**
84
+ * @var array Object Properties
85
+ */
86
+ var $properties = array();
87
+
88
+ /**
89
+ * Initialization properties
90
+ * @var array
91
+ */
92
+ protected $properties_init = null;
93
+
94
+ /**
95
+ * Structure: Property names stored as keys in group
96
+ * Root
97
+ * -> Group Name
98
+ * -> Property Name => Null
99
+ * Reason: Faster searching over large arrays
100
+ * @var array Groupings of Properties
101
+ */
102
+ var $property_groups = array();
103
+
104
+ /**
105
+ * Keys to filter out of properties array before setting properties
106
+ * @var array
107
+ */
108
+ var $property_filter = array('group');
109
+
110
+ /**
111
+ * Define order of properties
112
+ * Useful when processing order is important (e.g. one property depends on another)
113
+ * @var array
114
+ */
115
+ var $property_priority = array();
116
+
117
+ /**
118
+ * Data for object
119
+ * May also contain data for nested objects
120
+ * @var mixed
121
+ */
122
+ var $data = null;
123
+
124
+ /**
125
+ * Whether data has been fetched or not
126
+ * @var bool
127
+ */
128
+ var $data_loaded = false;
129
+
130
+ /**
131
+ * @var array Script resources to include for object
132
+ */
133
+ var $scripts = array();
134
+
135
+ /**
136
+ * @var array CSS style resources to include for object
137
+ */
138
+ var $styles = array();
139
+
140
+ /**
141
+ * Hooks (Filters/Actions) for object
142
+ * @var array
143
+ */
144
+ var $hooks = array();
145
+
146
+ /**
147
+ * Mapping of child properties to parent members
148
+ * Allows more flexibility when creating new instances of child objects using property arrays
149
+ * Associative array structure:
150
+ * > Key: Child property to map FROM
151
+ * > Val: Parent property to map TO
152
+ * @var array
153
+ */
154
+ var $map = null;
155
+
156
+ /**
157
+ * Options used when building collection (callbacks, etc.)
158
+ * Associative array
159
+ * > Key: Option name
160
+ * > Value: Option value
161
+ * @var array
162
+ */
163
+ var $build_vars = array();
164
+
165
+ var $build_vars_default = array();
166
+
167
+ /**
168
+ * Constructor
169
+ */
170
+ function __construct($id = '', $properties = null) {
171
+ parent::__construct();
172
+ //Normalize Properties
173
+ $args = func_get_args();
174
+ $defaults = $this->integrate_id($id);
175
+ $properties = $this->make_properties($args, $defaults);
176
+ //Save init properties
177
+ $this->properties_init = $properties;
178
+ //Set Properties
179
+ $this->set_properties($properties);
180
+ }
181
+
182
+ /* Getters/Setters */
183
+
184
+ /**
185
+ * Checks if the specified path exists in the object
186
+ * @param array $path Path to check for
187
+ * @return bool TRUE if path exists in object, FALSE otherwise
188
+ */
189
+ function path_isset($path = '') {
190
+ //Stop execution if no path is supplied
191
+ if ( empty($path) )
192
+ return false;
193
+ $args = func_get_args();
194
+ $path = $this->util->build_path($args);
195
+ $item =& $this;
196
+ //Iterate over path and check if each level exists before moving on to the next
197
+ for ($x = 0; $x < count($path); $x++) {
198
+ if ( $this->util->property_exists($item, $path[$x]) ) {
199
+ //Set $item as reference to next level in path for next iteration
200
+ $item =& $this->util->get_property($item, $path[$x]);
201
+ //$item =& $item[ $path[$x] ];
202
+ } else {
203
+ return false;
204
+ }
205
+ }
206
+ return true;
207
+ }
208
+
209
+ /**
210
+ * Retrieves a value from object using a specified path
211
+ * Checks to make sure path exists in object before retrieving value
212
+ * @param array $path Path to retrieve value from. Each item in array is a deeper dimension
213
+ * @return mixed Value at specified path
214
+ */
215
+ function &get_path_value($path = '') {
216
+ $ret = '';
217
+ $path = $this->util->build_path(func_get_args());
218
+ if ( $this->path_isset($path) ) {
219
+ $ret =& $this;
220
+ for ($x = 0; $x < count($path); $x++) {
221
+ if ( 0 == $x )
222
+ $ret =& $ret->{ $path[$x] };
223
+ else
224
+ $ret =& $ret[ $path[$x] ];
225
+ }
226
+ }
227
+ return $ret;
228
+ }
229
+
230
+ /**
231
+ * Search for specified member value in field type ancestors
232
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
233
+ * @param string $name Value to retrieve from member
234
+ * @return mixed Member value if found (Default: empty string)
235
+ */
236
+ function get_parent_value($member, $name = '', $default = '') {
237
+ $parent =& $this->get_parent();
238
+ return $this->get_object_value($parent, $member, $name, $default, 'parent');
239
+ }
240
+
241
+ /**
242
+ * Retrieves specified member value
243
+ * Handles inherited values
244
+ * Merging corresponding parents if value is an array (e.g. for property groups)
245
+ * @param string|array $member Member to search. May also contain a path to the desired member
246
+ * @param string $name Value to retrieve from member
247
+ * @param mixed $default Default value if no value found (Default: empty string)
248
+ * @param string $dir Direction to move through hierarchy to find value
249
+ * Possible Values:
250
+ * parent (default) - Search through field parents
251
+ * current - Do not search through connected objects
252
+ * container - Search through field containers
253
+ * caller - Search through field callers
254
+ * @return mixed Specified member value
255
+ * @todo Return reference
256
+ */
257
+ function &get_member_value($member, $name = '', $default = '', $dir = 'parent') {
258
+ //Check if path to member is supplied
259
+ $path = array();
260
+ if ( is_array($member) && isset($member['tag']) ) {
261
+ if ( isset($member['attributes']['ref_base']) ) {
262
+ if ( 'root' != $member['attributes']['ref_base'] )
263
+ $path[] = $member['attributes']['ref_base'];
264
+ } else {
265
+ $path[] = 'properties';
266
+ }
267
+
268
+ $path[] = $member['tag'];
269
+ } else {
270
+ $path = $member;
271
+ }
272
+
273
+ $path = $this->util->build_path($path, $name);
274
+ //Set defaults and prepare data
275
+ $val = $default;
276
+ $inherit = false;
277
+ $inherit_tag = '{inherit}';
278
+
279
+ /* Determine whether the value must be retrieved from a parent/container object
280
+ * Conditions:
281
+ * > Path does not exist in current field
282
+ * > Path exists and is not an object, but at least one of the following is true:
283
+ * > Value at path is an array (e.g. properties, elements, etc. array)
284
+ * > Parent/container values should be merged with retrieved array
285
+ * > Value at path is a string that inherits from another field
286
+ * > Value from other field will be retrieved and will replace inheritance placeholder in retrieved value
287
+ */
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_object($val) && ( is_array($val) || ($inherit = strpos($val, $inherit_tag)) !== false ) )
296
+ $deeper = true;
297
+ else
298
+ $deeper = false;
299
+ }
300
+ if ( $deeper && 'current' != $dir ) {
301
+ $ex_val = '';
302
+ //Get Parent value (recursive)
303
+ if ( 'parent' == $dir )
304
+ $ex_val = $this->get_parent_value($member, $name, $default);
305
+ elseif ( method_exists($this, 'get_container_value') )
306
+ $ex_val = $this->get_container_value($member, $name, $default);
307
+ //Handle inheritance
308
+ if ( is_array($val) ) {
309
+ //Combine Arrays
310
+ if ( is_array($ex_val) )
311
+ $val = array_merge($ex_val, $val);
312
+ } elseif ( $inherit !== false ) {
313
+ //Replace placeholder with inherited string
314
+ $val = str_replace($inherit_tag, $ex_val, $val);
315
+ } else {
316
+ //Default: Set parent value as value
317
+ $val = $ex_val;
318
+ }
319
+ }
320
+
321
+ return $val;
322
+ }
323
+
324
+ /**
325
+ * Search for specified member value in an object
326
+ * @param object $object Reference to object to retrieve value from
327
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
328
+ * @param string $name (optional) Value to retrieve from member
329
+ * @param mixed $default (optional) Default value to use if no value found (Default: empty string)
330
+ * @param string $dir Direction to move through hierarchy to find value @see SLB_Field_Type::get_member_value() for possible values
331
+ * @return mixed Member value if found (Default: $default)
332
+ */
333
+ function get_object_value(&$object, $member, $name = '', $default = '', $dir = 'parent') {
334
+ $ret = $default;
335
+ if ( is_object($object) && method_exists($object, 'get_member_value') )
336
+ $ret = $object->get_member_value($member, $name, $default, $dir);
337
+ return $ret;
338
+ }
339
+
340
+ /**
341
+ * Set item ID
342
+ * @param string $id Unique item ID
343
+ */
344
+ function set_id($id) {
345
+ if ( empty($id) || !is_string($id) )
346
+ return false;
347
+ $this->id = trim($id);
348
+ }
349
+
350
+ /**
351
+ * Retrieves field ID
352
+ * @param array|string $options (optional) Options or ID of format to use
353
+ * @return string item ID
354
+ */
355
+ function get_id($options = array()) {
356
+ $item_id = trim($this->id);
357
+ $formats = $this->get_id_formats();
358
+
359
+ //Setup options
360
+ $wrap_default = array('open' => '', 'close' => '', 'segment_open' => '', 'segment_close' => '');
361
+
362
+ $options_default = array(
363
+ 'format' => null,
364
+ 'wrap' => array(),
365
+ 'segments_pre' => null,
366
+ 'prefix' => '',
367
+ 'recursive' => false
368
+ );
369
+
370
+ //Load options based on format
371
+ if ( !is_array($options) )
372
+ $options = array('format' => $options);
373
+ if ( isset($options['format']) && is_string($options['format']) && isset($formats[$options['format']]) )
374
+ $options_default = wp_parse_args($formats[$options['format']], $options_default);
375
+ else
376
+ unset($options['format']);
377
+ $options = wp_parse_args($options, $options_default);
378
+ //Import options into function
379
+ extract($options);
380
+
381
+ //Validate options
382
+ $wrap = wp_parse_args($wrap, $wrap_default);
383
+
384
+ if ( !is_array($segments_pre) )
385
+ $segments_pre = array($segments_pre);
386
+ $segments_pre = array_reverse($segments_pre);
387
+
388
+ //Format ID based on options
389
+ $item_id = array($item_id);
390
+
391
+ //Add parent objects to ID
392
+ if ( !!$recursive ) {
393
+ //Create array of ID components
394
+ $m = 'get_caller';
395
+ $c = ( method_exists($this, $m) ) ? $this->{$m}() : null;
396
+ while ( !!$c ) {
397
+ //Add ID of current caller to array
398
+ if ( method_exists($c, 'get_id') && ( $itemp = $c->get_id() ) && !empty($itemp) )
399
+ $item_id = $itemp;
400
+ //Get parent object
401
+ $c = ( method_exists($c, $m) ) ? $c->{$m}() : null;
402
+ $itemp = '';
403
+ }
404
+ unset($c);
405
+ }
406
+
407
+ //Additional segments (Pre)
408
+ foreach ( $segments_pre as $seg ) {
409
+ if ( is_null($seg) )
410
+ continue;
411
+ if ( is_object($seg) )
412
+ $seg = (array)$seg;
413
+ if ( is_array($seg) )
414
+ $item_id = array_merge($item_id, array_reverse($seg));
415
+ elseif ( '' != strval($seg) )
416
+ $item_id[] = strval($seg);
417
+ }
418
+
419
+ //Prefix
420
+ if ( is_array($prefix) ) {
421
+ //Array is sequence of instance methods to call on object
422
+ //Last array member can be an array of parameters to pass to methods
423
+ $count = count($prefix);
424
+ $args = ( $count > 1 && is_array($prefix[$count - 1]) ) ? array_pop($prefix) : array();
425
+ $p = $this;
426
+ $val = '';
427
+ //Iterate through methods
428
+ foreach ( $prefix as $m ) {
429
+ if ( !method_exists($p, $m) )
430
+ continue;
431
+ //Build callback
432
+ $m = $this->util->m($p, $m);
433
+ //Call callback
434
+ $val = call_user_func_array($m, $args);
435
+ //Returned value may be an instance object
436
+ if ( is_object($val) )
437
+ $p = $val; //Use returned object in next round
438
+ else
439
+ array_unshift($args, $val); //Pass returned value as parameter to next method on using current object
440
+ }
441
+ $prefix = $val;
442
+ unset($p, $val);
443
+ }
444
+ if ( is_numeric($prefix) )
445
+ $prefix = strval($prefix);
446
+ if ( empty($prefix) || !is_string($prefix) )
447
+ $prefix = '';
448
+
449
+ //Convert array to string
450
+ $item_id = $prefix . $wrap['open'] . implode($wrap['segment_close'] . $wrap['segment_open'], array_reverse($item_id)) . $wrap['close'];
451
+ return $item_id;
452
+ }
453
+
454
+ /**
455
+ * Retrieve ID formatting options for class
456
+ * Format options arrays are merged together and saved to $id_formats
457
+ * @uses $id_formats
458
+ * @uses $id_formats_default
459
+ * @return array ID Formatting options
460
+ */
461
+ function &get_id_formats() {
462
+ if ( is_null($this->id_formats) ) {
463
+ $this->id_formats = wp_parse_args($this->id_formats, $this->id_formats_default);
464
+ }
465
+ return $this->id_formats;
466
+ }
467
+
468
+ /**
469
+ * Retrieve value from data member
470
+ * @param string $context Context to format data for
471
+ * @param bool $top (optional) Whether to traverse through the field hierarchy to get data for field (Default: TRUE)
472
+ * @return mixed Value at specified path
473
+ */
474
+ function get_data($context = '', $top = true) {
475
+ $opt_d = array('context' => '', 'top' => true);
476
+ $args = func_get_args();
477
+ $a = false;
478
+ if ( count($args) == 1 && is_array($args[0]) && !empty($args[0]) ) {
479
+ $a = true;
480
+ $args = wp_parse_args($args[0], $opt_d);
481
+ extract($args);
482
+ }
483
+
484
+ if ( is_string($top) ) {
485
+ if ( 'false' == $top )
486
+ $top = false;
487
+ elseif ( 'true' == $top )
488
+ $top = true;
489
+ elseif ( is_numeric($top) )
490
+ $top = intval($top);
491
+ }
492
+ $top = !!$top;
493
+ $obj =& $this;
494
+ $obj_path = array(&$this);
495
+ $path = array();
496
+ if ( $top ) {
497
+ //Iterate through hiearchy to get top-most object
498
+ while ( !empty($obj) ) {
499
+ $new = null;
500
+ //Try to get caller first
501
+ if ( method_exists($obj, 'get_caller') ) {
502
+ $checked = true;
503
+ $new =& $obj->get_caller();
504
+ }
505
+ //Try to get container if no caller found
506
+ if ( empty($new) && method_exists($obj, 'get_container') ) {
507
+ $checked = true;
508
+ $new =& $obj->get_container();
509
+ //Load data
510
+ if ( method_exists($new, 'load_data') ) {
511
+ $new->load_data();
512
+ }
513
+ }
514
+
515
+ $obj =& $new;
516
+ unset($new);
517
+ //Stop iteration
518
+ if ( !empty($obj) ) {
519
+ //Add object to path if it is valid
520
+ $obj_path[] =& $obj;
521
+ }
522
+ }
523
+ unset($obj);
524
+ }
525
+
526
+ //Check each object (starting with top-most) for matching data for current field
527
+
528
+ //Reverse array
529
+ $obj_path = array_reverse($obj_path);
530
+ //Build path for data location
531
+ foreach ( $obj_path as $obj ) {
532
+ if ( method_exists($obj, 'get_id') )
533
+ $path[] = $obj->get_id();
534
+ }
535
+ //Iterate through objects
536
+ while ( !empty($obj_path) ) {
537
+ //Get next object
538
+ $obj =& array_shift($obj_path);
539
+ //Shorten path
540
+ array_shift($path);
541
+ //Check for value in object and stop iteration if matching data found
542
+ $val = $this->get_object_value($obj, 'data', $path, null, 'current');
543
+ if ( !is_null($val) ) {
544
+ break;
545
+ }
546
+ }
547
+ return $this->format($val, $context);
548
+ }
549
+
550
+ /**
551
+ * Sets value in data member
552
+ * Sets value to data member itself by default
553
+ * @param mixed $value Value to set
554
+ * @param string|array $name Name of value to set (Can also be path to value)
555
+ */
556
+ function set_data($value, $name = '') {
557
+ $ref =& $this->get_path_value('data', $name);
558
+ $ref = $value;
559
+ }
560
+
561
+ /**
562
+ * Sets parent object of current instance
563
+ * Parent objects must be the same object type as current instance
564
+ * @uses SLB to get field type definition
565
+ * @uses SLB_Fields::has() to check if field type exists
566
+ * @uses SLB_Fields::get() to retrieve field type object reference
567
+ * @param string|object $parent Parent ID or reference
568
+ */
569
+ function set_parent($parent = null) {
570
+ //Stop processing if parent empty
571
+ if ( empty($parent) && !is_string($this->parent) )
572
+ return false;
573
+ //Parent passed as object reference wrapped in array
574
+ if ( is_array($parent) && isset($parent[0]) && is_object($parent[0]) )
575
+ $parent =& $parent[0];
576
+
577
+ //No parent set but parent ID (previously) set in object
578
+ if ( empty($parent) && is_string($this->parent) )
579
+ $parent = $this->parent;
580
+
581
+ //Retrieve reference object if ID was supplied
582
+ if ( is_string($parent) ) {
583
+ $parent = trim($parent);
584
+ //Get parent object reference
585
+ /**
586
+ * @var SLB
587
+ */
588
+ $b =& $this->get_base();
589
+ if ( $b && isset($b->fields) && $b->fields->has($parent) ) {
590
+ $parent =& $b->fields->get($parent);
591
+ }
592
+ }
593
+
594
+ //Set parent value on object
595
+ if ( is_string($parent) || is_object($parent) )
596
+ $this->parent =& $parent;
597
+ }
598
+
599
+ /**
600
+ * Retrieve field type parent
601
+ * @return SLB_Field_Type Reference to parent field
602
+ */
603
+ function &get_parent() {
604
+ return $this->parent;
605
+ }
606
+
607
+ /**
608
+ * Set object title
609
+ * @param string $title Title for object
610
+ * @param string $plural Plural form of title
611
+ */
612
+ function set_title($title = '') {
613
+ if ( is_scalar($title) )
614
+ $this->title = strip_tags(trim($title));
615
+ }
616
+
617
+ /**
618
+ * Retrieve object title
619
+ */
620
+ function get_title() {
621
+ return $this->get_member_value('title', '','', 'current');
622
+ }
623
+
624
+ /**
625
+ * Set object description
626
+ * @param string $description Description for object
627
+ */
628
+ function set_description($description = '') {
629
+ $this->description = strip_tags(trim($description));
630
+ }
631
+
632
+ /**
633
+ * Retrieve object description
634
+ * @return string Object description
635
+ */
636
+ function get_description() {
637
+ $dir = 'current';
638
+ return $this->get_member_value('description', '','', $dir);
639
+ return $desc;
640
+ }
641
+
642
+ /**
643
+ * Sets multiple properties on field type at once
644
+ * @param array $properties Properties. Each element is an array containing the arguments to set a new property
645
+ * @return boolean TRUE if successful, FALSE otherwise
646
+ */
647
+ function set_properties($properties) {
648
+ if ( !is_array($properties) ) {
649
+ return false;
650
+ }
651
+ //Normalize properties
652
+ $properties = $this->remap_properties($properties);
653
+ $properties = $this->sort_properties($properties);
654
+ //Set Member properties
655
+ foreach ( $properties as $prop => $val ) {
656
+ if ( ( $m = 'set_' . $prop ) && method_exists($this, $m) ) {
657
+ $this->{$m}($val);
658
+ //Remove member property from array
659
+ unset($properties[$prop]);
660
+ }
661
+ }
662
+
663
+ //Filter properties
664
+ $properties = $this->filter_properties($properties);
665
+ //Set additional instance properties
666
+ foreach ( $properties as $name => $val) {
667
+ $this->set_property($name, $val);
668
+ }
669
+ }
670
+
671
+ /**
672
+ * Remap properties based on $map
673
+ * @uses $map For determine how child properties should map to parent properties
674
+ * @uses SLB_Utlities::array_remap() to perform array remapping
675
+ * @param array $properties Associative array of properties
676
+ * @return array Remapped properties
677
+ */
678
+ function remap_properties($properties) {
679
+ //Return remapped properties
680
+ return $this->util->array_remap($properties, $this->map);
681
+ }
682
+
683
+ /**
684
+ * Sort properties based on priority
685
+ * @uses this::property_priority
686
+ * @return array Sorted priorities
687
+ */
688
+ function sort_properties($properties) {
689
+ //Stop if sorting not necessary
690
+ if ( empty($properties) || !is_array($properties) || empty($this->property_priority) || !is_array($this->property_priority) )
691
+ return $properties;
692
+ $props = array();
693
+ foreach ( $this->property_priority as $prop ) {
694
+ if ( !array_key_exists($prop, $properties) )
695
+ continue;
696
+ //Add to new array
697
+ $props[$prop] = $properties[$prop];
698
+ //Remove from old array
699
+ unset($properties[$prop]);
700
+ }
701
+ //Append any remaining properties
702
+ $props = array_merge($props, $properties);
703
+ return $props;
704
+ }
705
+
706
+ /**
707
+ * Build properties array
708
+ * @param array $props Instance properties
709
+ * @param array $signature (optional) Default properties
710
+ * @return array Normalized properties
711
+ */
712
+ function make_properties($props, $signature = array()) {
713
+ $p = array();
714
+ if ( is_array($props) ) {
715
+ foreach ( $props as $prop ) {
716
+ if ( is_array($prop) ) {
717
+ $p = array_merge($prop, $p);
718
+ }
719
+ }
720
+ }
721
+ $props = $p;
722
+ if ( is_array($signature) ) {
723
+ $props = array_merge($signature, $props);
724
+ }
725
+ return $props;
726
+ }
727
+
728
+ function validate_id($id) {
729
+ return ( is_scalar($id) && !empty($id) ) ? true : false;
730
+ }
731
+
732
+ function integrate_id($id) {
733
+ return ( $this->validate_id($id) ) ? array('id' => $id) : array();
734
+ }
735
+
736
+ /**
737
+ * Filter property members
738
+ * @uses $property_filter to remove define members to remove from $properties
739
+ * @param array $props Properties
740
+ * @return array Filtered properties
741
+ */
742
+ function filter_properties($props = array()) {
743
+ return $this->util->array_filter_keys($props, $this->property_filter);
744
+ }
745
+
746
+ /**
747
+ * Add/Set a property on the field definition
748
+ * @param string $name Name of property
749
+ * @param mixed $value Default value for property
750
+ * @param string|array $group Group(s) property belongs to
751
+ * @return boolean TRUE if property is successfully added to field type, FALSE otherwise
752
+ */
753
+ function set_property($name, $value = '', $group = null) {
754
+ //Do not add if property name is not a string
755
+ if ( !is_string($name) )
756
+ return false;
757
+ //Create property array
758
+ $prop_arr = array();
759
+ $prop_arr['value'] = $value;
760
+ //Add to properties array
761
+ $this->properties[$name] = $value;
762
+ //Add property to specified groups
763
+ if ( !empty($group) ) {
764
+ $this->set_group_property($group, $name);
765
+ }
766
+ return true;
767
+ }
768
+
769
+ /**
770
+ * Retreives property from field type
771
+ * @param string $name Name of property to retrieve
772
+ * @return mixed Specified Property if exists (Default: Empty string)
773
+ */
774
+ function get_property($name) {
775
+ $val = $this->get_member_value('properties', $name);
776
+ return $val;
777
+ }
778
+
779
+ /**
780
+ * Removes a property from item
781
+ * @param string $name Property ID
782
+ */
783
+ function remove_property($name) {
784
+ //Remove property
785
+ if ( isset($this->properties[$name]) )
786
+ unset($this->properties[$name]);
787
+ //Remove from group
788
+ foreach ( array_keys($this->property_groups) as $g ) {
789
+ if ( isset($this->property_groups[$g][$name]) ) {
790
+ unset($this->property_groups[$g][$name]);
791
+ break;
792
+ }
793
+ }
794
+ }
795
+
796
+ /**
797
+ * Adds Specified Property to a Group
798
+ * @param string|array $group Group(s) to add property to
799
+ * @param string $property Property to add to group
800
+ */
801
+ function set_group_property($group, $property) {
802
+ if ( is_string($group) && isset($this->property_groups[$group][$property]) )
803
+ return;
804
+ if ( !is_array($group) ) {
805
+ $group = array($group);
806
+ }
807
+
808
+ foreach ($group as $g) {
809
+ $g = trim($g);
810
+ //Initialize group if it doesn't already exist
811
+ if ( !isset($this->property_groups[$g]) )
812
+ $this->property_groups[$g] = array();
813
+
814
+ //Add property to group
815
+ $this->property_groups[$g][$property] = null;
816
+ }
817
+ }
818
+
819
+ /**
820
+ * Retrieve property group
821
+ * @param string $group Group to retrieve
822
+ * @return array Array of properties in specified group
823
+ */
824
+ function get_group($group) {
825
+ return $this->get_member_value('property_groups', $group, array());
826
+ }
827
+
828
+ /**
829
+ * Save field data
830
+ * Child classes will define their own
831
+ * functionality for this method
832
+ * @return bool TRUE if save was successful (FALSE otherwise)
833
+ */
834
+ function save() {
835
+ return true;
836
+ }
837
+
838
+ /*-** Hooks **-*/
839
+
840
+ /**
841
+ * Retrieve hooks added to object
842
+ * @return array Hooks
843
+ */
844
+ function get_hooks() {
845
+ return $this->get_member_value('hooks', '', array());
846
+ }
847
+
848
+ /**
849
+ * Add hook for object
850
+ * @see add_filter() for parameter defaults
851
+ * @param $tag
852
+ * @param $function_to_add
853
+ * @param $priority
854
+ * @param $accepted_args
855
+ */
856
+ function add_hook($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
857
+ //Create new array for tag (if not already set)
858
+ if ( !isset($this->hooks[$tag]) )
859
+ $this->hooks[$tag] = array();
860
+ //Build Unique ID
861
+ if ( is_string($function_to_add) )
862
+ $id = $function_to_add;
863
+ elseif ( is_array($function_to_add) && !empty($function_to_add) )
864
+ $id = strval($function_to_add[count($function_to_add) - 1]);
865
+ else
866
+ $id = 'function_' . ( count($this->hooks[$tag]) + 1 );
867
+ //Add hook
868
+ $this->hooks[$tag][$id] = func_get_args();
869
+ }
870
+
871
+ /**
872
+ * Convenience method for adding an action for object
873
+ * @see add_filter() for parameter defaults
874
+ * @param $tag
875
+ * @param $function_to_add
876
+ * @param $priority
877
+ * @param $accepted_args
878
+ */
879
+ function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
880
+ $this->add_hook($tag, $function_to_add, $priority, $accepted_args);
881
+ }
882
+
883
+ /**
884
+ * Convenience method for adding a filter for object
885
+ * @see add_filter() for parameter defaults
886
+ * @param $tag
887
+ * @param $function_to_add
888
+ * @param $priority
889
+ * @param $accepted_args
890
+ */
891
+ function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
892
+ $this->add_hook($tag, $function_to_add, $priority, $accepted_args);
893
+ }
894
+
895
+ /*-** Dependencies **-*/
896
+
897
+ /**
898
+ * Adds dependency to object
899
+ * @param string $type Type of dependency to add (script, style)
900
+ * @param array|string $context When dependency will be added (@see SLB_Utilities::get_action() for possible contexts)
901
+ * @see wp_enqueue_script for the following of the parameters
902
+ * @param $handle
903
+ * @param $src
904
+ * @param $deps
905
+ * @param $ver
906
+ * @param $ex
907
+ */
908
+ function add_dependency($type, $context, $handle, $src = false, $deps = array(), $ver = false, $ex = false) {
909
+ $args = func_get_args();
910
+ //Remove type/context from arguments
911
+ $args = array_slice($args, 2);
912
+
913
+ //Set context
914
+ if ( !is_array($context) ) {
915
+ //Wrap single contexts in an array
916
+ if ( is_string($context) )
917
+ $context = array($context);
918
+ else
919
+ $context = array();
920
+ }
921
+ //Add file to instance property
922
+ if ( isset($this->{$type}) && is_array($this->{$type}) )
923
+ $this->{$type}[$handle] = array('context' => $context, 'params' => $args);
924
+ }
925
+
926
+ /**
927
+ * Add script to object to be added in specified contexts
928
+ * @param array|string $context Array of contexts to add script to page
929
+ * @see wp_enqueue_script for the following of the parameters
930
+ * @param $handle
931
+ * @param $src
932
+ * @param $deps
933
+ * @param $ver
934
+ * @param $in_footer
935
+ */
936
+ function add_script( $context, $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) {
937
+ $args = func_get_args();
938
+ //Add file type to front of arguments array
939
+ array_unshift($args, 'scripts');
940
+ call_user_func_array($this->m('add_dependency'), $args);
941
+ }
942
+
943
+ /**
944
+ * Retrieve script dependencies for object
945
+ * @return array Script dependencies
946
+ */
947
+ function get_scripts() {
948
+ return $this->get_member_value('scripts', '', array());
949
+ }
950
+
951
+ /**
952
+ * Add style to object to be added in specified contexts
953
+ * @param array|string $context Array of contexts to add style to page
954
+ * @see wp_enqueue_style for the following of the parameters
955
+ * @param $handle
956
+ * @param $src
957
+ * @param $deps
958
+ * @param $ver
959
+ * @param $in_footer
960
+ */
961
+ function add_style( $handle, $src = false, $deps = array(), $ver = false, $media = false ) {
962
+ $args = func_get_args();
963
+ array_unshift($args, 'styles');
964
+ call_user_func_array($this->m('add_dependency'), $args);
965
+ }
966
+
967
+ /**
968
+ * Retrieve Style dependencies for object
969
+ * @return array Style dependencies
970
+ */
971
+ function get_styles() {
972
+ return $this->get_member_value('styles', '', array());
973
+ }
974
+
975
+ /* Helpers */
976
+
977
+ /**
978
+ * Format value based on specified context
979
+ * @param mixed $value Value to format
980
+ * @param string $context Current context
981
+ * @return mixed Formatted value
982
+ */
983
+ function format($value, $context = '') {
984
+ $handler = 'format_' . trim(strval($context));
985
+ //Only process if context is valid and has a handler
986
+ if ( !empty($context) && method_exists($this, $handler) ) {
987
+ //Pass value to handler
988
+ $value = $this->{$handler}($value, $context);
989
+ }
990
+ //Return formatted value
991
+ return $value;
992
+ }
993
+
994
+ /**
995
+ * Format value for output in form field
996
+ * @param mixed $value Value to format
997
+ * @return mixed Formatted value
998
+ */
999
+ function format_form($value) {
1000
+ if ( is_string($value) )
1001
+ $value = htmlspecialchars($value);
1002
+ return $value;
1003
+ }
1004
+
1005
+ /**
1006
+ * Final formatting before output
1007
+ * Restores special characters, etc.
1008
+ * @uses $special_chars
1009
+ * @uses $special_chars_default
1010
+ * @param mixed $value Pre-final field output
1011
+ * @param string $context (Optional) Formatting context
1012
+ * @return mixed Formatted value
1013
+ */
1014
+ function format_final($value, $context = '') {
1015
+ if ( !is_string($value) )
1016
+ return $value;
1017
+
1018
+ //Restore special chars
1019
+ return $this->restore_special_chars($value, $context);
1020
+ }
1021
+
1022
+ function preserve_special_chars($value, $context = '') {
1023
+ if ( !is_string($value) )
1024
+ return $value;
1025
+ $specials = $this->get_special_chars();
1026
+ return str_replace(array_keys($specials), $specials, $value);
1027
+ }
1028
+
1029
+ function restore_special_chars($value, $context = '') {
1030
+ if ( !is_string($value) )
1031
+ return $value;
1032
+ $specials = $this->get_special_chars();
1033
+ return str_replace($specials, array_keys($specials), $value);
1034
+ }
1035
+
1036
+ /**
1037
+ * Retrieve special characters/placeholders
1038
+ * Merges defaults with class-specific characters
1039
+ * @uses $special_chars
1040
+ * @uses $special_chars_default
1041
+ * @return array Special characters/placeholders
1042
+ */
1043
+ function get_special_chars() {
1044
+ return wp_parse_args($this->special_chars, $this->special_chars_default);
1045
+ }
1046
+ }
1047
+
1048
+ /**
1049
+ * Field Types
1050
+ * Stores properties for a specific field
1051
+ * @package Simple Lightbox
1052
+ * @subpackage Fields
1053
+ * @author Archetyped
1054
+ */
1055
+ class SLB_Field_Type extends SLB_Field_Base {
1056
+ /* Properties */
1057
+
1058
+ /**
1059
+ * @var array Array of Field types that make up current Field type
1060
+ */
1061
+ var $elements = array();
1062
+
1063
+ /**
1064
+ * @var array Field type layouts
1065
+ */
1066
+ var $layout = array();
1067
+
1068
+ /**
1069
+ * @var SLB_Field_Type Parent field type (reference)
1070
+ */
1071
+ var $parent = null;
1072
+
1073
+ /**
1074
+ * Object that field is in
1075
+ * @var SLB_Field|SLB_Field_Type|SLB_Field_Collection
1076
+ */
1077
+ var $container = null;
1078
+
1079
+ /**
1080
+ * Object that called field
1081
+ * Used to determine field hierarchy/nesting
1082
+ * @var SLB_Field|SLB_Field_Type|SLB_Field_Collection
1083
+ */
1084
+ var $caller = null;
1085
+
1086
+ function __construct($id = '', $parent = null) {
1087
+ $args = func_get_args();
1088
+ $defaults = $this->integrate_id($id);
1089
+ if ( !is_array($parent) )
1090
+ $defaults['parent'] = $parent;
1091
+
1092
+ $props = $this->make_properties($args, $defaults);
1093
+ parent::__construct($props);
1094
+ }
1095
+
1096
+ /* Getters/Setters */
1097
+
1098
+ /**
1099
+ * Search for specified member value in field's container object (if exists)
1100
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
1101
+ * @param string $name Value to retrieve from member
1102
+ * @return mixed Member value if found (Default: empty string)
1103
+ */
1104
+ function get_container_value($member, $name = '', $default = '') {
1105
+ $container =& $this->get_container();
1106
+ return $this->get_object_value($container, $member, $name, $default, 'container');
1107
+ }
1108
+
1109
+ /**
1110
+ * Search for specified member value in field's container object (if exists)
1111
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
1112
+ * @param string $name Value to retrieve from member
1113
+ * @return mixed Member value if found (Default: empty string)
1114
+ */
1115
+ function get_caller_value($member, $name = '', $default = '') {
1116
+ $caller =& $this->get_caller();
1117
+ return $this->get_object_value($caller, $member, $name, $default, 'caller');
1118
+ }
1119
+
1120
+ /**
1121
+ * Sets reference to container object of current field
1122
+ * Reference is cleared if no valid object is passed to method
1123
+ * @param object $container
1124
+ */
1125
+ function set_container(&$container) {
1126
+ if ( !empty($container) && is_object($container) ) {
1127
+ //Set as param as container for current field
1128
+ $this->container =& $container;
1129
+ } else {
1130
+ //Clear container member if argument is invalid
1131
+ $this->clear_container();
1132
+ }
1133
+ }
1134
+
1135
+ /**
1136
+ * Clears reference to container object of current field
1137
+ */
1138
+ function clear_container() {
1139
+ $this->container = null;
1140
+ }
1141
+
1142
+ /**
1143
+ * Retrieves reference to container object of current field
1144
+ * @return object Reference to container object
1145
+ */
1146
+ function &get_container() {
1147
+ $ret = null;
1148
+ if ( $this->has_container() )
1149
+ $ret =& $this->container;
1150
+ return $ret;
1151
+ }
1152
+
1153
+ /**
1154
+ * Checks if field has a container reference
1155
+ * @return bool TRUE if field is contained, FALSE otherwise
1156
+ */
1157
+ function has_container() {
1158
+ return !empty($this->container);
1159
+ }
1160
+
1161
+ /**
1162
+ * Sets reference to calling object of current field
1163
+ * Any existing reference is cleared if no valid object is passed to method
1164
+ * @param object $caller Calling object
1165
+ */
1166
+ function set_caller(&$caller) {
1167
+ if ( !empty($caller) && is_object($caller) )
1168
+ $this->caller =& $caller;
1169
+ else
1170
+ $this->clear_caller();
1171
+ }
1172
+
1173
+ /**
1174
+ * Clears reference to calling object of current field
1175
+ */
1176
+ function clear_caller() {
1177
+ unset($this->caller);
1178
+ }
1179
+
1180
+ /**
1181
+ * Retrieves reference to caller object of current field
1182
+ * @return object Reference to caller object
1183
+ */
1184
+ function &get_caller() {
1185
+ $ret = null;
1186
+ if ( $this->has_caller() )
1187
+ $ret =& $this->caller;
1188
+ return $ret;
1189
+ }
1190
+
1191
+ /**
1192
+ * Checks if field has a caller reference
1193
+ * @return bool TRUE if field is called by another field, FALSE otherwise
1194
+ */
1195
+ function has_caller() {
1196
+ return !empty($this->caller);
1197
+ }
1198
+
1199
+
1200
+
1201
+ /**
1202
+ * Sets an element for the field type
1203
+ * @param string $name Name of element
1204
+ * @param SLB_Field_Type $type Reference of field type to use for element
1205
+ * @param array $properties Properties for element (passed as keyed associative array)
1206
+ * @param string $id_prop Name of property to set $name to (e.g. ID, etc.)
1207
+ */
1208
+ function set_element($name, $type, $properties = array(), $id_prop = 'id') {
1209
+ $name = trim(strval($name));
1210
+ if ( empty($name) )
1211
+ return false;
1212
+ //Create new field for element
1213
+ $el = new SLB_Field($name, $type);
1214
+ //Set container to current field instance
1215
+ $el->set_container($this);
1216
+ //Add properties to element
1217
+ $el->set_properties($properties);
1218
+ //Save element to current instance
1219
+ $this->elements[$name] =& $el;
1220
+ }
1221
+
1222
+ /**
1223
+ * Add a layout to the field
1224
+ * @param string $name Name of layout
1225
+ * @param string $value Layout text
1226
+ */
1227
+ function set_layout($name, $value = '') {
1228
+ if ( !is_string($name) )
1229
+ return false;
1230
+ $name = trim($name);
1231
+ $this->layout[$name] = $value;
1232
+ return true;
1233
+ }
1234
+
1235
+ /**
1236
+ * Retrieve specified layout
1237
+ * @param string $name Layout name
1238
+ * @param bool $parse_nested (optional) Whether nested layouts should be expanded in retreived layout or not (Default: TRUE)
1239
+ * @return string Specified layout text
1240
+ */
1241
+ function get_layout($name = 'form', $parse_nested = true) {
1242
+ //Retrieve specified layout (use $name value if no layout by that name exists)
1243
+ if ( empty($name) )
1244
+ $name = $this->get_container_value('build_vars', 'layout', 'form');
1245
+ $layout = $this->get_member_value('layout', $name, $name);
1246
+
1247
+ //Find all nested layouts in current layout
1248
+ if ( !empty($layout) && !!$parse_nested ) {
1249
+ $ph = $this->get_placeholder_defaults();
1250
+
1251
+ while ($ph->match = $this->parse_layout($layout, $ph->pattern_layout)) {
1252
+ //Iterate through the different types of layout placeholders
1253
+ foreach ($ph->match as $tag => $instances) {
1254
+ //Iterate through instances of a specific type of layout placeholder
1255
+ foreach ($instances as $instance) {
1256
+ //Get nested layout
1257
+ $nested_layout = $this->get_member_value($instance);
1258
+
1259
+ //Replace layout placeholder with retrieved item data
1260
+ if ( !empty($nested_layout) )
1261
+ $layout = str_replace($ph->start . $instance['match'] . $ph->end, $nested_layout, $layout);
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1266
+
1267
+ return $layout;
1268
+ }
1269
+
1270
+ /**
1271
+ * Checks if specified layout exists
1272
+ * Finds layout if it exists in current object or any of its parents
1273
+ * @param string $layout Name of layout to check for
1274
+ * @return bool TRUE if layout exists, FALSE otherwise
1275
+ */
1276
+ function has_layout($layout) {
1277
+ $ret = false;
1278
+ if ( is_string($layout) && ($layout = trim($layout)) && !empty($layout) ) {
1279
+ $layout = $this->get_member_value('layout', $layout, false);
1280
+ if ( $layout !== false )
1281
+ $ret = true;
1282
+ }
1283
+
1284
+ return $ret;
1285
+ }
1286
+
1287
+ /**
1288
+ * Checks if layout content is valid
1289
+ * Layouts need to have placeholders to be valid
1290
+ * @param string $layout_content Layout content (markup)
1291
+ * @return bool TRUE if layout is valid, FALSE otherwise
1292
+ */
1293
+ function is_valid_layout($layout_content) {
1294
+ $ph = $this->get_placeholder_defaults();
1295
+ return preg_match($ph->pattern_general, $layout_content);
1296
+ }
1297
+
1298
+ /**
1299
+ * Parse field layout with a regular expression
1300
+ * @param string $layout Layout data
1301
+ * @param string $search Regular expression pattern to search layout for
1302
+ * @return array Associative array containing all of the regular expression matches in the layout data
1303
+ * Array Structure:
1304
+ * root => placeholder tags
1305
+ * => Tag instances (array)
1306
+ * 'tag' => (string) tag name
1307
+ * 'match' => (string) placeholder match
1308
+ * 'attributes' => (array) attributes
1309
+ */
1310
+ function parse_layout($layout, $search) {
1311
+ $ph_xml = '';
1312
+ $parse_match = '';
1313
+ $ph_root_tag = 'ph_root_element';
1314
+ $ph_start_xml = '<';
1315
+ $ph_end_xml = ' />';
1316
+ $ph_wrap_start = '<' . $ph_root_tag . '>';
1317
+ $ph_wrap_end = '</' . $ph_root_tag . '>';
1318
+ $parse_result = false;
1319
+
1320
+ //Find all nested layouts in layout
1321
+ $match_value = preg_match_all($search, $layout, $parse_match, PREG_PATTERN_ORDER);
1322
+
1323
+ if ($match_value !== false && $match_value > 0) {
1324
+ $parse_result = array();
1325
+ //Get all matched elements
1326
+ $parse_match = $parse_match[1];
1327
+
1328
+ //Build XML string from placeholders
1329
+ foreach ($parse_match as $ph) {
1330
+ $ph_xml .= $ph_start_xml . $ph . $ph_end_xml . ' ';
1331
+ }
1332
+ $ph_xml = $ph_wrap_start . $ph_xml . $ph_wrap_end;
1333
+ //Parse XML data
1334
+ $ph_prs = xml_parser_create();
1335
+ xml_parser_set_option($ph_prs, XML_OPTION_SKIP_WHITE, 1);
1336
+ xml_parser_set_option($ph_prs, XML_OPTION_CASE_FOLDING, 0);
1337
+ $ret = xml_parse_into_struct($ph_prs, $ph_xml, $parse_result['values'], $parse_result['index']);
1338
+ xml_parser_free($ph_prs);
1339
+
1340
+ //Build structured array with all parsed data
1341
+
1342
+ unset($parse_result['index'][$ph_root_tag]);
1343
+
1344
+ //Build structured array
1345
+ $result = array();
1346
+ foreach ($parse_result['index'] as $tag => $instances) {
1347
+ $result[$tag] = array();
1348
+ //Instances
1349
+ foreach ($instances as $instance) {
1350
+ //Skip instance if it doesn't exist in parse results
1351
+ if (!isset($parse_result['values'][$instance]))
1352
+ continue;
1353
+
1354
+ //Stop processing instance if a previously-saved instance with the same options already exists
1355
+ foreach ($result[$tag] as $tag_match) {
1356
+ if ($tag_match['match'] == $parse_match[$instance - 1])
1357
+ continue 2;
1358
+ }
1359
+
1360
+ //Init instance data array
1361
+ $inst_data = array();
1362
+
1363
+ //Add Tag to array
1364
+ $inst_data['tag'] = $parse_result['values'][$instance]['tag'];
1365
+
1366
+ //Add instance data to array
1367
+ $inst_data['attributes'] = (isset($parse_result['values'][$instance]['attributes'])) ? $inst_data['attributes'] = $parse_result['values'][$instance]['attributes'] : '';
1368
+
1369
+ //Add match to array
1370
+ $inst_data['match'] = $parse_match[$instance - 1];
1371
+
1372
+ //Add to result array
1373
+ $result[$tag][] = $inst_data;
1374
+ }
1375
+ }
1376
+ $parse_result = $result;
1377
+ }
1378
+
1379
+ return $parse_result;
1380
+ }
1381
+
1382
+ /**
1383
+ * Retrieves default properties to use when evaluating layout placeholders
1384
+ * @return object Object with properties for evaluating layout placeholders
1385
+ */
1386
+ function get_placeholder_defaults() {
1387
+ $ph = new stdClass();
1388
+ $ph->start = '{';
1389
+ $ph->end = '}';
1390
+ $ph->reserved = array('ref' => 'ref_base');
1391
+ $ph->pattern_general = '/' . $ph->start . '([a-zA-Z0-9_].*?)' . $ph->end . '/i';
1392
+ $ph->pattern_layout = '/' . $ph->start . '([a-zA-Z0-9].*?\s+' . $ph->reserved['ref'] . '="layout.*?".*?)' . $ph->end . '/i';
1393
+ return $ph;
1394
+ }
1395
+
1396
+ /**
1397
+ * Build item output
1398
+ * @param string $layout (optional) Layout to build
1399
+ * @param string $data Data to pass to layout
1400
+ */
1401
+ function build($layout = null, $data = null) {
1402
+ $this->util->do_action_ref_array('build_pre', array(&$this));
1403
+ echo $this->build_layout($layout, $data);
1404
+ $this->util->do_action_ref_array('build_post', array(&$this));
1405
+ }
1406
+
1407
+ /**
1408
+ * Builds HTML for a field based on its properties
1409
+ * @param string $layout (optional) Name of layout to build
1410
+ * @param array $data Additional data for current item
1411
+ */
1412
+ function build_layout($layout = 'form', $data = null) {
1413
+ $out_default = '';
1414
+ //Get base layout
1415
+ $out = $this->get_layout($layout);
1416
+ //Only parse valid layouts
1417
+ if ( $this->is_valid_layout($out) ) {
1418
+ //Parse Layout
1419
+ $ph = $this->get_placeholder_defaults();
1420
+
1421
+ //Search layout for placeholders
1422
+ while ( $ph->match = $this->parse_layout($out, $ph->pattern_general) ) {
1423
+ //Iterate through placeholders (tag, id, etc.)
1424
+ foreach ( $ph->match as $tag => $instances ) {
1425
+ //Iterate through instances of current placeholder
1426
+ foreach ( $instances as $instance ) {
1427
+ //Process value based on placeholder name
1428
+ $target_property = apply_filters($this->add_prefix('process_placeholder_' . $tag), '', $this, $instance, $layout, $data);
1429
+ //Process value using default processors (if necessary)
1430
+ if ( '' == $target_property ) {
1431
+ $target_property = apply_filters($this->add_prefix('process_placeholder'), $target_property, $this, $instance, $layout, $data);
1432
+ }
1433
+
1434
+ //Clear value if value not a string
1435
+ if ( !is_scalar($target_property) ) {
1436
+ $target_property = '';
1437
+ }
1438
+
1439
+ //Replace layout placeholder with retrieved item data
1440
+ $out = str_replace($ph->start . $instance['match'] . $ph->end, $target_property, $out);
1441
+ }
1442
+ }
1443
+ }
1444
+ } else {
1445
+ $out = $out_default;
1446
+ }
1447
+ /* Return generated value */
1448
+ $out = $this->format_final($out);
1449
+ return $out;
1450
+ }
1451
+ }
1452
+
1453
+ class SLB_Field extends SLB_Field_Type {}
1454
+
1455
+ /**
1456
+ * Managed collection of fields
1457
+ * @package Simple Lightbox
1458
+ * @subpackage Fields
1459
+ * @author Archetyped
1460
+ */
1461
+ class SLB_Field_Collection extends SLB_Field_Base {
1462
+
1463
+ /* Configuration */
1464
+
1465
+ protected $mode = 'sub';
1466
+
1467
+ /* Properties */
1468
+
1469
+ /**
1470
+ * Item type
1471
+ * @var string
1472
+ */
1473
+ var $item_type = 'SLB_Field';
1474
+
1475
+ /**
1476
+ * Indexed array of items in collection
1477
+ * @var array
1478
+ */
1479
+ var $items = array();
1480
+
1481
+ var $id_formats = array (
1482
+ 'formatted' => array(
1483
+ 'wrap' => array ( 'open' => '_' ),
1484
+ 'recursive' => false,
1485
+ 'prefix' => array('get_prefix')
1486
+ )
1487
+ );
1488
+
1489
+ var $build_vars_default = array (
1490
+ 'groups' => array(),
1491
+ 'context' => '',
1492
+ 'layout' => 'form',
1493
+ 'build' => true,
1494
+ 'build_groups' => true,
1495
+ );
1496
+
1497
+ /**
1498
+ * Associative array of groups in collection
1499
+ * Key: Group name
1500
+ * Value: object of group properties
1501
+ * > title
1502
+ * > description string Group description
1503
+ * > items array Items in group
1504
+ * @var array
1505
+ */
1506
+ var $groups = array();
1507
+
1508
+ protected $properties_init = null;
1509
+
1510
+ /* Constructors */
1511
+
1512
+ /**
1513
+ * Class constructor
1514
+ * @uses parent::__construct()
1515
+ * @uses self::make_properties()
1516
+ * @uses self::init()
1517
+ * @uses self::add_groups()
1518
+ * @uses self::add_items()
1519
+ * @param string $id Collection ID
1520
+ * @param array $properties (optional) Properties to set for collection (Default: none)
1521
+ */
1522
+ public function __construct($id, $properties = null) {
1523
+ $args = func_get_args();
1524
+ $properties = $this->make_properties($args);
1525
+ //Parent constructor
1526
+ parent::__construct($properties);
1527
+
1528
+ //Save initial properties
1529
+ $this->properties_init = $properties;
1530
+ }
1531
+
1532
+ public function _init() {
1533
+ parent::_init();
1534
+ $this->load($this->properties_init, false);
1535
+ }
1536
+
1537
+ /*-** Getters/Setters **-*/
1538
+
1539
+ /* Setup */
1540
+
1541
+ /**
1542
+ * Load collection with specified properties
1543
+ * Updates existing properties
1544
+ * @param array $properties Properties to load
1545
+ * @param bool $update (optional) Update (TRUE) or overwrite (FALSE) items/groups (Default: TRUE)
1546
+ * @return object Current instance
1547
+ */
1548
+ public function load($properties, $update = true) {
1549
+ $args = func_get_args();
1550
+ $properties = $this->make_properties($args);
1551
+ if ( !empty($properties) ) {
1552
+ //Groups
1553
+ if ( isset($properties['groups']) ) {
1554
+ $this->add_groups($properties['groups'], $update);
1555
+ }
1556
+ //Items
1557
+ if ( isset($properties['items']) ) {
1558
+ $this->add_items($properties['items'], $update);
1559
+ }
1560
+ }
1561
+ return $this;
1562
+ }
1563
+
1564
+ /* Data */
1565
+
1566
+ /**
1567
+ * Retrieve external data for items in collection
1568
+ * Retrieved data is saved to the collection's $data property
1569
+ * Uses class properties to determine how data is retrieved
1570
+ * Examples:
1571
+ * > DB
1572
+ * > XML
1573
+ * > JSON
1574
+ * @return void
1575
+ */
1576
+ function load_data() {
1577
+ $this->data_loaded = true;
1578
+ }
1579
+
1580
+ /**
1581
+ * Set data for an item
1582
+ * @param mixed $item Field to set data for
1583
+ * > string Field ID
1584
+ * > object Field Reference
1585
+ * > array Data for multiple items (associative array [field ID => data])
1586
+ * @param mixed $value Data to set
1587
+ * @param bool $save (optional) Whether or not data should be saved to DB (Default: Yes)
1588
+ */
1589
+ function set_data($item, $value = '', $save = true, $force_set = false) {
1590
+ //Set data for entire collection
1591
+ if ( is_array($item) ) {
1592
+ $this->data = wp_parse_args($item, $this->data);
1593
+ //Update save option
1594
+ $args = func_get_args();
1595
+ if ( 2 == count($args) && is_bool($args[1]) ) {
1596
+ $save = $args[1];
1597
+ }
1598
+ }
1599
+ //Get $item's ID
1600
+ elseif ( is_object($item) && method_exists($item, 'get_id') )
1601
+ $item = $item->get_id();
1602
+ //Set data
1603
+ if ( is_string($item) && !empty($item) && ( isset($this->items[$item]) || !!$force_set ) )
1604
+ $this->data[$item] = $value;
1605
+ if ( !!$save )
1606
+ $this->save();
1607
+ }
1608
+
1609
+ /* Item */
1610
+
1611
+ /**
1612
+ * Adds item to collection
1613
+ * @param string|obj $id Unique name for item or item instance
1614
+ * @param array $properties (optional) Item properties
1615
+ * @param bool $update (optional) Update or overwrite existing item (Default: FALSE)
1616
+ * @return object Reference to new item
1617
+ */
1618
+ function &add($id, $properties = array(), $update = false) {
1619
+ $item;
1620
+ $args = func_get_args();
1621
+ //Properties
1622
+ foreach ( array_reverse($args) as $arg ) {
1623
+ if ( is_array($arg) ) {
1624
+ $properties = $arg;
1625
+ break;
1626
+ }
1627
+ }
1628
+ if ( !is_array($properties) ) {
1629
+ $properties = array();
1630
+ }
1631
+
1632
+ //Handle item instance
1633
+ if ( $id instanceof $this->item_type ) {
1634
+ $item = $id;
1635
+ $item->set_properties($properties);
1636
+ } elseif ( class_exists($this->item_type) ) {
1637
+ $defaults = array (
1638
+ 'parent' => null,
1639
+ 'group' => null
1640
+ );
1641
+ $properties = array_merge($defaults, $properties);
1642
+ if ( is_string($id) ) {
1643
+ $properties['id'] = $id;
1644
+ }
1645
+ if ( !!$update && $this->has($properties['id']) ) {
1646
+ //Update existing item
1647
+ $item = $this->get($properties['id']);
1648
+ $item->set_properties($properties);
1649
+ } else {
1650
+ //Init item
1651
+ $type = $this->item_type;
1652
+ $item = new $type($properties);
1653
+ }
1654
+ }
1655
+
1656
+ if ( empty($item) || 0 == strlen($item->get_id()) ) {
1657
+ return false;
1658
+ }
1659
+
1660
+ //Set container
1661
+ $item->set_container($this);
1662
+
1663
+ //Add item to collection
1664
+ $this->items[$item->get_id()] = $item;
1665
+
1666
+ if ( isset($properties['group']) ) {
1667
+ $this->add_to_group($properties['group'], $item->get_id());
1668
+ }
1669
+
1670
+ return $item;
1671
+ }
1672
+
1673
+ /**
1674
+ * Removes item from collection
1675
+ * @param string|object $item Object or item ID to remove
1676
+ * @param bool $save (optional) Whether to save the collection after removing item (Default: YES)
1677
+ */
1678
+ function remove($item, $save = true) {
1679
+ //Remove item
1680
+ if ( $this->has($item) ) {
1681
+ $item = $this->get($item);
1682
+ $item = $item->get_id();
1683
+ //Remove from items array
1684
+ unset($this->items[$item]);
1685
+ //Remove item from groups
1686
+ $this->remove_from_group($item);
1687
+ }
1688
+ //Remove item data from collection
1689
+ $this->remove_data($item, false);
1690
+
1691
+ if ( !!$save )
1692
+ $this->save();
1693
+ }
1694
+
1695
+ /**
1696
+ * Remove item data from collection
1697
+ * @param string|object $item Object or item ID to remove
1698
+ * @param bool $save (optional) Whether to save the collection after removing item (Default: YES)
1699
+ */
1700
+ function remove_data($item, $save = true) {
1701
+ //Get item ID from object
1702
+ if ( $this->has($item) ) {
1703
+ $item = $this->get($item);
1704
+ $item = $item->get_id();
1705
+ }
1706
+
1707
+ //Remove data from data member
1708
+ if ( is_string($item) && is_array($this->data) ) {
1709
+ unset($this->data[$item]);
1710
+ if ( !!$save )
1711
+ $this->save();
1712
+ }
1713
+ }
1714
+
1715
+ /**
1716
+ * Checks if item exists in the collection
1717
+ * @param string $item Item ID
1718
+ * @return bool TRUE if item exists, FALSE otherwise
1719
+ */
1720
+ function has($item) {
1721
+ return ( !is_string($item) || empty($item) || is_null($this->get_member_value('items', $item, null)) ) ? false : true;
1722
+ }
1723
+
1724
+ /**
1725
+ * Retrieve specified item in collection
1726
+ * @param string|object $item Item object or ID to retrieve
1727
+ * @return SLB_Field Specified item
1728
+ */
1729
+ function get($item, $safe_mode = false) {
1730
+ if ( $this->has($item) ) {
1731
+ if ( !is_object($item) || !is_a($item, $this->item_type) ) {
1732
+ if ( is_string($item) ) {
1733
+ $item = trim($item);
1734
+ $item =& $this->items[$item];
1735
+ }
1736
+ else {
1737
+ $item = false;
1738
+ }
1739
+ }
1740
+ } else {
1741
+ $item = false;
1742
+ }
1743
+
1744
+ if ( !!$safe_mode && !is_object($item) ) {
1745
+ //Fallback: Return empty item if no item exists
1746
+ $type = $this->item_type;
1747
+ $item = new $type('');
1748
+ }
1749
+ return $item;
1750
+ }
1751
+
1752
+ /**
1753
+ * Retrieve item data
1754
+ * @param $item Item to get data for
1755
+ * @param $context (optional) Context
1756
+ * @param $top (optional) Iterate through ancestors to get data (Default: Yes)
1757
+ * @return mixed Item data
1758
+ */
1759
+ function get_data($item = null, $context = '', $top = true) {
1760
+ $this->load_data();
1761
+ $ret = null;
1762
+ if ( $this->has($item) ) {
1763
+ $item =& $this->get($item);
1764
+ $ret = $item->get_data($context, $top);
1765
+ } else {
1766
+ $ret = parent::get_data($context, $top);
1767
+ }
1768
+
1769
+ if ( is_string($item) && is_array($ret) && isset($ret[$item]) )
1770
+ $ret = $ret[$item];
1771
+ return $ret;
1772
+ }
1773
+
1774
+ /* Items (Collection) */
1775
+
1776
+ /**
1777
+ * Add multiple items to collection
1778
+ * @param array $items Items to add to collection
1779
+ * Array Structure:
1780
+ * > Key (string): Item ID
1781
+ * > Val (array): Item properties
1782
+ * @return void
1783
+ */
1784
+ function add_items($items = array(), $update = false) {
1785
+ //Validate
1786
+ if ( !is_array($items) || empty($items) ) {
1787
+ return false;
1788
+ }
1789
+ //Add items
1790
+ foreach ( $items as $id => $props ) {
1791
+ $this->add($id, $props, $update);
1792
+ }
1793
+ }
1794
+
1795
+ /**
1796
+ * Retrieve reference to items in collection
1797
+ * @return array Collection items (reference)
1798
+ */
1799
+ function get_items($group = null, $sort = 'priority') {
1800
+ $gset = $this->group_exists($group);
1801
+ if ( $gset ) {
1802
+ $items = $this->get_group_items($group);
1803
+ } elseif ( !empty($group) ) {
1804
+ $items = array();
1805
+ } else {
1806
+ $items = $this->items;
1807
+ }
1808
+ if ( !empty($items) ) {
1809
+ //Sort items
1810
+ if ( !empty($sort) && is_string($sort) ) {
1811
+ if ( 'priority' == $sort ) {
1812
+ if ( $gset ) {
1813
+ //Sort by priority
1814
+ ksort($items, SORT_NUMERIC);
1815
+ }
1816
+ }
1817
+ }
1818
+ //Release from buckets
1819
+ if ( $gset ) {
1820
+ $items = call_user_func_array('array_merge', $items);
1821
+ }
1822
+ }
1823
+ return $items;
1824
+ }
1825
+
1826
+ /**
1827
+ * Build output for items in specified group
1828
+ * If no group specified, all items in collection are built
1829
+ * @param string|object $group (optional) Group to build items for (ID or instance object)
1830
+ */
1831
+ function build_items($group = null) {
1832
+ //Get group items
1833
+ $items =& $this->get_items($group);
1834
+ if ( empty($items) ) {
1835
+ return false;
1836
+ }
1837
+
1838
+ $this->util->do_action_ref_array('build_items_pre', array(&$this));
1839
+ foreach ( $items as $item ) {
1840
+ $item->build();
1841
+ }
1842
+ $this->util->do_action_ref_array('build_items_post', array(&$this));
1843
+ }
1844
+
1845
+ /* Group */
1846
+
1847
+ /**
1848
+ * Add groups to collection
1849
+ * @param array $groups Associative array of group properties
1850
+ * Array structure:
1851
+ * > Key (string): group ID
1852
+ * > Val (string): Group Title
1853
+ */
1854
+ function add_groups($groups = array(), $update = false) {
1855
+ //Validate
1856
+ if ( !is_array($groups) || empty($groups) ) {
1857
+ return false;
1858
+ }
1859
+ //Iterate
1860
+ foreach ( $groups as $id => $props ) {
1861
+ $this->add_group($id, $props, null, $update);
1862
+ }
1863
+ }
1864
+
1865
+ /**
1866
+ * Adds group to collection
1867
+ * Groups are used to display related items in the UI
1868
+ * @param string $id Unique name for group
1869
+ * @param string $title Group title
1870
+ * @param string $description Short description of group's purpose
1871
+ * @param array $items (optional) ID's of existing items to add to group
1872
+ * @return object Group object
1873
+ */
1874
+ function &add_group($id, $properties = array(), $items = array(), $update = false) {
1875
+ //Create new group and set properties
1876
+ $default = array (
1877
+ 'title' => '',
1878
+ 'description' => '',
1879
+ 'priority' => 10
1880
+ );
1881
+ $p = ( is_array($properties) ) ? array_merge($default, $properties) : $default;
1882
+ if ( !is_int($p['priority']) || $p['priority'] < 0 ) {
1883
+ $p['priority'] = $default['priority'];
1884
+ }
1885
+ $id = trim($id);
1886
+ //Retrieve or init group
1887
+ if ( !!$update && $this->group_exists($id) ) {
1888
+ $grp = $this->get_group($id);
1889
+ $grp->title = $p['title'];
1890
+ $grp->description = $p['description'];
1891
+ $grp->priority = $p['priority'];
1892
+ } else {
1893
+ $this->groups[$id] =& $this->create_group($p['title'], $p['description'], $p['priority']);
1894
+ }
1895
+ //Add items to group (if supplied)
1896
+ if ( !empty($items) && is_array($items) ) {
1897
+ $this->add_to_group($id, $items);
1898
+ }
1899
+ return $this->groups[$id];
1900
+ }
1901
+
1902
+ /**
1903
+ * Remove specified group from collection
1904
+ * @param string $id Group ID to remove
1905
+ */
1906
+ function remove_group($id) {
1907
+ $id = trim($id);
1908
+ if ( $this->group_exists($id) ) {
1909
+ unset($this->groups[$id]);
1910
+ }
1911
+ }
1912
+
1913
+ /**
1914
+ * Standardized method to create a new item group
1915
+ * @param string $title Group title (used in meta boxes, etc.)
1916
+ * @param string $description Short description of group's purpose
1917
+ * @param int $priority (optional) Group priority (e.g. used to sort groups during output)
1918
+ * @return object Group object
1919
+ */
1920
+ function &create_group($title = '', $description = '', $priority = 10) {
1921
+ //Create new group object
1922
+ $group = new stdClass();
1923
+ /* Set group properties */
1924
+
1925
+ //Set Title
1926
+ $title = ( is_scalar($title) ) ? trim($title) : '';
1927
+ $group->title = $title;
1928
+ //Set Description
1929
+ $description = ( is_scalar($description) ) ? trim($description) : '';
1930
+ $group->description = $description;
1931
+ //Priority
1932
+ $group->priority = ( is_int($priority) ) ? $priority : 10;
1933
+ //Create array to hold items
1934
+ $group->items = array();
1935
+ return $group;
1936
+ }
1937
+
1938
+ /**
1939
+ * Checks if group exists in collection
1940
+ * @param string $id Group name
1941
+ * @return bool TRUE if group exists, FALSE otherwise
1942
+ */
1943
+ function group_exists($group) {
1944
+ $ret = false;
1945
+ if ( is_object($group) ) {
1946
+ $ret = true;
1947
+ } elseif ( is_string($group) && ($group = trim($group)) && strlen($group) > 0 ) {
1948
+ $group = trim($group);
1949
+ //Check if group exists
1950
+ $ret = !is_null($this->get_member_value('groups', $group, null));
1951
+ }
1952
+ return $ret;
1953
+ }
1954
+
1955
+ /**
1956
+ * Adds item to a group in the collection
1957
+ * Group is created if it does not already exist
1958
+ * @param string|array $group ID of group (or group parameters if new group) to add item to
1959
+ * @param string|array $items Name or array of item(s) to add to group
1960
+ */
1961
+ function add_to_group($group, $items, $priority = 10) {
1962
+ //Validate
1963
+ if ( empty($items) || empty($group) || ( !is_string($group) && !is_array($group) ) ) {
1964
+ return false;
1965
+ }
1966
+
1967
+ //Get group ID
1968
+ if ( is_string($group) ) {
1969
+ $group = array($group);
1970
+ }
1971
+ list($gid, $priority) = $group;
1972
+ $gid = trim(sanitize_title_with_dashes($gid));
1973
+ if ( empty($gid) ) {
1974
+ return false;
1975
+ }
1976
+ //Item priority
1977
+ if ( !is_int($priority) ) {
1978
+ $priority = 10;
1979
+ }
1980
+
1981
+ //Prepare group
1982
+ if ( !$this->group_exists($gid) ) {
1983
+ //TODO Follow
1984
+ call_user_func($this->m('add_group'), $gid, $group);
1985
+ }
1986
+ //Prepare items
1987
+ if ( !is_array($items) ) {
1988
+ $items = array($items);
1989
+ }
1990
+ //Add Items
1991
+ foreach ( $items as $item ) {
1992
+ //Skip if not in current collection
1993
+ $itm_ref =& $this->get($item);
1994
+ if ( !$itm_ref ) {
1995
+ continue;
1996
+ }
1997
+ $itm_id = $itm_ref->get_id();
1998
+ //Remove item from any other group it's in (items can only be in one group)
1999
+ foreach ( $this->get_groups() as $group ) {
2000
+ foreach ( $group->items as $tmp_pri => $tmp_items ) {
2001
+ if ( isset($group->items[$tmp_pri][$itm_id]) ) {
2002
+ unset($group->items[$tmp_pri][$itm_id]);
2003
+ }
2004
+ }
2005
+ }
2006
+ //Add reference to item in group
2007
+ $items =& $this->get_group($gid)->items;
2008
+ if ( !isset($items[$priority]) ) {
2009
+ $items[$priority] = array();
2010
+ }
2011
+ $items[$priority][$itm_id] =& $itm_ref;
2012
+ }
2013
+ unset($itm_ref);
2014
+ }
2015
+
2016
+ /**
2017
+ * Remove item from a group
2018
+ * If no group is specified, then item is removed from all groups
2019
+ * @param string|object $item Object or ID of item to remove from group
2020
+ * @param string $group (optional) Group ID to remove item from
2021
+ */
2022
+ function remove_from_group($item, $group = '') {
2023
+ //Get ID of item to remove or stop execution if item invalid
2024
+ $item = $this->get($item);
2025
+ $item = $item->get_id();
2026
+ if ( !$item )
2027
+ return false;
2028
+
2029
+ //Remove item from group
2030
+ if ( !empty($group) ) {
2031
+ //Remove item from single group
2032
+ if ( ($group =& $this->get_group($group)) && isset($group->items[$item]) ) {
2033
+ unset($group->items[$item]);
2034
+ }
2035
+ } else {
2036
+ //Remove item from all groups
2037
+ foreach ( array_keys($this->groups) as $group ) {
2038
+ if ( ($group =& $this->get_group($group)) && isset($group->items[$item]) ) {
2039
+ unset($group->items[$item]);
2040
+ }
2041
+ }
2042
+ }
2043
+ }
2044
+
2045
+ /**
2046
+ * Retrieve specified group
2047
+ * @param string $group ID of group to retrieve
2048
+ * @return object Reference to specified group
2049
+ */
2050
+ function &get_group($group) {
2051
+ if ( is_object($group) ) {
2052
+ return $group;
2053
+ }
2054
+ if ( is_string($group) ) {
2055
+ $group = trim($group);
2056
+ }
2057
+ //Create group if it doesn't already exist
2058
+ if ( ! $this->group_exists($group) ) {
2059
+ $this->add_group($group);
2060
+ }
2061
+ return $this->get_member_value('groups', $group);
2062
+ }
2063
+
2064
+ /**
2065
+ * Retrieve a group's items
2066
+ * @uses SLB_Field_Collection::get_group() to retrieve group object
2067
+ * @param object|string $group Group object or group ID
2068
+ * @return array Group's items
2069
+ */
2070
+ function &get_group_items($group) {
2071
+ $group =& $this->get_group($group);
2072
+ return $group->items;
2073
+ }
2074
+
2075
+ /**
2076
+ * Retrieve all groups in collection
2077
+ * @return array Reference to group objects
2078
+ */
2079
+ function &get_groups($opts = array()) {
2080
+ $groups =& $this->get_member_value('groups');
2081
+ if ( is_array($opts) && !empty($opts) ) {
2082
+ extract($opts, EXTR_SKIP);
2083
+ if ( !empty($groups) && !empty($sort) && is_string($sort) ) {
2084
+ if ( property_exists(current($groups), $sort) ) {
2085
+ //Sort groups by property
2086
+ $sfunc = create_function('$a,$b', '$ap = $a->' . $sort . '; $bp = $b->' . $sort . '; if ( $ap == $bp ) return 0; return ( $ap > $bp ) ? 1 : -1;');
2087
+ uasort($groups, $sfunc);
2088
+ }
2089
+ }
2090
+ }
2091
+ return $groups;
2092
+ }
2093
+
2094
+ /**
2095
+ * Output groups
2096
+ * @uses self::build_vars to determine groups to build
2097
+ */
2098
+ function build_groups() {
2099
+ $this->util->do_action_ref_array('build_groups_pre', array(&$this));
2100
+
2101
+ //Get groups to build
2102
+ $groups = ( !empty($this->build_vars['groups']) ) ? $this->build_vars['groups'] : array_keys($this->get_groups(array('sort' => 'priority')));
2103
+ //Check options
2104
+ if ( is_callable($this->build_vars['build_groups']) ) {
2105
+ //Pass groups to callback to build output
2106
+ call_user_func_array($this->build_vars['build_groups'], array($this, $groups));
2107
+ } elseif ( !!$this->build_vars['build_groups'] ) {
2108
+ //Build groups
2109
+ foreach ( $groups as $group ) {
2110
+ $this->build_group($group);
2111
+ }
2112
+ }
2113
+
2114
+ $this->util->do_action_ref_array('build_groups_post', array(&$this));
2115
+ }
2116
+
2117
+ /**
2118
+ * Build group
2119
+ */
2120
+ function build_group($group) {
2121
+ if ( !$this->group_exists($group) ) {
2122
+ return false;
2123
+ }
2124
+ $group =& $this->get_group($group);
2125
+ //Stop processing if group contains no items
2126
+ if ( !count($this->get_items($group)) ) {
2127
+ return false;
2128
+ }
2129
+
2130
+ //Pre action
2131
+ $this->util->do_action_ref_array('build_group_pre', array(&$this, $group));
2132
+
2133
+ //Build items
2134
+ $this->build_items($group);
2135
+
2136
+ //Post action
2137
+ $this->util->do_action_ref_array('build_group_post', array(&$this, $group));
2138
+ }
2139
+
2140
+ /* Collection */
2141
+
2142
+ /**
2143
+ * Build entire collection of items
2144
+ * Prints output
2145
+ */
2146
+ function build($build_vars = array()) {
2147
+ //Parse vars
2148
+ $this->parse_build_vars($build_vars);
2149
+ $this->util->do_action_ref_array('build_init', array(&$this));
2150
+ //Pre-build output
2151
+ $this->util->do_action_ref_array('build_pre', array(&$this));
2152
+ //Build groups
2153
+ $this->build_groups();
2154
+ //Post-build output
2155
+ $this->util->do_action_ref_array('build_post', array(&$this));
2156
+ }
2157
+
2158
+ /**
2159
+ * Parses build variables prior to use
2160
+ * @uses this->reset_build_vars() to reset build variables for each request
2161
+ * @param array $build_vars Variables to use for current request
2162
+ */
2163
+ function parse_build_vars($build_vars = array()) {
2164
+ $this->reset_build_vars();
2165
+ $this->build_vars = $this->util->apply_filters('parse_build_vars', wp_parse_args($build_vars, $this->build_vars), $this);
2166
+ }
2167
+
2168
+ /**
2169
+ * Reset build variables to defaults
2170
+ * Default Variables
2171
+ * > groups - array - Names of groups to build
2172
+ * > context - string - Context of current request
2173
+ * > layout - string - Name of default layout to use
2174
+ */
2175
+ function reset_build_vars() {
2176
+ $this->build_vars = wp_parse_args($this->build_vars, $this->build_vars_default);
2177
+ }
2178
+ }
2179
+
2180
+ /**
2181
+ * Collection of default system-wide fields
2182
+ * @package Simple Lightbox
2183
+ * @subpackage Fields
2184
+ * @author Archetyped
2185
+ *
2186
+ */
2187
+ class SLB_Fields extends SLB_Field_Collection {
2188
+
2189
+ var $item_type = 'SLB_Field_Type';
2190
+
2191
+ /**
2192
+ * Placeholder handlers
2193
+ * @var array
2194
+ */
2195
+ var $placholders = null;
2196
+
2197
+ /* Constructor */
2198
+
2199
+ function __construct() {
2200
+ parent::__construct('fields');
2201
+ }
2202
+
2203
+ protected function _hooks() {
2204
+ parent::_hooks();
2205
+ //Init fields
2206
+ add_action('init', $this->m('register_types'));
2207
+ //Init placeholders
2208
+ add_action('init', $this->m('register_placeholders'));
2209
+ }
2210
+
2211
+ /* Field Types */
2212
+
2213
+ /**
2214
+ * Initialize fields
2215
+ */
2216
+ function register_types() {
2217
+ /* Field Types */
2218
+
2219
+ //Base
2220
+ $base = new SLB_Field_Type('base');
2221
+ $base->set_description(__('Default Element', 'simple-lightbox'));
2222
+ $base->set_property('tag', 'span');
2223
+ $base->set_property('class', '', 'attr');
2224
+ $base->set_layout('form_attr', '{tag} name="{field_name}" id="{field_id}" {properties ref_base="root" group="attr"}');
2225
+ $base->set_layout('form', '<{form_attr ref_base="layout"} />');
2226
+ $base->set_layout('label', '<label for="{field_id}">{label}</label>');
2227
+ $base->set_layout('display', '{data context="display"}');
2228
+ $this->add($base);
2229
+
2230
+ //Base closed
2231
+ $base_closed = new SLB_Field_Type('base_closed');
2232
+ $base_closed->set_parent('base');
2233
+ $base_closed->set_description(__('Default Element (Closed Tag)', 'simple-lightbox'));
2234
+ $base_closed->set_layout('form_start', '<{tag} id="{field_id}" name="{field_name}" {properties ref_base="root" group="attr"}>');
2235
+ $base_closed->set_layout('form_end', '</{tag}>');
2236
+ $base_closed->set_layout('form', '{form_start ref_base="layout"}{data}{form_end ref_base="layout"}');
2237
+ $this->add($base_closed);
2238
+
2239
+ //Input
2240
+ $input = new SLB_Field_Type('input', 'base');
2241
+ $input->set_description(__('Default Input Element', 'simple-lightbox'));
2242
+ $input->set_property('tag', 'input');
2243
+ $input->set_property('type', 'text', 'attr');
2244
+ $input->set_property('value', '{data}', 'attr');
2245
+ $this->add($input);
2246
+
2247
+ //Text input
2248
+ $text = new SLB_Field_Type('text', 'input');
2249
+ $text->set_description(__('Text Box', 'simple-lightbox'));
2250
+ $text->set_property('size', 15, 'attr');
2251
+ $text->set_property('label');
2252
+ $text->set_layout('form', '{label ref_base="layout"} {inherit}');
2253
+ $this->add($text);
2254
+
2255
+ //Checkbox
2256
+ $cb = new SLB_Field_Type('checkbox', 'input');
2257
+ $cb->set_property('type', 'checkbox');
2258
+ $cb->set_property('value', null);
2259
+ $cb->set_layout('form_attr', '{inherit} {checked}');
2260
+ $cb->set_layout('form', '{label ref_base="layout"} <{form_attr ref_base="layout"} />');
2261
+ $this->add($cb);
2262
+
2263
+ //Textarea
2264
+ $ta = new SLB_Field_Type('textarea', 'base_closed');
2265
+ $ta->set_property('tag', 'textarea');
2266
+ $ta->set_property('cols', 40, 'attr');
2267
+ $ta->set_property('rows', 3, 'attr');
2268
+ $this->add($ta);
2269
+
2270
+ //Rich Text
2271
+ $rt = new SLB_Field_Type('richtext', 'textarea');
2272
+ $rt->set_property('class', 'theEditor {inherit}');
2273
+ $rt->set_layout('form', '<div class="rt_container">{inherit}</div>');
2274
+ $rt->add_action('admin_print_footer_scripts', 'wp_tiny_mce', 25);
2275
+ $this->add($rt);
2276
+
2277
+ //Hidden
2278
+ $hidden = new SLB_Field_Type('hidden');
2279
+ $hidden->set_parent('input');
2280
+ $hidden->set_description(__('Hidden Field', 'simple-lightbox'));
2281
+ $hidden->set_property('type', 'hidden');
2282
+ $this->add($hidden);
2283
+
2284
+ //Select
2285
+ $select = new SLB_Field_Type('select', 'base_closed');
2286
+ $select->set_description(__('Select tag', 'simple-lightbox'));
2287
+ $select->set_property('tag', 'select');
2288
+ $select->set_property('tag_option', 'option');
2289
+ $select->set_property('options', array());
2290
+ $select->set_layout('form', '{label ref_base="layout"} {form_start ref_base="layout"}{option_loop ref_base="layout"}{form_end ref_base="layout"}');
2291
+ $select->set_layout('option_loop', '{loop data="properties.options" layout="option" layout_data="option_data"}');
2292
+ $select->set_layout('option', '<{tag_option} value="{data_ext id="option_value"}">{data_ext id="option_text"}</{tag_option}>');
2293
+ $select->set_layout('option_data', '<{tag_option} value="{data_ext id="option_value"}" selected="selected">{data_ext id="option_text"}</{tag_option}>');
2294
+ $this->add($select);
2295
+
2296
+ //Span
2297
+ $span = new SLB_Field_Type('span', 'base_closed');
2298
+ $span->set_description(__('Inline wrapper', 'simple-lightbox'));
2299
+ $span->set_property('tag', 'span');
2300
+ $span->set_property('value', 'Hello there!');
2301
+ $this->add($span);
2302
+
2303
+ //Enable plugins to modify (add, remove, etc.) field types
2304
+ do_action_ref_array($this->add_prefix('register_fields'), array(&$this));
2305
+
2306
+ //Signal completion of field registration
2307
+ do_action_ref_array($this->add_prefix('fields_registered'), array(&$this));
2308
+ }
2309
+
2310
+ /* Placeholder handlers */
2311
+
2312
+ function register_placeholders() {
2313
+ //Default placeholder handlers
2314
+ $this->register_placeholder('all', $this->m('process_placeholder_default'), 11);
2315
+ $this->register_placeholder('field_id', $this->m('process_placeholder_id'));
2316
+ $this->register_placeholder('field_name', $this->m('process_placeholder_name'));
2317
+ $this->register_placeholder('data', $this->m('process_placeholder_data'));
2318
+ $this->register_placeholder('data_ext',$this->m('process_placeholder_data_ext'));
2319
+ $this->register_placeholder('loop', $this->m('process_placeholder_loop'));
2320
+ $this->register_placeholder('label', $this->m('process_placeholder_label'));
2321
+ $this->register_placeholder('checked', $this->m('process_placeholder_checked'));
2322
+
2323
+ //Allow other code to register placeholders
2324
+ do_action_ref_array($this->add_prefix('register_field_placeholders'), array(&$this));
2325
+
2326
+ //Signal completion of field placeholder registration
2327
+ do_action_ref_array($this->add_prefix('field_placeholders_registered'), array(&$this));
2328
+ }
2329
+
2330
+ /**
2331
+ * Register a function to handle a placeholder
2332
+ * Multiple handlers may be registered for a single placeholder
2333
+ * Adds filter hook to WP for handling specified placeholder
2334
+ * Placeholders are in layouts and are replaced with data at runtime
2335
+ * @uses add_filter()
2336
+ * @param string $placeholder Name of placeholder to add handler for (Using 'all' will set the function as a handler for all placeholders
2337
+ * @param callback $callback Function to set as a handler
2338
+ * @param int $priority (optional) Priority of handler
2339
+ * @return void
2340
+ */
2341
+ function register_placeholder($placeholder, $callback, $priority = 10) {
2342
+ if ( 'all' == $placeholder )
2343
+ $placeholder = '';
2344
+ else
2345
+ $placeholder = '_' . $placeholder;
2346
+ $hook = $this->add_prefix('process_placeholder' . $placeholder);
2347
+ add_filter($hook, $callback, $priority, 5);
2348
+ }
2349
+
2350
+ /**
2351
+ * Default placeholder processing
2352
+ * To be executed when current placeholder has not been handled by another handler
2353
+ * @param string $output Value to be used in place of placeholder
2354
+ * @param SLB_Field $item Field containing placeholder
2355
+ * @param array $placeholder Current placeholder
2356
+ * @see SLB_Field::parse_layout for structure of $placeholder array
2357
+ * @param string $layout Layout to build
2358
+ * @param array $data Extended data for item
2359
+ * @return string Value to use in place of current placeholder
2360
+ */
2361
+ function process_placeholder_default($output, $item, $placeholder, $layout, $data) {
2362
+ //Validate parameters before processing
2363
+ if ( empty($output) && is_a($item, 'SLB_Field_Type') && is_array($placeholder) ) {
2364
+ //Build path to replacement data
2365
+ $output = $item->get_member_value($placeholder);
2366
+
2367
+ //Check if value is group (properties, etc.)
2368
+ //All groups must have additional attributes (beyond reserved attributes) that define how items in group are used
2369
+ if (is_array($output)
2370
+ && !empty($placeholder['attributes'])
2371
+ && is_array($placeholder['attributes'])
2372
+ && ($ph = $item->get_placeholder_defaults())
2373
+ && $attribs = array_diff(array_keys($placeholder['attributes']), array_values($ph->reserved))
2374
+ ) {
2375
+ /* Targeted property is an array, but the placeholder contains additional options on how property is to be used */
2376
+
2377
+ //Find items matching criteria in $output
2378
+ //Check for group criteria
2379
+ if ( 'properties' == $placeholder['tag'] && ($prop_group = $item->get_group($placeholder['attributes']['group'])) && !empty($prop_group) ) {
2380
+ /* Process group */
2381
+ $group_out = array();
2382
+ //Iterate through properties in group and build string
2383
+ foreach ( array_keys($prop_group) as $prop_key ) {
2384
+ $prop_val = $item->get_property($prop_key);
2385
+ if ( !is_null($prop_val) )
2386
+ $group_out[] = $prop_key . '="' . $prop_val . '"';
2387
+ }
2388
+ $output = implode(' ', $group_out);
2389
+ }
2390
+ } elseif ( is_object($output) && is_a($output, $item->base_class) ) {
2391
+ /* Targeted property is actually a nested item */
2392
+ //Set caller to current item
2393
+ $output->set_caller($item);
2394
+ //Build layout for nested element
2395
+ $output = $output->build_layout($layout);
2396
+ }
2397
+ }
2398
+
2399
+ return $output;
2400
+ }
2401
+
2402
+ /**
2403
+ * Build Field ID attribute
2404
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2405
+ * @return string Placeholder output
2406
+ */
2407
+ function process_placeholder_id($output, $item, $placeholder, $layout, $data) {
2408
+ //Get attributes
2409
+ $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_id'));
2410
+ return $item->get_id($args);
2411
+ }
2412
+
2413
+ /**
2414
+ * Build Field name attribute
2415
+ * Name is formatted as an associative array for processing by PHP after submission
2416
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2417
+ * @return string Placeholder output
2418
+ */
2419
+ function process_placeholder_name($output, $item, $placeholder, $layout, $data) {
2420
+ //Get attributes
2421
+ $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_name'));
2422
+ return $item->get_id($args);
2423
+ }
2424
+
2425
+ /**
2426
+ * Build item label
2427
+ * @see SLB_Fields::process_placeholder_default for parameter descriptions
2428
+ * @return string Field label
2429
+ */
2430
+ function process_placeholder_label($output, $item, $placeholder, $layout, $data) {
2431
+ //Check if item has label property (e.g. sub-elements)
2432
+ $out = $item->get_property('label');
2433
+ //If property not set, use item title
2434
+ if ( empty($out) )
2435
+ $out = $item->get_title();
2436
+ return $out;
2437
+ }
2438
+
2439
+ /**
2440
+ * Retrieve data for item
2441
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2442
+ * @return string Placeholder output
2443
+ */
2444
+ function process_placeholder_data($output, $item, $placeholder, $layout) {
2445
+ $attr_default = array (
2446
+ 'context' => '',
2447
+ );
2448
+ $opts = wp_parse_args($placeholder['attributes'], $attr_default);
2449
+ //Save context to separate variable
2450
+ $context = $opts['context'];
2451
+ unset($opts['context']);
2452
+ //Get data
2453
+ $out = $item->get_data($opts);
2454
+ if ( !is_null($out) ) {
2455
+ //Get specific member in value (e.g. value from a specific item element)
2456
+ if ( isset($opts['element']) && is_array($out) && ( $el = $opts['element'] ) && isset($out[$el]) )
2457
+ $out = $out[$el];
2458
+ }
2459
+
2460
+ //Format data based on context
2461
+ $out = $item->preserve_special_chars($out, $context);
2462
+ $out = $item->format($out, $context);
2463
+ //Return data
2464
+ return $out;
2465
+ }
2466
+
2467
+ /**
2468
+ * Set checked attribute on item
2469
+ * Evaluates item's data to see if item should be checked or not
2470
+ * @see SLB_Fields::process_placeholder_default for parameter descriptions
2471
+ * @return string Appropriate checkbox attribute
2472
+ */
2473
+ function process_placeholder_checked($output, $item, $placeholder, $layout, $data) {
2474
+ $out = '';
2475
+ $c = $item->get_container();
2476
+ $d = ( isset($c->data[$item->get_id()]) ) ? $c->data[$item->get_id()] : null;
2477
+ $item->set_property('d', true);
2478
+ if ( $item->get_data() )
2479
+ $out = 'checked="checked"';
2480
+ $item->set_property('d', false);
2481
+ return $out;
2482
+ }
2483
+
2484
+ /**
2485
+ * Loops over data to build item output
2486
+ * Options:
2487
+ * data - Dot-delimited path in item that contains data to loop through
2488
+ * layout - Name of layout to use for each data item in loop
2489
+ * layout_data - Name of layout to use for data item that matches previously-saved item data
2490
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2491
+ * @return string Placeholder output
2492
+ */
2493
+ function process_placeholder_loop($output, $item, $placeholder, $layout, $data) {
2494
+ //Setup loop options
2495
+ $attr_defaults = array (
2496
+ 'layout' => '',
2497
+ 'layout_data' => null,
2498
+ 'data' => ''
2499
+ );
2500
+ $attr = wp_parse_args($placeholder['attributes'], $attr_defaults);
2501
+ if ( is_null($attr['layout_data']) )
2502
+ $attr['layout_data'] =& $attr['layout'];
2503
+ //Get data for loop
2504
+ $path = explode('.', $attr['data']);
2505
+ $loop_data = $item->get_member_value($path);
2506
+
2507
+ //Check if data is callback
2508
+ if ( is_callable($loop_data) )
2509
+ $loop_data = call_user_func($loop_data);
2510
+
2511
+ //Get item data
2512
+ $data = $item->get_data();
2513
+
2514
+ //Iterate over data and build output
2515
+ $out = array();
2516
+ if ( is_array($loop_data) && !empty($loop_data) ) {
2517
+ foreach ( $loop_data as $value => $label ) {
2518
+ //Load appropriate layout based on item value
2519
+ $layout = ( ($data === 0 && $value === $data) xor $data == $value ) ? $attr['layout_data'] : $attr['layout'];
2520
+ //Stop processing if no valid layout is returned
2521
+ if ( empty($layout) )
2522
+ continue;
2523
+ //Prep extended item data
2524
+ $data_ext = array('option_value' => $value, 'option_text' => $label);
2525
+ $out[] = $item->build_layout($layout, $data_ext);
2526
+ }
2527
+ }
2528
+
2529
+ //Return output
2530
+ return implode($out);
2531
+ }
2532
+
2533
+ /**
2534
+ * Returns specified value from extended data array for item
2535
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2536
+ * @return string Placeholder output
2537
+ */
2538
+ function process_placeholder_data_ext($output, $item, $placeholder, $layout, $data) {
2539
+ if ( isset($placeholder['attributes']['id']) && ($key = $placeholder['attributes']['id']) && isset($data[$key]) ) {
2540
+ $output = strval($data[$key]);
2541
+ }
2542
+
2543
+ return $output;
2544
+ }
2545
+
2546
+ /* Build */
2547
+
2548
+ /**
2549
+ * Output items in a group
2550
+ * @param string $group ID of Group to output
2551
+ * @return string Group output
2552
+ * TODO Make compatible with parent::build_group()
2553
+ */
2554
+ function build_group($group) {
2555
+ $out = array();
2556
+ $classnames = (object) array(
2557
+ 'multi' => 'multi_field',
2558
+ 'single' => 'single_field',
2559
+ 'elements' => 'has_elements'
2560
+ );
2561
+
2562
+ //Stop execution if group does not exist
2563
+ if ( $this->group_exists($group) && $group =& $this->get_group($group) ) {
2564
+ $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 : '' );
2565
+ $classname = array($this->add_prefix('attributes_wrap'), $group_items);
2566
+ $out[] = '<div class="' . implode(' ', $classname) . '">'; //Wrap all items in group
2567
+
2568
+ //Build layout for each item in group
2569
+ foreach ( array_keys($group->items) as $item_id ) {
2570
+ $item =& $group->items[$item_id];
2571
+ $item->set_caller($this);
2572
+ //Start item output
2573
+ $id = $this->add_prefix('field_' . $item->get_id());
2574
+ $out[] = '<div id="' . $id . '_wrap" class=' . $this->add_prefix('attribute_wrap') . '>';
2575
+ //Build item layout
2576
+ $out[] = $item->build_layout();
2577
+ //end item output
2578
+ $out[] = '</div>';
2579
+ $item->clear_caller();
2580
+ }
2581
+ $out[] = '</div>'; //Close items container
2582
+ //Add description if exists
2583
+ if ( !empty($group->description) )
2584
+ $out[] = '<p class=' . $this->add_prefix('group_description') . '>' . $group->description . '</p>';
2585
+ }
2586
+
2587
+ //Return group output
2588
+ return implode($out);
2589
+ }
2590
+ }
includes/class.options.php ADDED
@@ -0,0 +1,673 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Option object
5
+ * @package Simple Lightbox
6
+ * @subpackage Options
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Option extends SLB_Field {
10
+
11
+ /* Properties */
12
+
13
+ public $hook_prefix = 'option';
14
+
15
+ /**
16
+ * Determines whether option will be sent to client
17
+ * @var bool
18
+ */
19
+ var $in_client = false;
20
+
21
+ /**
22
+ * Child mapping
23
+ * @see SLB_Field_Base::map
24
+ * @var array
25
+ */
26
+ var $map = array (
27
+ 'default' => 'data',
28
+ 'attr' => 'properties'
29
+ );
30
+
31
+ var $property_priority = array ('id', 'data', 'parent');
32
+
33
+ /* Init */
34
+
35
+ /**
36
+ * @see SLB_Field::__construct()
37
+ * @uses parent::__construct() to initialize instance
38
+ * @param $id
39
+ * @param $title
40
+ * @param $default
41
+ */
42
+ function __construct($id, $title = '', $default = '') {
43
+ //Normalize properties
44
+ $args = func_get_args();
45
+ $defaults = array ('title' => '', 'default' => '');
46
+ $props = $this->make_properties($args, $defaults);
47
+ //Validate
48
+ if ( is_scalar($id) )
49
+ $props['id'] = $id;
50
+ if ( !is_string($props['title']) )
51
+ $props['title'] = '';
52
+ //Send to parent constructor
53
+ parent::__construct($props);
54
+ }
55
+
56
+ /* Getters/Setters */
57
+
58
+ /**
59
+ * Retrieve default value for option
60
+ * @return mixed Default option value
61
+ */
62
+ function get_default($context = '') {
63
+ return $this->get_data($context, false);
64
+ }
65
+
66
+ /**
67
+ * Sets parent based on default value
68
+ */
69
+ function set_parent($parent = null) {
70
+ $p =& $this->get_parent();
71
+ if ( empty($parent) && empty($p) ) {
72
+ $parent = 'text';
73
+ $d = $this->get_default();
74
+ if ( is_bool($d) )
75
+ $parent = 'checkbox';
76
+ $parent = 'option_' . $parent;
77
+ } elseif ( !empty($p) && !is_object($p) ) {
78
+ $parent =& $p;
79
+ }
80
+ parent::set_parent($parent);
81
+ }
82
+
83
+ /**
84
+ * Set in_client property
85
+ * @uses this::in_client
86
+ * @param bool Whether or not option should be included in client output (Default: false)
87
+ * @return void
88
+ */
89
+ function set_in_client($in_client = false) {
90
+ $this->in_client = !!$in_client;
91
+ }
92
+
93
+ /**
94
+ * Determines whether option should be included in client output
95
+ * @uses this::in_client
96
+ * @return bool TRUE if option is included in client output
97
+ */
98
+ function get_in_client() {
99
+ return $this->in_client;
100
+ }
101
+
102
+ /* Formatting */
103
+
104
+ /**
105
+ * Format data as string for browser output
106
+ * @see SLB_Field_Base::format()
107
+ * @param mixed $value Data to format
108
+ * @param string $context (optional) Current context
109
+ * @return string Formatted value
110
+ */
111
+ function format_display($value, $context = '') {
112
+ if ( !is_string($value) ) {
113
+ if ( is_bool($value) ) {
114
+ $value = ( $value ) ? __('Enabled', 'simple-lightbox') : __('Disabled', 'simple-lightbox');
115
+ }
116
+ elseif ( is_null($value) )
117
+ $value = '';
118
+ else
119
+ $value = strval($value);
120
+ } elseif ( empty($value) ) {
121
+ $value = 'empty';
122
+ }
123
+ return htmlentities($value);
124
+ }
125
+
126
+ /**
127
+ * Format data using same format as default value
128
+ * @see SLB_Field_Base::format()
129
+ * @param mixed $value Data to format
130
+ * @param string $context (optional) Current context
131
+ * @return mixed Formatted option value
132
+ */
133
+ function format_default($value, $context = '') {
134
+ //Get default value
135
+ $d = $this->get_default();
136
+ if ( empty($d) )
137
+ return $value;
138
+ if ( is_bool($d) )
139
+ $value = $this->format_bool($value);
140
+ elseif ( is_string($d) )
141
+ $value = $this->format_string($value);
142
+ return $value;
143
+ }
144
+
145
+ /**
146
+ * Format data as boolean (true/false)
147
+ * @see SLB_Field_Base::format()
148
+ * @param mixed $value Data to format
149
+ * @param string $context (optional) Current context
150
+ * @return bool Option value
151
+ */
152
+ function format_bool($value, $context = '') {
153
+ if ( !is_bool($value) )
154
+ $value = !!$value;
155
+ return $value;
156
+ }
157
+
158
+ /**
159
+ * Format data as string
160
+ * @see SLB_Field_Base::format()
161
+ * @param mixed $value Data to format
162
+ * @param string $context (optional) Current context
163
+ * @return string Option string value
164
+ */
165
+ function format_string($value, $context = '') {
166
+ if ( is_bool($value) ) {
167
+ $value = ( $value ) ? 'true' : 'false';
168
+ }
169
+ elseif ( is_object($value) ) {
170
+ $value = get_class($value);
171
+ }
172
+ elseif ( is_array($value) ) {
173
+ $value = implode(' ', $value);
174
+ }
175
+ else {
176
+ $value = strval($value);
177
+ }
178
+ return $value;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Options collection
184
+ * @package Simple Lightbox
185
+ * @subpackage Options
186
+ * @author Archetyped
187
+ * @uses SLB_Field_Collection
188
+ */
189
+ class SLB_Options extends SLB_Field_Collection {
190
+
191
+ /* Properties */
192
+
193
+ public $hook_prefix = 'options';
194
+
195
+ var $item_type = 'SLB_Option';
196
+
197
+ /**
198
+ * Key for saving version to DB
199
+ * @var string
200
+ */
201
+ private $version_key = 'version';
202
+
203
+ /**
204
+ * Whether version has been checked
205
+ * @var bool
206
+ */
207
+ var $version_checked = false;
208
+
209
+ var $items_migrated = false;
210
+
211
+ var $build_vars = array (
212
+ 'validate_pre' => false,
213
+ 'validate_post' => false,
214
+ 'save_pre' => false,
215
+ 'save_post' => false
216
+ );
217
+
218
+ /* Init */
219
+
220
+ function __construct($id = '', $props = array()) {
221
+ //Validate arguments
222
+ $args = func_get_args();
223
+ //Set default ID
224
+ if ( !$this->validate_id($id) ) {
225
+ $id = 'options';
226
+ }
227
+ $defaults = $this->integrate_id($id);
228
+ $props = $this->make_properties($args, $defaults);
229
+ parent::__construct($props);
230
+ $this->add_prefix_ref($this->version_key);
231
+ }
232
+
233
+ protected function _hooks() {
234
+ parent::_hooks();
235
+ //Register fields
236
+ add_action($this->add_prefix('register_fields'), $this->m('register_fields'));
237
+ //Set option parents
238
+ add_action($this->add_prefix('fields_registered'), $this->m('set_parents'));
239
+ //Building
240
+ $this->util->add_action('build_init', $this->m('build_init'));
241
+ }
242
+
243
+ /* Legacy/Migration */
244
+
245
+ /**
246
+ * Checks whether new version has been installed and migrates necessary settings
247
+ * @uses $version_key as option name
248
+ * @uses get_option() to retrieve saved version number
249
+ * @uses SLB_Utilities::get_plugin_version() to retrieve current version
250
+ * @return bool TRUE if version has been changed
251
+ */
252
+ function check_update() {
253
+ if ( !$this->version_checked ) {
254
+ $this->version_checked = true;
255
+ $version_changed = false;
256
+ //Get version from DB
257
+ $vo = $this->get_version();
258
+ //Get current version
259
+ $vn = $this->util->get_plugin_version();
260
+ //Compare versions
261
+ if ( $vo != $vn ) {
262
+ //Update saved version
263
+ $this->set_version($vn);
264
+ //Migrate old version to new version
265
+ if ( strcasecmp($vo, $vn) < 0 ) {
266
+ //Force full migration
267
+ $version_changed = true;
268
+ }
269
+ }
270
+ //Migrate
271
+ $this->migrate($version_changed);
272
+ }
273
+
274
+ return $this->version_checked;
275
+ }
276
+
277
+ /**
278
+ * Save plugin version to DB
279
+ * If no version supplied, will fetch plugin data to determine version
280
+ * @uses $version_key as option name
281
+ * @uses update_option() to save version to options table
282
+ * @param string $ver (optional) Plugin version
283
+ */
284
+ function set_version($ver = null) {
285
+ if ( empty($ver) ) {
286
+ $ver = $this->util->get_plugin_version();
287
+ }
288
+ return update_option($this->version_key, $ver);
289
+ }
290
+
291
+ /**
292
+ * Retrieve saved version data
293
+ * @return string Saved version
294
+ */
295
+ function get_version() {
296
+ return get_option($this->version_key, '');
297
+ }
298
+
299
+ /**
300
+ * Migrate options from old versions to current version
301
+ * @uses self::items_migrated to determine if simple migration has been performed in current request or not
302
+ * @uses self::save() to save data after migration
303
+ * @param bool $full Whether to perform a full migration or not (Default: No)
304
+ */
305
+ function migrate($full = false) {
306
+ if ( !$full && $this->items_migrated )
307
+ return false;
308
+
309
+ //Legacy options
310
+ $d = null;
311
+ $this->load_data();
312
+
313
+ $items =& $this->get_items();
314
+
315
+ //Migrate separate options to unified option
316
+ if ( $full ) {
317
+ foreach ( $items as $opt => $props ) {
318
+ $oid = $this->add_prefix($opt);
319
+ $o = get_option($oid, $d);
320
+ if ( $o !== $d ) {
321
+ //Migrate value to data array
322
+ $this->set_data($opt, $o, false);
323
+ //Delete legacy option
324
+ delete_option($oid);
325
+ }
326
+ }
327
+ }
328
+
329
+ //Migrate legacy items
330
+ if ( is_array($this->properties_init) && isset($this->properties_init['legacy']) && is_array($this->properties_init['legacy']) ) {
331
+ $l =& $this->properties_init['legacy'];
332
+ //Normalize legacy map
333
+ foreach ( $l as $opt => $dest ) {
334
+ if ( !is_array($dest) ) {
335
+ if ( is_string($dest) )
336
+ $l[$opt] = array($dest);
337
+ else
338
+ unset($l[$opt]);
339
+ }
340
+ }
341
+
342
+ /* Separate options */
343
+ if ( $full ) {
344
+ foreach ( $l as $opt => $dest ) {
345
+ $oid = $this->add_prefix($opt);
346
+ $o = get_option($oid, $d);
347
+ //Only migrate valid values
348
+ if ( $o !== $d ) {
349
+ //Process destinations
350
+ foreach ( $dest as $id ) {
351
+ $this->set_data($id, $o, false, true);
352
+ }
353
+ }
354
+ //Remove legacy option
355
+ delete_option($oid);
356
+ }
357
+ }
358
+
359
+ /* Simple Migration (Internal options only) */
360
+
361
+ //Get existing items that are also legacy items
362
+ $opts = array_intersect_key($this->get_data(), $l);
363
+ foreach ( $opts as $opt => $val ) {
364
+ $d = $this->get_data($opt);
365
+ //Migrate data from old option to new option
366
+ $dest = $l[$opt];
367
+ //Validate new options to send data to
368
+ foreach ( $dest as $id ) {
369
+ $this->set_data($id, $d, false, true);
370
+ }
371
+ //Remove legacy option
372
+ $this->remove($opt, false);
373
+ }
374
+ }
375
+ //Save changes
376
+ $this->save();
377
+ //Set flag
378
+ $this->items_migrated = true;
379
+ }
380
+
381
+ /* Option setup */
382
+
383
+ /**
384
+ * Get elements for creating fields
385
+ * @return obj
386
+ */
387
+ function get_field_elements() {
388
+ static $o = null;
389
+ if ( empty($o) ) {
390
+ $o = new stdClass();
391
+ /* Layout */
392
+ $layout = new stdClass();
393
+ $layout->label = '<label for="{field_id}" class="title block">{label}</label>';
394
+ $layout->label_ref = '{label ref_base="layout"}';
395
+ $layout->field_pre = '<div class="input block">';
396
+ $layout->field_post = '</div>';
397
+ $layout->opt_pre = '<div class="' . $this->add_prefix('option_item') . '">';
398
+ $layout->opt_post = '</div>';
399
+ $layout->form = '<{form_attr ref_base="layout"} /> <span class="description">(' . __('Default', 'simple-lightbox') . ': {data context="display" top="0"})</span>';
400
+ /* Combine */
401
+ $o->layout =& $layout;
402
+ }
403
+ return $o;
404
+ }
405
+
406
+ /**
407
+ * Register option-specific fields
408
+ * @param SLB_Fields $fields Reference to global fields object
409
+ * @return void
410
+ */
411
+ function register_fields(&$fields) {
412
+ //Layouts
413
+ $o = $this->get_field_elements();
414
+
415
+ $form = $o->layout->opt_pre . $o->layout->label_ref . $o->layout->field_pre . $o->layout->form . $o->layout->field_post . $o->layout->opt_post;
416
+
417
+ //Text input
418
+ $otxt = new SLB_Field_Type('option_text', 'text');
419
+ $otxt->set_property('class', '{inherit} code');
420
+ $otxt->set_property('size', null);
421
+ $otxt->set_property('value', '{data context="form"}');
422
+ $otxt->set_layout('label', $o->layout->label);
423
+ $otxt->set_layout('form', $form);
424
+ $fields->add($otxt);
425
+
426
+ //Checkbox
427
+ $ocb = new SLB_Field_Type('option_checkbox', 'checkbox');
428
+ $ocb->set_layout('label', $o->layout->label);
429
+ $ocb->set_layout('form', $form);
430
+ $fields->add($ocb);
431
+
432
+ //Select
433
+ $othm = new SLB_Field_Type('option_select', 'select');
434
+ $othm->set_layout('label', $o->layout->label);
435
+ $othm->set_layout('form_start', $o->layout->field_pre . '{inherit}');
436
+ $othm->set_layout('form_end', '{inherit}' . $o->layout->field_post);
437
+ $othm->set_layout('form', $o->layout->opt_pre . '{inherit}' . $o->layout->opt_post);
438
+ $fields->add($othm);
439
+ }
440
+
441
+ /**
442
+ * Set parent field types for options
443
+ * Parent only set for Admin pages
444
+ * @uses SLB_Option::set_parent() to set parent field for each option item
445
+ * @uses is_admin() to determine if current request is admin page
446
+ * @param object $fields Collection of default field types
447
+ * @return void
448
+ */
449
+ function set_parents(&$fields) {
450
+ if ( !is_admin() )
451
+ return false;
452
+ $items =& $this->get_items();
453
+ foreach ( array_keys($items) as $opt ) {
454
+ $items[$opt]->set_parent();
455
+ }
456
+ foreach ( $this->items as $opt ) {
457
+ $p = $opt->parent;
458
+ if ( is_object($p) )
459
+ $p = 'o:' . $p->id;
460
+ }
461
+ }
462
+
463
+ /* Processing */
464
+
465
+ /**
466
+ * Validate option values
467
+ * Used for validating options (e.g. admin form submission) prior to saving options to DB
468
+ * Reformats values based on options' default values (i.e. bool, int, string, etc.)
469
+ * Adds option items not included in original submission
470
+ * @param array $values (optional) Option values
471
+ * @return array Full options data
472
+ */
473
+ function validate($values = null, $force_save = false) {
474
+ if ( empty($values) && isset($_REQUEST[$this->add_prefix('options')]) ) {
475
+ $values_orig = $values;
476
+ if ( is_string($values_orig) )
477
+ $force_save = true;
478
+ $values = $_REQUEST[$this->add_prefix('options')];
479
+ }
480
+ if ( is_array($values) ) {
481
+ //Format data based on option type (bool, string, etc.)
482
+ foreach ( $values as $id => $val ) {
483
+ //Get default
484
+ $d = $this->get_default($id);
485
+ if ( is_bool($d) && !empty($val) )
486
+ $values[$id] = true;
487
+ }
488
+ //Merge in additional options that are not in post data
489
+ //Missing options (e.g. disabled checkboxes) & defaults
490
+ $items =& $this->get_items();
491
+ foreach ( $items as $id => $opt ) {
492
+ //Add options that were not included in form submission
493
+ if ( !array_key_exists($id, $values) ) {
494
+ if ( is_bool($opt->get_default()) )
495
+ $values[$id] = false;
496
+ else
497
+ $values[$id] = $opt->get_default();
498
+ }
499
+ }
500
+ }
501
+
502
+ if ( $force_save ) {
503
+ $this->set_data($values);
504
+ $values = $values_orig;
505
+ }
506
+
507
+ //Return value
508
+ return $values;
509
+ }
510
+
511
+ /* Data */
512
+
513
+ /**
514
+ * Retrieve options from database
515
+ * @uses get_option to retrieve option data
516
+ * @return array Options data
517
+ */
518
+ function fetch_data($sanitize = true) {
519
+ //Get data
520
+ $data = get_option($this->get_key(), null);
521
+ if ( $sanitize && is_array($data) ) {
522
+ //Sanitize loaded data based on default values
523
+ foreach ( $data as $id => $val ) {
524
+ if ( $this->has($id) ) {
525
+ $opt = $this->get($id);
526
+ if ( is_bool($opt->get_default()) )
527
+ $data[$id] = !!$val;
528
+ }/* else {
529
+ //Remove data that has no matching item
530
+ unset($data[$id]);
531
+ }*/
532
+ }
533
+ }
534
+ return $data;
535
+ }
536
+
537
+ /**
538
+ * Retrieves option data for collection
539
+ * @see SLB_Field_Collection::load_data()
540
+ */
541
+ function load_data() {
542
+ if ( !$this->data_loaded ) {
543
+ //Retrieve data
544
+ $this->data = $this->fetch_data();
545
+ $this->data_loaded = true;
546
+ //Check update
547
+ $this->check_update();
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Resets option values to their default values
553
+ * @param bool $hard Reset all options if TRUE (default), Reset only unset options if FALSE
554
+ */
555
+ function reset($hard = true) {
556
+ $this->load_data();
557
+ //Reset data
558
+ if ( $hard ) {
559
+ $this->data = null;
560
+ }
561
+ //Save
562
+ $this->save();
563
+ }
564
+
565
+ /**
566
+ * Save options data to database
567
+ */
568
+ function save() {
569
+ $this->normalize_data();
570
+ update_option($this->get_key(), $this->data);
571
+ }
572
+
573
+ /**
574
+ * Normalize data
575
+ * Assures that data in collection match items
576
+ * @uses self::data to reset and save collection data after normalization
577
+ */
578
+ function normalize_data() {
579
+ $data = array();
580
+ foreach ( $this->get_items() as $id => $opt ) {
581
+ $data[$id] = $opt->get_data();
582
+ }
583
+ $this->data =& $data;
584
+ return $data;
585
+ }
586
+
587
+ /* Collection */
588
+
589
+ /**
590
+ * Build key for saving/retrieving data to options table
591
+ * @return string Key
592
+ */
593
+ function get_key() {
594
+ return $this->add_prefix($this->get_id());
595
+ }
596
+
597
+ /**
598
+ * Add option to collection
599
+ * @uses SLB_Field_Collection::add() to add item
600
+ * @param string $id Unique item ID
601
+ * @param array $properties Item properties
602
+ * @param bool $update (optional) Should item be updated or overwritten (Default: FALSE)
603
+ * @return SLB_Option Option instance
604
+ */
605
+ function &add($id, $properties = array(), $update = false) {
606
+ //Create item
607
+ $args = func_get_args();
608
+ $ret =& call_user_func_array(array('parent', 'add'), $args);
609
+ return $ret;
610
+ }
611
+
612
+ /**
613
+ * Retrieve option value
614
+ * @uses get_data() to retrieve option data
615
+ * @param string $option Option ID to retrieve value for
616
+ * @param string $context (optional) Context for formatting data
617
+ * @return mixed Option value
618
+ */
619
+ function get_value($option, $context = '') {
620
+ return $this->get_data($option, $context);
621
+ }
622
+
623
+ /**
624
+ * Retrieve option value as boolean (true/false)
625
+ * @uses get_data() to retrieve option data
626
+ * @param string $option Option ID to retrieve value for
627
+ * @return bool Option value
628
+ */
629
+ function get_bool($option) {
630
+ return $this->get_value($option, 'bool');
631
+ }
632
+
633
+ function get_string($option) {
634
+ return $this->get_value($option, 'string');
635
+ }
636
+
637
+ /**
638
+ * Retrieve option's default value
639
+ * @uses get_data() to retrieve option data
640
+ * @param string $option Option ID to retrieve value for
641
+ * @param string $context (optional) Context for formatting data
642
+ * @return mixed Option's default value
643
+ */
644
+ function get_default($option, $context = '') {
645
+ return $this->get_data($option, $context, false);
646
+ }
647
+
648
+ /* Output */
649
+
650
+ function build_init() {
651
+ if ( $this->build_vars['validate_pre'] ) {
652
+ $values = $this->validate();
653
+ if ( $this->build_vars['save_pre'] ) {
654
+ $this->set_data($values);
655
+ }
656
+ }
657
+ }
658
+
659
+ /**
660
+ * Build array of option values for client output
661
+ * @return array Associative array of options
662
+ */
663
+ function build_client_output() {
664
+ $items =& $this->get_items();
665
+ $out = array();
666
+ foreach ( $items as $option ) {
667
+ if ( !$option->get_in_client() )
668
+ continue;
669
+ $out[$option->get_id()] = $option->get_data('default');
670
+ }
671
+ return $out;
672
+ }
673
+ }
includes/class.template_tag.php ADDED
@@ -0,0 +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 { }
includes/class.template_tags.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
36
+ add_action('wp_footer', $this->m('client_output'), $this->util->priority('client_footer_output'));
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
+ $defaults = array (
63
+ 'item' => array (
64
+ 'client_script' => $this->util->get_file_url('template-tags/item/tag.item.js'),
65
+ ),
66
+ 'ui' => array (
67
+ 'client_script' => $this->util->get_file_url('template-tags/ui/tag.ui.js'),
68
+ ),
69
+ );
70
+ foreach ( $defaults as $id => $props ) {
71
+ $tags->add($id, $props);
72
+ }
73
+ }
74
+
75
+ /* Output */
76
+
77
+ /**
78
+ * Client output
79
+ */
80
+ public function client_output() {
81
+ //Stop if not enabled
82
+ if ( !$this->has_parent() || !$this->get_parent()->is_enabled() ) {
83
+ return;
84
+ }
85
+ $out = array();
86
+ $out[] = '<!-- SLB-TPTG -->' . PHP_EOL;
87
+ $code = array();
88
+ //Load matched handlers
89
+ foreach ( $this->get() as $id => $tag ) {
90
+ //Define
91
+ $params = array(
92
+ sprintf("'%s'", $id),
93
+ sprintf("'%s'", $tag->get_client_script('uri')),
94
+ );
95
+ $code[] = $this->util->call_client_method('View.add_template_tag_handler', $params, false);
96
+ }
97
+ $out[] = $this->util->build_script_element(implode('', $code), 'add_template_tags', true, true);
98
+ $out[] = '<!-- /SLB-TPTG -->' . PHP_EOL;
99
+ echo implode('', $out);
100
+ }
101
+ }
includes/class.theme.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ /* Get/Set */
15
+
16
+ /**
17
+ * Retrieve theme's ancestors
18
+ * @return array Theme's ancestors (sorted by nearest to most distant ancestor)
19
+ */
20
+ public function get_ancestors() {
21
+ $ret = array();
22
+ /**
23
+ * @var SLB_Theme
24
+ */
25
+ $thm = $this;
26
+ while ( $thm->has_parent() ) {
27
+ $par = $thm->get_parent();
28
+ //Add ancestor
29
+ if ( $par->is_valid() && !in_array($par, $ret, true) ) {
30
+ $ret[] = $par;
31
+ }
32
+ //Get next ancestor
33
+ $thm = $par;
34
+ }
35
+ return $ret;
36
+ }
37
+
38
+ /* Style */
39
+
40
+ /**
41
+ * Set Theme style path
42
+ * @see `add_style()`
43
+ */
44
+ public function set_client_style($src, $deps = array()) {
45
+ if ( is_array($src) ) {
46
+ list($src, $deps) = func_get_arg(0);
47
+ }
48
+ return $this->add_style('client', $src, $deps);
49
+ }
50
+
51
+ /**
52
+ * Get Theme style path
53
+ * @see `get_style()`
54
+ */
55
+ public function get_client_style($format = null) {
56
+ return $this->get_style('client', $format);
57
+ }
58
+
59
+ /* Templates */
60
+
61
+ /**
62
+ * Add template file
63
+ * @see `add_file()`
64
+ * @param string $handle Template handle
65
+ * @param string $src Template URI
66
+ * @return obj Current instance
67
+ */
68
+ protected function add_template($handle, $src) {
69
+ return $this->add_file('template', $handle, $src);
70
+ }
71
+
72
+ /**
73
+ * Retrieve template file
74
+ * @see `get_file()`
75
+ * @param string $handle Template handle
76
+ * @param string $format (optional) Return value format
77
+ * @return mixed Template file (Default: array of file properties @see `Base_Object::add_file()`)
78
+ */
79
+ protected function get_template($handle, $format = null) {
80
+ return $this->get_file('template', $handle, $format);
81
+ }
82
+
83
+ /* Layout */
84
+
85
+ /**
86
+ * Set theme layout
87
+ * @uses `add_template()`
88
+ * @param string $src Layout file URI
89
+ * @return Current instance
90
+ */
91
+ public function set_layout($src) {
92
+ return $this->add_template('layout', $src);
93
+ }
94
+
95
+ /**
96
+ * Get layout
97
+ * @param string $format (optional) Layout data format
98
+ * @return mixed Theme layout
99
+ */
100
+ public function get_layout($format = null) {
101
+ return $this->get_template('layout', $format);
102
+ }
103
+ }
includes/class.themes.php ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Themes Collection
5
+ * @package Simple Lightbox
6
+ * @subpackage Themes
7
+ * @author Archetyped
8
+ */
9
+ class SLB_Themes extends SLB_Collection_Controller {
10
+ /* Configuration */
11
+
12
+ protected $item_type = 'SLB_Theme';
13
+
14
+ public $hook_prefix = 'themes';
15
+
16
+ protected $key_prop = 'get_id';
17
+
18
+ protected $key_call = true;
19
+
20
+ /* Properties */
21
+
22
+ protected $id_default = null;
23
+
24
+ /* Initialization */
25
+
26
+ protected function _hooks() {
27
+ parent::_hooks();
28
+ //Register themes
29
+ $this->util->add_action('init', $this->m('init_defaults'), 1);
30
+
31
+ //Client output
32
+ add_action('wp_footer', $this->m('client_output'), $this->util->priority('client_footer_output'));
33
+ }
34
+
35
+ protected function _options() {
36
+ $opts = array (
37
+ 'items' => array (
38
+ 'theme_default' => array (
39
+ 'title' => __('Theme', 'simple-lightbox'),
40
+ 'default' => $this->get_default_id(),
41
+ 'group' => array('ui', 0),
42
+ 'parent' => 'option_select',
43
+ 'options' => $this->m('opt_get_field_values'),
44
+ 'in_client' => true
45
+ ),
46
+ )
47
+ );
48
+
49
+ parent::_options($opts);
50
+ }
51
+
52
+ /**
53
+ * Add default themes
54
+ * @param SLB_Themes $themes Themes controller
55
+ */
56
+ function init_defaults($themes) {
57
+ $defaults = array (
58
+ $this->get_default_id() => array (
59
+ 'name' => __('Default (Light)', 'simple-lightbox'),
60
+ 'layout' => $this->util->get_file_url('themes/default/layout.html'),
61
+ 'client_script' => $this->util->get_file_url('themes/default/client.js'),
62
+ 'client_style' => $this->util->get_file_url('themes/default/css/style.css'),
63
+ ),
64
+ $this->add_prefix('black') => array (
65
+ 'name' => __('Default (Dark)', 'simple-lightbox'),
66
+ 'parent' => $this->get_default_id(),
67
+ 'client_style' => $this->util->get_file_url('themes/black/css/style.css'),
68
+ ),
69
+ );
70
+
71
+ foreach ( $defaults as $id => $props ) {
72
+ $themes->add($id, $props);
73
+ }
74
+ }
75
+
76
+ /* Collection management */
77
+
78
+ /**
79
+ * Add theme
80
+ * Accepts properties to create new theme or previously-created theme instance
81
+ * @uses parent::add()
82
+ * @param string|object $id Theme ID (or Theme object)
83
+ * @param array $props Theme properties
84
+ * @return object Current instance
85
+ */
86
+ public function add($id, $props = array()) {
87
+ //Prepare parent
88
+ if ( isset($props['parent']) && !($props['parent'] instanceof $this->item_type) ) {
89
+ $pid = $props['parent'];
90
+ $items = $this->get();
91
+ if ( isset($items[$pid]) ) {
92
+ $props['parent'] = $items[$pid];
93
+ }
94
+ }
95
+ $o = ( is_string($id) ) ? new $this->item_type($id, $props) : $id;
96
+ //Add to collection
97
+ return parent::add($o);
98
+ }
99
+
100
+ /* Helpers */
101
+
102
+ /**
103
+ * Retrieve default theme ID
104
+ * @uses `$id_default`
105
+ * @return string Default theme ID
106
+ */
107
+ public function get_default_id() {
108
+ if ( !$this->id_default ) {
109
+ $this->id_default = $this->add_prefix('default');
110
+ }
111
+ return $this->id_default;
112
+ }
113
+
114
+ /**
115
+ * Retrieve currently-selected theme
116
+ * @return SLB_Theme Selected theme
117
+ */
118
+ protected function get_selected() {
119
+ //Get themes
120
+ $thms = $this->get();
121
+ //Retrieve currently-selected theme
122
+ $id = $this->options->get_value('theme_default');
123
+ if ( !isset($thms[$id]) ) {
124
+ $id = $this->get_default_id();
125
+ }
126
+ return $thms[$id];
127
+ }
128
+
129
+ /* Output */
130
+
131
+ /**
132
+ * Client output
133
+ */
134
+ public function client_output() {
135
+ //Stop if not enabled
136
+ if ( !$this->has_parent() || !$this->get_parent()->is_enabled() ) {
137
+ return;
138
+ }
139
+
140
+ //Theme
141
+ /**
142
+ * @var SLB_Theme
143
+ */
144
+ $thm = $this->get_selected();
145
+
146
+ //Process theme ancestors
147
+ $thms = array_reverse($thm->get_ancestors());
148
+ $thms[] = $thm;
149
+
150
+ $id_fmt = 'add_theme_%s';
151
+ $out = array();
152
+ $out[] = '<!-- SLB-THM -->' . PHP_EOL;
153
+ $code = array();
154
+
155
+ //Build output for each theme
156
+ foreach ( $thms as $thm ) {
157
+ //Setup client parameters
158
+ $params = array(
159
+ sprintf("'%s'", $thm->get_id()),
160
+ );
161
+ //Theme properties
162
+ $thm_props = array(
163
+ 'name' => $thm->get_name(),
164
+ 'parent' => ( $thm->has_parent() ) ? $thm->get_parent()->get_id() : '',
165
+ );
166
+ /* Optional properties */
167
+ //Layout
168
+ $uri = $thm->get_layout('uri');
169
+ if ( !empty($uri) ) {
170
+ $thm_props['layout_uri'] = $uri;
171
+ }
172
+ //Script
173
+ $script = $thm->get_client_script('uri');
174
+ if ( !empty($script) ) {
175
+ $thm_props['client_script'] = $script;
176
+ }
177
+ //Style
178
+ $style = $thm->get_client_style('uri');
179
+ if ( !empty($style) ) {
180
+ $thm_props['client_style'] = $style;
181
+ }
182
+ //Add properties to parameters
183
+ $params[] = json_encode($thm_props);
184
+
185
+ //Add theme to client
186
+ $code[] = $this->util->call_client_method('View.add_theme', $params, false);
187
+ }
188
+
189
+ $out[] = $this->util->build_script_element(implode('', $code), 'add_themes', true, true);
190
+ $out[] = '<!-- /SLB-THM -->' . PHP_EOL;
191
+ echo implode('', $out);
192
+ }
193
+
194
+ /* Options */
195
+
196
+ /**
197
+ * Retrieve themes for use in option field
198
+ * @uses self::theme_default
199
+ * @return array Theme options
200
+ */
201
+ public function opt_get_field_values() {
202
+ //Get themes
203
+ $items = $this->get();
204
+ $d = $this->get_default_id();
205
+ //Pop out default theme
206
+ if ( isset($items[$d]) ) {
207
+ $itm_d = $items[$d];
208
+ unset($items[$d]);
209
+ }
210
+
211
+ //Sort themes by name
212
+ uasort($items, create_function('$a,$b', 'return strcmp($a->get_name(), $b->get_name());'));
213
+
214
+ //Insert default theme at top of array
215
+ if ( isset($itm_d) ) {
216
+ $items = array( $d => $itm_d ) + $items;
217
+ }
218
+
219
+ //Build options
220
+ foreach ( $items as $item ) {
221
+ $items[$item->get_id()] = $item->get_name();
222
+ }
223
+ return $items;
224
+ }
225
+ }
includes/class.utilities.php CHANGED
@@ -10,12 +10,47 @@
10
  */
11
  class SLB_Utilities {
12
 
13
- function SLB_Utilities() {
14
- $this->__construct();
15
- }
16
 
17
- function __construct() {
18
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
 
21
  /**
@@ -24,15 +59,492 @@ class SLB_Utilities {
24
  * @param string $method Name of method
25
  * @return array Callback array
26
  */
27
- function &m(&$obj, $method = '') {
28
- if ( $obj == null && isset($this) )
29
- $obj =& $this;
30
- $arr = array(&$obj, $method);
31
- return $arr;
 
 
 
 
 
32
  }
33
 
34
  /* Helper Functions */
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  /*-** WP **-*/
37
 
38
  /**
@@ -59,14 +571,220 @@ class SLB_Utilities {
59
  return true;
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  /*-** Request **-*/
63
 
64
  /**
65
- * Checks $_SERVER['SCRIPT_NAME'] to see if file base name matches specified file name
66
  * @param string $filename Filename to check for
67
  * @return bool TRUE if current page matches specified filename, FALSE otherwise
68
  */
69
- function is_file( $filename ) {
70
  return ( $filename == basename( $_SERVER['SCRIPT_NAME'] ) );
71
  }
72
 
@@ -76,112 +794,304 @@ class SLB_Utilities {
76
  */
77
  function is_admin_management_page() {
78
  return ( is_admin()
79
- && ( $this->is_file('edit.php')
80
- || ( $this->is_file('admin.php')
81
  && isset($_GET['page'])
82
  && strpos($_GET['page'], 'cnr') === 0 )
83
  )
84
  );
85
  }
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  /**
88
  * Joins and normalizes the slashes in the paths passed to method
89
  * All forward/back slashes are converted to forward slashes
90
  * Multiple path segments can be passed as additional argments
91
  * @param string $path Path to normalize
92
- * @param bool $trailing_slash (optional) Whether or not normalized path should have a trailing slash or not (Default: FALSE)
93
- * If multiple path segments are passed, $trailing_slash will be the LAST parameter (default value used if omitted)
 
94
  */
95
  function normalize_path($path, $trailing_slash = false) {
96
  $sl_f = '/';
97
  $sl_b = '\\';
98
  $parts = func_get_args();
 
 
99
  if ( func_num_args() > 1 ) {
100
- if ( is_bool(($tr = $parts[count($parts) - 1])) ) {
101
- $trailing_slash = $tr;
102
- //Remove from args array
 
 
 
 
 
103
  array_pop($parts);
104
- } else {
105
- $trailing_slash = false;
 
 
 
106
  }
107
- $first = true;
108
- //Trim trailing slashes from path parts
109
- foreach ( $parts as $key => $part ) {
110
- $part = trim($part);
111
- //Special Trim
112
- $parts[$key] = trim($part, $sl_f . $sl_b);
113
- //Verify path still contains value
114
- if ( empty($parts[$key]) ) {
115
- unset($parts[$key]);
116
- continue;
117
- }
118
- //Only continue processing the first valid path segment
119
- if ( $first )
120
- $first = !$first;
121
- else
122
- continue;
123
- //Add back leading slash if necessary
124
- if ( $part[0] == $sl_f || $part[0] == $sl_b )
125
- $parts[$key] = $sl_f . $parts[$key];
126
-
127
  }
128
  }
 
129
  //Join path parts together
130
  $parts = implode($sl_b, $parts);
131
  $parts = str_replace($sl_b, $sl_f, $parts);
132
  //Add trailing slash (if necessary)
133
  if ( $trailing_slash )
134
- $parts . $sl_f;
 
 
 
 
 
135
  return $parts;
136
  }
137
 
138
  /**
139
  * Returns URL of file (assumes that it is in plugin directory)
140
  * @param string $file name of file get URL
141
- * @return string File path
142
  */
143
  function get_file_url($file) {
144
- if (is_string($file) && '' != trim($file)) {
145
- $file = $this->normalize_path($this->get_url_base(), $file);
146
  }
147
  return $file;
148
  }
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  /**
151
  * Retrieves file extension
152
  * @param string $file file name/path
 
153
  * @return string File's extension
154
  */
155
- function get_file_extension($file) {
156
  $ret = '';
157
  $sep = '.';
158
- if ( is_string($icon) && ( $rpos = strrpos($file, $sep) ) !== false )
 
 
159
  $ret = substr($file, $rpos + 1);
 
 
160
  return $ret;
161
  }
162
 
163
  /**
164
  * Checks if file has specified extension
 
165
  * @param string $file File name/path
166
- * @param string $extension File ending to check $file for
 
167
  * @return bool TRUE if file has extension
168
  */
169
- function has_file_extension($file, $extension) {
170
- return ( $this->get_file_extension($file) == $extension ) ? true : false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  }
172
 
173
  /**
174
  * Retrieve base URL for plugin-specific files
 
 
175
  * @return string Base URL
176
  */
177
  function get_url_base() {
178
  static $url_base = '';
179
  if ( '' == $url_base ) {
180
- $url_base = $this->normalize_path(WP_PLUGIN_URL, $this->get_plugin_base());
181
  }
182
  return $url_base;
183
  }
184
 
 
 
 
 
 
 
 
185
  function get_path_base() {
186
  static $path_base = '';
187
  if ( '' == $path_base ) {
@@ -189,23 +1099,140 @@ class SLB_Utilities {
189
  }
190
  return $path_base;
191
  }
192
-
193
- function get_plugin_base() {
 
 
 
 
 
 
194
  static $plugin_dir = '';
195
  if ( '' == $plugin_dir ) {
196
  $plugin_dir = str_replace($this->normalize_path(WP_PLUGIN_DIR), '', $this->normalize_path(dirname(dirname(__FILE__))));
197
  }
 
 
198
  return $plugin_dir;
199
  }
200
 
 
 
 
 
 
 
201
  function get_plugin_base_file() {
202
- $file = 'main.php';
203
- return $this->get_path_base() . '/' . $file;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  }
205
 
 
 
 
 
 
 
 
206
  function get_plugin_base_name() {
207
- $file = $this->get_plugin_base_file();
208
- return plugin_basename($file);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  }
210
 
211
  /**
@@ -246,6 +1273,22 @@ class SLB_Utilities {
246
 
247
  /*-** General **-*/
248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  /**
250
  * Checks if a property exists in a class or object
251
  * (Compatibility method for PHP 4
@@ -284,6 +1327,60 @@ class SLB_Utilities {
284
  }
285
  }
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  /**
288
  * Merges 1 or more arrays together
289
  * Methodology
@@ -295,7 +1392,6 @@ class SLB_Utilities {
295
  * - Merge item in base array with current item based on key name
296
  * - If the current item's value AND the corresponding item in the base array are BOTH arrays, recursively merge the the arrays
297
  * - If the current item's value OR the corresponding item in the base array is NOT an array, current item overwrites base item
298
- * @todo Append numerical elements (as opposed to overwriting element at same index in base array)
299
  * @param array Variable number of arrays
300
  * @param array $arr1 Default array
301
  * @return array Merged array
@@ -303,24 +1399,33 @@ class SLB_Utilities {
303
  function array_merge_recursive_distinct($arr1) {
304
  //Get all arrays passed to function
305
  $args = func_get_args();
306
- if (empty($args))
307
  return false;
 
 
 
308
  //Set first array as base array
309
  $merged = $args[0];
310
  //Iterate through arrays to merge
311
  $arg_length = count($args);
312
- for ($x = 1; $x < $arg_length; $x++) {
313
  //Skip if argument is not an array (only merge arrays)
314
- if (!is_array($args[$x]))
315
  continue;
316
  //Iterate through argument items
317
- foreach ($args[$x] as $key => $val) {
318
- if (!isset($merged[$key]) || !is_array($merged[$key]) || !is_array($val)) {
319
- $merged[$key] = $val;
320
- } elseif (is_array($merged[$key]) && is_array($val)) {
321
- $merged[$key] = $this->array_merge_recursive_distinct($merged[$key], $val);
322
- }
323
- //$merged[$key] = (is_array($val) && isset($merged[$key])) ? $this->array_merge_recursive_distinct($merged[$key], $val) : $val;
 
 
 
 
 
 
324
  }
325
  }
326
  return $merged;
@@ -375,6 +1480,13 @@ class SLB_Utilities {
375
  return $item;
376
  }
377
 
 
 
 
 
 
 
 
378
  function get_array_path($attribute = '', $format = null) {
379
  //Formatted value
380
  $fmtd = '';
@@ -440,6 +1552,34 @@ class SLB_Utilities {
440
  return $path;
441
  }
442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  /**
444
  * Builds attribute string for HTML element
445
  * @param array $attr Attributes
@@ -460,6 +1600,11 @@ class SLB_Utilities {
460
  return $ret;
461
  }
462
 
 
 
 
 
 
463
  /**
464
  * Generate external stylesheet element
465
  * @param $url Stylesheet URL
@@ -470,6 +1615,45 @@ class SLB_Utilities {
470
  return $this->build_html_element(array('tag' => 'link', 'wrap' => false, 'attributes' => $attributes));
471
  }
472
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  /**
474
  * Generate external script element
475
  * @param $url Script URL
@@ -477,7 +1661,7 @@ class SLB_Utilities {
477
  */
478
  function build_ext_script_element($url = '') {
479
  $attributes = array('src' => $url, 'type' => 'text/javascript');
480
- return $this->build_html_element(array('tag' => 'script', 'attributes' => $attributes));
481
  }
482
 
483
  /**
@@ -495,9 +1679,20 @@ class SLB_Utilities {
495
  $el_start = '<';
496
  $el_end = '>';
497
  $el_close = '/';
498
- extract(wp_parse_args($args, $defaults), EXTR_SKIP);
 
 
 
 
 
 
 
 
 
 
499
  $content = trim($content);
500
 
 
501
  if ( !$wrap && strlen($content) > 0 )
502
  $wrap = true;
503
 
@@ -542,23 +1737,24 @@ class SLB_Utilities {
542
  //Get last submenu added
543
  $parent = $this->get_submenu_parent_file($parent);
544
  if ( isset($submenu[$parent]) ) {
545
- $subs =& $submenu[$parent];
546
- //Make sure menu isn't already in the desired position
547
- if ( $pos <= ( count($subs) - 1 ) ) {
548
- //Get submenu that was just added
549
- $sub = array_pop($subs);
550
- //Insert into desired position
551
- if ( 0 == $pos ) {
552
- array_unshift($subs, $sub);
553
- } else {
554
- $top = array_slice($subs, 0, $pos);
555
- $bottom = array_slice($subs, $pos);
556
- array_push($top, $sub);
557
- $subs = array_merge($top, $bottom);
558
- }
559
  }
560
  }
561
  }
 
562
 
563
  return $hookname;
564
  }
@@ -638,4 +1834,70 @@ class SLB_Utilities {
638
  $parent = $_wp_real_parent_file[$parent];
639
  return $parent;
640
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
  }
10
  */
11
  class SLB_Utilities {
12
 
13
+ /* Properties */
 
 
14
 
15
+ /**
16
+ * Instance parent
17
+ * @var object
18
+ */
19
+ var $parent = null;
20
+
21
+ /**
22
+ * Default plugin headers
23
+ * @var array
24
+ */
25
+ private $plugin_headers = array (
26
+ 'Name' => 'Plugin Name',
27
+ 'PluginURI' => 'Plugin URI',
28
+ 'Version' => 'Version',
29
+ 'Description' => 'Description',
30
+ 'Author' => 'Author',
31
+ 'AuthorURI' => 'Author URI',
32
+ 'TextDomain' => 'Text Domain',
33
+ 'DomainPath' => 'Domain Path',
34
+ 'Network' => 'Network',
35
+ );
36
+
37
+
38
+ /**
39
+ * Standard hook priorities
40
+ * @var array
41
+ */
42
+ private $priorities = array (
43
+ 'high' => 1,
44
+ 'low' => 99,
45
+ 'safe' => 15,
46
+ 'client_footer_output' => 25,
47
+ );
48
+
49
+ /* Constructors */
50
+
51
+ function __construct(&$obj) {
52
+ if ( is_object($obj) )
53
+ $this->parent =& $obj;
54
  }
55
 
56
  /**
59
  * @param string $method Name of method
60
  * @return array Callback array
61
  */
62
+ function m($obj, $method = '') {
63
+ if ( is_string($obj) ) {
64
+ $method = $obj;
65
+ $obj = null;
66
+ }
67
+ if ( !is_object($obj) && isset($this) ) {
68
+ $obj = $this;
69
+ }
70
+ $cb = array($obj, $method);
71
+ return $cb;
72
  }
73
 
74
  /* Helper Functions */
75
 
76
+ /*-** Prefix **-*/
77
+
78
+ /**
79
+ * Get valid separator
80
+ * @param string $sep (optional) Separator supplied
81
+ * @return string Separator
82
+ */
83
+ function get_sep($sep = false) {
84
+ if ( is_null($sep) )
85
+ $sep = '';
86
+ return ( is_string($sep) ) ? $sep : '_';
87
+ }
88
+
89
+ /**
90
+ * Retrieve class prefix (with separator if set)
91
+ * @param bool|string $sep Separator to append to class prefix (Default: no separator)
92
+ * @return string Class prefix
93
+ */
94
+ function get_prefix($sep = null) {
95
+ $sep = $this->get_sep($sep);
96
+ $prefix = ( !empty($this->parent->prefix) ) ? $this->parent->prefix . $sep : '';
97
+ return $prefix;
98
+ }
99
+
100
+ /**
101
+ * Check if a string is prefixed
102
+ * @param string $text Text to check for prefix
103
+ * @param string $sep (optional) Separator used
104
+ */
105
+ function has_prefix($text, $sep = null) {
106
+ return ( !empty($text) && stripos($text, $this->get_prefix($sep)) === 0 );
107
+ }
108
+
109
+ /**
110
+ * Prepend plugin prefix to some text
111
+ * @param string $text Text to add to prefix
112
+ * @param string $sep (optional) Text used to separate prefix and text
113
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
114
+ * @return string Text with prefix prepended
115
+ */
116
+ function add_prefix($text, $sep = '_', $once = true) {
117
+ if ( $once && $this->has_prefix($text, $sep) )
118
+ return $text;
119
+ return $this->get_prefix($sep) . $text;
120
+ }
121
+
122
+ /**
123
+ * Prepend uppercased plugin prefix to some text
124
+ * @param string $text Text to add to prefix
125
+ * @param string $sep (optional) Text used to separate prefix and text
126
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
127
+ * @return string Text with prefix prepended
128
+ */
129
+ function add_prefix_uc($text, $sep = '_', $once = true) {
130
+ $args = func_get_args();
131
+ $var = call_user_func_array($this->m($this, 'add_prefix'), $args);
132
+ $pre = $this->get_prefix();
133
+ return str_replace($pre . $sep, strtoupper($pre) . $sep, $var);
134
+ }
135
+
136
+ /**
137
+ * Add prefix to variable reference
138
+ * Updates actual variable rather than return value
139
+ * @uses add_prefix() to add prefix to variable
140
+ * @param string $var Variable to add prefix to
141
+ * @param string $sep (optional) Separator text
142
+ * @param bool $once (optional) Add prefix only once
143
+ * @return void
144
+ */
145
+ function add_prefix_ref(&$var, $sep = null, $once = true) {
146
+ $args = func_get_args();
147
+ $var = call_user_func_array($this->m($this, 'add_prefix'), $args);
148
+ }
149
+
150
+ /**
151
+ * Remove prefix from specified string
152
+ * @param string $text String to remove prefix from
153
+ * @param string $sep (optional) Separator used with prefix
154
+ */
155
+ function remove_prefix($text, $sep = '_') {
156
+ if ( $this->has_prefix($text,$sep) )
157
+ $text = substr($text, strlen($this->get_prefix($sep)));
158
+ return $text;
159
+ }
160
+
161
+ /**
162
+ * Returns Database prefix for plugin-related DB Tables
163
+ * @return string Database prefix
164
+ */
165
+ function get_db_prefix() {
166
+ global $wpdb;
167
+ return $wpdb->prefix . $this->get_prefix('_');
168
+ }
169
+
170
+ /*-** Priority **-*/
171
+
172
+ /**
173
+ * Retrieve standard priority
174
+ * @var string $id Priority ID to retrieve
175
+ * @return int Priority
176
+ */
177
+ public function priority($id = null) {
178
+ $pri = 10;
179
+ if ( !is_null($id) && array_key_exists($id, $this->priorities) ) {
180
+ $pri = $this->priorities[$id];
181
+ }
182
+ return $pri;
183
+ }
184
+
185
+ /* Wrapped Values */
186
+
187
+ /**
188
+ * Returns validated object of start/end wrapper values
189
+ * @param string|array $start Start text (Can also be array defining start & end values)
190
+ * @param string $end (optional) End text
191
+ * If $end not defined, then $start is used
192
+ * @return obj Wrapper
193
+ */
194
+ function get_wrapper($start = null, $end = null) {
195
+ //Return pre-built wrapper
196
+ if ( is_object($start) && isset($start->start) && isset($start->end) )
197
+ return $start;
198
+ //Default wrapper
199
+ if ( is_null($start) && is_null($end) )
200
+ $start = array('[', ']');
201
+ $wrapper = compact('start', 'end');
202
+ if ( is_array($start) && count($start) > 1 ) {
203
+ $wrapper['start'] = $start[0];
204
+ $wrapper['end'] = $start[1];
205
+ }
206
+ if ( !is_string($wrapper['start']) || empty($wrapper['start'] ) )
207
+ $wrapper['start'] = '';
208
+ if ( !is_string($wrapper['end']) || empty($wrapper['end']) )
209
+ $wrapper['end'] = $wrapper['start'];
210
+
211
+ return (object) $wrapper;
212
+ }
213
+
214
+ /**
215
+ * Check if text is wrapped by specified character(s)
216
+ * @uses this->get_wrapper() to Validate wrapper text
217
+ * @param string $text Text to check
218
+ * @param string|array $start (optional) Start text (Array defines both start/end text)
219
+ * @param string $end (optional) End text
220
+ */
221
+ function has_wrapper($text, $start = null, $end = null) {
222
+ if ( !is_string($text) || empty($text) )
223
+ return false;
224
+ //Validate wrapper)
225
+ $w = $this->get_wrapper($start, $end);
226
+
227
+ //Check for wrapper
228
+ return ( substr($text, 0, 1) == $w->start && substr($text, -1, 1) == $w->end ) ? true : false;
229
+ }
230
+
231
+ /**
232
+ * Remove wrapper from specified text
233
+ * @uses this->has_wrapper() to check if text is wrapped
234
+ * @uses this->get_wrapper() to retrieve wrapper object
235
+ * @param string $text Text to check
236
+ * @param string|array $start (optional) Start text (Array defines both start/end text)
237
+ * @param string $end (optional) End text
238
+ * @return string Unwrapped text
239
+ */
240
+ function remove_wrapper($text, $start = null, $end = null) {
241
+ if ( $this->has_wrapper($text, $start, $end) ) {
242
+ $w = $this->get_wrapper($start, $end);
243
+ $text = substr($text, strlen($w->start), strlen($text) - strlen($w->start) - strlen($w->end) );
244
+ }
245
+
246
+ return $text;
247
+ }
248
+
249
+ /**
250
+ * Add wrapper to specified text
251
+ * @uses this->get_wrapper() to retrieve wrapper object
252
+ * @param string $text Text to wrap
253
+ * @param string|array $start (optional) Start text (Array defines both start/end text)
254
+ * @param string $end (optional) End text
255
+ * @param bool $once (optional) Whether to wrap text only once (Default: TRUE)
256
+ * @return string Wrapped text
257
+ */
258
+ function add_wrapper($text, $start = null, $end = null, $once = true) {
259
+ $w = $this->get_wrapper($start, $end);
260
+ if ( !$once || !$this->has_wrapper($text, $w) )
261
+ $text = $w->start . $text . $w->end;
262
+ return $text;
263
+ }
264
+
265
+ /*-** Client **-*/
266
+
267
+ /**
268
+ * Parses client files array
269
+ * > Adds ID property (prefixed file key)
270
+ * > Parses and validates internal dependencies
271
+ * > Converts properties array to object
272
+ * Properties
273
+ * > file (string|array): File name (string) or callback (array) to retrieve file name
274
+ * > deps (array) [optional]: Dependencies
275
+ * > Values wrapped in square brackets (`[` & `]`) are internal files
276
+ * > callback (string|array) [optional]: Global callback to determine whether file should be loaded
277
+ * > Values wrapped in square brackets (`[` & `]`) are internal methods (of parent object)
278
+ * > context (array) [optional]: Context(s) in which to load the file
279
+ * Acceptable values
280
+ * > string: Context name
281
+ * > array: Context name + callback (both must return TRUE to load file)
282
+ * > Callback follows same pattern as `callback` member
283
+ * @param array $files Files array
284
+ * @return object Client files
285
+ */
286
+ function parse_client_files($files, $type = 'scripts') {
287
+ if ( is_array($files) && !empty($files) ) {
288
+ //Defaults
289
+ $defaults = array(
290
+ 'file' => null,
291
+ 'deps' => array(),
292
+ 'callback' => null,
293
+ 'context' => array(),
294
+ 'enqueue' => true,
295
+ );
296
+ switch ( $type ) {
297
+ case 'styles':
298
+ $defaults['media'] = 'all';
299
+ break;
300
+ default:
301
+ $defaults['in_footer'] = false;
302
+ }
303
+ //Iterate through files
304
+ foreach ( $files as $h => $p ) {
305
+ unset($file, $cb, $ctxs, $ctx);
306
+ //Set ID
307
+ $p['id'] = $this->add_prefix($h);
308
+ //Type Validation
309
+ foreach ( $defaults as $m => $d ) {
310
+ //Check if value requires validation
311
+ if ( !is_array($d) || !isset($p[$m]) || is_array($p[$m]) )
312
+ continue;
313
+ //Wrap value in array or destroy it
314
+ if ( is_scalar($p[$m]) )
315
+ $p[$m] = array($p[$m]);
316
+ else
317
+ unset($p[$m]);
318
+ }
319
+
320
+ $p = array_merge($defaults, $p);
321
+
322
+ /* File name */
323
+
324
+ //Validate file
325
+ $file =& $p['file'];
326
+
327
+ //Determine if filename or callback
328
+ if ( !$this->is_file($file) )
329
+ $file = $this->parse_client_file_callback($file);
330
+ //Remove invalid file and move on to next
331
+ if ( empty($file) ) {
332
+ unset($files[$h]);
333
+ continue;
334
+ }
335
+
336
+ /* Dependencies */
337
+
338
+ //Format internal dependencies
339
+ foreach ( $p['deps'] as $idx => $dep ) {
340
+ if ( $this->has_wrapper($dep) ) {
341
+ $dep = $this->remove_wrapper($dep);
342
+ $p['deps'][$idx] = $this->add_prefix($dep);
343
+ }
344
+ }
345
+
346
+ /* Context */
347
+
348
+ //Validate callback
349
+ $cb =& $p['callback'];
350
+ if ( !is_null($cb) ) {
351
+ $cb = $this->parse_client_file_callback($cb);
352
+ //Remove files with invalid callbacks (will never be loaded)
353
+ if ( is_null($cb) ) {
354
+ unset($files[$h]);
355
+ continue;
356
+ }
357
+ }
358
+
359
+ //Validate contexts
360
+ $ctxs =& $p['context'];
361
+ $ctxs = array_unique($ctxs);
362
+ $has_contexts = ( count($ctxs) > 0 ) ? true : false;
363
+ foreach ( $ctxs as $idx => $ctx ) {
364
+ //Convert to array
365
+ $ctx = array_values( array_slice( (array) $ctx, 0, 2 ) );
366
+ switch ( count($ctx) ) {
367
+ case 1 :
368
+ //Simple context
369
+ $ctx = $ctx[0];
370
+ break;
371
+ case 2 :
372
+ //Context + Callback
373
+ $ctx[1] = $this->parse_client_file_callback($ctx[1]);
374
+ if ( !is_null($ctx[1]) ) {
375
+ break;
376
+ }
377
+ //Continue to default case if callback is invalid
378
+ default :
379
+ //Context is invalid
380
+ $ctx = false;
381
+ break;
382
+ }
383
+
384
+ //Remove invalid contexts
385
+ if ( empty($ctx) ) {
386
+ unset($ctxs[$idx]);
387
+ } else {
388
+ $ctxs[$idx] = $ctx;
389
+ }
390
+ }
391
+ //Remove file if all specified contexts invalid (no context is OK)
392
+ if ( $has_contexts && empty($ctxs) ) {
393
+ unset($files[$h]);
394
+ continue;
395
+ }
396
+ $ctxs = array_values($ctxs);
397
+
398
+ /* Finalize Properties */
399
+
400
+ //Convert properties to object
401
+ $files[$h] = (object) $p;
402
+ }
403
+ }
404
+ //Cast to object before returning
405
+ $files = (object) $files;
406
+ return $files;
407
+ }
408
+
409
+ /**
410
+ * Parses callbacks set for client files
411
+ * @param string $callback Callback value
412
+ * > Values wrapped in square brackets (`[` & `]`) are internal methods (of parent object)
413
+ * @return callback|null Validated callback (NULL if callback is invalid)
414
+ */
415
+ function parse_client_file_callback($callback) {
416
+ if ( $this->has_wrapper($callback) ) {
417
+ $callback = $this->m($this->parent, $this->remove_wrapper($callback));
418
+ }
419
+ if ( !is_callable($callback) )
420
+ $callback = null;
421
+ return $callback;
422
+ }
423
+
424
+ /**
425
+ * Build JS client object
426
+ * @param string (optional) $path Additional object path
427
+ * @return string Client object
428
+ */
429
+ function get_client_object($path = null) {
430
+ $obj = strtoupper($this->get_prefix());
431
+ if ( !empty($path) && is_string($path) ) {
432
+ if ( 0 !== strpos($path, '[') )
433
+ $obj .= '.';
434
+ $obj .= $path;
435
+ }
436
+ return $obj;
437
+ }
438
+
439
+ /**
440
+ * Build jQuery JS expression to add data to specified client object
441
+ * @param string|obj $obj Name of client object (Set to root object if not a valid name)
442
+ * @param mixed $data Data to add to client object
443
+ * @param bool (optional) $out Whether or not to output code (Default: false)
444
+ * @return string JS expression to extend client object
445
+ */
446
+ function extend_client_object($obj, $data = null, $out = false) {
447
+ //Validate parameters
448
+ $args = func_get_args();
449
+ switch ( count($args) ) {
450
+ case 2:
451
+ if ( !is_scalar($args[0]) ) {
452
+ if ( is_bool($args[1]) )
453
+ $out = $args[1];
454
+ } else {
455
+ break;
456
+ }
457
+ case 1:
458
+ $data = $args[0];
459
+ $obj = null;
460
+ break;
461
+ }
462
+ //Default client object
463
+ if ( !is_string($obj) || empty($obj) )
464
+ $obj = null;
465
+ //Default data
466
+ if ( is_array($data) ) {
467
+ $data = (object)$data;
468
+ }
469
+ //Build expression
470
+ if ( empty($data) || ( empty($obj) && is_scalar($data) ) ) {
471
+ $ret = '';
472
+ } else {
473
+ $c_obj = $this->get_client_object($obj);
474
+ $ret = $this->validate_client_object($obj, sprintf('{$.extend(%1$s, %2$s);}', $c_obj, json_encode($data)) );
475
+ if ( $out )
476
+ echo $this->build_script_element($ret, 'context', true, true);
477
+ }
478
+ return $ret;
479
+ }
480
+
481
+ /**
482
+ * Validate client object $obj before running command $cmd
483
+ *
484
+ * @param string $obj Full object name
485
+ * @param string $cmd (optional) Command to wrap in validation
486
+ * @return string Command wrapped in validation block
487
+ * If no command is specified the validation conditions are returned
488
+ */
489
+ public function validate_client_object($obj, $cmd = null) {
490
+ //Build condition
491
+ $sep = '.';
492
+ $obj = trim( $this->get_client_object($obj) , $sep);
493
+ $offset = 0;
494
+ $len = strlen($obj);
495
+ $pos = $len;
496
+ $fmt = '(typeof %s != \'undefined\')';
497
+ $objs = array();
498
+ //Add segments to array (in reverse)
499
+ do {
500
+ $objs[] = sprintf($fmt, substr($obj, 0, $pos));
501
+ $offset = $pos - $len - 1;
502
+ } while ( $offset < $len && ( $pos = strrpos($obj, $sep, $offset) ) && $pos !== false );
503
+ //Format condition
504
+ $condition = implode(' && ', array_reverse($objs));
505
+
506
+ //Wrap command in validation
507
+ if ( is_string($cmd) && !empty($cmd) ) {
508
+ $condition = sprintf('if ( %1$s ) { %2$s }', $condition, $cmd);
509
+ }
510
+ return $condition;
511
+ }
512
+
513
+ /**
514
+ * Build client method call
515
+ * @uses get_client_object() to generate the body of the method call
516
+ * @param string $method Method name
517
+ * @param array|string $params (optional) Parameters to pass to method
518
+ * @param bool $encode (optional) JSON-encode parameters? (Default: TRUE)
519
+ * @param bool $validate (optional) Validate method before calling it?
520
+ * @return string Method call
521
+ */
522
+ function call_client_method($method, $params = null, $encode = true, $validate = true) {
523
+ $ret = '';
524
+ if ( !is_string($method) || empty($method) ) {
525
+ return $ret;
526
+ }
527
+ $encode = !!$encode;
528
+ $validate = !!$validate;
529
+
530
+ //Build parameters
531
+ if ( !is_null($params) ) {
532
+ if ( $encode ) {
533
+ $params = json_encode($params);
534
+ } elseif ( is_array($params) ) {
535
+ $params = implode(',', $params);
536
+ }
537
+ }
538
+ if ( !is_string($params) ) {
539
+ $params = '';
540
+ }
541
+ $ret = sprintf('%s(%s);', $this->get_client_object($method), $params);
542
+ if ( $validate ) {
543
+ $ret = $this->validate_client_object($method, $ret);
544
+ }
545
+ return $ret;
546
+ }
547
+
548
  /*-** WP **-*/
549
 
550
  /**
571
  return true;
572
  }
573
 
574
+ /* Hooks */
575
+
576
+ /**
577
+ * Retrieve parent object
578
+ * @return obj|bool Parent object (FALSE if no valid parent set)
579
+ */
580
+ function &get_parent() {
581
+ if ( is_object($this->parent) )
582
+ return $this->parent;
583
+ else
584
+ return false;
585
+ }
586
+
587
+ /**
588
+ * Retrieve parent property value
589
+ * @uses self::get_parent()
590
+ * @param string $prop Property name
591
+ * @param mixed $default Default value
592
+ * @return mixed Parent property value
593
+ */
594
+ function get_parent_property($prop, $default = '') {
595
+ $p =& $this->get_parent();
596
+ return ( !!$p && property_exists($p, $prop) ) ? $p->{$prop} : $default;
597
+ }
598
+
599
+ /**
600
+ * Retrieve formatted name for internal hooks
601
+ * Prefixes with parent prefix and hook prefix
602
+ * @uses self::get_parent_property() to retrieve hook prefix
603
+ * @uses self::add_prefix()
604
+ * @param string $tag Base tag
605
+ * @return string Formatted hook
606
+ */
607
+ function get_hook($tag) {
608
+ //Hook prefix
609
+ $hook = $this->get_parent_property('hook_prefix', '');
610
+ if ( !empty($hook) )
611
+ $hook .= '_';
612
+ //Prefix
613
+ return $this->add_prefix($hook . $tag);
614
+ }
615
+
616
+ /**
617
+ * Run internal action
618
+ * Namespaces $tag
619
+ * @uses self::get_hook()
620
+ * @see do_action()
621
+ */
622
+ function do_action($tag, $arg = '') {
623
+ $args = func_get_args();
624
+ $args[0] = $this->get_hook($tag);
625
+ return call_user_func_array('do_action', $args);
626
+ }
627
+
628
+ /**
629
+ * Run internal action passing arguments in array
630
+ * @uses do_action_ref_array()
631
+ */
632
+ function do_action_ref_array($tag, $args) {
633
+ return do_action_ref_array($this->get_hook($tag), $args);
634
+ }
635
+
636
+ /**
637
+ * Run internal filter
638
+ * Namespaces $tag
639
+ * @uses self::get_hook()
640
+ * @see apply_filters()
641
+ */
642
+ function apply_filters($tag, $value) {
643
+ $args = func_get_args();
644
+ $args[0] = $this->get_hook($tag);
645
+ return call_user_func_array('apply_filters', $args);
646
+ }
647
+
648
+ /**
649
+ * Run internal filter passing arguments in array
650
+ * @uses apply_filters_ref_array()
651
+ */
652
+ function apply_filters_ref_array($tag, $args) {
653
+ return apply_filters_ref_array($this->get_hook($tag), $args);
654
+ }
655
+
656
+ /**
657
+ * Add internal action
658
+ * Namespaces $tag
659
+ * @uses self::get_hook()
660
+ * @see add_action()
661
+ */
662
+ function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
663
+ return add_action($this->get_hook($tag), $function_to_add, $priority, $accepted_args);
664
+ }
665
+
666
+ /**
667
+ * Add internal filter
668
+ * Namespaces $tag
669
+ * @uses self::get_hook()
670
+ * @see add_filter()
671
+ */
672
+ function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
673
+ return add_filter($this->get_hook($tag), $function_to_add, $priority, $accepted_args);
674
+ }
675
+
676
+ /**
677
+ * Remove internal action
678
+ * Namespaces $tag
679
+ * @uses self::get_hook()
680
+ * @uses remove_action()
681
+ */
682
+ function remove_action($tag, $function_to_remove, $priority = 10, $accepted_args = 1) {
683
+ return remove_action($this->get_hook($tag), $function_to_remove, $priority, $accepted_args);
684
+ }
685
+
686
+ /**
687
+ * Remove internal filter
688
+ * Namespaces $tag
689
+ * @uses self::get_hook()
690
+ * @uses remove_filter()
691
+ */
692
+ function remove_filter($tag, $function_to_remove, $priority = 10, $accepted_args = 1) {
693
+ return remove_filter($this->get_hook($tag), $function_to_remove, $priority, $accepted_args);
694
+ }
695
+
696
+ /* Meta */
697
+
698
+ /**
699
+ * Retrieves post metadata for internal methods
700
+ * Metadata set internally is wrapped in an array so it is unwrapped before returned the retrieved value
701
+ * @see get_post_meta()
702
+ * @param int $post_id Post ID
703
+ * @param string $key Name of metadata to retrieve
704
+ * @param boolean $single Whether or not to retrieve single value or not
705
+ * @return mixed Retrieved post metadata
706
+ */
707
+ function post_meta_get($post_id, $key, $single = false) {
708
+ $meta_value = get_post_meta($post_id, $this->post_meta_get_key($key), $single);
709
+ if (is_array($meta_value) && count($meta_value) == 1)
710
+ $meta_value = $meta_value[0];
711
+ return $meta_value;
712
+ }
713
+
714
+ /**
715
+ * Wraps metadata in array for storage in database
716
+ * @param mixed $meta_value Value to be set as metadata
717
+ * @return array Wrapped metadata value
718
+ */
719
+ function post_meta_prepare_value($meta_value) {
720
+ return array($meta_value);
721
+ }
722
+
723
+ /**
724
+ * Adds Metadata for a post to database
725
+ * For internal methods
726
+ * @see add_post_meta
727
+ * @param $post_id
728
+ * @param $meta_key
729
+ * @param $meta_value
730
+ * @param $unique
731
+ * @return boolean Result of operation
732
+ */
733
+ function post_meta_add($post_id, $meta_key, $meta_value, $unique = false) {
734
+ $meta_value = $this->post_meta_value_prepare($meta_value);
735
+ return add_post_meta($post_id, $meta_key, $meta_value, $unique);
736
+ }
737
+
738
+ /**
739
+ * Updates post metadata for internal data/methods
740
+ * @see update_post_meta()
741
+ * @param $post_id
742
+ * @param $meta_key
743
+ * @param $meta_value
744
+ * @param $prev_value
745
+ * @return boolean Result of operation
746
+ */
747
+ function post_meta_update($post_id, $meta_key, $meta_value, $prev_value = '') {
748
+ $meta_value = $this->post_meta_prepare_value($meta_value);
749
+ return update_post_meta($post_id, $meta_key, $meta_value, $prev_value);
750
+ }
751
+
752
+ /**
753
+ * Builds postmeta key for custom data set by plugin
754
+ * @param string $key Base key name
755
+ * @return string Formatted postmeta key
756
+ */
757
+ function post_meta_get_key($key) {
758
+ $sep = '_';
759
+ if ( strpos($key, $sep . $this->prefix) !== 0 ) {
760
+ $key_base = func_get_args();
761
+ if ( !empty($key_base) ) {
762
+ $key = array_merge((array)$this->prefix, $key_base);
763
+ return $sep . implode($sep, $key);
764
+ }
765
+ }
766
+
767
+ return $key;
768
+ }
769
+
770
+ /**
771
+ * Creates a meta key for storing post meta data
772
+ * Prefixes standard prefixed text with underscore to hide meta data on post edit forms
773
+ * @param string $text Text to use as base of meta key
774
+ * @return string Formatted meta key
775
+ */
776
+ function make_meta_key($text = '') {
777
+ return '_' . $this->add_prefix($text);
778
+ }
779
+
780
  /*-** Request **-*/
781
 
782
  /**
783
+ * Checks if the currently executing file matches specified file name
784
  * @param string $filename Filename to check for
785
  * @return bool TRUE if current page matches specified filename, FALSE otherwise
786
  */
787
+ function is_current_file( $filename ) {
788
  return ( $filename == basename( $_SERVER['SCRIPT_NAME'] ) );
789
  }
790
 
794
  */
795
  function is_admin_management_page() {
796
  return ( is_admin()
797
+ && ( $this->is_current_file('edit.php')
798
+ || ( $this->is_current_file('admin.php')
799
  && isset($_GET['page'])
800
  && strpos($_GET['page'], 'cnr') === 0 )
801
  )
802
  );
803
  }
804
 
805
+ /* Class */
806
+
807
+ function is_a($obj, $class_name) {
808
+ return ( is_object($obj) && is_a($obj, $this->add_prefix_uc($class_name)) ) ? true : false;
809
+ }
810
+
811
+ /**
812
+ * Retrieve name of internal class
813
+ * @param string $class Base name of class
814
+ * @return string Full name of internal class
815
+ */
816
+ function get_class($class) {
817
+ return $this->add_prefix_uc($class);
818
+ }
819
+
820
+ /* Context */
821
+
822
+ /**
823
+ * Retrieve context for current request
824
+ * @return array Context
825
+ */
826
+ function get_context() {
827
+ //Context
828
+ static $ctx = null;
829
+ if ( !is_array($ctx) ) {
830
+ //Standard
831
+ $ctx = array($this->build_context());
832
+ //Action
833
+ $action = $this->get_action();
834
+ if ( !empty($action) ) {
835
+ $ctx[] = $this->build_context('action', $action);
836
+ }
837
+ //Post type
838
+ $post_type = $this->get_post_type();
839
+ if ( !empty($action) ) {
840
+ $ctx[] = $this->build_context('post-type', $post_type);
841
+ }
842
+ //Admin page
843
+ if ( is_admin() ) {
844
+ global $pagenow;
845
+ $pg = $this->strip_file_extension($pagenow);
846
+ $ctx[] = $this->build_context('page', $pg);
847
+ //Query String
848
+ parse_str($_SERVER['QUERY_STRING'], $qv);
849
+ if ( isset($qv['page']) ) {
850
+ $ctx[] = $this->build_context('page', $qv['page']);
851
+ }
852
+ //Action
853
+ if ( !empty($action) ) {
854
+ $ctx[] = $this->build_context('page', $pg, 'action', $action);
855
+ $ctx[] = $this->build_context('post-type', $post_type, 'action', $action);
856
+ }
857
+ }
858
+ //User
859
+ $u = wp_get_current_user();
860
+ $ctx[] = $this->build_context('user', ( $u->ID ) ? 'registered' : 'guest', false);
861
+ }
862
+
863
+ return $ctx;
864
+ }
865
+
866
+ /**
867
+ * Builds context from multiple components
868
+ * Usage:
869
+ * > $prefix can be omitted and context strings can be added as needed
870
+ * > Multiple context strings may be passed to be joined together
871
+ *
872
+ * @param string (optional) $context Variable number of components to add to context
873
+ * @param bool (optional) $prefix Whether or not to prefix context with request type (public or admin) [Default: TRUE]
874
+ * @return string Context
875
+ */
876
+ function build_context($context = null, $prefix = true) {
877
+ $args = func_get_args();
878
+ //Get prefix option
879
+ if ( !empty($args) ) {
880
+ $prefix = ( is_bool($args[count($args) - 1]) ) ? array_pop($args) : true;
881
+ }
882
+
883
+ //Validate
884
+ $context = array_filter($args, 'is_string');
885
+ $sep = '_';
886
+
887
+ //Context Prefix
888
+ if ( $prefix )
889
+ array_unshift($context, ( is_admin() ) ? 'admin' : 'public' );
890
+ return implode($sep, $context);
891
+ }
892
+
893
+ /**
894
+ * Check if context exists in current request
895
+ * @param string $context Context to check for
896
+ * @return bool TRUE if context exists FALSE otherwise
897
+ */
898
+ function is_context($context) {
899
+ $ret = false;
900
+ if ( is_scalar($context) )
901
+ $context = array($context);
902
+ if ( is_array($context) && !empty($context) ) {
903
+ $ictx = array_intersect($this->get_context(), $context);
904
+ if ( !empty($ictx) )
905
+ $ret = true;
906
+ }
907
+ return $ret;
908
+ }
909
+
910
+ /**
911
+ * Output current context to client-side
912
+ * @uses `wp_head` action hook
913
+ * @uses `admin_head` action hook
914
+ * @return void
915
+ */
916
+ function set_client_context() {
917
+ $ctx = new stdClass();
918
+ $ctx->context = $this->get_context();
919
+ $this->extend_client_object($ctx, true);
920
+ }
921
+
922
  /**
923
  * Joins and normalizes the slashes in the paths passed to method
924
  * All forward/back slashes are converted to forward slashes
925
  * Multiple path segments can be passed as additional argments
926
  * @param string $path Path to normalize
927
+ * @param bool|array $trailing_slash (optional) Whether or not normalized path should have a trailing slash or not (Default: FALSE)
928
+ * If array is passed, first index is trailing, second is leading slash
929
+ * If multiple path segments are passed, $trailing_slash will be the LAST parameter (default value used if omitted)
930
  */
931
  function normalize_path($path, $trailing_slash = false) {
932
  $sl_f = '/';
933
  $sl_b = '\\';
934
  $parts = func_get_args();
935
+ //Slash defaults (trailing, leading);
936
+ $slashes = array(false, true);
937
  if ( func_num_args() > 1 ) {
938
+ //Get last argument
939
+ $arg_last = $parts[count($parts) - 1];
940
+ if ( is_bool($arg_last) ) {
941
+ $arg_last = array($arg_last);
942
+ }
943
+
944
+ if ( is_array($arg_last) && count($arg_last) > 0 && is_bool($arg_last[0]) ) {
945
+ //Remove slash paramter from args array
946
  array_pop($parts);
947
+ //Normalize slashes options
948
+ if ( isset($arg_last[0]) )
949
+ $slashes[0] = $arg_last[0];
950
+ if ( isset($arg_last[1]) )
951
+ $slashes[1] = $arg_last[1];
952
  }
953
+ }
954
+ //Extract to slash options local variables
955
+ list($trailing_slash, $leading_slash) = $slashes;
956
+
957
+ //Clean path segments
958
+ foreach ( $parts as $key => $part ) {
959
+ //Trim slashes/spaces
960
+ $parts[$key] = trim($part, " " . $sl_f . $sl_b);
961
+
962
+ //Verify path segment still contains value
963
+ if ( empty($parts[$key]) ) {
964
+ unset($parts[$key]);
965
+ continue;
 
 
 
 
 
 
 
966
  }
967
  }
968
+
969
  //Join path parts together
970
  $parts = implode($sl_b, $parts);
971
  $parts = str_replace($sl_b, $sl_f, $parts);
972
  //Add trailing slash (if necessary)
973
  if ( $trailing_slash )
974
+ $parts .= $sl_f;
975
+ //Add leading slash (if necessary)
976
+ $regex = '#^.+:[\\/]#';
977
+ if ( $leading_slash && !preg_match($regex, $parts) ) {
978
+ $parts = $sl_f . $parts;
979
+ }
980
  return $parts;
981
  }
982
 
983
  /**
984
  * Returns URL of file (assumes that it is in plugin directory)
985
  * @param string $file name of file get URL
986
+ * @return string File URL
987
  */
988
  function get_file_url($file) {
989
+ if ( is_string($file) && '' != trim($file) ) {
990
+ $file = str_replace(' ', '%20', $this->normalize_path($this->get_url_base(), $file));
991
  }
992
  return $file;
993
  }
994
 
995
+ /**
996
+ * Returns path to plugin file
997
+ * @param string $file file name
998
+ * @return string File path
999
+ */
1000
+ function get_file_path($file) {
1001
+ if ( is_string($file) && '' != trim($file) ) {
1002
+ $file = $this->normalize_path($this->get_path_base(), $file);
1003
+ }
1004
+ return $file;
1005
+ }
1006
+
1007
+ function get_plugin_file_path($file, $trailing_slash = false) {
1008
+ if ( is_string($file) && '' != trim($file) )
1009
+ $file = $this->normalize_path($this->get_plugin_base(), $file, $trailing_slash);
1010
+ return $file;
1011
+ }
1012
+
1013
+ /**
1014
+ * Checks if value is valid file name
1015
+ * @param string $filename File name to check
1016
+ * @return bool TRUE if valid file name, FALSE otherwise
1017
+ */
1018
+ function is_file($filename) {
1019
+ $ext = $this->get_file_extension($filename);
1020
+ return ( empty($ext) ) ? false : true;
1021
+ }
1022
+
1023
  /**
1024
  * Retrieves file extension
1025
  * @param string $file file name/path
1026
+ * @param bool (optional) $lowercase Whether lowercase extension should be returned (Default: TRUE)
1027
  * @return string File's extension
1028
  */
1029
+ function get_file_extension($file, $lowercase = true) {
1030
  $ret = '';
1031
  $sep = '.';
1032
+ if ( !is_string($file) )
1033
+ return $ret;
1034
+ if ( ( $rpos = strrpos($file, $sep) ) > 0 )
1035
  $ret = substr($file, $rpos + 1);
1036
+ if ( !!$lowercase )
1037
+ $ret = strtolower($ret);
1038
  return $ret;
1039
  }
1040
 
1041
  /**
1042
  * Checks if file has specified extension
1043
+ * @uses get_file_extension()
1044
  * @param string $file File name/path
1045
+ * @param string|array $extension File ending(s) to check $file for
1046
+ * @param bool (optional) Whether check should be case senstive or not (Default: FALSE)
1047
  * @return bool TRUE if file has extension
1048
  */
1049
+ function has_file_extension($file, $extension, $case_sensitive = false) {
1050
+ if ( !is_array($extension) )
1051
+ $extension = array(strval($extension));
1052
+ if ( !$case_sensitive ) {
1053
+ //Normalize extensions
1054
+ $extension = array_map('strtolower', $extension);
1055
+ }
1056
+ return ( in_array($this->get_file_extension($file, !$case_sensitive), $extension) ) ? true : false;
1057
+ }
1058
+
1059
+ /**
1060
+ * Removes file extension from file name
1061
+ * The extension is the text following the last period ('.') in the file name
1062
+ * @uses get_file_extension()
1063
+ * @param string $file File name
1064
+ * @return string File name without extension
1065
+ */
1066
+ function strip_file_extension($file) {
1067
+ $ext = $this->get_file_extension($file);
1068
+ if ( !empty($ext) ) {
1069
+ $file = substr($file, 0, (strlen($ext) + 1) * -1);
1070
+ }
1071
+ return $file;
1072
  }
1073
 
1074
  /**
1075
  * Retrieve base URL for plugin-specific files
1076
+ * @uses get_plugin_base()
1077
+ * @uses normalize_path()
1078
  * @return string Base URL
1079
  */
1080
  function get_url_base() {
1081
  static $url_base = '';
1082
  if ( '' == $url_base ) {
1083
+ $url_base = $this->normalize_path(plugins_url(), $this->get_plugin_base());
1084
  }
1085
  return $url_base;
1086
  }
1087
 
1088
+ /**
1089
+ * Retrieve plugin's base path
1090
+ * @uses WP_PLUGIN_DIR
1091
+ * @uses get_plugin_base()
1092
+ * @uses normalize_path()
1093
+ * @return string Base path
1094
+ */
1095
  function get_path_base() {
1096
  static $path_base = '';
1097
  if ( '' == $path_base ) {
1099
  }
1100
  return $path_base;
1101
  }
1102
+
1103
+ /**
1104
+ * Retrieve plugin's base directory
1105
+ * @uses WP_PLUGIN_DIR
1106
+ * @uses normalize_path()
1107
+ * @return string Base directory
1108
+ */
1109
+ function get_plugin_base($trim = false) {
1110
  static $plugin_dir = '';
1111
  if ( '' == $plugin_dir ) {
1112
  $plugin_dir = str_replace($this->normalize_path(WP_PLUGIN_DIR), '', $this->normalize_path(dirname(dirname(__FILE__))));
1113
  }
1114
+ if ( $trim )
1115
+ $plugin_dir = trim($plugin_dir, ' \/');
1116
  return $plugin_dir;
1117
  }
1118
 
1119
+ /**
1120
+ * Retrieve plugin's base file path
1121
+ * @uses get_path_base()
1122
+ * @uses get_file_path()
1123
+ * @return string Base file path
1124
+ */
1125
  function get_plugin_base_file() {
1126
+ static $file = '';
1127
+ if ( empty($file) ) {
1128
+ $dir = @ opendir($this->get_path_base());
1129
+ if ( $dir ) {
1130
+ while ( ($ftemp = readdir($dir)) !== false ) {
1131
+ //Only process PHP files
1132
+ $ftemp = $this->get_file_path($ftemp);
1133
+ if ( !$this->has_file_extension($ftemp, 'php') || !is_readable($ftemp) )
1134
+ continue;
1135
+ //Check for data
1136
+ $data = get_file_data($ftemp, $this->plugin_headers);
1137
+ if ( !empty($data['Name']) ) {
1138
+ //Set base file
1139
+ $file = $ftemp;
1140
+ break;
1141
+ }
1142
+ }
1143
+ }
1144
+ @closedir($dir);
1145
+ }
1146
+ //Return
1147
+ return $file;
1148
  }
1149
 
1150
+ /**
1151
+ * Retrieve plugin's internal name
1152
+ * Internal name is used by WP core
1153
+ * @uses get_plugin_base_file()
1154
+ * @uses plugin_basename()
1155
+ * @return string Internal plugin name
1156
+ */
1157
  function get_plugin_base_name() {
1158
+ static $name = false;
1159
+ if ( !$name ) {
1160
+ $file = $this->get_plugin_base_file();
1161
+ $name = plugin_basename($file);
1162
+ }
1163
+ return $name;
1164
+ }
1165
+
1166
+ /**
1167
+ * Retrieve plugin info
1168
+ * Parses info comment in main plugin file
1169
+ * @uses get_plugin_base_file()
1170
+ */
1171
+ function get_plugin_info($field = '') {
1172
+ static $data = array();
1173
+ $ret = '';
1174
+ //Get plugin data
1175
+ if ( empty($data) ) {
1176
+ $file = $this->get_plugin_base_file();
1177
+ $data = get_file_data($file, $this->plugin_headers);
1178
+ }
1179
+ //Return specified field
1180
+ if ( !empty($field) ) {
1181
+ if ( isset($data[$field]) )
1182
+ $ret = $data[$field];
1183
+ } else {
1184
+ $ret = $data;
1185
+ }
1186
+ return $ret;
1187
+ }
1188
+
1189
+ /**
1190
+ * Retrieve plugin version
1191
+ * @uses get_plugin_info()
1192
+ * @param bool $strip_desc Strip any additional version text
1193
+ * @return string Plugin version
1194
+ */
1195
+ function get_plugin_version($strip_desc = true) {
1196
+ static $v = '';
1197
+ //Retrieve version
1198
+ if ( empty($v) ) {
1199
+ $field = 'Version';
1200
+ $v = $this->get_plugin_info($field);
1201
+ }
1202
+ //Format
1203
+ $ret = $v;
1204
+ if ( $strip_desc ) {
1205
+ $ret = explode(' ', $ret, 2);
1206
+ $ret = $ret[0];
1207
+ }
1208
+ //Return
1209
+ return $ret;
1210
+ }
1211
+
1212
+ /**
1213
+ * Retrieve plugin textdomain (for localization)
1214
+ * @return string
1215
+ */
1216
+ function get_plugin_textdomain() {
1217
+ static $dom = '';
1218
+ if ( empty($dom) )
1219
+ $dom = $this->get_plugin_base(true);
1220
+ return $dom;
1221
+ }
1222
+
1223
+ /**
1224
+ * Retrieve current post type based on URL query variables
1225
+ * @return string|null Current post type
1226
+ */
1227
+ public function get_post_type() {
1228
+ if ( isset($_GET['post_type']) && !empty($_GET['post_type']) ) {
1229
+ return $_GET['post_type'];
1230
+ }
1231
+ $pt = null;
1232
+ if ( isset($_GET['post']) && is_numeric($_GET['post']) ) {
1233
+ $pt = get_post_type($_GET['post']);
1234
+ }
1235
+ return $pt;
1236
  }
1237
 
1238
  /**
1273
 
1274
  /*-** General **-*/
1275
 
1276
+ /**
1277
+ * Checks if last parameter sent to a function is an array of options and returns it
1278
+ * Calling function should use `func_get_args()` and pass the value to this method
1279
+ * @param array $args Parameters passed to calling function
1280
+ * @return array Options array (Default: empty array)
1281
+ */
1282
+ function func_get_options($args) {
1283
+ $r = array();
1284
+ if ( is_array($args) && !empty($args) ) {
1285
+ $last = count($args) - 1;
1286
+ if ( is_array($args[$last]) )
1287
+ $r = $args[$last];
1288
+ }
1289
+ return $r;
1290
+ }
1291
+
1292
  /**
1293
  * Checks if a property exists in a class or object
1294
  * (Compatibility method for PHP 4
1327
  }
1328
  }
1329
 
1330
+ /**
1331
+ * Remap array members based on a
1332
+ * mapping of source/destination keys
1333
+ * @param array $properties Associative array of properties
1334
+ * @param array $map Source/Destination mapping
1335
+ * > Key: Source member
1336
+ * > Val: Destination member
1337
+ * @param bool $overwrite If TRUE, source value will be set in destination regardless of whether member already exists or not
1338
+ * @return array Remapped properties
1339
+ */
1340
+ function array_remap($arr, $map = array(), $overwrite = false) {
1341
+ if ( !empty($map) && is_array($map) ) {
1342
+ //Iterate through mappings
1343
+ foreach ( $map as $from => $to ) {
1344
+ if ( !array_key_exists($from, $arr) )
1345
+ continue;
1346
+ $move = $overwrite;
1347
+ //Only remap if parent property doesn't already exist in array
1348
+ if ( !array_key_exists($to, $arr) )
1349
+ $move = true;
1350
+ if ( $move ) {
1351
+ //Move member value to new key
1352
+ $arr[$to] = $arr[$from];
1353
+ //Remove source member
1354
+ unset($arr[$from]);
1355
+ }
1356
+ }
1357
+ }
1358
+ //Return remapped properties
1359
+ return $arr;
1360
+ }
1361
+
1362
+ function array_filter_keys($arr, $keys) {
1363
+ if ( is_array($arr) && !empty($arr) && is_array($keys) && !empty($keys) ) {
1364
+ foreach ( $keys as $rem ) {
1365
+ if ( array_key_exists($rem, $arr) )
1366
+ unset($arr[$rem]);
1367
+ }
1368
+ }
1369
+
1370
+ return $arr;
1371
+ }
1372
+
1373
+ /**
1374
+ * Insert an item into an array at the specified position
1375
+ * @param mixed $item Item to insert into array
1376
+ * @param int $pos Index position to insert item into array
1377
+ * @return array Modified array
1378
+ */
1379
+ function array_insert($array, $item, $pos = null) {
1380
+ array_splice($array, $pos, 0, $item);
1381
+ return $array;
1382
+ }
1383
+
1384
  /**
1385
  * Merges 1 or more arrays together
1386
  * Methodology
1392
  * - Merge item in base array with current item based on key name
1393
  * - If the current item's value AND the corresponding item in the base array are BOTH arrays, recursively merge the the arrays
1394
  * - If the current item's value OR the corresponding item in the base array is NOT an array, current item overwrites base item
 
1395
  * @param array Variable number of arrays
1396
  * @param array $arr1 Default array
1397
  * @return array Merged array
1399
  function array_merge_recursive_distinct($arr1) {
1400
  //Get all arrays passed to function
1401
  $args = func_get_args();
1402
+ if ( empty($args) )
1403
  return false;
1404
+ //Return empty array if first parameter is not an array
1405
+ if ( !is_array($args[0]) )
1406
+ return array();
1407
  //Set first array as base array
1408
  $merged = $args[0];
1409
  //Iterate through arrays to merge
1410
  $arg_length = count($args);
1411
+ for ( $x = 1; $x < $arg_length; $x++ ) {
1412
  //Skip if argument is not an array (only merge arrays)
1413
+ if ( !is_array($args[$x]) )
1414
  continue;
1415
  //Iterate through argument items
1416
+ foreach ( $args[$x] as $key => $val ) {
1417
+ //Generate key for numeric indexes
1418
+ if ( is_int($key) ) {
1419
+ //Add new item to merged array
1420
+ $merged[] = null;
1421
+ //Get key of new item
1422
+ $key = array_pop(array_keys($merged));
1423
+ }
1424
+ if ( !isset($merged[$key]) || !is_array($merged[$key]) || !is_array($val) ) {
1425
+ $merged[$key] = $val;
1426
+ } elseif ( is_array($merged[$key]) && is_array($val) ) {
1427
+ $merged[$key] = $this->array_merge_recursive_distinct($merged[$key], $val);
1428
+ }
1429
  }
1430
  }
1431
  return $merged;
1480
  return $item;
1481
  }
1482
 
1483
+ /**
1484
+ * Build formatted string based on array values
1485
+ * Array values in formatted string will be ordered by index order
1486
+ * @param array $attribute Values to build string with
1487
+ * @param string $format (optional) Format name (Default: Multidimensional array representation > ['value1']['value2']['value3'], etc.)
1488
+ * @return string Formatted string based on array values
1489
+ */
1490
  function get_array_path($attribute = '', $format = null) {
1491
  //Formatted value
1492
  $fmtd = '';
1552
  return $path;
1553
  }
1554
 
1555
+ /**
1556
+ * Parse string of attributes into array
1557
+ * For XML/XHTML tag attributes
1558
+ * @param string $txt Attribute text (Can be full tag or just attributes)
1559
+ * @return array Attributes as associative array
1560
+ */
1561
+ function parse_attribute_string($txt, $defaults = array()) {
1562
+ $txt = trim($txt, ' >');
1563
+ $matches = $attr = array();
1564
+ //Strip tag
1565
+ if ( $txt[0] == '<' && ($s = strpos($txt, ' ')) && $s !== false ) {
1566
+ $txt = trim(substr($txt, $s + 1));
1567
+ }
1568
+ //Parse attributes
1569
+ $rgx = "/\b(\w+.*?)=([\"'])(.*?)\\2(?:\s|$)/i";
1570
+ preg_match_all($rgx, $txt, $matches);
1571
+ if ( count($matches) > 3 ) {
1572
+ foreach ( $matches[1] as $sub_idx => $val ) {
1573
+ if ( isset($matches[3][$sub_idx]) )
1574
+ $attr[trim($val)] = trim($matches[3][$sub_idx]);
1575
+ }
1576
+ }
1577
+ //Destroy parsing vars
1578
+ unset($txt, $matches);
1579
+
1580
+ return array_merge($defaults, $attr);
1581
+ }
1582
+
1583
  /**
1584
  * Builds attribute string for HTML element
1585
  * @param array $attr Attributes
1600
  return $ret;
1601
  }
1602
 
1603
+ function build_html_link($uri, $content, $attributes = array()) {
1604
+ $attributes = array_merge(array('href' => $uri, 'title' => $content), $attributes);
1605
+ return $this->build_html_element(array('tag' => 'a', 'wrap' => true, 'content' => $content, 'attributes' => $attributes));
1606
+ }
1607
+
1608
  /**
1609
  * Generate external stylesheet element
1610
  * @param $url Stylesheet URL
1615
  return $this->build_html_element(array('tag' => 'link', 'wrap' => false, 'attributes' => $attributes));
1616
  }
1617
 
1618
+ /**
1619
+ * Build client-side script element
1620
+ *
1621
+ * @param string $content Script content
1622
+ * @param string $id (optional) Element ID
1623
+ * @param bool $wrap_jquery (optional) Wrap commands in jQuery? (Default: Yes)
1624
+ * @param bool $wait_doc_ready (optional) Wait until document is fully loaded before executing commands? (Default: No)
1625
+ */
1626
+ function build_script_element($content = '', $id = '', $wrap_jquery = true, $wait_doc_ready = false) {
1627
+ //Stop processing invalid content
1628
+ if ( is_array($content) && !empty($content) ) {
1629
+ $content = implode(PHP_EOL, $content);
1630
+ }
1631
+ if ( empty($content) || !is_string($content) ) {
1632
+ return '';
1633
+ }
1634
+ $attributes = array('type' => 'text/javascript');
1635
+ $start = array('/* <![CDATA[ */');
1636
+ $end = array('/* ]]> */');
1637
+ if ( $wrap_jquery ) {
1638
+ $start[] = '(function($){';
1639
+ $end[] = '})(jQuery);';
1640
+
1641
+ //Add event handler (if necessary)
1642
+ if ( $wait_doc_ready ) {
1643
+ $start[] = '$(document).ready(function(){';
1644
+ $end[] = '})';
1645
+ }
1646
+ }
1647
+
1648
+ //Reverse order of end values
1649
+ $end = array_reverse($end);
1650
+ $content = implode('', array_merge($start, array($content), $end));
1651
+ if ( is_string($id) && !empty($id) ) {
1652
+ $attributes['id'] = $this->add_prefix($id);
1653
+ }
1654
+ return $this->build_html_element(array('tag' => 'script', 'content' => $content, 'attributes' => $attributes)) . PHP_EOL;
1655
+ }
1656
+
1657
  /**
1658
  * Generate external script element
1659
  * @param $url Script URL
1661
  */
1662
  function build_ext_script_element($url = '') {
1663
  $attributes = array('src' => $url, 'type' => 'text/javascript');
1664
+ return $this->build_html_element(array('tag' => 'script', 'attributes' => $attributes)) . PHP_EOL;
1665
  }
1666
 
1667
  /**
1679
  $el_start = '<';
1680
  $el_end = '>';
1681
  $el_close = '/';
1682
+ $args = wp_parse_args($args, $defaults);
1683
+ //Collect attributes
1684
+ $attr_exclude = array( 'content', 'tag', 'wrap', 'attributes' );
1685
+ $attr_extra = array_diff_key($args, array_fill_keys($attr_exclude, null));
1686
+ if ( count($attr_extra) ) {
1687
+ //Merge attributes
1688
+ $args['attributes'] = wp_parse_args($attr_extra, $args['attributes']);
1689
+ //Remove attributes from top-level arguments
1690
+ $args = array_diff_key($args, $attr_extra);
1691
+ }
1692
+ extract($args, EXTR_SKIP);
1693
  $content = trim($content);
1694
 
1695
+
1696
  if ( !$wrap && strlen($content) > 0 )
1697
  $wrap = true;
1698
 
1737
  //Get last submenu added
1738
  $parent = $this->get_submenu_parent_file($parent);
1739
  if ( isset($submenu[$parent]) ) {
1740
+ $subs =& $submenu[$parent];
1741
+
1742
+ //Make sure menu isn't already in the desired position
1743
+ if ( $pos <= ( count($subs) - 1 ) ) {
1744
+ //Get submenu that was just added
1745
+ $sub = array_pop($subs);
1746
+ //Insert into desired position
1747
+ if ( 0 == $pos ) {
1748
+ array_unshift($subs, $sub);
1749
+ } else {
1750
+ $top = array_slice($subs, 0, $pos);
1751
+ $bottom = array_slice($subs, $pos);
1752
+ array_push($top, $sub);
1753
+ $subs = array_merge($top, $bottom);
1754
  }
1755
  }
1756
  }
1757
+ }
1758
 
1759
  return $hookname;
1760
  }
1834
  $parent = $_wp_real_parent_file[$parent];
1835
  return $parent;
1836
  }
1837
+
1838
+ /* Shortcodes */
1839
+
1840
+ /**
1841
+ * Generate shortcode to be used in content
1842
+ * @param string $tag Shortcode tag
1843
+ * @param array $attr Associative array of attributes
1844
+ * @return string Shortcode markup
1845
+ */
1846
+ public function make_shortcode($tag, $attr = array()) {
1847
+ return '[' . $tag . ']';
1848
+ }
1849
+
1850
+ /**
1851
+ * Build shortcode regex pattern for specific shortcode
1852
+ * @uses $shortcode_tags
1853
+ * @param string $tag Shortcode tag
1854
+ * @return string Shortcode regex pattern
1855
+ */
1856
+ public function get_shortcode_regex($tag) {
1857
+ global $shortcode_tags;
1858
+ //Backup shortcodes
1859
+ $tgs_temp = $shortcode_tags;
1860
+ $ret = '';
1861
+ if ( !is_string($tag) || empty($tag) ) {
1862
+ return $ret;
1863
+ }
1864
+ //Modify
1865
+ $shortcode_tags = array( $tag => null );
1866
+ //Build pattern
1867
+ $ret = get_shortcode_regex();
1868
+ //Restore shortcodes
1869
+ $shortcode_tags = $tgs_temp;
1870
+
1871
+ return $ret;
1872
+ }
1873
+ /**
1874
+ * Check if content contains shortcode
1875
+ * @param string $tag Name of shortcode to check for
1876
+ * @param string $content Content to check for shortcode
1877
+ * @return bool TRUE if content contains shortcode
1878
+ */
1879
+ public function has_shortcode($content, $tag) {
1880
+ $ptn = $this->get_shortcode_regex($tag);
1881
+ $ret = ( is_string($content) && preg_match("/$ptn/s", $content) == 1 ) ? true : false;
1882
+ return $ret;
1883
+ }
1884
+
1885
+ /**
1886
+ * Add shortcode to content
1887
+ * @param string $content Content to add shortcode to
1888
+ * @param bool $in_footer (optional) Add shortcode to head or footer of content (Default: footer)
1889
+ * @return string Modified content
1890
+ */
1891
+ public function add_shortcode($content, $tag, $attr = null, $in_footer = true) {
1892
+ if ( !is_string($content) ) {
1893
+ $content = '';
1894
+ }
1895
+ $sc = $this->make_shortcode($tag, $attr);
1896
+ if ( !!$in_footer ) {
1897
+ $content .= $sc;
1898
+ } else {
1899
+ $content = $sc . $content;
1900
+ }
1901
+ return $content;
1902
+ }
1903
  }
js/dev/lightbox.js DELETED
@@ -1,736 +0,0 @@
1
- // -----------------------------------------------------------------------------------
2
- //
3
- // Simple Lightbox
4
- // by Archetyped - http://archetyped.com/tools/simple-lightbox/
5
- // Updated: 2010-06-11
6
- //
7
- // Largely based on Lightbox Slideshow v1.1
8
- // by Justin Barkhuff - http://www.justinbarkhuff.com/lab/lightbox_slideshow/
9
- // 2007/08/15
10
- //
11
- // Largely based on Lightbox v2.02
12
- // by Lokesh Dhakar - http://huddletogether.com/projects/lightbox2/
13
- // 2006/03/31
14
- //
15
- // Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
16
- //
17
- // The code inserts html at the bottom of the page that looks similar to this:
18
- //
19
- // <div id="overlay"></div>
20
- // <div id="lightbox">
21
- // <div id="outerImageContainer">
22
- // <div id="imageContainer">
23
- // <img id="lightboxImage" />
24
- // <div id="hoverNav">
25
- // <a href="javascript:void(0);" id="prevLinkImg">&laquo; prev</a>
26
- // <a href="javascript:void(0);" id="nextLinkImg">next &raquo;</a>
27
- // </div>
28
- // <div id="loading">
29
- // <a href="javascript:void(0);" id="loadingLink">loading</a>
30
- // </div>
31
- // </div>
32
- // </div>
33
- // <div id="imageDataContainer">
34
- // <div id="imageData">
35
- // <div id="imageDetails">
36
- // <span id="caption"></span>
37
- // <span id="numberDisplay"></span>
38
- // <span id="detailsNav">
39
- // <a id="prevLinkDetails" href="javascript:void(0);">&laquo; prev</a>
40
- // <a id="nextLinkDetails" href="javascript:void(0);">next &raquo;</a>
41
- // <a id="slideShowControl" href="javascript:void(0);">stop slideshow</a>
42
- // </span>
43
- // </div>
44
- // <div id="close">
45
- // <a id="closeLink" href="javascript:void(0);">close</a>
46
- // </div>
47
- // </div>
48
- // </div>
49
- // </div>
50
- //
51
- // -----------------------------------------------------------------------------------
52
-
53
- //
54
- // Lightbox Object
55
- //
56
- var Lightbox = null;
57
- (function($) {
58
- Lightbox = {
59
- activeImage : null,
60
- badObjects : ['select','object','embed'],
61
- container : null,
62
- enableSlideshow : null,
63
- groupName : null,
64
- imageArray : [],
65
- options : null,
66
- overlayDuration : null,
67
- overlayOpacity : null,
68
- playSlides : null,
69
- refTags : ['a','area'],
70
- relAttribute : null,
71
- resizeDuration : null,
72
- slideShowTimer : null,
73
- startImage : null,
74
-
75
- //
76
- // initialize()
77
- // Constructor sets class properties and configuration options and
78
- // inserts html at the bottom of the page which is used to display the shadow
79
- // overlay and the image container.
80
- //
81
- initialize: function(options) {
82
- this.options = $.extend(true, {
83
- animate : true, // resizing animations
84
- autoPlay : true, // should slideshow start automatically
85
- borderSize : 10, // if you adjust the padding in the CSS, you will need to update this variable
86
- containerID : document, // lightbox container object
87
- enableSlideshow : true, // enable slideshow feature
88
- googleAnalytics : false, // track individual image views using Google Analytics
89
- imageDataLocation : 'south', // location of image caption information
90
- initImage : '', // ID of image link to automatically launch when upon script initialization
91
- loop : true, // whether to continuously loop slideshow images
92
- overlayDuration : .2, // time to fade in shadow overlay
93
- overlayOpacity : .8, // transparency of shadow overlay
94
- prefix : '', // ID prefix for all dynamically created html elements
95
- relAttribute : 'lightbox', // specifies the rel attribute value that triggers lightbox
96
- resizeSpeed : 400, // controls the speed of the image resizing (milliseconds)
97
- showGroupName : false, // show group name of images in image details
98
- slideTime : 4, // time to display images during slideshow
99
- strings : { // allows for localization
100
- closeLink : 'close',
101
- loadingMsg : 'loading',
102
- nextLink : 'next &raquo;',
103
- prevLink : '&laquo; prev',
104
- startSlideshow : 'start slideshow',
105
- stopSlideshow : 'stop slideshow',
106
- numDisplayPrefix : 'Image',
107
- numDisplaySeparator : 'of'
108
- }
109
- }, options);
110
-
111
- if ( this.options.animate ) {
112
- this.overlayDuration = Math.max(this.options.overlayDuration,0);
113
- this.resizeDuration = this.options.resizeSpeed;
114
- } else {
115
- this.overlayDuration = 0;
116
- this.resizeDuration = 0;
117
- }
118
-
119
- this.enableSlideshow = this.options.enableSlideshow;
120
- this.overlayOpacity = Math.max(Math.min(this.options.overlayOpacity,1),0);
121
- this.playSlides = this.options.autoPlay;
122
- this.container = $(this.options.containerID);
123
- this.relAttribute = this.options.relAttribute;
124
- this.updateImageList();
125
- var t = this;
126
- var objBody = $(this.container).get(0) != document ? this.container : $('body');
127
-
128
- var objOverlay = $('<div/>', {
129
- 'id': this.getID('overlay'),
130
- 'css': {'display': 'none'}
131
- }).appendTo(objBody)
132
- .click(function() {t.end()});
133
-
134
- var objLightbox = $('<div/>', {
135
- 'id': this.getID('lightbox'),
136
- 'css': {'display': 'none'}
137
- }).appendTo(objBody)
138
- .click(function() {t.end()});
139
-
140
- var objImageDataContainer = $('<div/>', {
141
- 'id': this.getID('imageDataContainer'),
142
- 'class': this.getID('clearfix')
143
- }).click(function(ev) {ev.stopPropagation();});
144
-
145
- var objImageData = $('<div/>', {
146
- 'id': this.getID('imageData')
147
- }).appendTo(objImageDataContainer);
148
-
149
- var objImageDetails = $('<div/>', {
150
- 'id': this.getID('imageDetails')
151
- }).appendTo(objImageData);
152
-
153
- var objCaption = $('<span/>', {
154
- 'id': this.getID('caption')
155
- }).appendTo(objImageDetails);
156
-
157
- var objNumberDisplay = $('<span/>', {
158
- 'id': this.getID('numberDisplay')
159
- }).appendTo(objImageDetails);
160
-
161
- var objDetailsNav = $('<span/>', {
162
- 'id': this.getID('detailsNav')
163
- }).appendTo(objImageDetails);
164
-
165
- var objPrevLink = $('<a/>', {
166
- 'id': this.getID('prevLinkDetails'),
167
- 'href': 'javascript:void(0);',
168
- 'html': this.options.strings.prevLink
169
- }).appendTo(objDetailsNav)
170
- .click(function() {t.showPrev()});
171
-
172
- var objNextLink = $('<a/>', {
173
- 'id': this.getID('nextLinkDetails'),
174
- 'href': 'javascript:void(0);',
175
- 'html': this.options.strings.nextLink
176
- }).appendTo(objDetailsNav)
177
- .click(function() {t.showNext()});
178
-
179
- var objSlideShowControl = $('<a/>', {
180
- 'id': this.getID('slideShowControl'),
181
- 'href': 'javascript:void(0);'
182
- }).appendTo(objDetailsNav)
183
- .click(function() {t.toggleSlideShow()});
184
-
185
- var objClose = $('<div/>', {
186
- 'id': this.getID('close')
187
- }).appendTo(objImageData);
188
-
189
- var objCloseLink = $('<a/>', {
190
- 'id': this.getID('closeLink'),
191
- 'href': 'javascript:void(0);',
192
- 'html': this.options.strings.closeLink
193
- }).appendTo(objClose)
194
- .click(function() {t.end()});
195
-
196
- if (this.options.imageDataLocation == 'north') {
197
- $(objLightbox).append(objImageDataContainer);
198
- }
199
-
200
- var objOuterImageContainer = $('<div/>', {
201
- 'id': this.getID('outerImageContainer')
202
- }).appendTo(objLightbox)
203
- .click(function(ev) {ev.stopPropagation();});
204
-
205
- var objImageContainer = $('<div/>', {
206
- 'id': this.getID('imageContainer')
207
- }).appendTo(objOuterImageContainer);
208
-
209
- var objLightboxImage = $('<img/>', {
210
- 'id': this.getID('lightboxImage')
211
- }).appendTo(objImageContainer);
212
-
213
- var objHoverNav = $('<div/>', {
214
- 'id': this.getID('hoverNav')
215
- }).appendTo(objImageContainer);
216
-
217
- var objPrevLinkImg = $('<a/>', {
218
- 'id': this.getID('prevLinkImg'),
219
- 'href': 'javascript:void(0);'
220
- }).appendTo(objHoverNav)
221
- .click(function() {t.showPrev()});
222
-
223
- var objNextLinkImg = $('<a/>', {
224
- 'id': this.getID('nextLinkImg'),
225
- 'href': 'javascript:void(0);'
226
- }).appendTo(objHoverNav)
227
- .click(function() {t.showNext()});
228
-
229
- var objLoading = $('<div/>', {
230
- 'id': this.getID('loading')
231
- }).appendTo(objImageContainer);
232
-
233
- var objLoadingLink = $('<a/>', {
234
- 'id': this.getID('loadingLink'),
235
- 'href': 'javascript:void(0);',
236
- 'html': this.options.strings.loadingMsg
237
- }).appendTo(objLoading)
238
- .click(function() {t.end()});
239
-
240
- if (this.options.imageDataLocation != 'north') {
241
- $(objLightbox).append(objImageDataContainer);
242
- }
243
-
244
- if (this.options.initImage != '') {
245
- this.start($(this.options.initImage));
246
- }
247
- },
248
-
249
- //
250
- // updateImageList()
251
- // Loops through specific tags within 'container' looking for
252
- // 'lightbox' references and applies onclick events to them.
253
- //
254
- updateImageList: function() {
255
- var el, els, rel;
256
- var t = this;
257
- for(var i=0; i < this.refTags.length; i++) {
258
- els = $(this.container).find(this.refTags[i]);
259
- for(var j=0; j < els.length; j++) {
260
- el = els[j];
261
- rel = $(el).attr('rel');
262
- if ($(el).attr('href') && (rel.toLowerCase().match(this.relAttribute))) {
263
- $(el).click(function() {
264
- t.start(this);
265
- return false;
266
- });
267
- }
268
- }
269
- }
270
- },
271
-
272
- getCaption: function(imageLink) {
273
- imageLink = $(imageLink);
274
- var caption = imageLink.attr('title') || '';
275
- if ( caption == '' ) {
276
- var inner = $(imageLink).find('img').first();
277
- if ( $(inner).length )
278
- caption = $(inner).attr('title') || $(inner).attr('alt');
279
- if ( !caption )
280
- caption = imageLink.text() || imageLink.attr('href') || '';
281
- }
282
- return caption;
283
- },
284
-
285
- //
286
- // start()
287
- // Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
288
- //
289
- start: function(imageLink) {
290
- imageLink = $(imageLink);
291
- this.hideBadObjects();
292
-
293
- // stretch overlay to fill page and fade in
294
- var pageSize = this.getPageSize();
295
- this.getEl('overlay')
296
- .height(pageSize.pageHeight)
297
- .fadeTo(this.overlayDuration, this.overlayOpacity);
298
-
299
- this.imageArray = [];
300
- this.groupName = null;
301
-
302
- var rel = $(imageLink).attr('rel');
303
- var imageTitle = '';
304
-
305
- // if image is NOT part of a group..
306
- if (rel == this.relAttribute) {
307
- // add single image to imageArray
308
- imageTitle = this.getCaption(imageLink);
309
- this.imageArray.push({'link':$(imageLink).attr('href'), 'title':imageTitle});
310
- this.startImage = 0;
311
- } else {
312
- // if image is part of a group..
313
-
314
- var els = $(this.container).find($(imageLink).get(0).tagName.toLowerCase());
315
- // loop through anchors, find other images in group, and add them to imageArray
316
- for (var i=0; i < els.length; i++) {
317
- var el = $(els[i]);
318
- if (el.attr('href') && (el.attr('rel') == rel)) {
319
- imageTitle = this.getCaption(el);
320
- this.imageArray.push({'link':el.attr('href'),'title':imageTitle});
321
- if ($(el).get(0) == $(imageLink).get(0)) {
322
- this.startImage = this.imageArray.length - 1;
323
- }
324
- }
325
- }
326
- // get group name
327
- this.groupName = rel.substring(this.relAttribute.length + 1, rel.length - 1);
328
- }
329
-
330
- // calculate top offset for the lightbox and display
331
- var pageScroll = this.getPageScroll();
332
- var lightboxTop = pageScroll.y + (pageSize.winHeight / 15);
333
-
334
- this.getEl('lightbox').css('top', lightboxTop + 'px').show();
335
- this.changeImage(this.startImage);
336
- },
337
-
338
- //
339
- // changeImage()
340
- // Hide most elements and preload image in preparation for resizing image container.
341
- //
342
- changeImage: function(imageNum) {
343
- this.activeImage = imageNum;
344
-
345
- this.disableKeyboardNav();
346
- this.pauseSlideShow();
347
-
348
- // hide elements during transition
349
- this.getEl('loading').show();
350
- this.getEl('lightboxImage').hide();
351
- this.getEl('hoverNav').hide();
352
- this.getEl('imageDataContainer').hide();
353
- this.getEl('numberDisplay').hide();
354
- this.getEl('detailsNav').hide();
355
- var imgPreloader = new Image();
356
- var t = this;
357
- // once image is preloaded, resize image container
358
- $(imgPreloader).bind('load', function() {
359
- t.getEl('lightboxImage').attr('src', imgPreloader.src);
360
- t.resizeImageContainer(imgPreloader.width, imgPreloader.height);
361
- //Restart slideshow if active
362
- if ( t.isSlideShowActive() )
363
- t.startSlideshow();
364
- });
365
-
366
- imgPreloader.src = this.imageArray[this.activeImage].link;
367
-
368
- if (this.options.googleAnalytics) {
369
- urchinTracker(this.imageArray[this.activeImage].link);
370
- }
371
- },
372
-
373
- //
374
- // resizeImageContainer()
375
- //
376
- resizeImageContainer: function(imgWidth, imgHeight) {
377
- // get current height and width
378
- var el = this.getEl('outerImageContainer');
379
- var borderSize = this.options.borderSize * 2;
380
-
381
- this.getEl('outerImageContainer').animate({width: imgWidth + borderSize, height: imgHeight + borderSize}, this.resizeDuration)
382
-
383
- this.getEl('prevLinkImg').height(imgHeight);
384
- this.getEl('nextLinkImg').height(imgHeight);
385
- this.getEl('imageDataContainer').width(imgWidth + borderSize)
386
-
387
- this.showImage();
388
- },
389
-
390
- //
391
- // showImage()
392
- // Display image and begin preloading neighbors.
393
- //
394
- showImage: function() {
395
- this.getEl('loading').hide();
396
- var t = this;
397
- this.getEl('lightboxImage').fadeIn(500, function() { t.updateDetails(); });
398
- this.preloadNeighborImages();
399
- },
400
-
401
- //
402
- // updateDetails()
403
- // Display caption, image number, and bottom nav.
404
- //
405
- updateDetails: function() {
406
- this.getEl('caption').text(this.imageArray[this.activeImage].title);
407
- this.getEl('caption').show();
408
-
409
- // if image is part of set display 'Image x of y'
410
- if (this.hasImages()) {
411
- var num_display = this.options.strings.numDisplayPrefix + ' ' + eval(this.activeImage + 1) + ' ' + this.options.strings.numDisplaySeparator + ' ' + this.imageArray.length;
412
- if (this.options.showGroupName && this.groupName != '') {
413
- num_display += ' ' + this.options.strings.numDisplaySeparator + ' ' + this.groupName;
414
- }
415
- this.getEl('numberDisplay')
416
- .text(num_display)
417
- .show();
418
- if (!this.enableSlideshow) {
419
- this.getEl('slideShowControl').hide();
420
- }
421
- this.getEl('detailsNav').show();
422
- }
423
-
424
- var t = this;
425
- this.getEl('imageDataContainer').animate({height: 'toggle', opacity: 'toggle'}, 650, function() {t.updateNav();});
426
- },
427
-
428
- //
429
- // updateNav()
430
- // Display appropriate previous and next hover navigation.
431
- //
432
- updateNav: function() {
433
- if (this.hasImages()) {
434
- this.getEl('hoverNav').show();
435
- if (this.enableSlideshow) {
436
- if (this.playSlides) {
437
- this.startSlideShow();
438
- } else {
439
- this.stopSlideShow();
440
- }
441
- }
442
- }
443
- this.enableKeyboardNav();
444
- },
445
-
446
- isSlideShowActive: function() {
447
- return this.playSlides;
448
- },
449
-
450
- //
451
- // startSlideShow()
452
- // Starts the slide show
453
- //
454
- startSlideShow: function() {
455
- this.playSlides = true;
456
- var t = this;
457
- this.slideShowTimer = setInterval(function() { t.showNext(); t.pauseSlideShow(); }, this.options.slideTime * 1000);
458
- this.getEl('slideShowControl').text(this.options.strings.stopSlideshow);
459
- },
460
-
461
- //
462
- // stopSlideShow()
463
- // Stops the slide show
464
- //
465
- stopSlideShow: function() {
466
- this.playSlides = false;
467
- if (this.slideShowTimer) {
468
- clearInterval(this.slideShowTimer);
469
- }
470
- this.getEl('slideShowControl').text(this.options.strings.startSlideshow);
471
- },
472
-
473
- //
474
- // stopSlideShow()
475
- // Stops the slide show
476
- //
477
- toggleSlideShow: function() {
478
- if (this.playSlides) {
479
- this.stopSlideShow();
480
- }else{
481
- this.startSlideShow();
482
- }
483
- },
484
-
485
- //
486
- // pauseSlideShow()
487
- // Pauses the slide show (doesn't change the value of this.playSlides)
488
- //
489
- pauseSlideShow: function() {
490
- if (this.slideShowTimer) {
491
- clearInterval(this.slideShowTimer);
492
- }
493
- },
494
-
495
- hasImage: function() {
496
- return ( this.imageArray.length > 0 );
497
- },
498
-
499
- hasImages: function() {
500
- return ( this.imageArray.length > 1 );
501
- },
502
-
503
- isFirstImage: function() {
504
- return ( this.activeImage == 0 );
505
- },
506
-
507
- isLastImage: function() {
508
- return ( this.activeImage == this.imageArray.length - 1 );
509
- },
510
-
511
- //
512
- // showNext()
513
- // Display the next image in a group
514
- //
515
- showNext : function() {
516
- if (this.hasImages()) {
517
- if ( !this.options.loop && this.isLastImage() ) {
518
- return this.end();
519
- }
520
- if ( this.isLastImage() ) {
521
- this.showFirst();
522
- } else {
523
- this.changeImage(this.activeImage + 1);
524
- }
525
- }
526
- },
527
-
528
- //
529
- // showPrev()
530
- // Display the next image in a group
531
- //
532
- showPrev : function() {
533
- if (this.hasImages()) {
534
- if ( !this.options.loop && this.isFirstImage() )
535
- return this.end();
536
- if (this.activeImage == 0) {
537
- this.showLast();
538
- } else {
539
- this.changeImage(this.activeImage - 1);
540
- }
541
- }
542
- },
543
-
544
- //
545
- // showFirst()
546
- // Display the first image in a group
547
- //
548
- showFirst : function() {
549
- if (this.hasImages()) {
550
- this.changeImage(0);
551
- }
552
- },
553
-
554
- //
555
- // showFirst()
556
- // Display the first image in a group
557
- //
558
- showLast : function() {
559
- if (this.hasImages()) {
560
- this.changeImage(this.imageArray.length - 1);
561
- }
562
- },
563
-
564
- //
565
- // enableKeyboardNav()
566
- //
567
- enableKeyboardNav: function() {
568
- document.onkeydown = this.keyboardAction;
569
- },
570
-
571
- //
572
- // disableKeyboardNav()
573
- //
574
- disableKeyboardNav: function() {
575
- document.onkeydown = '';
576
- },
577
-
578
- //
579
- // keyboardAction()
580
- //
581
- keyboardAction: function(e) {
582
- if (e == null) { // ie
583
- keycode = event.keyCode;
584
- } else { // mozilla
585
- keycode = e.which;
586
- }
587
-
588
- key = String.fromCharCode(keycode).toLowerCase();
589
- var t = this;
590
-
591
- if (key == 'x' || key == 'o' || key == 'c') { // close lightbox
592
- t.end();
593
- } else if (key == 'p' || key == '%') { // display previous image
594
- t.showPrev();
595
- } else if (key == 'n' || key =='\'') { // display next image
596
- t.showNext();
597
- } else if (key == 'f') { // display first image
598
- t.showFirst();
599
- } else if (key == 'l') { // display last image
600
- t.showLast();
601
- } else if (key == 's') { // toggle slideshow
602
- if (t.hasImage() && t.options.enableSlideshow) {
603
- t.toggleSlideShow();
604
- }
605
- }
606
- },
607
-
608
- //
609
- // preloadNeighborImages()
610
- // Preload previous and next images.
611
- //
612
- preloadNeighborImages: function() {
613
- var nextImageID = this.imageArray.length - 1 == this.activeImage ? 0 : this.activeImage + 1;
614
- nextImage = new Image();
615
- nextImage.src = this.imageArray[nextImageID].link;
616
-
617
- var prevImageID = this.activeImage == 0 ? this.imageArray.length - 1 : this.activeImage - 1;
618
- prevImage = new Image();
619
- prevImage.src = this.imageArray[prevImageID].link;
620
- },
621
-
622
- //
623
- // end()
624
- //
625
- end: function() {
626
- this.disableKeyboardNav();
627
- this.pauseSlideShow();
628
- this.getEl('lightbox').hide();
629
- this.getEl('overlay').fadeOut(this.overlayDuration);
630
- this.showBadObjects();
631
- },
632
-
633
- //
634
- // showBadObjects()
635
- //
636
- showBadObjects: function (show) {
637
- show = ( typeof(show) == 'undefined' ) ? true : !!show;
638
- var vis = (show) ? 'visible' : 'hidden';
639
- $(this.badObjects.join(',')).css('visibility', vis);
640
- },
641
-
642
- //
643
- // hideBadObjects()
644
- //
645
- hideBadObjects: function () {
646
- this.showBadObjects(false);
647
- },
648
-
649
- //
650
- // pause(numberMillis)
651
- // Pauses code execution for specified time. Uses busy code, not good.
652
- // Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602
653
- //
654
- pause: function(numberMillis) {
655
- var now = new Date();
656
- var exitTime = now.getTime() + numberMillis;
657
- while(true) {
658
- now = new Date();
659
- if (now.getTime() > exitTime)
660
- return;
661
- }
662
- },
663
-
664
- //
665
- // getPageScroll()
666
- // Returns array with x,y page scroll values.
667
- // Core code from - quirksmode.org
668
- //
669
- getPageScroll: function() {
670
- var x,y;
671
- if (self.pageYOffset) {
672
- x = self.pageXOffset;
673
- y = self.pageYOffset;
674
- } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
675
- x = document.documentElement.scrollLeft;
676
- y = document.documentElement.scrollTop;
677
- } else if (document.body) {// all other Explorers
678
- x = document.body.scrollLeft;
679
- y = document.body.scrollTop;
680
- }
681
- return {x:x,y:y};
682
- },
683
-
684
- //
685
- // getPageSize()
686
- // Returns array with page width, height and window width, height
687
- // Core code from - quirksmode.org
688
- // Edit for Firefox by pHaez
689
- //
690
- getPageSize: function() {
691
- var scrollX,scrollY,windowX,windowY,pageX,pageY;
692
- if (window.innerHeight && window.scrollMaxY) {
693
- scrollX = document.body.scrollWidth;
694
- scrollY = window.innerHeight + window.scrollMaxY;
695
- } else if (document.body.scrollHeight > document.body.offsetHeight) { // all but Explorer Mac
696
- scrollX = document.body.scrollWidth;
697
- scrollY = document.body.scrollHeight;
698
- } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
699
- scrollX = document.body.offsetWidth;
700
- scrollY = document.body.offsetHeight;
701
- }
702
-
703
- if (self.innerHeight) { // all except Explorer
704
- windowX = self.innerWidth;
705
- windowY = self.innerHeight;
706
- } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
707
- windowX = document.documentElement.clientWidth;
708
- windowY = document.documentElement.clientHeight;
709
- } else if (document.body) { // other Explorers
710
- windowX = document.body.clientWidth;
711
- windowY = document.body.clientHeight;
712
- }
713
-
714
- pageY = (scrollY < windowY) ? windowY : scrollY; // for small pages with total height less then height of the viewport
715
- pageX = (scrollX < windowX) ? windowX : scrollX; // for small pages with total width less then width of the viewport
716
-
717
- return {pageWidth:pageX,pageHeight:pageY,winWidth:windowX,winHeight:windowY};
718
- },
719
-
720
- //
721
- // getID()
722
- // Returns formatted Lightbox element ID
723
- //
724
- getID: function(id) {
725
- return this.options.prefix+id;
726
- },
727
-
728
- getSel: function(id) {
729
- return '#' + this.getID(id);
730
- },
731
-
732
- getEl: function(id) {
733
- return $(this.getSel(id));
734
- }
735
- }
736
- })(jQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/lib.js DELETED
@@ -1,22 +0,0 @@
1
- var Lightbox=null;
2
- (function(c){Lightbox={activeImage:null,badObjects:["select","object","embed"],container:null,enableSlideshow:null,groupName:null,imageArray:[],options:null,overlayDuration:null,overlayOpacity:null,playSlides:null,refTags:["a","area"],relAttribute:null,resizeDuration:null,slideShowTimer:null,startImage:null,initialize:function(a){this.options=c.extend(true,{animate:true,autoPlay:true,borderSize:10,containerID:document,enableSlideshow:true,googleAnalytics:false,imageDataLocation:"south",initImage:"",
3
- loop:true,overlayDuration:0.2,overlayOpacity:0.8,prefix:"",relAttribute:"lightbox",resizeSpeed:400,showGroupName:false,slideTime:4,strings:{closeLink:"close",loadingMsg:"loading",nextLink:"next &raquo;",prevLink:"&laquo; prev",startSlideshow:"start slideshow",stopSlideshow:"stop slideshow",numDisplayPrefix:"Image",numDisplaySeparator:"of"}},a);if(this.options.animate){this.overlayDuration=Math.max(this.options.overlayDuration,0);this.resizeDuration=this.options.resizeSpeed}else this.resizeDuration=
4
- this.overlayDuration=0;this.enableSlideshow=this.options.enableSlideshow;this.overlayOpacity=Math.max(Math.min(this.options.overlayOpacity,1),0);this.playSlides=this.options.autoPlay;this.container=c(this.options.containerID);this.relAttribute=this.options.relAttribute;this.updateImageList();var b=this;a=c(this.container).get(0)!=document?this.container:c("body");c("<div/>",{id:this.getID("overlay"),css:{display:"none"}}).appendTo(a).click(function(){b.end()});a=c("<div/>",{id:this.getID("lightbox"),
5
- css:{display:"none"}}).appendTo(a).click(function(){b.end()});var d=c("<div/>",{id:this.getID("imageDataContainer"),"class":this.getID("clearfix")}).click(function(g){g.stopPropagation()}),e=c("<div/>",{id:this.getID("imageData")}).appendTo(d),f=c("<div/>",{id:this.getID("imageDetails")}).appendTo(e);c("<span/>",{id:this.getID("caption")}).appendTo(f);c("<span/>",{id:this.getID("numberDisplay")}).appendTo(f);f=c("<span/>",{id:this.getID("detailsNav")}).appendTo(f);c("<a/>",{id:this.getID("prevLinkDetails"),
6
- href:"javascript:void(0);",html:this.options.strings.prevLink}).appendTo(f).click(function(){b.showPrev()});c("<a/>",{id:this.getID("nextLinkDetails"),href:"javascript:void(0);",html:this.options.strings.nextLink}).appendTo(f).click(function(){b.showNext()});c("<a/>",{id:this.getID("slideShowControl"),href:"javascript:void(0);"}).appendTo(f).click(function(){b.toggleSlideShow()});e=c("<div/>",{id:this.getID("close")}).appendTo(e);c("<a/>",{id:this.getID("closeLink"),href:"javascript:void(0);",html:this.options.strings.closeLink}).appendTo(e).click(function(){b.end()});
7
- this.options.imageDataLocation=="north"&&c(a).append(d);e=c("<div/>",{id:this.getID("outerImageContainer")}).appendTo(a).click(function(g){g.stopPropagation()});e=c("<div/>",{id:this.getID("imageContainer")}).appendTo(e);c("<img/>",{id:this.getID("lightboxImage")}).appendTo(e);f=c("<div/>",{id:this.getID("hoverNav")}).appendTo(e);c("<a/>",{id:this.getID("prevLinkImg"),href:"javascript:void(0);"}).appendTo(f).click(function(){b.showPrev()});c("<a/>",{id:this.getID("nextLinkImg"),href:"javascript:void(0);"}).appendTo(f).click(function(){b.showNext()});
8
- e=c("<div/>",{id:this.getID("loading")}).appendTo(e);c("<a/>",{id:this.getID("loadingLink"),href:"javascript:void(0);",html:this.options.strings.loadingMsg}).appendTo(e).click(function(){b.end()});this.options.imageDataLocation!="north"&&c(a).append(d);this.options.initImage!=""&&this.start(c(this.options.initImage))},updateImageList:function(){for(var a,b,d,e=this,f=0;f<this.refTags.length;f++){b=c(this.container).find(this.refTags[f]);for(var g=0;g<b.length;g++){a=b[g];d=c(a).attr("rel");c(a).attr("href")&&
9
- d.toLowerCase().match(this.relAttribute)&&c(a).click(function(){e.start(this);return false})}}},getCaption:function(a){a=c(a);var b=a.attr("title")||"";if(b==""){var d=c(a).find("img").first();if(c(d).length)b=c(d).attr("title")||c(d).attr("alt");b||(b=a.text()||a.attr("href")||"")}return b},start:function(a){a=c(a);this.hideBadObjects();var b=this.getPageSize();this.getEl("overlay").height(b.pageHeight).fadeTo(this.overlayDuration,this.overlayOpacity);this.imageArray=[];this.groupName=null;var d=
10
- c(a).attr("rel"),e="";if(d==this.relAttribute){e=this.getCaption(a);this.imageArray.push({link:c(a).attr("href"),title:e});this.startImage=0}else{for(var f=c(this.container).find(c(a).get(0).tagName.toLowerCase()),g=0;g<f.length;g++){var h=c(f[g]);if(h.attr("href")&&h.attr("rel")==d){e=this.getCaption(h);this.imageArray.push({link:h.attr("href"),title:e});if(c(h).get(0)==c(a).get(0))this.startImage=this.imageArray.length-1}}this.groupName=d.substring(this.relAttribute.length+1,d.length-1)}a=this.getPageScroll().y+
11
- b.winHeight/15;this.getEl("lightbox").css("top",a+"px").show();this.changeImage(this.startImage)},changeImage:function(a){this.activeImage=a;this.disableKeyboardNav();this.pauseSlideShow();this.getEl("loading").show();this.getEl("lightboxImage").hide();this.getEl("hoverNav").hide();this.getEl("imageDataContainer").hide();this.getEl("numberDisplay").hide();this.getEl("detailsNav").hide();var b=new Image,d=this;c(b).bind("load",function(){d.getEl("lightboxImage").attr("src",b.src);d.resizeImageContainer(b.width,
12
- b.height);d.isSlideShowActive()&&d.startSlideshow()});b.src=this.imageArray[this.activeImage].link;this.options.googleAnalytics&&urchinTracker(this.imageArray[this.activeImage].link)},resizeImageContainer:function(a,b){this.getEl("outerImageContainer");var d=this.options.borderSize*2;this.getEl("outerImageContainer").animate({width:a+d,height:b+d},this.resizeDuration);this.getEl("prevLinkImg").height(b);this.getEl("nextLinkImg").height(b);this.getEl("imageDataContainer").width(a+d);this.showImage()},
13
- showImage:function(){this.getEl("loading").hide();var a=this;this.getEl("lightboxImage").fadeIn(500,function(){a.updateDetails()});this.preloadNeighborImages()},updateDetails:function(){this.getEl("caption").text(this.imageArray[this.activeImage].title);this.getEl("caption").show();if(this.hasImages()){var a=this.options.strings.numDisplayPrefix+" "+eval(this.activeImage+1)+" "+this.options.strings.numDisplaySeparator+" "+this.imageArray.length;if(this.options.showGroupName&&this.groupName!="")a+=
14
- " "+this.options.strings.numDisplaySeparator+" "+this.groupName;this.getEl("numberDisplay").text(a).show();this.enableSlideshow||this.getEl("slideShowControl").hide();this.getEl("detailsNav").show()}var b=this;this.getEl("imageDataContainer").animate({height:"toggle",opacity:"toggle"},650,function(){b.updateNav()})},updateNav:function(){if(this.hasImages()){this.getEl("hoverNav").show();if(this.enableSlideshow)this.playSlides?this.startSlideShow():this.stopSlideShow()}this.enableKeyboardNav()},isSlideShowActive:function(){return this.playSlides},
15
- startSlideShow:function(){this.playSlides=true;var a=this;this.slideShowTimer=setInterval(function(){a.showNext();a.pauseSlideShow()},this.options.slideTime*1E3);this.getEl("slideShowControl").text(this.options.strings.stopSlideshow)},stopSlideShow:function(){this.playSlides=false;this.slideShowTimer&&clearInterval(this.slideShowTimer);this.getEl("slideShowControl").text(this.options.strings.startSlideshow)},toggleSlideShow:function(){this.playSlides?this.stopSlideShow():this.startSlideShow()},pauseSlideShow:function(){this.slideShowTimer&&
16
- clearInterval(this.slideShowTimer)},hasImage:function(){return this.imageArray.length>0},hasImages:function(){return this.imageArray.length>1},isFirstImage:function(){return this.activeImage==0},isLastImage:function(){return this.activeImage==this.imageArray.length-1},showNext:function(){if(this.hasImages()){if(!this.options.loop&&this.isLastImage())return this.end();this.isLastImage()?this.showFirst():this.changeImage(this.activeImage+1)}},showPrev:function(){if(this.hasImages()){if(!this.options.loop&&
17
- this.isFirstImage())return this.end();this.activeImage==0?this.showLast():this.changeImage(this.activeImage-1)}},showFirst:function(){this.hasImages()&&this.changeImage(0)},showLast:function(){this.hasImages()&&this.changeImage(this.imageArray.length-1)},enableKeyboardNav:function(){document.onkeydown=this.keyboardAction},disableKeyboardNav:function(){document.onkeydown=""},keyboardAction:function(a){keycode=a==null?event.keyCode:a.which;key=String.fromCharCode(keycode).toLowerCase();if(key=="x"||
18
- key=="o"||key=="c")this.end();else if(key=="p"||key=="%")this.showPrev();else if(key=="n"||key=="'")this.showNext();else if(key=="f")this.showFirst();else if(key=="l")this.showLast();else key=="s"&&this.hasImage()&&this.options.enableSlideshow&&this.toggleSlideShow()},preloadNeighborImages:function(){var a=this.imageArray.length-1==this.activeImage?0:this.activeImage+1;nextImage=new Image;nextImage.src=this.imageArray[a].link;a=this.activeImage==0?this.imageArray.length-1:this.activeImage-1;prevImage=
19
- new Image;prevImage.src=this.imageArray[a].link},end:function(){this.disableKeyboardNav();this.pauseSlideShow();this.getEl("lightbox").hide();this.getEl("overlay").fadeOut(this.overlayDuration);this.showBadObjects()},showBadObjects:function(a){c(this.badObjects.join(",")).css("visibility",(typeof a=="undefined"?true:!!a)?"visible":"hidden")},hideBadObjects:function(){this.showBadObjects(false)},pause:function(a){var b=new Date;for(a=b.getTime()+a;;){b=new Date;if(b.getTime()>a)return}},getPageScroll:function(){var a,
20
- b;if(self.pageYOffset){a=self.pageXOffset;b=self.pageYOffset}else if(document.documentElement&&document.documentElement.scrollTop){a=document.documentElement.scrollLeft;b=document.documentElement.scrollTop}else if(document.body){a=document.body.scrollLeft;b=document.body.scrollTop}return{x:a,y:b}},getPageSize:function(){var a,b,d,e;if(window.innerHeight&&window.scrollMaxY){a=document.body.scrollWidth;b=window.innerHeight+window.scrollMaxY}else if(document.body.scrollHeight>document.body.offsetHeight){a=
21
- document.body.scrollWidth;b=document.body.scrollHeight}else{a=document.body.offsetWidth;b=document.body.offsetHeight}if(self.innerHeight){d=self.innerWidth;e=self.innerHeight}else if(document.documentElement&&document.documentElement.clientHeight){d=document.documentElement.clientWidth;e=document.documentElement.clientHeight}else if(document.body){d=document.body.clientWidth;e=document.body.clientHeight}return{pageWidth:a<d?d:a,pageHeight:b<e?e:b,winWidth:d,winHeight:e}},getID:function(a){return this.options.prefix+
22
- a},getSel:function(a){return"#"+this.getID(a)},getEl:function(a){return c(this.getSel(a))}}})(jQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
l10n/simple-lightbox-tr_TR.mo ADDED
Binary file
l10n/simple-lightbox-tr_TR.po ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2010
2
+ # This file is distributed under the same license as the package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Simple-Lightbox\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/simple-lightbox\n"
7
+ "POT-Creation-Date: 2011-10-14 03:38:17+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2011-11-21 19:22-1000\n"
12
+ "Last-Translator: Archetyped <contact@archetyped.com>\n"
13
+ "Language-Team: <support@lettoblog.com>\n"
14
+ "X-Poedit-Country: Turkey\n"
15
+
16
+ #: model.php:88
17
+ msgid "Activation"
18
+ msgstr "EtkinleÅŸtirme"
19
+
20
+ #: model.php:89
21
+ msgid "Grouping"
22
+ msgstr "Gruplama"
23
+
24
+ #: model.php:90
25
+ msgid "UI"
26
+ msgstr "UI"
27
+
28
+ #: model.php:91
29
+ msgid "Labels"
30
+ msgstr "Etiketler"
31
+
32
+ #: model.php:94
33
+ msgid "Enable Lightbox Functionality"
34
+ msgstr "Lightbox FonksiyonelliÄŸini EtkinleÅŸtir"
35
+
36
+ #: model.php:95
37
+ msgid "Enable on Home page"
38
+ msgstr "Anasayfa'da etkin"
39
+
40
+ #: model.php:96
41
+ msgid "Enable on Posts"
42
+ msgstr "Gönderi Sayfasında Etkin"
43
+
44
+ #: model.php:97
45
+ msgid "Enable on Pages"
46
+ msgstr "Sayfalarda Etkin"
47
+
48
+ #: model.php:98
49
+ msgid "Enable on Archive Pages (tags, categories, etc.)"
50
+ msgstr "Arşiv Sayfalarında Etkin (etiketler, kategoriler, gibi.)"
51
+
52
+ #: model.php:99
53
+ msgid "Enable backwards-compatibility with legacy lightbox links"
54
+ msgstr "Eski lightbox bağlantılar ile geri- uyumluluk etkinleştir"
55
+
56
+ #: model.php:100
57
+ msgid "Activate image attachment links"
58
+ msgstr "Resimlerdeki bağlantıları etkinleştir"
59
+
60
+ #: model.php:101
61
+ msgid "Validate links"
62
+ msgstr "Bağlantı kontrolü"
63
+
64
+ #: model.php:102
65
+ msgid "Group image links (for displaying as a slideshow)"
66
+ msgstr "Grup resimleri bağlantıları (slayt gösterisi olarak yayınlamak için)"
67
+
68
+ #: model.php:103
69
+ msgid "Group image links by Post (e.g. on pages with multiple posts)"
70
+ msgstr "Grup resimleri linki (örneğin, birden fazla ileti ile sayfalarda)"
71
+
72
+ #: model.php:104
73
+ msgid "Group gallery links separately"
74
+ msgstr "Grup galeri linkleri (ayrı ayrı)"
75
+
76
+ #: model.php:105
77
+ msgid "Theme"
78
+ msgstr "Tema"
79
+
80
+ #: model.php:106
81
+ msgid "Animate lightbox resizing"
82
+ msgstr "Lightbox yeniden boyutlandırma efekti"
83
+
84
+ #: model.php:107
85
+ msgid "Automatically Start Slideshow"
86
+ msgstr "Otomatik olarak Slayt gösterisi"
87
+
88
+ #: model.php:108
89
+ msgid "Slide Duration (Seconds)"
90
+ msgstr "Slayt Süresi (Saniye)"
91
+
92
+ #: model.php:109
93
+ msgid "Loop through images"
94
+ msgstr "Döngü görüntüleri üzerinden"
95
+
96
+ #: model.php:110
97
+ msgid "Overlay Opacity (0 - 1)"
98
+ msgstr ""
99
+
100
+ #: model.php:111
101
+ msgid "Enable caption"
102
+ msgstr "Balığı etkinleştir"
103
+
104
+ #: model.php:112
105
+ msgid "Use image URI as caption when link title not set"
106
+ msgstr ""
107
+
108
+ #: model.php:113
109
+ msgid "Enable description"
110
+ msgstr "Açıklama etkin"
111
+
112
+ #: model.php:114
113
+ msgid "Close link (for accessibility only, image used for button)"
114
+ msgstr ""
115
+
116
+ #: model.php:115
117
+ msgid "Loading indicator"
118
+ msgstr "Yükleniyor göstergesi"
119
+
120
+ #: model.php:116
121
+ msgid "Next Image link"
122
+ msgstr "Sonraki Görselin linki"
123
+
124
+ #: model.php:117
125
+ msgid "Previous Image link"
126
+ msgstr "Önceki Resme bağlantı"
127
+
128
+ #: model.php:118
129
+ msgid "Start Slideshow link"
130
+ msgstr "Slay gösterisine başlama linki"
131
+
132
+ #: model.php:119
133
+ msgid "Stop Slideshow link"
134
+ msgstr "Slayt gösterisini durdurma linki"
135
+
136
+ #: model.php:120
137
+ msgid "Image number prefix (e.g. <strong>Image</strong> x of y)"
138
+ msgstr "Görsel numaraları öneki (örn. <strong>Image</strong> x of y)"
139
+
140
+ #: model.php:121
141
+ msgid "Image number separator (e.g. Image x <strong>of</strong> y)"
142
+ msgstr "Görsel numaralarını ayırıcı (örn. Image x <strong>of</strong> y)"
143
+
144
+ #: model.php:728
145
+ msgid "Settings"
146
+ msgstr "Ayarlar"
147
+
148
+ #: model.php:729
149
+ msgid "Reset"
150
+ msgstr "Sıfırla"
151
+
152
+ #: model.php:730
153
+ msgid "Are you sure you want to reset your settings?"
154
+ msgstr "Ayarlarınızı sıfırlamak istediğinizden emin misiniz?"
155
+
156
+ #: model.php:746
157
+ msgid "You do not have sufficient permissions to manage plugins for this blog."
158
+ msgstr "Bu blogun eklentileri yönetmek için yeterli izinlere sahip değilsiniz."
159
+
160
+ #: model.php:796
161
+ msgid "Lightbox Settings"
162
+ msgstr "Lightbox Ayarları"
163
+
l10n/simple-lightbox.pot ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2013
2
+ # This file is distributed under the same license as the package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: \n"
6
+ "Report-Msgid-Bugs-To: https://github.com/archetyped/simple-lightbox/wiki/Reporting-Issues\n"
7
+ "POT-Creation-Date: 2013-04-16 00:22:28+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2013-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+
15
+ #: includes/class.admin.php:686
16
+ msgid "The settings have been reset"
17
+ msgstr ""
18
+
19
+ #: includes/class.admin.php:687
20
+ msgid "<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."
21
+ msgstr ""
22
+
23
+ #: includes/class.admin.php:688 includes/class.admin.php:1394
24
+ #: includes/class.admin.php:1443 includes/class.admin.php:1531
25
+ msgid "Access Denied"
26
+ msgstr ""
27
+
28
+ #: includes/class.admin.php:1318
29
+ msgid "Save Changes"
30
+ msgstr ""
31
+
32
+ #: includes/class.fields.php:2223
33
+ msgid "Default Element"
34
+ msgstr ""
35
+
36
+ #: includes/class.fields.php:2235
37
+ msgid "Default Element (Closed Tag)"
38
+ msgstr ""
39
+
40
+ #: includes/class.fields.php:2243
41
+ msgid "Default Input Element"
42
+ msgstr ""
43
+
44
+ #: includes/class.fields.php:2251
45
+ msgid "Text Box"
46
+ msgstr ""
47
+
48
+ #: includes/class.fields.php:2282
49
+ msgid "Hidden Field"
50
+ msgstr ""
51
+
52
+ #: includes/class.fields.php:2288
53
+ msgid "Select tag"
54
+ msgstr ""
55
+
56
+ #: includes/class.fields.php:2300
57
+ msgid "Inline wrapper"
58
+ msgstr ""
59
+
60
+ #: includes/class.options.php:115
61
+ msgid "Enabled"
62
+ msgstr ""
63
+
64
+ #: includes/class.options.php:115
65
+ msgid "Disabled"
66
+ msgstr ""
67
+
68
+ #: includes/class.options.php:400
69
+ msgid "Default"
70
+ msgstr ""
71
+
72
+ #: includes/class.themes.php:41
73
+ msgid "Theme"
74
+ msgstr ""
75
+
76
+ #: includes/class.themes.php:61
77
+ msgid "Default (Light)"
78
+ msgstr ""
79
+
80
+ #: includes/class.themes.php:67
81
+ msgid "Default (Dark)"
82
+ msgstr ""
83
+
84
+ #: model.php:151
85
+ msgid "Activation"
86
+ msgstr ""
87
+
88
+ #: model.php:152
89
+ msgid "Grouping"
90
+ msgstr ""
91
+
92
+ #: model.php:153
93
+ msgid "UI"
94
+ msgstr ""
95
+
96
+ #: model.php:154
97
+ msgid "Labels"
98
+ msgstr ""
99
+
100
+ #: model.php:157
101
+ msgid "Enable Lightbox Functionality"
102
+ msgstr ""
103
+
104
+ #: model.php:158
105
+ msgid "Enable on Home page"
106
+ msgstr ""
107
+
108
+ #: model.php:159
109
+ msgid "Enable on Posts"
110
+ msgstr ""
111
+
112
+ #: model.php:160
113
+ msgid "Enable on Pages"
114
+ msgstr ""
115
+
116
+ #: model.php:161
117
+ msgid "Enable on Archive Pages (tags, categories, etc.)"
118
+ msgstr ""
119
+
120
+ #: model.php:162
121
+ msgid "Enable for Widgets"
122
+ msgstr ""
123
+
124
+ #: model.php:163
125
+ msgid "Group items (for displaying as a slideshow)"
126
+ msgstr ""
127
+
128
+ #: model.php:164
129
+ msgid "Group items by Post (e.g. on pages with multiple posts)"
130
+ msgstr ""
131
+
132
+ #: model.php:165
133
+ msgid "Group gallery items separately"
134
+ msgstr ""
135
+
136
+ #: model.php:166
137
+ msgid "Group widget items separately"
138
+ msgstr ""
139
+
140
+ #: model.php:167
141
+ msgid "Resize lightbox to fit in window"
142
+ msgstr ""
143
+
144
+ #: model.php:168
145
+ msgid "Enable animations"
146
+ msgstr ""
147
+
148
+ #: model.php:169
149
+ msgid "Start Slideshow Automatically"
150
+ msgstr ""
151
+
152
+ #: model.php:170
153
+ msgid "Slide Duration (Seconds)"
154
+ msgstr ""
155
+
156
+ #: model.php:171
157
+ msgid "Loop through items"
158
+ msgstr ""
159
+
160
+ #: model.php:172
161
+ msgid "Overlay Opacity (0 - 1)"
162
+ msgstr ""
163
+
164
+ #: model.php:173
165
+ msgid "Loading indicator"
166
+ msgstr ""
167
+
168
+ #: model.php:174
169
+ msgid "Close button"
170
+ msgstr ""
171
+
172
+ #: model.php:175
173
+ msgid "Next Item button"
174
+ msgstr ""
175
+
176
+ #: model.php:176
177
+ msgid "Previous Item button"
178
+ msgstr ""
179
+
180
+ #: model.php:177
181
+ msgid "Start Slideshow button"
182
+ msgstr ""
183
+
184
+ #: model.php:178
185
+ msgid "Stop Slideshow button"
186
+ msgstr ""
187
+
188
+ #: model.php:179
189
+ msgid "Slideshow status format"
190
+ msgstr ""
191
+
192
+ #: model.php:228
193
+ msgid "Lightbox"
194
+ msgstr ""
195
+
196
+ #: model.php:229
197
+ msgid "Lightbox Settings"
198
+ msgstr ""
199
+
200
+ #: model.php:230
201
+ msgid "Settings"
202
+ msgstr ""
203
+
204
+ #: model.php:234
205
+ msgid "Reset"
206
+ msgstr ""
207
+
208
+ #: model.php:235
209
+ msgid "Are you sure you want to reset settings?"
210
+ msgstr ""
211
+
212
+ #: model.php:236
213
+ msgid "Settings have been reset"
214
+ msgstr ""
215
+
216
+ #: model.php:237
217
+ msgid "Settings were not reset"
218
+ msgstr ""
main.php CHANGED
@@ -1,14 +1,14 @@
1
  <?php
2
  /*
3
- Plugin Name: Simple Lightbox (Beta)
4
- Plugin URI: http://archetyped.com/tools/simple-lightbox/
5
- Description: Customizable Lightbox for Wordpress
6
- Version: 1.4
7
  Author: Archetyped
8
  Author URI: http://archetyped.com
9
  */
10
  /*
11
- Copyright 2010 Solomon Marchessault (wp@archetyped.com)
12
 
13
  This program is free software; you can redistribute it and/or
14
  modify it under the terms of the GNU General Public License
@@ -25,13 +25,33 @@ along with this program; if not, write to the Free Software
25
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26
  */
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  require_once 'model.php';
29
 
30
- $slb =& new SLB_Lightbox();
31
 
32
- function slb_enabled() {
33
  global $slb;
34
- return $slb->is_enabled();
35
- }
36
-
37
- ?>
1
  <?php
2
  /*
3
+ Plugin Name: Simple Lightbox
4
+ Plugin URI: http://archetyped.com/lab/slb-2-0rc4/
5
+ Description: The highly customizable lightbox for WordPress
6
+ Version: 2.0
7
  Author: Archetyped
8
  Author URI: http://archetyped.com
9
  */
10
  /*
11
+ Copyright 2013 Solomon Marchessault (sol@archetyped.com)
12
 
13
  This program is free software; you can redistribute it and/or
14
  modify it under the terms of the GNU General Public License
25
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26
  */
27
 
28
+ /**
29
+ * Class loading handler
30
+ * @param string $classname Class to load
31
+ */
32
+ function slb_autoload($classname) {
33
+ $prefix = 'SLB_';
34
+ //Remove prefix
35
+ if ( 0 !== strpos($classname, $prefix) ) {
36
+ return false;
37
+ }
38
+ //Format class for filename
39
+ $fn = 'class.' . strtolower( substr($classname, strlen($prefix)) ) . '.php';
40
+ //Build path
41
+ $path = dirname(__FILE__) . '/' . "includes/" . $fn;
42
+ //Load file
43
+ if ( is_readable($path) ) {
44
+ require $path;
45
+ }
46
+ }
47
+
48
+ spl_autoload_register('slb_autoload');
49
+
50
  require_once 'model.php';
51
 
52
+ $slb = new SLB_Lightbox();
53
 
54
+ function slb_register_theme($name, $title, $stylesheet_url, $layout) {
55
  global $slb;
56
+ $slb->register_theme($name, $title, $stylesheet_url, $layout);
57
+ }
 
 
model.php CHANGED
@@ -1,9 +1,7 @@
1
  <?php
2
 
3
- require_once 'includes/class.base.php';
4
-
5
  /**
6
- * Lightbox functionality class
7
  * @package Simple Lightbox
8
  * @author Archetyped
9
  */
@@ -11,487 +9,1089 @@ class SLB_Lightbox extends SLB_Base {
11
 
12
  /*-** Properties **-*/
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  /**
15
- * Page that plugin options are on
16
- * @var string
17
  */
18
- var $options_admin_page = 'options-media.php';
19
 
20
  /**
21
- * Page that processes options
22
- * @var string
 
 
 
 
 
 
23
  */
24
- var $options_admin_form = 'options.php';
25
 
26
  /**
27
- * Default options
28
- * 0: Value
29
- * 1: Label
 
 
 
 
 
 
 
30
  * @var array
31
  */
32
- var $options_default = array (
33
- 'header_enabled' => 'Activation',
34
- 'enabled' => array(true, 'Enable Lightbox Functionality'),
35
- 'enabled_home' => array(true, 'Enable on Home page'),
36
- 'enabled_single' => array(true, 'Enable on Single Posts/Pages'),
37
- 'enabled_archive' => array(true, 'Enable on Archive Pages (tags, categories, etc.)'),
38
- 'activate_links' => array(true, 'Activate all image links on page'),
39
- 'header_activation' => 'Grouping',
40
- 'group_links' => array(true, 'Group automatically activated links (for displaying as a slideshow)'),
41
- 'group_post' => array(true, 'Group image links by Post (e.g. on pages with multiple posts)'),
42
- 'header_ui' => 'UI',
43
- 'animate' => array(true, 'Animate lightbox resizing'),
44
- 'autostart' => array(true, 'Automatically Start Slideshow'),
45
- 'duration' => array(6, 'Slide Duration (Seconds)', array('size' => 3, 'maxlength' => 3)),
46
- 'loop' => array(true, 'Loop through images'),
47
- 'overlay_opacity' => array(0.8, 'Overlay Opacity (0 - 1)', array('size' => 3, 'maxlength' => 3)),
48
- 'header_strings' => 'Labels',
49
- 'txt_closeLink' => array('close', 'Close link (for accessibility only, image used for button)'),
50
- 'txt_loadingMsg' => array('loading', 'Loading indicator'),
51
- 'txt_nextLink' => array('next &raquo;', 'Next Image link'),
52
- 'txt_prevLink' => array('&laquo; prev', 'Previous Image link'),
53
- 'txt_startSlideshow' => array('start slideshow', 'Start Slideshow link'),
54
- 'txt_stopSlideshow' => array('stop slideshow', 'Stop Slideshow link'),
55
- 'txt_numDisplayPrefix' => array('Image', 'Image number prefix (e.g. <strong>Image</strong> x of y)'),
56
- 'txt_numDisplaySeparator' => array('of', 'Image number separator (e.g. Image x <strong>of</strong> y)')
57
- );
58
 
59
- /*-** Init **-*/
60
 
61
- function SLB_Lightbox() {
62
- $this->__construct();
63
- }
 
 
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  function __construct() {
66
  parent::__construct();
67
- $this->init();
 
 
 
 
 
68
  }
69
 
70
- function init() {
71
- $this->register_hooks();
72
- }
73
 
74
- function register_hooks() {
75
- register_activation_hook($this->util->get_plugin_base_file(), $this->m('activate'));
 
 
 
 
 
76
  /* Admin */
77
- //Init lightbox admin
78
- add_action('admin_init', $this->m('admin_settings'));
79
- //Enqueue header files (CSS/JS)
80
- add_action('admin_enqueue_scripts', $this->m('admin_enqueue_files'));
81
- //Reset Settings
82
- add_action('admin_action_' . $this->add_prefix('reset'), $this->m('admin_reset'));
83
- add_action('admin_notices', $this->m('admin_notices'));
84
 
85
  /* Client-side */
86
- //Init lightbox (client-side)
87
- add_action('wp_enqueue_scripts', $this->m('enqueue_files'));
88
- add_action('wp_head', $this->m('client_init'));
89
- add_filter('plugin_action_links_' . $this->util->get_plugin_base_name(), $this->m('admin_plugin_action_links'), 10, 4);
90
- add_filter('the_content', $this->m('activate_post_links'));
 
 
 
 
 
 
 
 
91
  }
92
 
93
- function activate() {
94
- //Set default options (if not yet set)
95
- $this->reset_options(false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  }
 
 
97
 
 
 
98
  /**
99
- * Resets option values to their default values
100
- * @param bool $hard Reset all options if TRUE (default), Reset only unset options if FALSE
101
  */
102
- function reset_options($hard = true) {
103
- foreach ( $this->options_default as $id => $data ) {
104
- $opt = $this->add_prefix($id);
105
- if ( !$hard && !is_null(get_option($opt, null)) ) {
106
- continue;
107
- }
108
- update_option($opt, $data[0]);
109
- }
 
 
 
 
 
 
 
 
110
  }
111
-
112
- /*-** Helpers **-*/
113
 
114
  /**
115
  * Checks whether lightbox is currently enabled/disabled
116
  * @return bool TRUE if lightbox is currently enabled, FALSE otherwise
117
  */
118
  function is_enabled($check_request = true) {
119
- $ret = ( get_option($this->add_prefix('enabled')) ) ? true : false;
120
  if ( $ret && $check_request ) {
121
  $opt = '';
122
  //Determine option to check
123
  if ( is_home() )
124
  $opt = 'home';
125
- elseif ( is_single() )
126
- $opt = 'single';
 
127
  elseif ( is_archive() || is_search() )
128
  $opt = 'archive';
129
  //Check option
130
- if ( ! empty($opt) && ( $opt = 'enabled_' . $opt ) && isset($this->options_default[$opt]) ) {
131
- $ret = ( get_option($this->add_prefix($opt)) ) ? true : false;
132
  }
133
  }
134
  return $ret;
135
  }
136
 
137
  /**
138
- * Builds object of option data
139
- * Properties:
140
- * > id: Option ID
141
- * > value: Option's value (uses default value if option not yet set)
142
- * > value_default: Option's default value (formatted)
143
  *
144
- * @param string $option Option name
145
- * @return object Option data
146
- */
147
- function get_option($option) {
148
- $ret = new stdClass();
149
- $ret->id = $this->add_prefix($option);
150
- $ret->value = get_option($ret->id, $this->get_default_value($option, false));
151
- $ret->value_default = $this->get_default_value($option, false);
152
- $ret->value_default_formatted = $this->get_default_value($option);
153
- $ret->attr = $this->get_default_attr($option);
154
- return $ret;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
156
 
157
  /**
158
- * Retrieve an option's value
159
- * @param string $option Option name
160
- * @return mixed Option value
 
 
 
161
  */
162
- function get_option_value($option) {
163
- $opt = $this->get_option($option);
164
- return $opt->value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  }
166
-
167
  /**
168
- * Retrieve default attributes for an option
169
- * @param string $option Option name
170
- * @return array Default attributes
 
171
  */
172
- function get_default_attr($option) {
173
- $ret = array();
174
- if ( isset($this->options_default[$option][2]) )
175
- $ret = $this->options_default[$option][2];
176
- return $ret;
 
 
 
177
  }
178
 
179
  /**
180
- * Retrieve default value for specified option
181
- * @param string $option Option name
182
- * @param bool $formatted Whether to return formatted value (e.g. for use in admin UI)
183
- * @return mixed Option default value
184
- */
185
- function get_default_value($option, $formatted = true) {
186
- $ret = '';
187
- if ( isset($this->options_default[$option][0]) ) {
188
- $ret = $this->options_default[$option][0];
189
- //Format value (if required)
190
- if ( $formatted ) {
191
- if ( is_bool($ret) || ( is_string($ret) && 'on' == $ret ) )
192
- $ret = ( $ret ) ? 'Enabled' : 'Disabled';
193
- if ( is_numeric($ret) )
194
- $ret = strval($ret);
195
- $ret = htmlentities($ret);
196
- }
197
- } elseif ( ! is_array($this->options_default[$option]) ) {
198
- $ret = $this->options_default[$option];
199
  }
200
- return $ret;
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
-
203
- /*-** Frontend **-*/
204
 
205
  /**
206
- * Scans post content for image links and activates them
207
- *
208
- * Lightbox will not be activated for feeds
209
- * @param $content
210
  */
211
- function activate_post_links($content) {
212
- //Check option
213
- if ( ! is_feed() && $this->is_enabled() && $this->get_option_value('activate_links') && $this->get_option_value('group_links') && $this->get_option_value('group_post') ) {
214
- //Scan for links
215
- $matches = array();
216
- if ( preg_match_all("/\<a[^\>]*href=[^\s]+\.(?:jp[e]*g|gif|png).*?\>/i", $content, $matches) ) {
217
- global $post;
218
- //Iterate through links & add lightbox if necessary
219
- foreach ($matches[0] as $link) {
220
- //Check if rel attribute exists
221
- $link_new = $link;
222
- $rel = '';
223
- if ( strpos(strtolower($link_new), ' rel=') !== false && preg_match("/\s+rel=(?:\"|')(.*?)(?:\"|')(\s|\>)/i", $link_new, $rel) ) {
224
- //Check if lightbox is already set in rel attribute
225
- $link_new = str_replace($rel[0], $rel[2], $link_new);
226
- $rel = $rel[1];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  }
228
-
229
- if ( strpos($rel, 'lightbox') === false) {
230
- //Add rel attribute to link
231
- $rel .= ' lightbox[' . $this->add_prefix($post->ID) . ']';
232
- $link_new = '<a rel="' . $rel . '"' . substr($link_new,2);
233
- //Insert modified link
234
- $content = str_replace($link, $link_new, $content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  }
 
 
 
 
 
 
 
238
  }
239
- return $content;
240
  }
241
 
242
  /**
243
- * Enqueue files in template head
 
 
244
  */
245
- function enqueue_files() {
246
- if ( ! $this->is_enabled() )
247
- return;
248
- wp_enqueue_script($this->add_prefix('lib'), $this->util->get_file_url('js/lib.js'), array('jquery'));
249
- wp_enqueue_style($this->add_prefix('lightbox_css'), $this->util->get_file_url('css/lightbox.css'));
250
  }
251
 
252
  /**
253
- * Sets options/settings to initialize lightbox functionality on page load
254
- * @return void
 
255
  */
256
- function client_init() {
257
- if ( ! $this->is_enabled() )
258
- return;
259
-
260
- $options = array();
261
- $out = array();
262
- $out['script_start'] = '<script type="text/javascript">(function($){$(document).ready(function(){';
263
- $out['script_end'] = '})})(jQuery);</script>';
264
- $js_code = array();
265
- //Activate links on page
266
- if ( $this->get_option_value('activate_links') ) {
267
- $rel = ( $this->get_option_value('group_links') ) ? 'lightbox[' . $this->get_prefix() . ']' : 'lightbox';
268
- ob_start();
269
- ?>
270
- $('a[href$=".jpg"]:not([rel~="lightbox"])','a[href$=".jpeg"]:not([rel~="lightbox"])','a[href$=".gif"]:not([rel~="lightbox"])','a[href$=".png"]:not([rel~="lightbox"])').each(function(i, el){if (! /(^|\b)lightbox\[.+\]($|\b)/i.test($(el).attr('rel'))){var rel=($(el).attr('rel').length > 0) ? $(el).attr('rel') + ' ' : '';$(el).attr('rel', =rel + '<?php echo $rel; ?>');}});
271
- <?php
272
- $test = ob_get_clean();
273
- }
274
- //Get options
275
- $options = array(
276
- 'autoPlay' => $this->get_option_value('autostart'),
277
- 'slideTime' => $this->get_option_value('duration'),
278
- 'loop' => $this->get_option_value('loop'),
279
- 'overlayOpacity' => $this->get_option_value('overlay_opacity'),
280
- 'animate' => $this->get_option_value('animate')
281
- );
282
- $lb_obj = array();
283
- foreach ($options as $option => $val) {
284
- if ($val === TRUE || $val == 'on')
285
- $val = 'true';
286
- elseif ($val === FALSE || empty($val))
287
- $val = 'false';
288
- $lb_obj[] = "'{$option}':{$val}";
289
- }
290
- //Load UI Strings
291
- if ( ($strings = $this->build_strings()) && !empty($strings) )
292
- $lb_obj[] = $strings;
293
- $js_code[] = 'Lightbox.initialize({' . implode(',', $lb_obj) . '});';
294
- echo $out['script_start'] . implode('', $js_code) . $out['script_end'];
295
  }
296
 
297
  /**
298
- * Build JS object of UI strings when initializing lightbox
299
- * @return string JS object of UI strings
300
  */
301
- function build_strings() {
302
- $ret = '';
303
- $prefix = 'txt_';
304
- $opt_strings = array_filter(array_keys($this->options_default), create_function('$opt', 'return ( strpos($opt, "' . $prefix . '") === 0 );'));
305
- if ( $opt_strings ) {
306
- $strings = array();
307
- foreach ( $opt_strings as $key ) {
308
- $name = substr($key, strlen($prefix));
309
- $strings[] = "'" . $name . "':'" . $this->get_option_value($key) . "'";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  }
311
- $ret = "'strings':{" . implode(',', $strings) . "}";
312
  }
313
- return $ret;
314
  }
 
 
315
 
316
- /*-** Admin **-*/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
  /**
319
- * Adds custom links below plugin on plugin listing page
320
- * @param $actions
321
- * @param $plugin_file
322
- * @param $plugin_data
323
- * @param $context
324
- */
325
- function admin_plugin_action_links($actions, $plugin_file, $plugin_data, $context) {
326
- //Add link to settings (only if active)
327
- if ( is_plugin_active($this->util->get_plugin_base_name()) ) {
328
- $settings = __('Settings');
329
- $reset = __('Reset');
330
- $reset_confirm = "'" . __('Are you sure you want to reset your settings?') . "'";
331
- $action = $this->add_prefix('reset');
332
- $reset_link = wp_nonce_url(add_query_arg('action', $action, remove_query_arg(array($this->add_prefix('action'), 'action'), $_SERVER['REQUEST_URI'])), $action);
333
- array_unshift($actions, '<a class="delete" href="options-media.php#' . $this->admin_get_settings_section() . '" title="' . $settings . '">' . $settings . '</a>');
334
- array_splice($actions, 1, 0, '<a id="' . $this->add_prefix('reset') . '" href="' . $reset_link . '" onclick="return confirm(' . $reset_confirm . ');" title="' . $reset . '">' . $reset . '</a>');
335
- }
336
- return $actions;
337
  }
338
 
339
  /**
340
- * Reset plugin settings
341
- * Redirects to referring page upon completion
 
 
342
  */
343
- function admin_reset() {
344
- //Validate user
345
- if ( ! current_user_can('activate_plugins') || ! check_admin_referer($this->add_prefix('reset')) )
346
- wp_die(__('You do not have sufficient permissions to manage plugins for this blog.'));
347
- $action = 'reset';
348
- if ( isset($_REQUEST['action']) && $this->add_prefix($action) == $_REQUEST['action'] ) {
349
- //Reset settings
350
- $this->reset_options(true);
351
- $uri = remove_query_arg(array('_wpnonce', 'action'), add_query_arg(array($this->add_prefix('action') => $action), $_SERVER['REQUEST_URI']));
352
- //Redirect user
353
- wp_redirect($uri);
354
- exit;
355
  }
 
356
  }
357
 
 
 
358
  /**
359
- * Displays notices for admin operations
 
 
 
 
360
  */
361
- function admin_notices() {
362
- if ( is_admin() && isset($_REQUEST[$this->add_prefix('action')]) ) {
363
- $action = $_REQUEST[$this->add_prefix('action')];
364
- $msg = null;
365
- if ( $action ) {
366
- switch ( $action ) {
367
- case 'reset':
368
- $msg = "Simple Lightbox's settings have been <strong>reset</strong>";
369
- break;
370
- }
371
- if ( ! is_null($msg) ) {
372
- ?>
373
- <div id="message" class="updated fade"><p><?php _e($msg); ?></p></div>
374
- <?php
 
 
 
 
 
375
  }
376
  }
377
  }
 
 
378
  }
379
 
380
  /**
381
- * Adds settings section for Lightbox functionality
382
- * Section is added to Settings > Media Admin menu
 
 
 
 
 
 
 
383
  */
384
- function admin_settings() {
385
- $page = $this->options_admin_page;
386
- $form = $this->options_admin_form;
387
- $curr = basename($_SERVER['SCRIPT_NAME']);
388
- if ( $curr != $page && $curr != $form ) {
389
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  }
391
-
392
- $page = 'media';
393
- $section = $this->get_prefix();
394
- //Section
395
- add_settings_section($section, '<span id="' . $this->admin_get_settings_section() . '">' . __('Lightbox Settings') . '</span>', $this->m('admin_section'), $page);
396
- //Fields
397
- foreach ($this->options_default as $key => $defaults) {
398
- $id = $this->add_prefix($key);
399
- $func = 'admin_field_' . $key;
400
- $label = ( isset($defaults[1]) ) ? $defaults[1] : '';
401
- $callback = ( method_exists($this, $func) ) ? $this->m($func) : $this->m('admin_field_default');
402
- $args = array('opt' => $key);
403
- //Check if option is a section header
404
- if ( ! is_array($defaults) ) {
405
- $label = '<h4 class="subhead">' . $defaults . '</h4>';
406
- $callback = $this->m('admin_field_header');
407
- } elseif ( is_null(get_option($id, null)) ) {
408
- //Add option to DB if not yet set
409
- $args['label_for'] = $id;
410
- update_option($id, htmlentities2($defaults[0]));
411
- }
412
- add_settings_field($id, __($label), $callback, $page, $section, $args);
413
- register_setting($page, $id);
414
  }
 
415
  }
416
-
417
- function admin_enqueue_files() {
418
- if ( is_admin() && basename($_SERVER['SCRIPT_NAME']) == $this->options_admin_page ) {
419
- wp_enqueue_style($this->add_prefix('admin_styles'), $this->util->get_file_url('css/admin.css'));
 
 
 
 
 
 
 
 
 
 
420
  }
 
 
 
 
 
 
 
421
  }
422
 
423
  /**
424
- * Get ID of settings section on admin page
425
- * @return string ID of settings section
 
 
426
  */
427
- function admin_get_settings_section() {
428
- return $this->add_prefix('settings');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  }
430
 
431
  /**
432
- * Placeholder function for lightbox admin settings
433
- * Required because setting init function requires a callback
434
- */
435
- function admin_section() { }
436
-
437
- /**
438
- * General field builder
439
- * @param string $option Option name to build field for
440
- * @param string $format Field markup (using sprintf specifiers)
441
- * @param string $type (optional) Type of field being build (e.g. checkbox, text, etc.)
442
- * Specifiers:
443
- * 1. Field ID
444
- * 2. Field Value
445
- * 3. Field Default Value (formatted)
446
- * 4. Field Type
447
- */
448
- function admin_the_field($option, $format = '', $type = '') {
449
- $opt = $this->get_option($option);
450
- $format_default = '<input id="%1$s" name="%1$s" %4$s class="code" /> (Default: %3$s)';
451
- if ( empty($format) && $format !== false )
452
- $format = $format_default;
453
- if ( empty($type) || !is_string($type) ) {
454
- $type_default = 'text';
455
- $type = ( is_bool($opt->value_default) ) ? 'checkbox' : $type_default;
456
- }
457
- //Adjust type and value formatting based on type
458
- switch ( $type ) {
459
- case 'checkbox' :
460
- if ( $opt->value )
461
- $opt->attr['checked'] = 'checked';
462
- break;
463
- case 'text' :
464
- if ( $format == $format_default )
465
- $format = str_replace('%4$s', '%4$s value="%2$s"', $format);
466
- break;
467
- }
468
- $opt->attr['type'] = $type;
469
- //Build attribute string
470
- $attr = '';
471
- if ( ! empty($opt->attr) ) {
472
- $attr = $this->util->build_attribute_string($opt->attr);
473
  }
474
-
475
- echo sprintf($format, $opt->id, htmlentities($opt->value), $opt->value_default_formatted, $attr);
 
 
476
  }
477
 
478
  /**
479
- * Builds header for settings subsection
480
- * @param array $args Arguments set in admin_settings
 
 
 
 
 
 
481
  */
482
- function admin_field_header($args) {
483
- $opt = ( isset($args['opt']) ) ? $args['opt'] : '';
484
- $this->admin_the_field($opt, false, 'header');
 
 
 
 
 
 
 
 
485
  }
486
 
487
  /**
488
- * Default field output generator
489
- * @param array $args Arguments set in admin_settings
490
  */
491
- function admin_field_default($args = array()) {
492
- $opt = ( isset($args['opt']) ) ? $args['opt'] : '';
493
- $this->admin_the_field($opt);
 
 
 
 
 
 
 
 
 
 
494
  }
495
- }
496
-
497
- ?>
1
  <?php
2
 
 
 
3
  /**
4
+ * Model (Core functionality)
5
  * @package Simple Lightbox
6
  * @author Archetyped
7
  */
9
 
10
  /*-** Properties **-*/
11
 
12
+ protected $model = true;
13
+
14
+ /* Files */
15
+
16
+ var $scripts = array (
17
+ 'core' => array (
18
+ 'file' => 'client/js/lib.core.js',
19
+ 'deps' => 'jquery',
20
+ 'enqueue' => false,
21
+ 'in_footer' => true,
22
+ ),
23
+ 'view' => array (
24
+ 'file' => 'client/js/lib.view.js',
25
+ 'deps' => array('jquery', '[core]'),
26
+ 'context' => array( array('public', '[is_enabled]') ),
27
+ 'in_footer' => true,
28
+ ),
29
+ );
30
+
31
  /**
32
+ * Fields
33
+ * @var SLB_Fields
34
  */
35
+ public $fields = null;
36
 
37
  /**
38
+ * Themes collection
39
+ * @var SLB_Themes
40
+ */
41
+ var $themes = null;
42
+
43
+ /**
44
+ * Content types
45
+ * @var SLB_Content_Handlers
46
  */
47
+ var $handlers = null;
48
 
49
  /**
50
+ * Template tags
51
+ * @var SLB_Template_Tags
52
+ */
53
+ var $template_tags = null;
54
+
55
+ /**
56
+ * Properties for media attachments in current request
57
+ * > Key (string) Attachment URI
58
+ * > Value (assoc-array) Attachment properties (url, etc.)
59
+ * > source: Source URL
60
  * @var array
61
  */
62
+ var $media_items = array();
63
+
64
+ /**
65
+ * Raw media items
66
+ * Used for populating media object on client-side
67
+ * > Key: Item URI
68
+ * > Value: Associative array of media properties
69
+ * > type: Item type (Default: null)
70
+ * > id: Item ID (Default: null)
71
+ * @var array
72
+ */
73
+ private $media_items_raw = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ /* Widget properties */
76
 
77
+ /**
78
+ * Widget callback key
79
+ * @var string
80
+ */
81
+ var $widget_callback = 'callback';
82
 
83
+ /**
84
+ * Key to use to store original callback
85
+ * @var string
86
+ */
87
+ var $widget_callback_orig = 'callback_orig';
88
+
89
+ /**
90
+ * Used to track if widget is currently being processed or not
91
+ * @var bool
92
+ */
93
+ var $widget_processing = false;
94
+
95
+ /**
96
+ * Constructor
97
+ */
98
  function __construct() {
99
  parent::__construct();
100
+ //Init instances
101
+ $this->fields = new SLB_Fields();
102
+ $this->themes = new SLB_Themes($this);
103
+ if ( !is_admin() ) {
104
+ $this->template_tags = new SLB_Template_Tags($this);
105
+ }
106
  }
107
 
108
+ /* Init */
 
 
109
 
110
+ /**
111
+ * Register hooks
112
+ * @uses parent::_hooks()
113
+ */
114
+ protected function _hooks() {
115
+ parent::_hooks();
116
+
117
  /* Admin */
118
+ add_action('admin_menu', $this->m('admin_menus'));
 
 
 
 
 
 
119
 
120
  /* Client-side */
121
+
122
+ //Init lightbox
123
+ $priority = $this->util->priority('low');
124
+ add_action('wp_footer', $this->m('client_init'), $this->util->priority('client_footer_output'));
125
+ add_action('wp_footer', $this->m('client_footer'), $priority);
126
+ //Link activation
127
+ add_filter('the_content', $this->m('activate_links'), $priority);
128
+ //Gallery wrapping
129
+ add_filter('the_content', $this->m('gallery_wrap'), 1);
130
+ add_filter('the_content', $this->m('gallery_unwrap'), $priority + 1);
131
+
132
+ /* Widgets */
133
+ add_filter('sidebars_widgets', $this->m('sidebars_widgets'));
134
  }
135
 
136
+ /**
137
+ * Init options
138
+ *
139
+ */
140
+ protected function _options() {
141
+ //Setup options
142
+ $opts = array (
143
+ 'groups' => array (
144
+ 'activation' => array ( 'title' => __('Activation', 'simple-lightbox'), 'priority' => 10),
145
+ 'grouping' => array ( 'title' => __('Grouping', 'simple-lightbox'), 'priority' => 20),
146
+ 'ui' => array ( 'title' => __('UI', 'simple-lightbox'), 'priority' => 30),
147
+ 'labels' => array ( 'title' => __('Labels', 'simple-lightbox'), 'priority' => 40),
148
+ ),
149
+ 'items' => array (
150
+ 'enabled' => array('title' => __('Enable Lightbox Functionality', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 10)),
151
+ 'enabled_home' => array('title' => __('Enable on Home page', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 20)),
152
+ 'enabled_post' => array('title' => __('Enable on Posts', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 30)),
153
+ 'enabled_page' => array('title' => __('Enable on Pages', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 40)),
154
+ 'enabled_archive' => array('title' => __('Enable on Archive Pages (tags, categories, etc.)', 'simple-lightbox'), 'default' => true, 'group' => array('activation', 50)),
155
+ 'enabled_widget' => array('title' => __('Enable for Widgets', 'simple-lightbox'), 'default' => false, 'group' => array('activation', 60)),
156
+ 'group_links' => array('title' => __('Group items (for displaying as a slideshow)', 'simple-lightbox'), 'default' => true, 'group' => array('grouping', 10)),
157
+ 'group_post' => array('title' => __('Group items by Post (e.g. on pages with multiple posts)', 'simple-lightbox'), 'default' => true, 'group' => array('grouping', 20)),
158
+ 'group_gallery' => array('title' => __('Group gallery items separately', 'simple-lightbox'), 'default' => false, 'group' => array('grouping', 30)),
159
+ 'group_widget' => array('title' => __('Group widget items separately', 'simple-lightbox'), 'default' => false, 'group' => array('grouping', 40)),
160
+ 'ui_autofit' => array('title' => __('Resize lightbox to fit in window', 'simple-lightbox'), 'default' => true, 'group' => array('ui', 10), 'in_client' => true),
161
+ 'ui_animate' => array('title' => __('Enable animations', 'simple-lightbox'), 'default' => true, 'group' => array('ui', 20), 'in_client' => true),
162
+ 'slideshow_autostart' => array('title' => __('Start Slideshow Automatically', 'simple-lightbox'), 'default' => true, 'group' => array('ui', 30), 'in_client' => true),
163
+ 'slideshow_duration' => array('title' => __('Slide Duration (Seconds)', 'simple-lightbox'), 'default' => '6', 'attr' => array('size' => 3, 'maxlength' => 3), 'group' => array('ui', 40), 'in_client' => true),
164
+ 'group_loop' => array('title' => __('Loop through items', 'simple-lightbox'),'default' => true, 'group' => array('ui', 50), 'in_client' => true),
165
+ '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),
166
+ 'txt_loading' => array('title' => __('Loading indicator', 'simple-lightbox'), 'default' => 'Loading', 'group' => array('labels', 20)),
167
+ 'txt_close' => array('title' => __('Close button', 'simple-lightbox'), 'default' => 'Close', 'group' => array('labels', 10)),
168
+ 'txt_nav_next' => array('title' => __('Next Item button', 'simple-lightbox'), 'default' => 'Next', 'group' => array('labels', 30)),
169
+ 'txt_nav_prev' => array('title' => __('Previous Item button', 'simple-lightbox'), 'default' => 'Previous', 'group' => array('labels', 40)),
170
+ 'txt_slideshow_start' => array('title' => __('Start Slideshow button', 'simple-lightbox'), 'default' => 'Start slideshow', 'group' => array('labels', 50)),
171
+ 'txt_slideshow_stop' => array('title' => __('Stop Slideshow button', 'simple-lightbox'),'default' => 'Stop slideshow', 'group' => array('labels', 60)),
172
+ 'txt_group_status' => array('title' => __('Slideshow status format', 'simple-lightbox'), 'default' => 'Item %current% of %total%', 'group' => array('labels', 70))
173
+ ),
174
+ 'legacy' => array (
175
+ 'header_activation' => null,
176
+ 'header_enabled' => null,
177
+ 'header_strings' => null,
178
+ 'header_ui' => null,
179
+ 'activate_attachments' => null,
180
+ 'validate_links' => null,
181
+ 'enabled_compat' => null,
182
+ 'enabled_single' => array('enabled_post', 'enabled_page'),
183
+ 'enabled_caption' => null,
184
+ 'enabled_desc' => null,
185
+ 'ui_enabled_caption' => null,
186
+ 'ui_caption_src' => null,
187
+ 'ui_enabled_desc' => null,
188
+ 'caption_src' => null,
189
+ 'animate' => 'ui_animate',
190
+ 'overlay_opacity' => 'ui_overlay_opacity',
191
+ 'loop' => 'group_loop',
192
+ 'autostart' => 'slideshow_autostart',
193
+ 'duration' => 'slideshow_duration',
194
+ 'txt_numDisplayPrefix' => null,
195
+ 'txt_numDisplaySeparator' => null,
196
+ 'txt_closeLink' => 'txt_link_close',
197
+ 'txt_nextLink' => 'txt_link_next',
198
+ 'txt_prevLink' => 'txt_link_prev',
199
+ 'txt_startSlideshow' => 'txt_slideshow_start',
200
+ 'txt_stopSlideshow' => 'txt_slideshow_stop',
201
+ 'txt_loadingMsg' => 'txt_loading',
202
+ 'txt_link_next' => 'txt_nav_next',
203
+ 'txt_link_prev' => 'txt_nav_prev',
204
+ 'txt_link_close' => 'txt_close',
205
+ )
206
+ );
207
+
208
+ parent::_options($opts);
209
  }
210
+
211
+ /* Methods */
212
 
213
+ /*-** Admin **-*/
214
+
215
  /**
216
+ * Add admin menus
217
+ * @uses this->admin->add_theme_page
218
  */
219
+ function admin_menus() {
220
+ $options_labels = array(
221
+ 'menu' => __('Lightbox', 'simple-lightbox'),
222
+ 'header' => __('Lightbox Settings', 'simple-lightbox'),
223
+ 'plugin_action' => __('Settings', 'simple-lightbox')
224
+ );
225
+
226
+ $labels_reset = array (
227
+ 'title' => __('Reset', 'simple-lightbox'),
228
+ 'confirm' => __('Are you sure you want to reset settings?', 'simple-lightbox'),
229
+ 'success' => __('Settings have been reset', 'simple-lightbox'),
230
+ 'failure' => __('Settings were not reset', 'simple-lightbox')
231
+ );
232
+
233
+ $this->admin->add_theme_page('options', $options_labels, $this->options);
234
+ $this->admin->add_reset('reset', $labels_reset, $this->options);
235
  }
236
+
237
+ /*-** Functionality **-*/
238
 
239
  /**
240
  * Checks whether lightbox is currently enabled/disabled
241
  * @return bool TRUE if lightbox is currently enabled, FALSE otherwise
242
  */
243
  function is_enabled($check_request = true) {
244
+ $ret = ( $this->options->get_bool('enabled') && !is_feed() ) ? true : false;
245
  if ( $ret && $check_request ) {
246
  $opt = '';
247
  //Determine option to check
248
  if ( is_home() )
249
  $opt = 'home';
250
+ elseif ( is_singular() ) {
251
+ $opt = ( is_page() ) ? 'page' : 'post';
252
+ }
253
  elseif ( is_archive() || is_search() )
254
  $opt = 'archive';
255
  //Check option
256
+ if ( !empty($opt) && ( $opt = 'enabled_' . $opt ) && $this->options->has($opt) ) {
257
+ $ret = $this->options->get_bool($opt);
258
  }
259
  }
260
  return $ret;
261
  }
262
 
263
  /**
264
+ * Scans post content for image links and activates them
 
 
 
 
265
  *
266
+ * Lightbox will not be activated for feeds
267
+ * @param $content
268
+ * @return string Post content
269
+ */
270
+ function activate_links($content) {
271
+ //Activate links only if enabled
272
+ if ( !$this->is_enabled() ) {
273
+ return $content;
274
+ }
275
+
276
+ $groups = array();
277
+ $w = $this->group_get_wrapper();
278
+ $g_ph_f = '[%s]';
279
+
280
+ //Strip groups
281
+ if ( $this->options->get_bool('group_gallery') ) {
282
+ $groups = array();
283
+ static $g_idx = 1;
284
+ $g_end_idx = 0;
285
+ //Iterate through galleries
286
+ while ( ($g_start_idx = strpos($content, $w->open, $g_end_idx)) && $g_start_idx !== false
287
+ && ($g_end_idx = strpos($content, $w->close, $g_start_idx)) && $g_end_idx != false ) {
288
+ $g_start_idx += strlen($w->open);
289
+ //Extract gallery content & save for processing
290
+ $g_len = $g_end_idx - $g_start_idx;
291
+ $groups[$g_idx] = substr($content, $g_start_idx, $g_len);
292
+ //Replace content with placeholder
293
+ $g_ph = sprintf($g_ph_f, $g_idx);
294
+ $content = substr_replace($content, $g_ph, $g_start_idx, $g_len);
295
+ //Increment gallery count
296
+ $g_idx++;
297
+ //Update end index
298
+ $g_end_idx = $g_start_idx + strlen($w->open);
299
+ }
300
+ }
301
+
302
+ //General link processing
303
+ $content = $this->process_links($content);
304
+
305
+ //Reintegrate Groups
306
+ foreach ( $groups as $group => $g_content ) {
307
+ $g_ph = $w->open . sprintf($g_ph_f, $group) . $w->close;
308
+ //Skip group if placeholder does not exist in content
309
+ if ( strpos($content, $g_ph) === false ) {
310
+ continue;
311
+ }
312
+ //Replace placeholder with processed content
313
+ $content = str_replace($g_ph, $w->open . $this->process_links($g_content, 'gallery_' . $group) . $w->close, $content);
314
+ }
315
+ return $content;
316
  }
317
 
318
  /**
319
+ * Process links in content
320
+ * @global obj $wpdb DB instance
321
+ * @global obj $post Current post
322
+ * @param string $content Text containing links
323
+ * @param string (optional) $group Group to add links to (Default: none)
324
+ * @return string Content with processed links
325
  */
326
+ function process_links($content, $group = null) {
327
+ //Validate
328
+ if ( !is_string($content) || empty($content) ) {
329
+ return $content;
330
+ }
331
+ //Extract links
332
+ $links = $this->get_links($content, true);
333
+ //Do not process content without links
334
+ if ( empty($links) ) {
335
+ return $content;
336
+ }
337
+ //Process links
338
+ $protocol = array('http://', 'https://');
339
+ $domain = str_replace($protocol, '', strtolower(get_bloginfo('url')));
340
+ $qv_att = 'attachment_id';
341
+
342
+ //Setup group properties
343
+ $g_props = (object) array(
344
+ 'enabled' => $this->options->get_bool('group_links'),
345
+ 'attr' => 'group',
346
+ 'base' => '',
347
+ 'legacy_prefix' => 'lightbox[',
348
+ 'legacy_suffix' => ']'
349
+ );
350
+ if ( $g_props->enabled ) {
351
+ $g_props->base = ( is_scalar($group) ) ? trim(strval($group)) : '';
352
+ }
353
+
354
+ //Initialize content handlers
355
+ if ( !( $this->handlers instanceof SLB_Content_Handlers ) ) {
356
+ $this->handlers = new SLB_Content_Handlers($this);
357
+ }
358
+
359
+ //Iterate through links & add lightbox if necessary
360
+ foreach ( $links as $link ) {
361
+ //Init vars
362
+ $pid = 0;
363
+ $link_new = $link;
364
+ $internal = false;
365
+ $q = null;
366
+ $uri = (object) array('raw' => '', 'base' => '', 'source' => '');
367
+ $type = false;
368
+
369
+ //Parse link attributes
370
+ $attrs = $this->util->parse_attribute_string($link_new, array('href' => ''));
371
+ $attrs_legacy = ( isset($attrs['rel']) && !empty($attrs['rel']) ) ? explode(' ', trim($attrs['rel'])) : array();
372
+ //Get URI
373
+ $uri->raw = $uri->base = $uri->source = $attrs['href'];
374
+
375
+ //Stop processing invalid links
376
+ if ( empty($uri->raw) //Empty
377
+ || 0 === strpos($uri->raw, '#') //Invalid
378
+ || $this->has_attribute($attrs, 'active', false) //Prev-processed
379
+ || in_array($this->add_prefix('off'), $attrs_legacy) //Disabled
380
+ ) {
381
+ continue;
382
+ }
383
+
384
+ //Process legacy attributes
385
+ if ( !empty($attrs_legacy) ) {
386
+ //Group
387
+ if ( $g_props->enabled ) {
388
+ foreach ( $attrs_legacy as $attr ) {
389
+ if ( 0 === strpos($attr, $g_props->legacy_prefix) && substr($attr, -1) == $g_props->legacy_suffix ) {
390
+ $this->set_attribute($attrs, $g_props->attr, substr($attr, strlen($g_props->legacy_prefix), -1));
391
+ break;
392
+ }
393
+ }
394
+ unset($attr);
395
+ }
396
+ }
397
+
398
+ //Check if item links to internal media (attachment)
399
+ $uri_dom = str_replace($protocol, '', strtolower($uri->raw));
400
+ if ( strpos($uri_dom, $domain) === 0 ) {
401
+ //Save URL for further processing
402
+ $internal = true;
403
+ }
404
+
405
+ //Sanitize URI
406
+ $qpos = strpos($uri->raw, '?');
407
+ if ( $qpos !== false ) {
408
+ $uri->base = substr($uri->raw, 0, $qpos);
409
+ if ( $internal ) {
410
+ //Extract query string
411
+ $q = substr($uri->raw, $qpos + 1);
412
+ //Check for attachment ID
413
+ if ( strpos($q, $qv_att . '=') !== false ) {
414
+ //Parse query string
415
+ wp_parse_str($q, $q);
416
+ //Strip other variables from query string
417
+ $uri->base = add_query_arg($qv_att, $q[$qv_att], $uri->base);
418
+ }
419
+ }
420
+ }
421
+
422
+ //Get source URI
423
+ $uri->source = $uri->base;
424
+ if ( $internal && is_local_attachment($uri->source) ) {
425
+ $pid = url_to_postid($uri->base);
426
+ $src = wp_get_attachment_url($pid);
427
+ if ( !!$src ) {
428
+ $uri->source = $src;
429
+ }
430
+ unset($src);
431
+ }
432
+
433
+ /* Determine link type */
434
+
435
+ //Check if URI has already been processed
436
+ if ( $this->media_item_cached($uri->base) ) {
437
+ $i = $this->get_cached_media_item($uri->base);
438
+ $type = $i->type;
439
+ }
440
+
441
+ //Get handler match
442
+ else {
443
+ $handler = $this->handlers->match($uri->source);
444
+ if ( !!$handler ) {
445
+ $type = $handler->get_id();
446
+ }
447
+ }
448
+
449
+ //Stop processing if link type not valid
450
+ if ( !$type ) {
451
+ continue;
452
+ }
453
+
454
+ //Set group (if necessary)
455
+ if ( $g_props->enabled ) {
456
+ //Get preset group attribute
457
+ $g = ( $this->has_attribute($attrs, $g_props->attr) ) ? $this->get_attribute($attrs, $g_props->attr) : '';
458
+ if ( is_string($g) && ($g = trim($g)) && !empty($g) ) {
459
+ $group = $g;
460
+ } else {
461
+ $group = $g_props->base;
462
+ }
463
+ //Group links by post?
464
+ if ( !$this->widget_processing && $this->options->get_bool('group_post') ) {
465
+ global $post;
466
+ $group = ( !empty($group) ) ? implode('_', array($post->ID, $group)) : $post->ID;
467
+ }
468
+ //Default group
469
+ if ( empty($group) ) {
470
+ $group = $this->get_prefix();
471
+ }
472
+
473
+ //Set group attribute
474
+ $this->set_attribute($attrs, $g_props->attr, $group);
475
+ unset($g);
476
+ }
477
+
478
+ //Activate link
479
+ $this->set_attribute($attrs, 'active');
480
+
481
+ //Process internal links
482
+ if ( $internal ) {
483
+ //Mark as internal
484
+ $this->set_attribute($attrs, 'internal', $pid);
485
+ }
486
+
487
+ //Cache item attributes
488
+ $this->cache_media_item($uri, $type, $internal, $pid);
489
+
490
+ //Update link in content
491
+ $link_new = '<a ' . $this->util->build_attribute_string($attrs) . '>';
492
+ $content = str_replace($link, $link_new, $content);
493
+ }
494
+ return $content;
495
  }
496
+
497
  /**
498
+ * Retrieve HTML links in content
499
+ * @param string $content Content to get links from
500
+ * @param bool (optional) $unique Remove duplicates from returned links (Default: FALSE)
501
+ * @return array Links in content
502
  */
503
+ function get_links($content, $unique = false) {
504
+ $rgx = "/\<a[^\>]+href=.*?\>/i";
505
+ $links = array();
506
+ preg_match_all($rgx, $content, $links);
507
+ $links = $links[0];
508
+ if ( $unique )
509
+ $links = array_unique($links);
510
+ return $links;
511
  }
512
 
513
  /**
514
+ * Sets options/settings to initialize lightbox functionality on page load
515
+ * @return void
516
+ */
517
+ function client_init() {
518
+ if ( ! $this->is_enabled() ) {
519
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  }
521
+ echo PHP_EOL . '<!-- SLB -->' . PHP_EOL;
522
+ //Get options
523
+ $options = $this->options->build_client_output();
524
+
525
+ //Load UI Strings
526
+ if ( ($labels = $this->build_labels()) && !empty($labels) ) {
527
+ $options['ui_labels'] = $labels;
528
+ }
529
+
530
+ //Build client output
531
+ echo $this->util->build_script_element($this->util->call_client_method('View.init', $options), 'init', true, true);
532
+ echo '<!-- /SLB -->' . PHP_EOL;
533
  }
 
 
534
 
535
  /**
536
+ * Output code in footer
537
+ * > Media attachment URLs
538
+ * @uses `_wp_attached_file` to match attachment ID to URI
539
+ * @uses `_wp_attachment_metadata` to retrieve attachment metadata
540
  */
541
+ function client_footer() {
542
+ echo '<!-- X-M -->';
543
+ //Stop if not enabled
544
+ if ( !$this->is_enabled() ) {
545
+ return;
546
+ }
547
+ echo '<!-- SLB-M -->' . PHP_EOL;
548
+
549
+ $client_out = array();
550
+
551
+ /* Load cached media */
552
+ if ( $this->has_cached_media_items() ) {
553
+ global $wpdb;
554
+
555
+ $this->media_items = array();
556
+ $props = array('id', 'type', 'description', 'title', 'source', 'caption');
557
+ $props = (object) array_combine($props, $props);
558
+ $props_map = array('description' => 'post_content', 'title' => 'post_title', 'caption' => 'post_excerpt');
559
+
560
+ //Separate media into buckets by type
561
+ $m_bucket = array();
562
+ $m_internals = array();
563
+ $type = $id = null;
564
+
565
+ $m_items = $this->media_items = $this->get_cached_media_items();
566
+ foreach ( $m_items as $uri => $p ) {
567
+ $type = $p->{$props->type};
568
+ //Initialize bucket (if necessary)
569
+ if ( !isset($m_bucket[$type]) ) {
570
+ $m_bucket[$type] = array();
571
+ }
572
+ //Add item to bucket
573
+ $m_bucket[$type][$uri] =& $m_items[$uri];
574
+ //Set aside internal links for additional processing
575
+ if ( $p->internal && !isset($m_internals[$uri]) ) {
576
+ $m_internals[$uri] =& $m_items[$uri];
577
+ }
578
+ }
579
+ unset($uri, $p);
580
+
581
+ //Process internal links
582
+ if ( !empty($m_internals) ) {
583
+ $uris_base = array();
584
+ $uri_prefix = wp_upload_dir();
585
+ $uri_prefix = $this->util->normalize_path($uri_prefix['baseurl'], true);
586
+ foreach ( $m_internals as $uri => $p ) {
587
+ //Prepare internal links
588
+ if ( !$p->id && strpos($p->source, $uri_prefix) === 0 ) {
589
+ $uris_base[str_replace($uri_prefix, '', $p->source)] = $uri;
590
+ }
591
+ }
592
+ unset($uri, $p);
593
+
594
+ //Retrieve attachment IDs
595
+ $uris_flat = "('" . implode("','", array_keys($uris_base)) . "')";
596
+ $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));
597
+ $pids = $wpdb->get_results($q);
598
+ //Match IDs to URIs
599
+ if ( $pids ) {
600
+ foreach ( $pids as $pd ) {
601
+ $file =& $pd->meta_value;
602
+ if ( isset($uris_base[$file]) ) {
603
+ $m_internals[ $uris_base[$file] ]->{$props->id} = absint($pd->post_id);
604
+ }
605
+ }
606
+ }
607
+ //Destroy worker vars
608
+ unset($uris_base, $uris_flat, $q, $pids, $pd);
609
+ }
610
+
611
+ //Process items with attachment IDs
612
+ $pids = array();
613
+ foreach ( $m_items as $uri => $p ) {
614
+ //Add post ID to query
615
+ if ( !!$p->id ) {
616
+ //Create array for ID (support multiple URIs per ID)
617
+ if ( !isset($pids[$p->id]) ) {
618
+ $pids[$p->id] = array();
619
+ }
620
+ //Add URI to ID
621
+ $pids[$p->id][] = $uri;
622
+ }
623
+ }
624
+ unset($uri, $p);
625
+
626
+ //Retrieve attachment properties
627
+ if ( !empty($pids) ) {
628
+ $pids_flat = array_keys($pids);
629
+ //Retrieve attachment post data
630
+ $atts = get_posts(array('post_type' => 'attachment', 'include' => $pids_flat));
631
+
632
+ //Process attachments
633
+ if ( $atts ) {
634
+ //Retrieve attachment metadata
635
+ $pids_flat = "('" . implode("','", $pids_flat) . "')";
636
+ $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)));
637
+ //Restructure metadata array by post ID
638
+ if ( $atts_meta ) {
639
+ $meta = array();
640
+ foreach ( $atts_meta as $att_meta ) {
641
+ $meta[$att_meta->post_id] = $att_meta->meta_value;
642
+ }
643
+ $atts_meta = $meta;
644
+ unset($meta);
645
+ } else {
646
+ $atts_meta = array();
647
  }
648
+ $props_size = array('file', 'width', 'height');
649
+ $props_exclude = array('hwstring_small');
650
+ foreach ( $atts as $att ) {
651
+ //Set post data
652
+ $m = array();
653
+ //Remap post data to properties
654
+ foreach ( $props_map as $prop_key => $prop_source ) {
655
+ $m[$props->{$prop_key}] = $att->{$prop_source};
656
+ }
657
+ unset($prop_key, $prop_source);
658
+
659
+ //Add metadata
660
+ if ( isset($atts_meta[$att->ID]) && ($a = unserialize($atts_meta[$att->ID])) && is_array($a) ) {
661
+ //Move original size into `sizes` array
662
+ foreach ( $props_size as $d ) {
663
+ if ( !isset($a[$d]) ) {
664
+ continue;
665
+ }
666
+ $a['sizes']['original'][$d] = $a[$d];
667
+ unset($a[$d]);
668
+ }
669
+
670
+ //Strip extraneous metadata
671
+ foreach ( $props_exclude as $d ) {
672
+ if ( isset($a[$d]) ) {
673
+ unset($a[$d]);
674
+ }
675
+ }
676
+
677
+ //Merge post data & meta data
678
+ $m = array_merge($a, $m);
679
+ //Destroy worker vars
680
+ unset($a, $d);
681
+ }
682
+
683
+ //Save attachment data (post & meta) to original object(s)
684
+ foreach ( $pids[$att->ID] as $uri ) {
685
+ $this->media_items[$uri] = (object) array_merge( (array) $m_items[$uri], $m);
686
+ }
687
  }
688
  }
689
+ unset($atts, $atts_meta, $m, $a, $uri, $pids, $pids_flat);
690
+ }
691
+
692
+ //Build client output
693
+ $obj = 'View.assets';
694
+ $client_out[] = $this->util->extend_client_object($obj, $this->media_items);
695
+ }
696
+ if ( !empty($client_out) ) {
697
+ echo $this->util->build_script_element($client_out, 'footer', true, true);
698
+ }
699
+ echo PHP_EOL . '<!-- /SLB-M -->' . PHP_EOL;
700
+ }
701
+
702
+ /*-** Media **-*/
703
+
704
+ /**
705
+ * Cache media properties for later processing
706
+ * @param object $uri URI to cache
707
+ * Members
708
+ * > raw: Raw Link URI
709
+ * > base: Sanitized URI
710
+ * > source: Source URI (e.g. for attachment URIs)
711
+ * @param string $type Media type (image, attachment, etc.)
712
+ * @param int $id (optional) ID of media item (for internal items) (Default: NULL)
713
+ */
714
+ private function cache_media_item($uri, $type, $internal, $id = null) {
715
+ //Validate
716
+ if ( !is_object($uri) || !is_string($type) ) {
717
+ return false;
718
+ }
719
+ if ( !$this->media_item_cached($uri->base) ) {
720
+ //Set properties
721
+ $i = array('type' => $type, 'source' => $uri->source, 'internal' => $internal, 'id' => null, '_entries' => array());
722
+ //ID
723
+ if ( is_numeric($id) && !!$id ) {
724
+ $i['id'] = absint($id);
725
  }
726
+ //Cache media item
727
+ $this->media_items_raw[$uri->base] = (object) $i;
728
+ }
729
+ //Add URI variants
730
+ $entries =& $this->media_items_raw[$uri->base]->_entries;
731
+ if ( !in_array($uri->raw, $entries) ) {
732
+ $entries[] = $uri->raw;
733
  }
 
734
  }
735
 
736
  /**
737
+ * Checks if media item has already been cached
738
+ * @param string $uri URI of media item
739
+ * @return boolean Whether media item has been cached
740
  */
741
+ private function media_item_cached($uri) {
742
+ return ( is_string($uri) && isset($this->media_items_raw[$uri]) ) ? true : false;
 
 
 
743
  }
744
 
745
  /**
746
+ * Retrieve cached media item
747
+ * @param string $uri Media item URI
748
+ * @return object|null Media item properties (NULL if not set)
749
  */
750
+ private function get_cached_media_item($uri) {
751
+ return ( $this->media_item_cached($uri) ) ? $this->media_items_raw[$uri] : null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752
  }
753
 
754
  /**
755
+ * Retrieve cached media items
756
+ * @return array Cached media items
757
  */
758
+ private function &get_cached_media_items() {
759
+ return $this->media_items_raw;
760
+ }
761
+
762
+ /**
763
+ * Check if media items have been cached
764
+ * @return boolean
765
+ */
766
+ private function has_cached_media_items() {
767
+ return ( empty($this->media_items_raw) ) ? false : true;
768
+ }
769
+
770
+ /*-** Theme **-*/
771
+
772
+ /**
773
+ * Retrieve theme
774
+ * @param string $id ID of theme to retrieve
775
+ * @return SLB_Theme Theme instance
776
+ * @TODO Refactor
777
+ */
778
+ function get_theme($id = '') {
779
+ //Default: Get current theme if no theme specified
780
+ if ( !$this->themes->has_item($id) ) {
781
+ $id = $this->options->get_value('theme');
782
+ if ( !$this->themes->has_item($id) ) {
783
+ $id = $this->themes->get_default_id();
784
  }
 
785
  }
786
+ return $this->themes->get_item($id);
787
  }
788
+
789
+ /*-** Grouping **-*/
790
 
791
+ /**
792
+ * Builds wrapper for grouping
793
+ * @return object Wrapper properties
794
+ * > open
795
+ * > close
796
+ */
797
+ function group_get_wrapper() {
798
+ static $wrapper = null;
799
+ if ( is_null($wrapper) ) {
800
+ $start = '<';
801
+ $end = '>';
802
+ $terminate = '/';
803
+ $val = $this->add_prefix('group');
804
+ //Build properties
805
+ $wrapper = array(
806
+ 'open' => $start . $val . $end,
807
+ 'close' => $start . $terminate . $val . $end
808
+ );
809
+ //Convert to object
810
+ $wrapper = (object) $wrapper;
811
+ }
812
+ return $wrapper;
813
+ }
814
+
815
+ /**
816
+ * Wraps galleries for grouping
817
+ * @uses `the_content` Filter hook
818
+ * @uses gallery_wrap_callback to Wrap shortcodes for grouping
819
+ * @param string $content Post content
820
+ * @return string Modified post content
821
+ */
822
+ function gallery_wrap($content) {
823
+ if ( !$this->is_enabled() )
824
+ return $content;
825
+ //Stop processing if option not enabled
826
+ if ( !$this->options->get_bool('group_gallery') )
827
+ return $content;
828
+ global $shortcode_tags;
829
+ //Save default shortcode handlers to temp variable
830
+ $sc_temp = $shortcode_tags;
831
+ //Find gallery shortcodes
832
+ $shortcodes = array('gallery', 'nggallery');
833
+ $m = $this->m('gallery_wrap_callback');
834
+ $shortcode_tags = array();
835
+ foreach ( $shortcodes as $tag ) {
836
+ $shortcode_tags[$tag] = $m;
837
+ }
838
+ //Wrap gallery shortcodes
839
+ $content = do_shortcode($content);
840
+ //Restore default shortcode handlers
841
+ $shortcode_tags = $sc_temp;
842
+
843
+ return $content;
844
+ }
845
 
846
  /**
847
+ * Wraps gallery shortcodes for later processing
848
+ * @param array $attr Shortcode attributes
849
+ * @param string $content Content enclosed in shortcode
850
+ * @param string $tag Shortcode name
851
+ * @return string Wrapped gallery shortcode
852
+ */
853
+ function gallery_wrap_callback($attr, $content = null, $tag) {
854
+ //Rebuild shortcode
855
+ $sc = '[' . $tag . ' ' . $this->util->build_attribute_string($attr) . ']';
856
+ if ( !empty($content) )
857
+ $sc .= $content . '[/' . $tag .']';
858
+ //Wrap shortcode
859
+ $w = $this->group_get_wrapper();
860
+ $sc = $w->open . $sc . $w->close;
861
+ return $sc;
 
 
 
862
  }
863
 
864
  /**
865
+ * Removes wrapping from galleries
866
+ * @uses `the_content` filter hook
867
+ * @param $content Post content
868
+ * @return string Modified post content
869
  */
870
+ function gallery_unwrap($content) {
871
+ if ( !$this->is_enabled() )
872
+ return $content;
873
+ //Stop processing if option not enabled
874
+ if ( !$this->options->get_bool('group_gallery') )
875
+ return $content;
876
+ $w = $this->group_get_wrapper();
877
+ if ( strpos($content, $w->open) !== false ) {
878
+ $content = str_replace($w->open, '', $content);
879
+ $content = str_replace($w->close, '', $content);
 
 
880
  }
881
+ return $content;
882
  }
883
 
884
+ /*-** Widgets **-*/
885
+
886
  /**
887
+ * Reroute widget display handlers to internal method
888
+ * @param array $sidebar_widgets List of sidebars & their widgets
889
+ * @uses WP Hook `sidebars_widgets` to intercept widget list
890
+ * @global $wp_registered_widgets to reroute display callback
891
+ * @return array Sidebars and widgets (unmodified)
892
  */
893
+ function sidebars_widgets($sidebars_widgets) {
894
+ global $wp_registered_widgets;
895
+ static $widgets_processed = false;
896
+ if ( is_admin() || empty($wp_registered_widgets) || $widgets_processed || !is_object($this->options) || !$this->is_enabled() || !$this->options->get_bool('enabled_widget') )
897
+ return $sidebars_widgets;
898
+ $widgets_processed = true;
899
+ //Fetch active widgets from all sidebars
900
+ foreach ( $sidebars_widgets as $sb => $ws ) {
901
+ //Skip inactive widgets and empty sidebars
902
+ if ( 'wp_inactive_widgets' == $sb || empty($ws) || !is_array($ws) )
903
+ continue;
904
+ foreach ( $ws as $w ) {
905
+ if ( isset($wp_registered_widgets[$w]) && isset($wp_registered_widgets[$w][$this->widget_callback]) ) {
906
+ $wref =& $wp_registered_widgets[$w];
907
+ //Backup original callback
908
+ $wref[$this->widget_callback_orig] = $wref[$this->widget_callback];
909
+ //Reroute callback
910
+ $wref[$this->widget_callback] = $this->m('widget_callback');
911
+ unset($wref);
912
  }
913
  }
914
  }
915
+
916
+ return $sidebars_widgets;
917
  }
918
 
919
  /**
920
+ * Widget display handler
921
+ * Original widget display handler is called inside of an output buffer & links in output are processed before sending to browser
922
+ * @param array $args Widget instance properties
923
+ * @param int (optional) $widget_args Additional widget args (usually the widget's instance number)
924
+ * @see WP_Widget::display_callback() for more information
925
+ * @see sidebars_widgets() for callback modification
926
+ * @global $wp_registered_widgets
927
+ * @uses widget_process_links() to Process links in widget content
928
+ * @return void
929
  */
930
+ function widget_callback($args, $widget_args = 1) {
931
+ global $wp_registered_widgets;
932
+ $wid = ( isset($args['widget_id']) ) ? $args['widget_id'] : false;
933
+ //Stop processing if widget data invalid
934
+ if ( !$wid || !isset($wp_registered_widgets[$wid]) || !($w =& $wp_registered_widgets[$wid]) || !isset($w['id']) || $wid != $w['id'] )
935
+ return false;
936
+ //Get original callback
937
+ if ( !isset($w[$this->widget_callback_orig]) || !($cb = $w[$this->widget_callback_orig]) || !is_callable($cb) )
938
+ return false;
939
+ $params = func_get_args();
940
+ $this->widget_processing = true;
941
+ //Start output buffer
942
+ ob_start();
943
+ //Call original callback
944
+ call_user_func_array($cb, $params);
945
+ //Flush output buffer
946
+ echo $this->widget_process_links(ob_get_clean(), $wid);
947
+ $this->widget_processing = false;
948
+ }
949
+
950
+ /**
951
+ * Process links in widget content
952
+ * @param string $content Widget content
953
+ * @return string Processed widget content
954
+ * @uses process_links() to process links
955
+ */
956
+ function widget_process_links($content, $id) {
957
+ $id = ( $this->options->get_bool('group_widget') ) ? "widget_$id" : null;
958
+ return $this->process_links($content, $id);
959
+ }
960
+
961
+ /*-** Helpers **-*/
962
+
963
+ /**
964
+ * Build attribute name
965
+ * Makes sure name is only prefixed once
966
+ * @param string $name (optional) Attribute base name
967
+ * @return string Formatted attribute name
968
+ */
969
+ function make_attribute_name($name = '') {
970
+ //Validate
971
+ if ( !is_string($name) ) {
972
+ $name = '';
973
+ } else {
974
+ $name = trim($name);
975
  }
976
+ //Setup
977
+ $sep = '-';
978
+ $top = 'data';
979
+ //Generate valid name
980
+ if ( strpos($name, $top . $sep . $this->get_prefix()) !== 0 ) {
981
+ $name = $top . $sep . $this->add_prefix($name, $sep);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
982
  }
983
+ return $name;
984
  }
985
+
986
+ /**
987
+ * Set attribute to array
988
+ * Attribute is added to array if it does not exist
989
+ * @param array $attrs Array to add attribute to (Passed by reference)
990
+ * @param string $name Name of attribute to add
991
+ * @param string (optional) $value Attribute value
992
+ * @return array Updated attribute array
993
+ */
994
+ function set_attribute(&$attrs, $name, $value = true) {
995
+ //Validate
996
+ $attrs = $this->get_attributes($attrs, false);
997
+ if ( !is_string($name) || empty($name) ) {
998
+ return $attrs;
999
  }
1000
+ if ( !is_scalar($value) ) {
1001
+ $value = true;
1002
+ }
1003
+ //Add attribute
1004
+ $attrs = array_merge($attrs, array( $this->make_attribute_name($name) => strval($value) ));
1005
+
1006
+ return $attrs;
1007
  }
1008
 
1009
  /**
1010
+ * Convert attribute string into array
1011
+ * @param string $attr_string Attribute string
1012
+ * @param bool (optional) $internal Whether only internal attributes should be evaluated (Default: TRUE)
1013
+ * @return array Attributes as associative array
1014
  */
1015
+ function get_attributes($attr_string, $internal = true) {
1016
+ if ( is_string($attr_string) ) {
1017
+ $attr_string = $this->util->parse_attribute_string($attr_string);
1018
+ }
1019
+ $ret = ( is_array($attr_string) ) ? $attr_string : array();
1020
+ //Filter out external attributes
1021
+ if ( !empty($ret) && is_bool($internal) && $internal ) {
1022
+ $ret_f = array();
1023
+ foreach ( $ret as $key => $val ) {
1024
+ if ( strpos($key, $this->make_attribute_name()) == 0 ) {
1025
+ $ret_f[$key] = $val;
1026
+ }
1027
+ }
1028
+ if ( !empty($ret_f) ) {
1029
+ $ret = $ret_f;
1030
+ }
1031
+ }
1032
+
1033
+ return $ret;
1034
  }
1035
 
1036
  /**
1037
+ * Retrieve attribute value
1038
+ * @param string|array $attrs Attributes to retrieve attribute value from
1039
+ * @param string $attr Attribute name to retrieve
1040
+ * @param bool (optional) $internal Whether only internal attributes should be evaluated (Default: TRUE)
1041
+ * @return string|bool Attribute value (Default: FALSE)
1042
+ */
1043
+ function get_attribute($attrs, $attr, $internal = true) {
1044
+ $ret = false;
1045
+ //Validate
1046
+ $attrs = $this->get_attributes($attrs, $internal);
1047
+ if ( $internal ) {
1048
+ $attr = $this->make_attribute_name($attr);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1049
  }
1050
+ if ( isset($attrs[$attr]) ) {
1051
+ $ret = $attrs[$attr];
1052
+ }
1053
+ return $ret;
1054
  }
1055
 
1056
  /**
1057
+ * Checks if attribute exists
1058
+ * If supplied, the attribute's value is also validated
1059
+ * @param string|array $attrs Attributes to retrieve attribute value from
1060
+ * @param string $attr Attribute name to retrieve
1061
+ * @param mixed $value (optional) Attribute value to check for
1062
+ * @param bool $internal (optional) Whether to check only internal attributes (Default: TRUE)
1063
+ * @see get_attribute()
1064
+ * @return bool Whether or not attribute (with matching value if specified) exists
1065
  */
1066
+ function has_attribute($attrs, $attr, $value = null, $internal = true) {
1067
+ $a = $this->get_attribute($attrs, $attr, $internal);
1068
+ $ret = false;
1069
+ if ( $a !== false ) {
1070
+ $ret = true;
1071
+ //Check value
1072
+ if ( null != $value && is_scalar($value) ) {
1073
+ $ret = ( $a == strval($value) ) ? true : false;
1074
+ }
1075
+ }
1076
+ return $ret;
1077
  }
1078
 
1079
  /**
1080
+ * Build JS object of UI strings when initializing lightbox
1081
+ * @return array UI strings
1082
  */
1083
+ function build_labels() {
1084
+ $ret = array();
1085
+ //Get all UI options
1086
+ $prefix = 'txt_';
1087
+ $opt_strings = array_filter(array_keys($this->options->get_items()), create_function('$opt', 'return ( strpos($opt, "' . $prefix . '") === 0 );'));
1088
+ if ( count($opt_strings) ) {
1089
+ //Build array of UI options
1090
+ foreach ( $opt_strings as $key ) {
1091
+ $name = substr($key, strlen($prefix));
1092
+ $ret[$name] = $this->options->get_value($key);
1093
+ }
1094
+ }
1095
+ return $ret;
1096
  }
1097
+ }
 
 
readme.txt CHANGED
@@ -1,80 +1,202 @@
1
- === Plugin Name ===
2
- Contributors: archetyped
3
- Tags: lightbox, gallery, photography, images
4
- Requires at least: 3.0
5
- Tested up to: 3.0.4
6
- Stable tag: trunk
7
-
8
- A simple and customizable Lightbox for Wordpress
9
-
10
- == Description ==
11
- Simple Lightbox is a very simple and customizable lightbox that is easy to add to your Wordpress website
12
-
13
- #### Customization
14
- Options for customizing the lightbox behavior are located in the **Settings > Media** admin menu in the **Lightbox Settings** section (or just click the **Settings** link below the plugin's name when viewing the list of installed plugins)
15
-
16
- * Customizable UI Text
17
- * Enable/Disable Lightbox Functionality (Default: Enabled)
18
- * Enable Lightbox depending on Page Type (Home, Pages, Archive, etc.)
19
- * Automatically activate lightbox for links to images on page (no need to add `rel="lightbox"` attribute to link)
20
- * Group automatically-activated links (play as a slideshow)
21
- * Group image links by Post (separate slideshow for each Post on page)
22
- * Enable/Disable Lightbox resizing animation
23
- * Automatically Start Slideshow (Default: Enabled)
24
- * Slide Duration (Seconds) (Default: 6)
25
- * Loop through images (Default: Enabled)
26
- * Overlay Opacity (0 - 1) (Default: 0.8)
27
-
28
- #### Usage
29
- * The necessary Javascript and CSS files will be automatically added to the page as long as `wp_head()` is called in the theme
30
- * That's it!
31
-
32
- == Installation ==
33
-
34
- 1. Verify that your theme uses the `wp_head()` template tag (this is where the necessary files will be added to your theme output)
35
- 1. Let plugin automatically add lightbox functionality for links to images or manually add `rel="lightbox"` to any image links that you want to be displayed in a lightbox
36
-
37
- == Upgrade Notice ==
38
-
39
- No upgrade notices
40
-
41
- == Frequently Asked Questions ==
42
-
43
- Send your questions to wp@archetyped.com or post a comment on [Simple Lightbox's official page](http://archetyped.com/tools/simple-lightbox/)
44
-
45
- == Screenshots ==
46
-
47
- 1. Lightbox Customization Options
48
- 2. Customized UI Text
49
-
50
- == Changelog ==
51
- = 1.4 =
52
- * Integrated with jQuery
53
- * Javascript filesize 9x smaller
54
- * Close lightbox by clicking to left/right outside of image (an oft-requested feature)
55
- = 1.3.2 =
56
- * Added: Option to enable/disable lightbox resizing animation (thanks Maria!)
57
- = 1.3.1 =
58
- * Updated: Utilities code (internal)
59
- = 1.3 =
60
- * Added: Customizable UI label text (close, next, and prev button images can be replaced in `images` directory)
61
- * Added: Group image links by Post (separate slideshow for each post)
62
- * Added: Reset settings link on plugin listings page
63
- * Optimized: Organized settings page
64
-
65
- = 1.2.1 =
66
- * Fixed: Image title given higher precedence than Image alt (more compatible w/WP workflow)
67
-
68
- = 1.2 =
69
- * Added: Option to group automatically activated links
70
- * Optimized: Lightbox caption retrieval
71
-
72
- = 1.1 =
73
- * Added: Enable/disable lightbox functionality by page type (Home, Pages/Posts, Archive, etc.)
74
- * Added: Automatically activate lightbox functionality for image links
75
- * Added: Link to settings menu on plugin listing page
76
- * Optimized: Options menu field building
77
- * Optimized: Loading of default values for plugin options
78
- * Optimized: General code optimizations
79
- = 1.0 =
80
- * Initial release
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Simple Lightbox ===
2
+ Contributors: Archetyped
3
+ Donate link: http://gum.co/slb-donate
4
+ License: GPLv2
5
+ Tags: lightbox, gallery, photography, images, theme, template, style
6
+ Requires at least: 3.6
7
+ Tested up to: 3.6.1
8
+ Stable tag: trunk
9
+
10
+ The highly customizable lightbox for WordPress
11
+
12
+ == Description ==
13
+ Simple Lightbox is a very simple and customizable lightbox that is easy to add to your WordPress website.
14
+
15
+ #### Features
16
+ Options for customizing the lightbox behavior are located in the **Appearance > Lightbox** admin menu (or just click the **Settings** link below the plugin's name when viewing the list of installed plugins)
17
+
18
+ * Automatically activate links (no manual coding required)
19
+ * Automatically resize lightbox to fit in window
20
+ * Customize lightbox with **themes**
21
+ * Mobile-optimized responsive themes included
22
+ * Customizable lightbox animations
23
+ * Infinitely customizable with **add-ons**
24
+ * Supports WordPress **image attachment** links
25
+ * Supports links in **widgets**
26
+ * Keyboard Navigation
27
+ * Display media metadata (caption, description, etc.) in lightbox
28
+ * Enable Lightbox depending on Page Type (Home, Pages, Archive, etc.)
29
+ * Group image links (play as a slideshow)
30
+ * Group image links by Post (separate slideshow for each post on page)
31
+
32
+ #### Usage
33
+ 1. Insert links to images/image attachments into your posts/pages
34
+
35
+ **That's it! The image will be displayed in a lightbox automatically.**
36
+
37
+ * For more usage tips, go to [Simple Lightbox's official page](http://archetyped.com/tools/simple-lightbox/)
38
+
39
+ == Installation ==
40
+
41
+ 1. Install and activate SLB
42
+ 1. Verify that your site's theme uses the `wp_head()`, `wp_footer()`, & `the_content()` template tags (standard in any professional theme)
43
+
44
+ == Upgrade Notice ==
45
+
46
+ No upgrade notices
47
+
48
+ == Frequently Asked Questions ==
49
+
50
+ Get more information on [Simple Lightbox's official page](http://archetyped.com/tools/simple-lightbox/)
51
+
52
+ == Screenshots ==
53
+
54
+ 1. Lightbox Customization Options
55
+ 2. Light Theme
56
+ 3. Dark Theme
57
+
58
+ == Changelog ==
59
+
60
+ = 2.0 =
61
+ * Completely rewritten lightbox code
62
+ * Add: Automatically resize lightbox to fit window
63
+ * Add: APIs for third-party add-ons
64
+ * Add: Flexible theme support
65
+ * Add: Flexible content handler support
66
+ * Add: Mobile-optimized responsive themes (2)
67
+ * Optimize: PHP class autoloading
68
+ * Optimize: Improved performance and compatibility
69
+ * Optimize: Full internationalization support
70
+
71
+ = 1.6 =
72
+ * Add: Widget support
73
+ * Add: WordPress 3.3 support
74
+ * Add: Localization support
75
+ * Add: Option to group gallery links separately (supports WordPress & NextGen galleries)
76
+ * Add: Upgrade notice
77
+ * Optimize: WP 3.3 compatibility
78
+ * Optimize: Improved compatibility with URI case-sensitivity
79
+ * Optimize: Activation processing
80
+ * Optimize: Image grouping
81
+ * Optimize: Image metadata loading performance
82
+ * Optimize: File loading
83
+ * Optimize: Improved safeguards against interference by bugs in other plugins
84
+ * Optimize: Link processing performance
85
+ * Optimize: Lightbox styling isolated from site styles
86
+ * Optimize: Improved link processing performance
87
+ * Optimize: Improved image metadata support
88
+ * Optimize: Improved support for HTTP/HTTPS requests
89
+ * Fix: SLB is not defined in JS (Jezz Hands)
90
+ * Fix: Boolean case-sensitivity (78 Truths)
91
+ * Fix: YouTube embed using iFrame overlaps lightbox (Elena in Hiding)
92
+ * Fix: Issue when scanning links without valid URLs (McCloskey Iteration)
93
+ * Fix: Image activation is case-sensitive (Sensitive Tanya)
94
+ * Fix: Visible lightbox overlay edges when image larger than browser window (Chibi Overlay)
95
+ * Fix: Options availability for some users
96
+ * Fix: Inconsistent loading of image metadata
97
+ * Fix: Links not fully processed when group is set manually
98
+
99
+ = 1.5.6 =
100
+ * Add: Display image description in lightbox (with HTML support)
101
+ * Add: Support for W3 Total Cache plugin
102
+ * Add: Initial support for NextGEN galleries
103
+ * Update: **Important:** [System Requirements](http://wordpress.org/about/requirements/) aligned with WP 3.2.1
104
+ * Optimize: Improved support for small images in default template
105
+ * Optimize: Support for non-English text in user options
106
+ * Optimize: Improved IE compatibility
107
+ * Optimize: Improved data handling
108
+ * Optimize: Skin loading performance
109
+ * Optimize: Skin CSS Cleanup
110
+ * Optimize: Caption support for galleries
111
+ * Optimize: Options code cleanup (Juga Sweep)
112
+ * Fix: User-defined UI text not used (Ivan gets Even (cooler))
113
+ * Fix: Options reset after update (KRazy Donna)
114
+
115
+ = 1.5.5.1 =
116
+ * Fix: Disabled links not being disabled (Disabling Sascha)
117
+
118
+ = 1.5.5 =
119
+ * Add: Distinct link activation (will not affect other lightboxes)
120
+ * Add: Backwards compatibility with legacy lightbox links (optional)
121
+ * Add: Support for WordPress 3.2
122
+ * Add: Support for links added after page load (e.g. via AJAX, etc.)
123
+ * Add: Admin option to enable/disable attachment links
124
+ * Add: Support for image attachment links
125
+ * Update: Options management overhaul
126
+ * Update: Additional WordPress 3.2 support (Gallery)
127
+ * Update: Cache-management for enqueued files
128
+ * Update: Improved UI consistency
129
+ * Update: Improved compatibility for older versions of PHP
130
+ * Update: Internal optimizations
131
+ * Update: Improved URL handling
132
+ * Fix: Improved options migration from old versions (Hutchison Migration)
133
+ * Fix: XHTML Validation (Hajo Validation)
134
+
135
+ = 1.5.4 =
136
+ * Add: Optional Link validation
137
+ * Add: Keyboard Navigation
138
+ * Add: Option to enable/disable image caption
139
+ * Add: `rel` attribute supported again
140
+ * Add: Use `slb_off` in link's `rel` attribute to disable automatic activation for link
141
+ * Fix: HTTPS compatibility (J&uuml;rgen Protocol)
142
+ * Fix: Enabling SLB on Pages issue
143
+ * Fix: Zmanu is_single
144
+ * Fix: Image order is sometimes incorrect
145
+ * Optimize: Filter double clicks
146
+ * Optimize: Separate options to enable/disable SLB on Posts and Pages
147
+ * Optimize: Better grouping support
148
+
149
+ = 1.5.3 =
150
+ * Fix: Caption may not display under certain circumstances (Caption Erin)
151
+ * Fix: Images not grouped when "separate by post" option is activated (Logical Ross)
152
+ * Update: Lightbox will not be activated for links that already have `rel` attribute set
153
+
154
+ = 1.5.2 =
155
+ * Fix: Slideshow loops out of control (Mirage of Wallentin)
156
+ * Fix: Lightbox fails when group by posts disabled (Lange Find)
157
+ * Add: Option to use the image's URI as caption when link title not set (Under UI options)
158
+
159
+ = 1.5.1 =
160
+ * Add: WP Gallery support
161
+ * Fix: Navigation hidden when only one image
162
+ * Fix: Use user-defined UI text
163
+
164
+ = 1.5 =
165
+ * Add: Theme support
166
+ * Optimize: Javascript cleanup and file size reductions
167
+ * Optimize: CSS cleanup
168
+
169
+ = 1.4 =
170
+ * Update: Integrated with jQuery
171
+ * Optimize: Javascript filesize 9x smaller
172
+ * Add: Close lightbox by clicking to left/right outside of image (an oft-requested feature)
173
+
174
+ = 1.3.2 =
175
+ * Add: Option to enable/disable lightbox resizing animation (thanks Maria!)
176
+
177
+ = 1.3.1 =
178
+ * Update: Utilities code (internal)
179
+
180
+ = 1.3 =
181
+ * Add: Customizable UI label text (close, next, and prev button images can be replaced in `images` directory)
182
+ * Add: Group image links by Post (separate slideshow for each post)
183
+ * Add: Reset settings link on plugin listings page
184
+ * Optimize: Organized settings page
185
+
186
+ = 1.2.1 =
187
+ * Fixed: Image title given higher precedence than Image alt (more compatible w/WP workflow)
188
+
189
+ = 1.2 =
190
+ * Added: Option to group automatically activated links
191
+ * Optimized: Lightbox caption retrieval
192
+
193
+ = 1.1 =
194
+ * Added: Enable/disable lightbox functionality by page type (Home, Pages/Posts, Archive, etc.)
195
+ * Added: Automatically activate lightbox functionality for image links
196
+ * Added: Link to settings menu on plugin listing page
197
+ * Optimized: Options menu field building
198
+ * Optimized: Loading of default values for plugin options
199
+ * Optimized: General code optimizations
200
+
201
+ = 1.0 =
202
+ * Initial release
screenshot-1.gif DELETED
Binary file
screenshot-2.gif DELETED
Binary file
template-tags/item/tag.item.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+ return {
3
+ render: function(item, tag) {
4
+ var dfr = $.Deferred();
5
+ var m = 'get_' + tag.get_prop();
6
+ var ret = ( this.util.is_method(item, m) ) ? item[m]() : item.get_attribute(tag.get_prop(), '');
7
+ if ( this.util.is_promise(ret) ) {
8
+ ret.done(function(output) {
9
+ dfr.resolve(output);
10
+ });
11
+ } else {
12
+ dfr.resolve(ret);
13
+ }
14
+ return dfr.promise();
15
+ }
16
+ }
17
+ })(jQuery);
template-tags/ui/tag.ui.js ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+ return {
3
+ init: function(item, tag, v) {
4
+ //Add event handlers
5
+ v.on('events-complete', function(ev, v) {
6
+ //Register event handlers
7
+
8
+ /* Close */
9
+
10
+ var close = function() {
11
+ return v.close();
12
+ };
13
+ //Close button
14
+ v.get_theme().dom_get_tag('ui', 'close').click(close);
15
+
16
+ /* Navigation */
17
+
18
+ var nav_next = function() {
19
+ v.item_next();
20
+ };
21
+
22
+ var nav_prev = function() {
23
+ v.item_prev();
24
+ };
25
+
26
+ v.get_theme().dom_get_tag('ui', 'nav_next').click(nav_next);
27
+ v.get_theme().dom_get_tag('ui', 'nav_prev').click(nav_prev);
28
+
29
+ /* Slideshow */
30
+
31
+ var slideshow_control = function() {
32
+ v.slideshow_toggle();
33
+ };
34
+
35
+ v.get_theme().dom_get_tag('ui', 'slideshow_control').click(slideshow_control);
36
+ });
37
+
38
+ v.on('slideshow-toggle', function(ev, v) {
39
+ //Update slideshow control tag
40
+ var tags = v.get_theme().get_tags('ui', 'slideshow_control');
41
+ if ( tags.length ) {
42
+ for ( var x = 0; x < tags.length; x++ ) {
43
+ tags[x].render(v.get_item()).done(function(r) {
44
+ r.tag.dom_get().html(r.output);
45
+ });
46
+ }
47
+ }
48
+ });
49
+ },
50
+ render: function(item, tag) {
51
+ //Initialize event handlers (once per viewer)
52
+ var v = item.get_viewer();
53
+ var st = ['events-init', tag.get_ns(), tag.get_name()].join('_');
54
+ var fmt = function(output) {
55
+ return output;
56
+ };
57
+ if ( !v.get_status(st) ) {
58
+ v.set_status(st);
59
+ this.call_attribute('init', item, tag, v);
60
+ }
61
+ //Process content
62
+ var dfr = $.Deferred();
63
+ var ret = this.handle_prop(tag.get_prop(), item, tag);
64
+ if ( this.util.is_promise(ret) ) {
65
+ ret.done(function(output) {
66
+ dfr.resolve(fmt(output));
67
+ });
68
+ } else {
69
+ dfr.resolve(fmt(ret));
70
+ }
71
+ return dfr.promise();
72
+ },
73
+ props: {
74
+ 'slideshow_control': function(item, tag) {
75
+ //Get slideshow status
76
+ var prop = ( item.get_viewer().slideshow_active() ) ? 'slideshow_stop' : 'slideshow_start';
77
+ return item.get_viewer().get_label(prop);
78
+ },
79
+ 'group_status': function(item, tag) {
80
+ //Handle single items
81
+ if ( item.get_group().is_single() ) {
82
+ return '';
83
+ }
84
+ //Handle groups with multiple items
85
+ var out = item.get_viewer().get_label('group_status');
86
+ var key,
87
+ ph,
88
+ delim = '%',
89
+ handlers = {
90
+ current: function() {
91
+ return item.get_group(true).get_pos() + 1;
92
+ },
93
+ total: function() {
94
+ return item.get_group().get_size();
95
+ }
96
+ };
97
+ //Parse placeholders
98
+ for ( key in handlers ) {
99
+ ph = delim + key + delim;
100
+ if ( out.indexOf(ph) != -1 ) {
101
+ out = out.replace(ph, handlers[key]());
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+ }
107
+ }
108
+ })(jQuery);
themes/black/config.rb ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Require any additional compass plugins here.
2
+
3
+ # Set this to the root of your project when deployed:
4
+ http_path = "/"
5
+ css_dir = "css"
6
+ sass_dir = "sass"
7
+ images_dir = "images"
8
+
9
+ # You can select your preferred output style here (can be overridden via the command line):
10
+ output_style = :compressed
11
+
12
+ # To enable relative paths to assets via compass helper functions. Uncomment:
13
+ # relative_assets = true
14
+
15
+ # To disable debugging comments that display the original location of your selectors. Uncomment:
16
+ # line_comments = false
17
+
18
+
19
+ # If you prefer the indented syntax, you might want to regenerate this
20
+ # project again passing --syntax sass, or you can uncomment this:
21
+ # preferred_syntax = :sass
22
+ # and then run:
23
+ # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
themes/black/css/style.css ADDED
@@ -0,0 +1 @@
 
1
+ #slb_viewer_wrap .slb_theme_slb_black a,#slb_viewer_wrap .slb_theme_slb_black a:hover{color:#fff}#slb_viewer_wrap .slb_theme_slb_black .slb_loading{background-image:url("../images/loading.gif")}#slb_viewer_wrap .slb_theme_slb_black .slb_container{background-color:#151515}#slb_viewer_wrap .slb_theme_slb_black .slb_prev a{background-image:url("../images/nav_prev.png")}#slb_viewer_wrap .slb_theme_slb_black .slb_next a{background-image:url("../images/nav_next.png")}#slb_viewer_wrap .slb_theme_slb_black .slb_data_title{color:#e3e3e3}#slb_viewer_wrap .slb_theme_slb_black .slb_data_desc{color:#cecece}#slb_viewer_wrap .slb_theme_slb_black .slb_group_status{color:#999}
themes/black/images/loading.gif ADDED
Binary file
themes/black/images/nav_next.png ADDED
Binary file
themes/black/images/nav_prev.png ADDED
Binary file
themes/black/sass/style.scss ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #slb_viewer_wrap {
2
+ .slb_theme_slb_black {
3
+
4
+ a,
5
+ a:hover {
6
+ color: #fff;
7
+ }
8
+
9
+ .slb_loading {
10
+ background-image: url('../images/loading.gif');
11
+ }
12
+
13
+ .slb_container {
14
+ background-color: #151515;
15
+ }
16
+
17
+ .slb_prev a {
18
+ background-image: url('../images/nav_prev.png');
19
+ }
20
+
21
+ .slb_next a {
22
+ background-image: url('../images/nav_next.png');
23
+ }
24
+
25
+ .slb_data_title {
26
+ color: #e3e3e3;
27
+ }
28
+
29
+ .slb_data_desc {
30
+ color: #cecece;
31
+ }
32
+
33
+ .slb_group_status {
34
+ color: #999;
35
+ }
36
+ }
37
+ }
themes/default/client.js ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+ return {
3
+ /**
4
+ * State transition handlers
5
+ */
6
+ 'transition': {
7
+ /**
8
+ * Open event
9
+ * @param View.Viewer v Viewer instance
10
+ * @param jQuery.Deferred dfr Deferred instance to be resolved when animation is complete
11
+ * @return jQuery.Promise Resolved when transition is complete
12
+ */
13
+ 'open': function(v, dfr) {
14
+ var t = this;
15
+ var d = v.dom_get(),
16
+ l = v.get_layout().hide(),
17
+ o = v.get_overlay().hide();
18
+ var pos = {'top' : ''};
19
+ var final = function() {
20
+ //Show overlay
21
+ o.fadeIn(function() {
22
+ l.css(pos);
23
+ dfr.resolve();
24
+ });
25
+ };
26
+ //Clean UI
27
+ d.find('.slb_content').css({width: '', height: ''}).find('.slb_template_tag').hide();
28
+ d.find('.slb_details').height(0);
29
+ //Show viewer DOM
30
+ d.show(function() {
31
+ if ( document.documentElement.clientWidth > 480 ) {
32
+ /* Standard */
33
+ //Center vertically
34
+ var top_scr = $(document).scrollTop();
35
+ pos.top = ( top_scr + $(window).height() / 2 ) - ( l.height() / 2 );
36
+ if ( pos.top < top_scr ) {
37
+ pos.top = top_scr;
38
+ }
39
+ } else {
40
+ //Position at top
41
+ /* Small screen */
42
+ pos.top = $(document).scrollTop();
43
+ }
44
+ final();
45
+ });
46
+ return dfr.promise();
47
+ },
48
+ /**
49
+ * Close event
50
+ * @param View.Viewer v Viewer instance
51
+ * @param jQuery.Deferred dfr Deferred instance to be resolved when animation is complete
52
+ * @return jQuery.Promise Resolved when transition is complete
53
+ */
54
+ 'close': function(v, dfr) {
55
+ var l = v.get_layout(),
56
+ c = l.find('.slb_content');
57
+ var t = this;
58
+ var reset = function() {
59
+ //Reset state
60
+ c.width('').height('');
61
+ l.css('opacity', '');
62
+ dfr.resolve();
63
+ }
64
+ if ( v.animation_enabled() && document.documentElement.clientWidth > 480 ) { /* Standard */
65
+ var lanim = {opacity: 0, top: $(document).scrollTop() + ( $(window).height() / 2 )},
66
+ canim = {width: 0, height: 0};
67
+ //Shrink & fade out viewer
68
+ var pos = l.animate(lanim).promise();
69
+ var size = ( $.isEmptyObject(canim) ) ? true : c.animate(canim).promise();
70
+ $.when(pos, size).done(function() {
71
+ //Fade out overlay
72
+ v.get_overlay().fadeOut(function() {
73
+ reset();
74
+ });
75
+ });
76
+ } else {
77
+ l.css('opacity', 0);
78
+ reset();
79
+ }
80
+ return dfr.promise();
81
+ },
82
+ /**
83
+ * Item loading event
84
+ * @param View.Viewer v Viewer instance
85
+ * @param jQuery.Deferred dfr Deferred instance to be resolved when animation is complete
86
+ * @return jQuery.Promise Resolved when transition is complete
87
+ */
88
+ 'load': function(v, dfr) {
89
+ v.get_layout().find('.slb_loading').show();
90
+ if ( document.documentElement.clientWidth > 480 ) {
91
+ return v.get_layout().fadeIn().promise()
92
+ } else {
93
+ v.get_layout().show();
94
+ dfr.resolve();
95
+ return dfr;
96
+ }
97
+ },
98
+ /**
99
+ * Item unloaded event
100
+ * @param View.Viewer v Viewer instance
101
+ * @param jQuery.Deferred dfr Deferred instance to be resolved when animation is complete
102
+ * @return jQuery.Promise Resolved when transition is complete
103
+ */
104
+ 'unload': function(v, dfr) {
105
+ var l = v.get_layout(),
106
+ det = l.find('.slb_details'),
107
+ cont = l.find('.slb_content .slb_template_tag');
108
+ var props = {height: 0};
109
+ if ( document.documentElement.clientWidth > 480 ) {
110
+ //Hide details
111
+ det.animate(props, 'slow');
112
+ //Hide content
113
+ cont.fadeOut();
114
+ } else {
115
+ det.css(props);
116
+ cont.hide();
117
+ }
118
+ $.when(det.promise(), cont.promise()).done(function() {
119
+ dfr.resolve();
120
+ });
121
+ return dfr.promise();
122
+ },
123
+ /**
124
+ * Item loading completed event
125
+ * @param View.Viewer v Viewer instance
126
+ * @param jQuery.Deferred dfr Deferred instance to be resolved when animation is complete
127
+ * @return jQuery.Promise Resolved when transition is complete
128
+ */
129
+ 'complete': function(v, dfr) {
130
+ //Elements
131
+ var l = v.get_layout(),
132
+ loader = l.find('.slb_loading'),
133
+ det = l.find('.slb_details'),
134
+ det_data = det.find('.slb_data'),
135
+ c = l.find('.slb_content'),
136
+ c_tag = c.find('.slb_template_tag'),
137
+ c_tag_cont = c.find('.slb_template_tag_item_content');
138
+ //Transition
139
+ if ( document.documentElement.clientWidth > 480 ) {
140
+ //Resize viewer to fit item
141
+ var dims = this.get_item_dimensions();
142
+ //Show detail tags (container still hidden)
143
+ det.find('.slb_template_tag').show();
144
+ var top_scr = $(document).scrollTop();
145
+ var pos = { 'top': top_scr + ( $(window).height() / 2 ) - ( this.get_dimensions().height / 2 ) };
146
+ if ( pos.top < top_scr ) {
147
+ pos.top = top_scr;
148
+ }
149
+ pos.top = pos.top || 0;
150
+ //Resize container
151
+ pos = l.animate(pos).promise();
152
+ dims = c.animate(dims).promise();
153
+ $.when(pos, dims).done(function() {
154
+ //Hide loading indicator
155
+ loader.fadeOut('fast', function() {
156
+ //Display content
157
+ c_tag_cont.fadeIn(function() {
158
+ //Show UI
159
+ c_tag.show();
160
+ //Show details
161
+ det.animate({height: det_data.outerHeight()}, 'slow').promise().done(function() {
162
+ det.height('');
163
+ dfr.resolve();
164
+ });
165
+ });
166
+ });
167
+ });
168
+ } else {
169
+ loader.hide();
170
+ c_tag.show();
171
+ det.height('');
172
+ dfr.resolve();
173
+ }
174
+ return dfr.promise();
175
+ }
176
+ },
177
+ /**
178
+ * Theme offsets
179
+ * Reports additional space required for theme UI
180
+ */
181
+ 'offset': function() {
182
+ var dims = {'width': 0, 'height': 0};
183
+ if ( document.documentElement.clientWidth > 480 ) {
184
+ var d = this.get_viewer().get_layout().find('.slb_details');
185
+ d.find('.slb_template_tag').show();
186
+ $.extend(dims, {'width': 32, 'height': d.find('.slb_data').outerHeight()})
187
+ }
188
+ return dims;
189
+ },
190
+ /**
191
+ * Theme margins
192
+ * Reports additional margins used for positioning viewer
193
+ */
194
+ 'margin': function() {
195
+ var m = {'height': 0, 'width': 0};
196
+ if ( document.documentElement.clientWidth > 480 ) {
197
+ $.extend(m, {'height': 50, 'width': 20});
198
+ }
199
+ return m;
200
+ }
201
+ };
202
+ })(jQuery);
themes/default/config.rb ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Require any additional compass plugins here.
2
+
3
+ # Set this to the root of your project when deployed:
4
+ http_path = "/"
5
+ css_dir = "css"
6
+ sass_dir = "sass"
7
+ images_dir = "images"
8
+
9
+ # You can select your preferred output style here (can be overridden via the command line):
10
+ output_style = :compressed
11
+
12
+ # To enable relative paths to assets via compass helper functions. Uncomment:
13
+ # relative_assets = true
14
+
15
+ # To disable debugging comments that display the original location of your selectors. Uncomment:
16
+ # line_comments = false
17
+
18
+
19
+ # If you prefer the indented syntax, you might want to regenerate this
20
+ # project again passing --syntax sass, or you can uncomment this:
21
+ # preferred_syntax = :sass
22
+ # and then run:
23
+ # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
themes/default/css/style.css ADDED
@@ -0,0 +1 @@
 
1
+ @import url("//fonts.googleapis.com/css?family=Yanone+Kaffeesatz");#slb_viewer_wrap .slb_theme_slb_default{position:absolute;top:0;left:0;width:100%;z-index:99999;text-align:center;line-height:0;color:#000;font-family:arial, verdana, sans-serif;font-size:12px}#slb_viewer_wrap .slb_theme_slb_default *{margin:0;padding:0;line-height:1.4em;text-align:left;vertical-align:baseline;white-space:normal;outline:none;border:0px;background:none;opacity:1;width:auto;height:auto;position:static;float:none;clear:none}#slb_viewer_wrap .slb_theme_slb_default a,#slb_viewer_wrap .slb_theme_slb_default a:hover{border-bottom:none;color:#000;text-decoration:underline}#slb_viewer_wrap .slb_theme_slb_default a img{border:none}#slb_viewer_wrap .slb_theme_slb_default .slb_viewer_layout{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;z-index:2;position:absolute;top:20px;width:100%;text-align:center}#slb_viewer_wrap .slb_theme_slb_default .slb_viewer_overlay{position:fixed;top:0;left:0;z-index:1;min-height:105%;min-width:100%;background-color:#151410;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8}#slb_viewer_wrap .slb_theme_slb_default .slb_container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:0 0 64px -40px #fcfcfc;-moz-box-shadow:0 0 64px -40px #fcfcfc;box-shadow:0 0 64px -40px #fcfcfc;position:relative;display:inline-block;background-color:#fff;margin:0 auto;border-radius:5px;padding:16px}#slb_viewer_wrap .slb_theme_slb_default .slb_loading{background:url("../images/loading.gif") center center no-repeat;position:absolute;left:0%;top:0;width:100%;height:100%;min-width:31px;min-height:31px;text-align:center;text-indent:-2000em;line-height:0;display:none}#slb_viewer_wrap .slb_theme_slb_default .slb_template_tag_ui{cursor:pointer;transition:opacity .5s}#slb_viewer_wrap .slb_theme_slb_default .slb_controls{position:absolute;top:8px;right:8px;width:75%;text-align:right}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_template_tag_ui{width:25px;height:25px;float:right;margin-left:2px;text-indent:-2000em;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=50);opacity:0.5}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_template_tag_ui:hover{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_slideshow .slb_template_tag{background:url("../images/ui_slideshow_play.png") 0 0 no-repeat}#slb_viewer_wrap .slb_theme_slb_default .slb_controls .slb_close .slb_template_tag{background:url("../images/ui_close.png") 0 0 no-repeat}#slb_viewer_wrap .slb_theme_slb_default.slideshow_active .slb_controls .slb_slideshow .slb_template_tag{background:url("../images/ui_slideshow_pause.png") 0 0 no-repeat}#slb_viewer_wrap .slb_theme_slb_default .slb_prev .slb_template_tag,#slb_viewer_wrap .slb_theme_slb_default .slb_next .slb_template_tag{position:absolute;top:20%;height:71%;width:45%;margin-left:4px;background-repeat:no-repeat;text-indent:-2000em;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=50);opacity:0.5}#slb_viewer_wrap .slb_theme_slb_default .slb_prev .slb_template_tag{background-image:url("../images/nav_prev.png");background-position:left 45%}#slb_viewer_wrap .slb_theme_slb_default .slb_next .slb_template_tag{right:4px;background-image:url("../images/nav_next.png");background-position:right 45%}#slb_viewer_wrap .slb_theme_slb_default .slb_prev .slb_template_tag:hover,#slb_viewer_wrap .slb_theme_slb_default .slb_next .slb_template_tag:hover{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);opacity:1}#slb_viewer_wrap .slb_theme_slb_default .slb_content{min-width:200px;min-height:200px;position:relative}#slb_viewer_wrap .slb_theme_slb_default .slb_details{margin:0 auto;line-height:1.4em;text-align:left;overflow:hidden;position:relative}#slb_viewer_wrap .slb_theme_slb_default .slb_details .inner{display:table;width:100%}#slb_viewer_wrap .slb_theme_slb_default .slb_details .slb_data{display:table-caption;caption-side:bottom}#slb_viewer_wrap .slb_theme_slb_default .slb_template_tag_item_content *{width:100%;height:100%}#slb_viewer_wrap .slb_theme_slb_default .slb_data_title,#slb_viewer_wrap .slb_theme_slb_default .slb_group_status{font-family:'Yanone Kaffeesatz', sans-serif;font-size:23px}#slb_viewer_wrap .slb_theme_slb_default .slb_group_status{color:#777;font-style:italic;font-size:18.4px}#slb_viewer_wrap .slb_theme_slb_default .slb_data_desc{display:block;margin-top:0.5em}#slb_viewer_wrap .slb_theme_slb_default.item_single .slb_group_status,#slb_viewer_wrap .slb_theme_slb_default.item_single .slb_nav,#slb_viewer_wrap .slb_theme_slb_default.item_single .slb_slideshow{display:none}#slb_viewer_wrap .slb_theme_slb_default.loading .slb_loading{display:block}#slb_viewer_wrap .slb_theme_slb_default.loading .slb_template_tag_ui{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=0);opacity:0}@media screen and (max-width: 480px){#slb_viewer_wrap .slb_theme_slb_default{min-height:100%;min-width:320px;width:100%}#slb_viewer_wrap .slb_theme_slb_default .slb_viewer_layout{min-height:100%;min-width:320px;width:100%;display:block}#slb_viewer_wrap .slb_theme_slb_default .slb_container{min-height:100%;min-width:320px;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;max-width:100%;border-radius:0;margin:0;padding:5px;position:absolute;top:0;left:0}#slb_viewer_wrap .slb_theme_slb_default .slb_container .slb_content img,#slb_viewer_wrap .slb_theme_slb_default .slb_container .slb_content iframe,#slb_viewer_wrap .slb_theme_slb_default .slb_container .slb_content object,#slb_viewer_wrap .slb_theme_slb_default .slb_container .slb_content .slb_inner{max-width:100%}#slb_viewer_wrap .slb_theme_slb_default .slb_container .slb_content img{height:auto}#slb_viewer_wrap .slb_theme_slb_default .slb_controls{top:3px;right:3px}#slb_viewer_wrap .slb_theme_slb_default .slb_prev .slb_template_tag,#slb_viewer_wrap .slb_theme_slb_default .slb_next .slb_template_tag{top:17%;height:79%}}
{images → themes/default/images}/loading.gif RENAMED
File without changes
themes/default/images/nav_next.png ADDED
Binary file
themes/default/images/nav_prev.png ADDED
Binary file
themes/default/images/ui_close.png ADDED
Binary file
themes/default/images/ui_slideshow_pause.png ADDED
Binary file
themes/default/images/ui_slideshow_play.png ADDED
Binary file
themes/default/layout.html ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="slb_container">
2
+ <div class="slb_content">
3
+ {{item.content}}
4
+ <span class="slb_nav">
5
+ <div class="slb_prev">
6
+ {{ui.nav_prev}}
7
+ </div>
8
+ <div class="slb_next">
9
+ {{ui.nav_next}}
10
+ </div>
11
+ </span>
12
+ <span class="slb_controls">
13
+ <div class="slb_close">
14
+ {{ui.close}}
15
+ </div>
16
+ <div class="slb_slideshow">
17
+ {{ui.slideshow_control}}
18
+ </div>
19
+ </span>
20
+ <div class="slb_loading">
21
+ {{ui.loading}}
22
+ </div>
23
+ </div>
24
+ <div class="slb_details">
25
+ <div class="inner">
26
+ <div class="slb_data">
27
+ <div class="slb_data_content">
28
+ <span class="slb_data_title">
29
+ {{item.title}}
30
+ </span>
31
+ <span class="slb_group_status">
32
+ {{ui.group_status}}
33
+ </span>
34
+ <span class="slb_data_desc">
35
+ {{item.description}}
36
+ </span>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </div>
themes/default/sass/style.scss ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //Imports
2
+
3
+ @import "compass";
4
+ @import url('//fonts.googleapis.com/css?family=Yanone+Kaffeesatz');
5
+
6
+ //Mixins
7
+
8
+ @mixin box-sizing-border {
9
+ @include box-sizing(border-box);
10
+ }
11
+
12
+ @mixin min-dims($width: 200px, $height: 200px) {
13
+ min-width: $width;
14
+ min-height: $height;
15
+ }
16
+
17
+ #slb_viewer_wrap {
18
+ .slb_theme_slb_default {
19
+ position: absolute;
20
+ top: 0;
21
+ left: 0;
22
+ width: 100%;
23
+ z-index: 99999;
24
+ text-align: center;
25
+ line-height: 0;
26
+ color:#000;
27
+ font: {
28
+ family: arial, verdana, sans-serif;
29
+ size: 12px;
30
+ }
31
+
32
+ /* Reset */
33
+ * {
34
+ margin: 0;
35
+ padding: 0;
36
+ line-height: 1.4em;
37
+ text-align: left;
38
+ vertical-align: baseline;
39
+ white-space: normal;
40
+ outline: none;
41
+ border: 0px;
42
+ background: none;
43
+ opacity: 1;
44
+ width: auto;
45
+ height: auto;
46
+ position: static;
47
+ float: none;
48
+ clear: none;
49
+ }
50
+
51
+ /* General */
52
+ a,
53
+ a:hover {
54
+ border-bottom:none;
55
+ color:#000;
56
+ text-decoration:underline;
57
+ }
58
+
59
+ a img {
60
+ border: none;
61
+ }
62
+
63
+ .slb_viewer_layout {
64
+ @include box-sizing-border;
65
+ z-index: 2;
66
+ position: absolute;
67
+ top: 20px;
68
+ width: 100%;
69
+ text-align: center;
70
+ }
71
+
72
+ .slb_viewer_overlay {
73
+ position: fixed;
74
+ top: 0;
75
+ left: 0;
76
+ z-index: 1;
77
+ min-height: 105%;
78
+ min-width: 100%;
79
+ background-color: #151410;
80
+ @include opacity(0.8);
81
+ }
82
+
83
+ .slb_container {
84
+ @include box-sizing-border;
85
+ @include box-shadow(0 0 64px -40px #fcfcfc);
86
+ position: relative;
87
+ display: inline-block;
88
+ background-color: #fff;
89
+ margin: 0 auto;
90
+ border-radius: 5px;
91
+ padding: 16px;
92
+ }
93
+
94
+ .slb_loading {
95
+ background: url('../images/loading.gif') center center no-repeat;
96
+ position: absolute;
97
+ left: 0%;
98
+ top: 0;
99
+ width: 100%;
100
+ height: 100%;
101
+ min-width: 31px;
102
+ min-height: 31px;
103
+ text-align: center;
104
+ text-indent: -2000em;
105
+ line-height: 0;
106
+ display: none;
107
+ }
108
+
109
+ .slb_template_tag_ui {
110
+ cursor: pointer;
111
+ transition: opacity .5s;
112
+ }
113
+
114
+ /* UI */
115
+ .slb_controls {
116
+ position: absolute;
117
+ top: 8px;
118
+ right: 8px;
119
+ width: 75%;
120
+ text-align: right;
121
+
122
+ .slb_template_tag_ui {
123
+ width: 25px;
124
+ height: 25px;
125
+ float: right;
126
+ margin-left: 2px;
127
+ text-indent: -2000em;
128
+ @include opacity(.5);
129
+ }
130
+
131
+ .slb_template_tag_ui:hover {
132
+ @include opacity(.8);
133
+ }
134
+
135
+ .slb_slideshow .slb_template_tag {
136
+ background: url('../images/ui_slideshow_play.png') 0 0 no-repeat;
137
+ }
138
+
139
+ .slb_close .slb_template_tag {
140
+ background: url('../images/ui_close.png') 0 0 no-repeat;
141
+ }
142
+ }
143
+
144
+ &.slideshow_active .slb_controls .slb_slideshow .slb_template_tag {
145
+ background: url('../images/ui_slideshow_pause.png') 0 0 no-repeat;
146
+ }
147
+
148
+ /* Navigation */
149
+ $ui_nav_pos: 45%;
150
+ %ui_nav {
151
+ position: absolute;
152
+ top: 20%;
153
+ height: 71%;
154
+ width: 45%;
155
+ margin-left: 4px;
156
+ background-repeat: no-repeat;
157
+ text-indent: -2000em;
158
+ @include opacity(.5);
159
+ }
160
+
161
+ .slb_prev .slb_template_tag {
162
+ background-image: url('../images/nav_prev.png');
163
+ background-position: left $ui_nav_pos;
164
+ }
165
+
166
+ .slb_next .slb_template_tag {
167
+ right: 4px;
168
+ background-image: url('../images/nav_next.png');
169
+ background-position: right $ui_nav_pos;
170
+ }
171
+
172
+ .slb_prev, .slb_next {
173
+ .slb_template_tag {
174
+ @extend %ui_nav;
175
+ &:hover { @include opacity(1); }
176
+ }
177
+ }
178
+
179
+ /* Content */
180
+ .slb_content {
181
+ @include min-dims;
182
+ position: relative;
183
+ }
184
+
185
+ .slb_details {
186
+ margin: 0 auto;
187
+ line-height: 1.4em;
188
+ text-align: left;
189
+ overflow: hidden;
190
+ position: relative;
191
+ .inner {
192
+ display: table;
193
+ width: 100%;
194
+ }
195
+ .slb_data {
196
+ display: table-caption;
197
+ caption-side: bottom;
198
+ }
199
+ }
200
+
201
+ .slb_template_tag_item_content * {
202
+ width: 100%;
203
+ height: 100%;
204
+ }
205
+
206
+ /* Title */
207
+ $title-size: 23px;
208
+
209
+ .slb_data_title {
210
+ font-family: 'Yanone Kaffeesatz', sans-serif;
211
+ font-size: $title-size;
212
+ }
213
+
214
+ .slb_group_status {
215
+ @extend .slb_data_title;
216
+ color: #777;
217
+ font-style: italic;
218
+ font-size: $title-size * .8;
219
+ }
220
+
221
+ .slb_data_desc {
222
+ display: block;
223
+ margin-top: 0.5em;
224
+ }
225
+
226
+ /* Single */
227
+ &.item_single {
228
+ .slb_group_status,
229
+ .slb_nav,
230
+ .slb_slideshow {
231
+ display: none;
232
+ }
233
+ }
234
+ /* Loading */
235
+ &.loading {
236
+ .slb_loading {
237
+ display: block;
238
+ }
239
+ .slb_details,
240
+ .slb_template_tag {
241
+ //display: none;
242
+ }
243
+
244
+ .slb_template_tag_ui {
245
+ @include opacity(0);
246
+ }
247
+ }
248
+
249
+ //Media
250
+
251
+ //Small screen
252
+ @media screen and (max-width: 480px) {
253
+ @mixin vsizing {
254
+ min-height: 100%;
255
+ min-width: 320px;
256
+ width: 100%;
257
+ }
258
+ @include vsizing;
259
+ .slb_viewer_layout {
260
+ @include vsizing;
261
+ display: block;
262
+ }
263
+ .slb_container {
264
+ @include vsizing;
265
+ @include box-sizing-border;
266
+ @include box-shadow(none);
267
+ max-width: 100%;
268
+ border-radius: 0;
269
+ margin: 0;
270
+ padding: 5px;
271
+ position: absolute;
272
+ top: 0;
273
+ left: 0;
274
+ .slb_content {
275
+ img, iframe, object, .slb_inner {
276
+ max-width: 100%;
277
+ }
278
+ img {
279
+ height: auto;
280
+ }
281
+ }
282
+ }
283
+ .slb_controls {
284
+ top: 3px;
285
+ right: 3px;
286
+ }
287
+
288
+ %ui_nav {
289
+ top: 17%;
290
+ height: 79%;
291
+ }
292
+ }
293
+ }
294
+ }