Cornerstone - Version 0.7.7

Version Description

  • Fix: Spelling.
Download this release

Release Info

Developer Archetyped
Plugin Icon wp plugin Cornerstone
Version 0.7.7
Comparing to
See all releases

Version 0.7.7

COPYING ADDED
@@ -0,0 +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.
Gruntfile.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function(grunt) {
2
+ // Load tasks
3
+ require('load-grunt-tasks')(grunt);
4
+ // Display task timing
5
+ require('time-grunt')(grunt);
6
+ // Project configuration.
7
+ grunt.initConfig({
8
+ // Metadata
9
+ pkg : grunt.file.readJSON('package.json'),
10
+ // Variables
11
+ paths : {
12
+ // Base dir assets dir
13
+ base : 'client',
14
+
15
+ // PHP assets
16
+ php : {
17
+ files_std : ['*.php', '**/*.php', '!node_modules/**/*.php'], // Standard file match
18
+ files : '<%= paths.php.files_std %>' // Dynamic file match
19
+ },
20
+
21
+ // JavaScript assets
22
+ js : {
23
+ base : 'js', // Base dir
24
+ src : '<%= paths.js.base %>/dev', // Development code
25
+ dest : '<%= paths.js.base %>/prod', // Production code
26
+ files_std : '**/<%= paths.js.src %>/**/*.js', // Standard file match
27
+ files : '<%= paths.js.files_std %>' // Dynamic file match
28
+ },
29
+
30
+ // Sass assets
31
+ sass : {
32
+ src : 'sass', // Source files dir
33
+ dest : 'css', // Compiled files dir
34
+ ext : '.css', // Compiled extension
35
+ target : '*.scss', // Only Sass files in CWD
36
+ exclude : '!_*.scss', // Do not process partials
37
+ base_src : '<%= paths.base %>/<%= paths.sass.src %>', // Base source dir
38
+ base_dest : '<%= paths.base %>/<%= paths.sass.dest %>', // Base compile dir
39
+ }
40
+ },
41
+ });
42
+
43
+ // Load task configurations
44
+ grunt.loadTasks('grunt');
45
+
46
+ // Default Tasks
47
+ grunt.registerTask('build', ['phplint', 'jshint:all', 'uglify', 'sass']);
48
+ grunt.registerTask('watch_all', ['watch:js', 'watch:sass']);
49
+ };
README.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cornerstone
2
+ Enhanced Content Management for WordPress
3
+
4
+ Cornerstone transforms WordPress into a full-fledged Content Management System. Say *adios* to the hacks and tricks used to shoehorn your content into something that resembles a non-blog site and say hello to content management simplified.
5
+
6
+ [Learn more][home]
7
+
8
+ ## Support
9
+ Found a bug or otherwise experiencing an issue with Cornerstone? [Report the issue here][issue-report]
10
+
11
+ [issue-report]: https://github.com/archetyped/cornerstone/wiki/Reporting-Issues "Report an issue"
12
+ [home]: http://archetyped.com/tools/cornerstone/ "Cornerstone home page"
controller.php ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package Cornerstone
5
+ */
6
+ class Cornerstone extends CNR_Base {
7
+ /* Variables */
8
+
9
+ /**
10
+ * Script files
11
+ * @var array
12
+ * @see CNR_Base::files
13
+ */
14
+ var $scripts = array (
15
+ 'core' => array (
16
+ 'file' => 'js/lib.core.js',
17
+ 'deps' => 'jquery'
18
+ ),
19
+ 'admin' => array (
20
+ 'file' => 'js/lib.admin.js',
21
+ 'deps' => array('jquery', '[core]'),
22
+ 'context' => 'admin'
23
+ ),
24
+ 'inline_edit' => array (
25
+ 'file' => 'js/lib.posts.inline_edit.js',
26
+ 'deps' => array('inline-edit-post','jquery', '[posts]'),
27
+ 'context' => 'admin_page_edit'
28
+ )
29
+ );
30
+
31
+ /**
32
+ * Style files
33
+ * @var array
34
+ * @see CNR_Base::files
35
+ */
36
+ var $styles = array (
37
+ 'admin' => array (
38
+ 'file' => 'css/admin.css',
39
+ 'context' => 'admin'
40
+ )
41
+ );
42
+
43
+ /* Featured Content variables */
44
+
45
+ /**
46
+ * Category slug value that denotes a "featured" post
47
+ * @var string
48
+ * @see posts_featured_cat()
49
+ * @todo Remove need for this property
50
+ */
51
+ var $posts_featured_cat = "feature";
52
+
53
+ /**
54
+ * Featured posts container
55
+ * @var CNR_Post_Query
56
+ */
57
+ var $posts_featured = null;
58
+
59
+ /* Children Content Variables */
60
+
61
+ /**
62
+ * Children posts
63
+ * @var CNR_Post_Query
64
+ */
65
+ var $post_children_collection = null;
66
+
67
+ /* Instance Variables */
68
+
69
+ /**
70
+ * Structure instance
71
+ * @var CNR_Structure
72
+ */
73
+ var $structure = null;
74
+
75
+ /**
76
+ * Media instance
77
+ * @var CNR_Media
78
+ */
79
+ var $media = null;
80
+
81
+ /**
82
+ * Post class instance
83
+ * @var CNR_Post
84
+ */
85
+ var $post = null;
86
+
87
+ /**
88
+ * Feeds instance
89
+ * @var CNR_Feeds
90
+ */
91
+ var $feeds = null;
92
+
93
+ /* Constructor */
94
+
95
+ function __construct() {
96
+ //Parent Constructor
97
+ parent::__construct();
98
+
99
+ //Init
100
+ $this->init();
101
+
102
+ //Special Queries
103
+ $this->posts_featured = new CNR_Post_Query( array( 'category' => $this->posts_featured_get_cat_id(), 'numberposts' => 4 ) );
104
+ $this->post_children_collection = new CNR_Post_Query();
105
+
106
+ $this->post = new CNR_Post();
107
+ $this->post->init();
108
+
109
+
110
+ //Init class instances
111
+ $this->structure = new CNR_Structure();
112
+ $this->structure->init();
113
+
114
+ $this->media = new CNR_Media();
115
+ $this->media->init();
116
+
117
+ $this->feeds = new CNR_Feeds();
118
+ $this->feeds->init();
119
+ }
120
+
121
+ /* Init */
122
+
123
+ /**
124
+ * Initialize environment
125
+ * Overrides parent method
126
+ * @see parent::init_env
127
+ * @return void
128
+ */
129
+ function init_env() {
130
+ //Localization
131
+ $ldir = 'l10n';
132
+ $lpath = $this->util->get_plugin_file_path($ldir, array(false, false));
133
+ $lpath_abs = $this->util->get_file_path($ldir);
134
+ if ( is_dir($lpath_abs) ) {
135
+ load_plugin_textdomain($this->util->get_plugin_textdomain(), false, $lpath);
136
+ }
137
+
138
+ //Context
139
+ add_action(( is_admin() ) ? 'admin_head' : 'wp_head', $this->m('set_client_context'));
140
+ }
141
+
142
+ /* Methods */
143
+
144
+ /*-** Request **-*/
145
+
146
+ /**
147
+ * Output current context to client-side
148
+ * @uses `wp_head` action hook
149
+ * @uses `admin_head` action hook
150
+ * @return void
151
+ */
152
+ function set_client_context() {
153
+ $ctx = new stdClass();
154
+ $ctx->context = $this->util->get_context();
155
+ $this->util->extend_client_object($ctx, true);
156
+ }
157
+
158
+ /*-** Child Content **-*/
159
+
160
+ /**
161
+ * Gets children posts of specified page and stores them for later use
162
+ * This method hooks into 'the_posts' filter to retrieve child posts for any single page retrieved by WP
163
+ * @return array $posts Posts array (required by 'the_posts' filter)
164
+ * @param array $posts Array of Posts (@see WP_QUERY)
165
+ */
166
+ function post_children_get($posts) {
167
+ //Global variables
168
+ global $wp_query;
169
+
170
+ //Reset post children collection
171
+ $this->post_children_collection->init();
172
+
173
+ //Stop here if post is not a page
174
+ if ( ! is_page() || empty($posts) )
175
+ return $posts;
176
+
177
+ //Get children posts
178
+ $post =& $posts[0];
179
+ $this->post_children_collection =& CNR_Post::get_children($post);
180
+
181
+ //Return posts (required by filter)
182
+ return $posts;
183
+ }
184
+
185
+ /*-** Featured Content **-*/
186
+
187
+ /**
188
+ * Retrieves featured post category object
189
+ * @return object Featured post category object
190
+ * @todo integrate into CNR_Post_Query
191
+ */
192
+ function posts_featured_get_cat() {
193
+ static $cat = null;
194
+
195
+ //Only fetch category object if it hasn't already been retrieved
196
+ if (is_null($cat) || !is_object($cat)) {
197
+ //Retrieve category object
198
+ if (is_int($this->posts_featured_cat)) {
199
+ $cat = get_category((int)$this->posts_featured_cat);
200
+ }
201
+ elseif (is_string($this->posts_featured_cat) && strlen($this->posts_featured_cat) > 0) {
202
+ $cat = get_category_by_slug($this->posts_featured_cat);
203
+ }
204
+ }
205
+
206
+ return $cat;
207
+ }
208
+
209
+ /**
210
+ * @todo integrate into CNR_Post_Query
211
+ */
212
+ function posts_featured_get_cat_id() {
213
+ static $id = '';
214
+ if ($id == '') {
215
+ $cat = $this->posts_featured_get_cat();
216
+ if (!is_null($cat) && is_object($cat) && $this->util->property_exists($cat, 'cat_ID'))
217
+ $id = $cat->cat_ID;
218
+ }
219
+ return $id;
220
+ }
221
+
222
+ /**
223
+ * Checks if post has content to display
224
+ * @param object $post (optional) Post object
225
+ * @return bool TRUE if post has content, FALSE otherwise
226
+ * @todo Review for deletion/relocation
227
+ */
228
+ function post_has_content($post = null) {
229
+ if ( !$this->util->check_post($post) )
230
+ return false;
231
+ if ( isset($post->post_content) && trim($post->post_content) != '' )
232
+ return true;
233
+ return false;
234
+ }
235
+ }
css/admin.css ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .site-pages .actions {
2
+ clear: both;
3
+ }
4
+
5
+ .group-pages, .list-pages {
6
+ padding-left: 1em;
7
+ margin-top: 1em;
8
+ width: 20em;
9
+ float: left;
10
+ margin-right: 5em;
11
+ }
12
+
13
+ .ph {
14
+ background: #FFE1E2;
15
+ }
16
+
17
+ .item-page, .page_item a {
18
+ border: 1px solid #bbb;
19
+ background: #eee;
20
+ padding: .5em;
21
+ width: 15em;
22
+ display: block;
23
+ margin: .5em 0;
24
+ }
25
+
26
+ .site-pages li ul {
27
+ padding: 0 1.5em;
28
+ }
29
+
30
+ .group-pages {
31
+ min-height: 5em;
32
+ }
33
+
34
+ ul.empty {
35
+ border: 1px dashed #ccc;
36
+ }
37
+
38
+ .group-pages .empty {
39
+ border: 0;
40
+ background: none;
41
+
42
+ }
43
+
44
+ .item-page,
45
+ .item-page a {
46
+ color: #333;
47
+ }
48
+
49
+ .item-page {
50
+ position: relative;
51
+ }
52
+
53
+ .js .page-group .actions-commit {
54
+ display: none;
55
+ }
56
+
57
+ .item-page .page-delete {
58
+ position: absolute;
59
+ right: 10px;
60
+ top: 30%;
61
+ text-indent: -2000em;
62
+ cursor: pointer;
63
+ height: 11px;
64
+ width: 11px;
65
+ background: url("images/page_delete.gif") top left no-repeat;
66
+ opacity: .5;
67
+ }
68
+
69
+ .item-page .page-delete.on {
70
+ opacity: 1;
71
+ }
72
+
73
+ .page-group .sorting {
74
+
75
+ }
76
+
77
+ .checking {
78
+ border: 1px solid green;
79
+ background-color: #E6FFE6;
80
+ }
81
+
82
+ .removing {
83
+ background-color: #FFCECE;
84
+ opacity: 0;
85
+ }
86
+
87
+ /* Post/Page Edit Form */
88
+
89
+ #quicktags #cnr_inturl {
90
+ color: #00f;
91
+ text-decoration: underline;
92
+ }
93
+
94
+ .buttons {
95
+ padding: 6px;
96
+ }
97
+
98
+ .options-default,
99
+ .confirmation-default {
100
+ visibility: hidden;
101
+ }
102
+
103
+ /* Fields */
104
+
105
+ .rt_container {
106
+ border: 1px solid #dfdfdf;
107
+ }
108
+
109
+ .rt_container textarea {
110
+ border: 0;
111
+ }
112
+
113
+ /* Management Page */
114
+
115
+ .tablenav #cnr_section {
116
+ width: 150px;
117
+ }
118
+
119
+ /* Content Types */
120
+
121
+ .cnr_group_wrap {
122
+ overflow: hidden;
123
+ }
124
+
125
+ .cnr_attribute_wrap {
126
+ margin: 1em;
127
+ display: block;
128
+ }
129
+
130
+ .cnr_attribute_wrap .actions {
131
+ margin-top: 1em;
132
+ }
133
+
134
+ .cnr_attribute_wrap .media_frame,
135
+ .cnr_attribute_wrap .media_link {
136
+ clear: both;
137
+ display: block;
138
+ }
139
+
140
+ .cnr_attribute_wrap .media_frame {
141
+ margin-bottom: 1em;
142
+ border: 1px solid #F5F5F5;
143
+ }
144
+
145
+ /* Single Field Groups */
146
+
147
+ .cnr_group_wrap .single_field textarea,
148
+ .cnr_group_wrap .single_field input[type=text] {
149
+ width: 98%;
150
+ }
151
+
152
+ .cnr_group_wrap .single_field .group_field_title label,
153
+ .cnr_group_wrap .single_field_has_elements legend {
154
+ display: none;
155
+ }
156
+
157
+ .cnr_pages_dropdown select {
158
+ width: 100%;
159
+ }
functions.php ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Functions
4
+ * Provides global access to specific functionality
5
+ * @package Cornerstone
6
+ * @author Archetyped
7
+ */
8
+
9
+ /* Template tags */
10
+
11
+ /**
12
+ * Outputs feed links based on current page
13
+ * @return void
14
+ */
15
+ function cnr_the_feed_links() {
16
+ global $cnr;
17
+ $cnr->feeds->the_links();
18
+ }
19
+
20
+ /*-** Child Content **-*/
21
+
22
+ function cnr_is_section() {
23
+ return ( is_page() && cnr_have_children() ) ? true : false;
24
+ }
25
+
26
+ /**
27
+ * Checks if current post/page has children elements
28
+ * @return bool TRUE if post/page has children, FALSE otherwise
29
+ */
30
+ function cnr_have_children() {
31
+ global $cnr;
32
+ return $cnr->post_children_collection->has();
33
+ }
34
+
35
+ /**
36
+ * Prepares next child post for output to page
37
+ *
38
+ * @return void
39
+ */
40
+ function cnr_next_child() {
41
+ global $cnr;
42
+ $cnr->post_children_collection->next();
43
+ }
44
+
45
+ /**
46
+ * Returns number of children in current request
47
+ * May not return total number of existing children (e.g. if output is paged, etc.)
48
+ * @return int Number of children returned in current request
49
+ */
50
+ function cnr_children_count() {
51
+ global $cnr;
52
+ return $cnr->post_children_collection->count();
53
+ }
54
+
55
+ /**
56
+ * Returns total number of existing children
57
+ * @return int Total number of children
58
+ */
59
+ function cnr_children_found() {
60
+ global $cnr;
61
+ return $cnr->post_children_collection->found();
62
+ }
63
+
64
+ /**
65
+ * Returns total number of pages of children
66
+ * Based on 'posts_per_page' option
67
+ * @return int Maximum number of pages
68
+ */
69
+ function cnr_children_max_num_pages() {
70
+ global $cnr;
71
+ return $cnr->post_children_collection->max_num_pages();
72
+ }
73
+
74
+ /**
75
+ * Checks if current child item is the first child item
76
+ * @return bool TRUE if current item is first, FALSE otherwise
77
+ */
78
+ function cnr_is_first_child() {
79
+ global $cnr;
80
+ return $cnr->post_children_collection->is_first();
81
+ }
82
+
83
+ /**
84
+ * Checks if current child item is the last child item
85
+ * @return bool TRUE if current item is last, FALSE otherwise
86
+ */
87
+ function cnr_is_last_child() {
88
+ global $cnr;
89
+ return $cnr->post_children_collection->is_last();
90
+ }
91
+
92
+ /*-** Featured Content **-*/
93
+
94
+ /**
95
+ * Retrieves featured posts
96
+ * @return array Featured posts matching criteria
97
+ * @param int $limit (optional) Maximum number of featured posts to retrieve
98
+ * @param int|bool $parent (optional) Section to get featured posts of (Defaults to current section). FALSE if latest featured posts should be retrieved regardless of section
99
+ */
100
+ function cnr_get_featured($limit = 0, $parent = null) {
101
+ global $cnr;
102
+ return $cnr->posts_featured->get($limit, $parent);
103
+ }
104
+
105
+ function cnr_in_featured($post_id = null) {
106
+ global $cnr;
107
+ return $cnr->posts_featured->contains($post_id);
108
+ }
109
+
110
+ function cnr_have_featured() {
111
+ global $cnr;
112
+ return $cnr->posts_featured->has();
113
+ }
114
+
115
+ function cnr_next_featured() {
116
+ global $cnr;
117
+ return $cnr->posts_featured->next();
118
+
119
+ }
120
+
121
+ function cnr_current_featured() {
122
+ global $cnr;
123
+ return $cnr->posts_featured->current();
124
+ }
125
+
126
+ function cnr_is_first_featured() {
127
+ global $cnr;
128
+ return $cnr->posts_featured->is_first();
129
+ }
130
+
131
+ function cnr_is_last_featured() {
132
+ global $cnr;
133
+ return $cnr->posts_featured->is_last();
134
+ }
135
+
136
+ function cnr_featured_count() {
137
+ global $cnr;
138
+ return $cnr->posts_featured->count();
139
+ }
140
+
141
+ /**
142
+ * Returns total number of found posts
143
+ * @return int Total number of posts
144
+ */
145
+ function cnr_featured_found() {
146
+ global $cnr;
147
+ return $cnr->posts_featured->found();
148
+ }
149
+
150
+ /*-** Post-Specific **-*/
151
+
152
+ /**
153
+ * Checks if post has content to display
154
+ * @param object $post (optional) Post object
155
+ * @return bool TRUE if post has content, FALSE otherwise
156
+ */
157
+ function cnr_has_content($post = null) {
158
+ global $cnr;
159
+ return $cnr->post_has_content($post);
160
+ }
161
+
162
+ /* Images */
163
+
164
+ function cnr_get_attachments($post = null) {
165
+ $m = new CNR_Media();
166
+ return $m->post_get_attachments($post);
167
+ }
168
+
169
+ function cnr_get_filesize($post = null, $formatted = true) {
170
+ $m = new CNR_Media();
171
+ return $m->get_attachment_filesize($post, $formatted);
172
+ }
173
+
174
+ /* Section */
175
+
176
+ /**
177
+ * Retrieves the post's section data
178
+ * @uses CNR_Post::get_section()
179
+ * @param string $data (optional) Type of data to return (Default: ID)
180
+ * Possible values:
181
+ * NULL Full section post object
182
+ * Column name Post column data (if exists)
183
+ *
184
+ * @param int $id (optional) Post ID (Default: current post)
185
+ * @return mixed post's section (or column data if specified via $data parameter)
186
+ */
187
+ function cnr_get_the_section($data = 'ID', $id = null) {
188
+ return CNR_Post::get_section($id, $data);
189
+ }
190
+
191
+ /**
192
+ * Prints the post's section data
193
+ * @uses CNR_Post::the_section()
194
+ * @param string $data (optional) Type of data to return (Default: ID)
195
+ */
196
+ function cnr_the_section($data = 'ID') {
197
+ CNR_Post::the_section(null, $data);
198
+ }
199
+
200
+ /* Content Types */
201
+
202
+ /**
203
+ * Register handler for a placeholder in a content type template
204
+ * Placeholders allow templates to be populated with dynamic content at runtime
205
+ * Multiple handlers can be registered for a placeholder,
206
+ * thus allowing custom handlers to override default processing, etc.
207
+ * @uses CNR_Field_Type::register_placeholder_handler() to register placeholder
208
+ * @param string $placeholder Placeholder identifier
209
+ * @param callback $handler Callback function to use as handler for placeholder
210
+ * @param int $priority (optional) Priority of registered handler (Default: 10)
211
+ */
212
+ function cnr_register_placeholder_handler($placeholder, $handler, $priority = 10) {
213
+ CNR_Field_Type::register_placeholder_handler($placeholder, $handler, $priority);
214
+ }
215
+
216
+ /**
217
+ * Checks if data exists for specified field
218
+ * @global $cnr_content_utilities
219
+ * @param string $field_id ID of field to check for data
220
+ * @param int|obj $item (optional) Post ID or object to check for field data (Default: global post)
221
+ * @return bool TRUE if field data exists
222
+ */
223
+ function cnr_has_data($field_id = null, $item = null) {
224
+ global $cnr_content_utilities;
225
+ return $cnr_content_utilities->has_item_data($item, $field_id);
226
+ }
227
+
228
+ /**
229
+ * Retrieve data from a field
230
+ * @global $cnr_content_utilities
231
+ * @see CNR_Content_Utilities::get_item_data() for more information
232
+ * @param string $field_id ID of field to retrieve
233
+ * @param string $layout (optional) Name of layout to use when returning data
234
+ * @param array $attr (optional) Additional attributes to pass to field
235
+ * @param int|object $item (optional) Post object to retrieve data from (Default: global post object)
236
+ * @param mixed $default Default value to return in case of errors (invalid field, no data, etc.)
237
+ * @return mixed Specified field data
238
+ */
239
+ function cnr_get_data($field_id = null, $layout = 'display', $attr = null, $item = null, $default = '') {
240
+ global $cnr_content_utilities;
241
+ return $cnr_content_utilities->get_item_data($item, $field_id, $layout, $default, $attr);
242
+ }
243
+
244
+ /**
245
+ * Prints an item's field data
246
+ * @see CNR_Content_Utilities::the_item_data() for more information
247
+ * @param string $field_id Name of field to retrieve
248
+ * @param string $layout(optional) Layout to use when returning field data (Default: display)
249
+ * @param array $attr Additional items to pass to field
250
+ * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used)
251
+ * @param mixed $default Default value to return in case of errors, etc.
252
+ */
253
+ function cnr_the_data($field_id = null, $layout = 'display', $attr = null, $item = null, $default = '') {
254
+ global $cnr_content_utilities;
255
+ $cnr_content_utilities->the_item_data($item, $field_id, $layout, $default, $attr);
256
+ }
grunt/jshint.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('jshint', {
4
+ options : {
5
+ reporter: require('jshint-stylish'),
6
+ curly : true,
7
+ eqeqeq : true,
8
+ immed : true,
9
+ latedef : true,
10
+ newcap : false,
11
+ noarg : true,
12
+ sub : true,
13
+ undef : true,
14
+ unused : true,
15
+ boss : true,
16
+ eqnull : true,
17
+ browser : true,
18
+ jquery : true,
19
+ globals : {}
20
+ },
21
+ grunt : {
22
+ options : {
23
+ node : true
24
+ },
25
+ src : ['Gruntfile.js', 'grunt/*.js']
26
+ },
27
+ all : {
28
+ options : {
29
+ globals : {
30
+ 'SLB' : true,
31
+ 'console' : true
32
+ }
33
+ },
34
+ src : ['<%= paths.js.files %>']
35
+ },
36
+ });
37
+
38
+ };
grunt/phplint.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('phplint', {
4
+ options : {
5
+ phpArgs : {
6
+ '-lf': null
7
+ }
8
+ },
9
+ all : {
10
+ src : '<%= paths.php.files %>'
11
+ }
12
+ });
13
+
14
+ };
grunt/sass.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('sass', {
4
+ options : {
5
+ outputStyle : 'compressed',
6
+ },
7
+ core : {
8
+ files : [{
9
+ expand : true,
10
+ cwd : '<%= paths.sass.base_src %>/',
11
+ dest : '<%= paths.sass.base_dest %>/',
12
+ src : ['<%= paths.sass.target %>', '<%= paths.sass.exclude %>'],
13
+ ext : '<%= paths.sass.ext %>'
14
+ }]
15
+ },
16
+ themes : {
17
+ options : {
18
+ //includePaths : require('node-bourbon').includePaths
19
+ },
20
+ files : [{
21
+ expand : true,
22
+ cwd : 'themes/',
23
+ src : ['*/**/*.scss', '<%= paths.sass.exclude %>'],
24
+ dest : '<%= paths.sass.dest %>/',
25
+ srcd : '<%= paths.sass.src %>/',
26
+ ext : '<%= paths.sass.ext %>',
27
+ rename : function(dest, matchedSrcPath, options) {
28
+ var path = [options.cwd, matchedSrcPath.replace(options.srcd, dest)].join('');
29
+ return path;
30
+ }
31
+ }]
32
+ }
33
+ });
34
+
35
+ };
grunt/uglify.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('uglify', {
4
+ options : {
5
+ mangle: false,
6
+ report: 'min'
7
+ },
8
+ all : {
9
+ files : [{
10
+ expand : true,
11
+ cwd : '',
12
+ dest : '',
13
+ src : ['<%= paths.js.files %>'],
14
+ rename : function(dest, srcPath) {
15
+ return srcPath.replace('/' + grunt.config.get('paths.js.src') + '/', '/' + grunt.config.get('paths.js.dest') + '/');
16
+ }
17
+ }]
18
+ },
19
+ });
20
+
21
+ };
grunt/watch.js ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.config('watch', {
4
+ phplint : {
5
+ files : '<%= paths.php.files_std %>',
6
+ tasks : ['phplint'],
7
+ options : {
8
+ spawn : false
9
+ }
10
+ },
11
+ sass_core : {
12
+ files : ['<%= paths.sass.base_src %>/**/*.scss'],
13
+ tasks : ['sass:core']
14
+ },
15
+ sass_themes : {
16
+ files : ['themes/**/<%= paths.sass.src %>/**/*.scss'],
17
+ tasks : ['sass:themes']
18
+ },
19
+ jshint : {
20
+ files : '<%= paths.js.files_std %>',
21
+ tasks : ['jshint:all'],
22
+ options : {
23
+ spawn : false
24
+ }
25
+ },
26
+ js : {
27
+ files : '<%= paths.js.files_std %>',
28
+ tasks : ['jshint:all', 'uglify:all'],
29
+ options : {
30
+ spawn : false
31
+ }
32
+ }
33
+ });
34
+
35
+ grunt.event.on('watch', function(action, filepath) {
36
+ // Determine task based on filepath
37
+ var get_ext = function(path) {
38
+ var ret = '';
39
+ var i = path.lastIndexOf('.');
40
+ if ( -1 !== i && i <= path.length ) {
41
+ ret = path.substr(i + 1);
42
+ }
43
+ return ret;
44
+ };
45
+ switch ( get_ext(filepath) ) {
46
+ // PHP
47
+ case 'php' :
48
+ grunt.config('paths.php.files', [filepath]);
49
+ break;
50
+ // JavaScript
51
+ case 'js' :
52
+ grunt.config('paths.js.files', [filepath]);
53
+ break;
54
+ }
55
+ });
56
+
57
+ };
images/page_delete.gif ADDED
Binary file
includes/class.base.php ADDED
@@ -0,0 +1,364 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package Cornerstone
5
+ * @subpackage Base
6
+ * @author Archetyped
7
+ *
8
+ */
9
+ class CNR_Base {
10
+
11
+ /**
12
+ * Variable name of base object in global scope
13
+ * @var string
14
+ */
15
+ var $base = 'cnr';
16
+
17
+ /**
18
+ * Prefix for plugin-related data (attributes, DB tables, etc.)
19
+ * @var string
20
+ */
21
+ var $prefix = 'cnr';
22
+
23
+ /**
24
+ * Client files
25
+ * @var array
26
+ * Structure
27
+ * > Key: unique file ID
28
+ * > Properties
29
+ * > file (string) File path (Relative to plugin base)
30
+ * > deps (array) Script dependencies
31
+ * > Internal dependencies are wrapped in square brackets ([])
32
+ * > context (string|array)
33
+ * > Context in which the script should be included
34
+ * > in_footer (bool) optional [Default: FALSE]
35
+ * > If TRUE, file will be included in footer of page, otherwise it will be included in the header
36
+ *
37
+ * Array is processed and converted to an object on init
38
+ */
39
+ var $client_files = array(
40
+ 'scripts' => array(),
41
+ 'styles' => array()
42
+ );
43
+
44
+ /**
45
+ * Utilities
46
+ * @var CNR_Utilities
47
+ */
48
+ var $util = null;
49
+
50
+ /**
51
+ * Options
52
+ * @var CNR_Options
53
+ */
54
+ var $options = null;
55
+
56
+ /**
57
+ * Constructor
58
+ */
59
+ function __construct() {
60
+ $this->util = new CNR_Utilities($this);
61
+ }
62
+
63
+ /*-** Init **-*/
64
+
65
+ /**
66
+ * Default initialization method
67
+ * To be overridden by child classes
68
+ * @uses this::init_options()
69
+ * @uses this::init_client_files()
70
+ * @uses this::register_hooks()
71
+ * @uses this::init_env()
72
+ * @uses add_action()
73
+ */
74
+ function init() {
75
+ if ( !isset($this) )
76
+ return false;
77
+
78
+ //Options
79
+ $this->init_options();
80
+ add_action('admin_init', $this->m('init_options_text'));
81
+
82
+ /* Client files */
83
+ $this->init_client_files();
84
+
85
+ /* Hooks */
86
+ $this->register_hooks();
87
+
88
+ /* Environment */
89
+ $hook = 'init';
90
+ if ( did_action( $hook ) ) {
91
+ $this->init_env();
92
+ } else {
93
+ add_action( $hook, $this->m('init_env'), 1 );
94
+ }
95
+ }
96
+
97
+ function register_hooks() {
98
+ //Activation
99
+ $func_activate = 'activate';
100
+ if ( method_exists($this, $func_activate) )
101
+ register_activation_hook($this->util->get_plugin_base_file(), $this->m($func_activate));
102
+ //Deactivation
103
+ $func_deactivate = 'deactivate';
104
+ if ( method_exists($this, $func_deactivate) )
105
+ register_deactivation_hook($this->util->get_plugin_base_file(), $this->m($func_deactivate));
106
+ }
107
+
108
+ /**
109
+ * Checks if options are valid
110
+ * > Validates:
111
+ * > Option data
112
+ * > Option class existence
113
+ * > Optional: Options instance variable (Default: yes)
114
+ * @param array $data Data to be used on options
115
+ * @return bool TRUE if options are valid, FALSE otherwise
116
+ */
117
+ function is_options_valid($data, $check_var = true) {
118
+ $class = $this->get_options_class();
119
+ $ret = ( empty($data) || !is_array($data) || !class_exists($class) ) ? false : true;
120
+ if ( $ret && $check_var && !is_a($this->options, $class) )
121
+ $ret = false;
122
+ return $ret;
123
+ }
124
+
125
+ /**
126
+ * Retrieves options class name
127
+ * @return string
128
+ */
129
+ function get_options_class() {
130
+ static $class = null;
131
+ if ( is_null($class) )
132
+ $class = $this->add_prefix_uc('Options');
133
+ return $class;
134
+ }
135
+
136
+ /**
137
+ * Initialize options
138
+ * To be called by child class
139
+ */
140
+ function init_options($options_config = null) {
141
+ if ( !$this->is_options_valid($options_config, false) )
142
+ return false;
143
+ $class = $this->get_options_class();
144
+ $this->options = new $class($options_config);
145
+ }
146
+
147
+ /**
148
+ * Initialize options text
149
+ * Must be called separately from standard options init because textdomain is not available until later
150
+ * To be called by method in child class
151
+ * @param array $opts_text Options passed by method in child class
152
+ * @return void
153
+ */
154
+ function init_options_text($options_text = null) {
155
+ if ( !$this->is_options_valid($options_text) )
156
+ return false;
157
+
158
+ //Groups
159
+ if ( isset($options_text['groups']) ) {
160
+ foreach ( $options_text['groups'] as $id => $title) {
161
+ $g_temp =& $this->options->get_group($id);
162
+ $g_temp->title = $title;
163
+ }
164
+ }
165
+
166
+ //Options
167
+ if ( isset($options_text['items']) ) {
168
+ foreach ( $options_text['items'] as $opt => $title ) {
169
+ $option_temp =& $this->options->get($opt);
170
+ $option_temp->set_title($title);
171
+ }
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Initialize environment (Localization, etc.)
177
+ * To be overriden by child class
178
+ * @uses `init` Action hook as trigger
179
+ */
180
+ function init_env() {}
181
+
182
+ function init_client_files() {
183
+ foreach ( $this->client_files as $key => $val ) {
184
+ if ( empty($val) && isset($this->{$key}) )
185
+ $this->client_files[$key] =& $this->{$key};
186
+ $g =& $this->client_files[$key];
187
+ if ( is_array($g) && !empty($g) ) {
188
+ $g = $this->util->parse_client_files($g, $key);
189
+ }
190
+ }
191
+
192
+ //Register
193
+ add_action('init', $this->m('register_client_files'));
194
+
195
+ //Enqueue
196
+ $hook_enqueue = ( ( is_admin() ) ? 'admin' : 'wp' ) . '_enqueue_scripts' ;
197
+ add_action($hook_enqueue, $this->m('enqueue_client_files'));
198
+ }
199
+
200
+ /**
201
+ * Register client files
202
+ * @uses `init` Action hook for execution
203
+ * @return void
204
+ */
205
+ function register_client_files() {
206
+ $v = $this->util->get_plugin_version();
207
+ foreach ( $this->client_files as $type => $files ) {
208
+ if ( !empty($files) ) {
209
+ $func = $this->get_client_files_handler($type, 'register');
210
+ if ( !$func )
211
+ continue;
212
+ foreach ( $files as $f ) {
213
+ $f->file = ( is_array($f->file) && is_callable($f->file) ) ? call_user_func($f->file) : $this->util->get_file_url($f->file);
214
+ $params = array($f->id, $f->file, $f->deps, $v);
215
+ switch ( $type ) {
216
+ case 'scripts':
217
+ $params[] = $f->in_footer;
218
+ break;
219
+ case 'styles':
220
+ $params[] = $f->media;
221
+ break;
222
+ }
223
+ call_user_func_array($func, $params);
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Enqueues files for client output (scripts/styles)
231
+ * @uses `admin_enqueue_scripts` Action hook depending on context
232
+ * @uses `wp_enqueue_scripts` Action hook depending on context
233
+ * @return void
234
+ */
235
+ function enqueue_client_files() {
236
+ //Enqueue files
237
+ foreach ( $this->client_files as $type => $files ) {
238
+ if ( !empty($files) ) {
239
+ $func = $this->get_client_files_handler($type, 'enqueue');
240
+ if ( !$func )
241
+ continue;
242
+ foreach ( $files as $f ) {
243
+ $load = true;
244
+ //Callback
245
+ if ( is_callable($f->callback) && !call_user_func($f->callback) )
246
+ $load = false;
247
+
248
+ //Context
249
+ if ( $load && !empty($f->context) && !$this->util->is_context($f->context) )
250
+ $load = false;
251
+
252
+ //Load valid file
253
+ if ( $load )
254
+ $func($f->id);
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Build function name for handling client operations
262
+ */
263
+ function get_client_files_handler($type, $action) {
264
+ $func = 'wp_' . $action . '_' . substr($type, 0, -1);
265
+ if ( !function_exists($func) )
266
+ $func = false;
267
+ return $func;
268
+ }
269
+
270
+ /*-** Reflection **-*/
271
+
272
+ /**
273
+ * Retrieve base object
274
+ * @return object|bool Base object (FALSE if object does not exist)
275
+ */
276
+ function &get_base() {
277
+ $base = false;
278
+ if ( isset($GLOBALS[$this->base]) )
279
+ $base =& $GLOBALS[$this->base];
280
+ return $base;
281
+ }
282
+
283
+ /*-** Method/Function calling **-*/
284
+
285
+ /**
286
+ * Returns callback to instance method
287
+ * @param string $method Method name
288
+ * @return array Callback array
289
+ */
290
+ function &m($method) {
291
+ return $this->util->m($this, $method);
292
+ }
293
+
294
+ /*-** Prefix **-*/
295
+
296
+ /**
297
+ * Retrieve class prefix (with separator if set)
298
+ * @param bool|string $sep Separator to append to class prefix (Default: no separator)
299
+ * @return string Class prefix
300
+ */
301
+ function get_prefix($sep = null) {
302
+ $args = func_get_args();
303
+ return call_user_func_array($this->util->m($this->util, 'get_prefix'), $args);
304
+ }
305
+
306
+ /**
307
+ * Check if a string is prefixed
308
+ * @param string $text Text to check for prefix
309
+ * @param string $sep (optional) Separator used
310
+ */
311
+ function has_prefix($text, $sep = null) {
312
+ $args = func_get_args();
313
+ return call_user_func_array($this->util->m($this->util, 'has_prefix'), $args);
314
+ }
315
+
316
+ /**
317
+ * Prepend plugin prefix to some text
318
+ * @param string $text Text to add to prefix
319
+ * @param string $sep (optional) Text used to separate prefix and text
320
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
321
+ * @return string Text with prefix prepended
322
+ */
323
+ function add_prefix($text, $sep = null, $once = true) {
324
+ $args = func_get_args();
325
+ return call_user_func_array($this->util->m($this->util, 'add_prefix'), $args);
326
+ }
327
+
328
+ /**
329
+ * Prepend uppercased plugin prefix to some text
330
+ * @param string $text Text to add to prefix
331
+ * @param string $sep (optional) Text used to separate prefix and text
332
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
333
+ * @return string Text with prefix prepended
334
+ */
335
+ function add_prefix_uc($text, $sep = null, $once = true) {
336
+ $args = func_get_args();
337
+ return call_user_func_array($this->util->m($this->util, 'add_prefix_uc'), $args);
338
+ }
339
+
340
+ /**
341
+ * Add prefix to variable reference
342
+ * Updates actual variable rather than return value
343
+ * @uses CNR_Utilities::add_prefix_ref();
344
+ * @param string $var Variable to add prefix to
345
+ * @param string $sep (optional) Separator text
346
+ * @param bool $once (optional) Add prefix only once
347
+ * @return void
348
+ */
349
+ function add_prefix_ref(&$var, $sep = null, $once = true) {
350
+ $args = func_get_args();
351
+ $args[0] =& $var;
352
+ call_user_func_array($this->util->m($this->util, 'add_prefix_ref'), $args);
353
+ }
354
+
355
+ /**
356
+ * Remove prefix from specified string
357
+ * @param string $text String to remove prefix from
358
+ * @param string $sep (optional) Separator used with prefix
359
+ */
360
+ function remove_prefix($text, $sep = null) {
361
+ $args = func_get_args();
362
+ return call_user_func_array($this->util->m($this->util, 'remove_prefix'), $args);
363
+ }
364
+ }
includes/class.content_base.php ADDED
@@ -0,0 +1,615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Content Types - Base Class
4
+ * Core properties/methods for Content Type derivative classes
5
+ * @package Cornerstone
6
+ * @subpackage Content Types
7
+ * @author Archetyped
8
+ */
9
+ class CNR_Content_Base extends CNR_Base {
10
+
11
+ /**
12
+ * Base class name
13
+ * @var string
14
+ */
15
+ var $base_class = 'cnr_content_base';
16
+
17
+ /**
18
+ * @var string Unique name
19
+ */
20
+ var $id = '';
21
+
22
+ /**
23
+ * Reference to parent object that current instance inherits from
24
+ * @var object
25
+ */
26
+ var $parent = null;
27
+
28
+ /**
29
+ * Title
30
+ * @var string
31
+ */
32
+ var $title = '';
33
+
34
+ /**
35
+ * Plural Title
36
+ * @var string
37
+ */
38
+ var $title_plural = '';
39
+
40
+ /**
41
+ * @var string Short description
42
+ */
43
+ var $description = '';
44
+
45
+ /**
46
+ * @var array Object Properties
47
+ */
48
+ var $properties = array();
49
+
50
+ /**
51
+ * Data for object
52
+ * May also contain data for nested objects
53
+ * @var mixed
54
+ */
55
+ var $data = null;
56
+
57
+ /**
58
+ * @var array Script resources to include for object
59
+ */
60
+ var $scripts = array();
61
+
62
+ /**
63
+ * @var array CSS style resources to include for object
64
+ */
65
+ var $styles = array();
66
+
67
+ /**
68
+ * Hooks (Filters/Actions) for object
69
+ * @var array
70
+ */
71
+ var $hooks = array();
72
+
73
+ /**
74
+ * Constructor
75
+ */
76
+ function __construct($id = '', $parent = null) {
77
+ parent::__construct();
78
+ $id = trim($id);
79
+ $this->id = $id;
80
+ if ( is_bool($parent) && $parent )
81
+ $parent = $id;
82
+ $this->set_parent($parent);
83
+ }
84
+
85
+ /* Getters/Setters */
86
+
87
+ /**
88
+ * Checks if the specified path exists in the object
89
+ * @param array $path Path to check for
90
+ * @return bool TRUE if path exists in object, FALSE otherwise
91
+ */
92
+ function path_isset($path = '') {
93
+ //Stop execution if no path is supplied
94
+ if ( empty($path) )
95
+ return false;
96
+ $args = func_get_args();
97
+ $path = $this->util->build_path($args);
98
+ $item =& $this;
99
+ //Iterate over path and check if each level exists before moving on to the next
100
+ for ($x = 0; $x < count($path); $x++) {
101
+ if ( $this->util->property_exists($item, $path[$x]) ) {
102
+ //Set $item as reference to next level in path for next iteration
103
+ $item =& $this->util->get_property($item, $path[$x]);
104
+ //$item =& $item[ $path[$x] ];
105
+ } else {
106
+ return false;
107
+ }
108
+ }
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * Retrieves a value from object using a specified path
114
+ * Checks to make sure path exists in object before retrieving value
115
+ * @param array $path Path to retrieve value from. Each item in array is a deeper dimension
116
+ * @return mixed Value at specified path
117
+ */
118
+ function &get_path_value($path = '') {
119
+ $ret = '';
120
+ $path = $this->util->build_path(func_get_args());
121
+ if ( $this->path_isset($path) ) {
122
+ $ret =& $this;
123
+ for ($x = 0; $x < count($path); $x++) {
124
+ if ( 0 == $x )
125
+ $ret =& $ret->{ $path[$x] };
126
+ else
127
+ $ret =& $ret[ $path[$x] ];
128
+ }
129
+ }
130
+ return $ret;
131
+ }
132
+
133
+ /**
134
+ * Search for specified member value in field type ancestors
135
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
136
+ * @param string $name Value to retrieve from member
137
+ * @return mixed Member value if found (Default: empty string)
138
+ */
139
+ function get_parent_value($member, $name = '', $default = '') {
140
+ $parent =& $this->get_parent();
141
+ return $this->get_object_value($parent, $member, $name, $default, 'parent');
142
+ }
143
+
144
+ /**
145
+ * Retrieves specified member value
146
+ * Handles inherited values
147
+ * Merging corresponding parents if value is an array (e.g. for property groups)
148
+ * @param string|array $member Member to search. May also contain a path to the desired member
149
+ * @param string $name Value to retrieve from member
150
+ * @param mixed $default Default value if no value found (Default: empty string)
151
+ * @param string $dir Direction to move through hierarchy to find value
152
+ * Possible Values:
153
+ * parent (default) - Search through field parents
154
+ * current - Do not search through connected objects
155
+ * container - Search through field containers
156
+ * caller - Search through field callers
157
+ * @return mixed Specified member value
158
+ */
159
+ function get_member_value($member, $name = '', $default = '', $dir = 'parent') {
160
+ //Check if path to member is supplied
161
+ $path = array();
162
+ if ( is_array($member) && isset($member['tag']) ) {
163
+ if ( isset($member['attributes']['ref_base']) ) {
164
+ if ( 'root' != $member['attributes']['ref_base'] )
165
+ $path[] = $member['attributes']['ref_base'];
166
+ } else {
167
+ $path[] = 'properties';
168
+ }
169
+
170
+ $path[] = $member['tag'];
171
+ } else {
172
+ $path = $member;
173
+ }
174
+
175
+ $path = $this->util->build_path($path, $name);
176
+ //Set defaults and prepare data
177
+ $val = $default;
178
+ $inherit = false;
179
+ $inherit_tag = '{inherit}';
180
+
181
+ /* Determine whether the value must be retrieved from a parent/container object
182
+ * Conditions:
183
+ * > Path does not exist in current field
184
+ * > Path exists and is not an object, but at least one of the following is true:
185
+ * > Value at path is an array (e.g. properties, elements, etc. array)
186
+ * > Parent/container values should be merged with retrieved array
187
+ * > Value at path is a string that inherits from another field
188
+ * > Value from other field will be retrieved and will replace inheritance placeholder in retrieved value
189
+ */
190
+
191
+ $deeper = false;
192
+
193
+ if ( !$this->path_isset($path) )
194
+ $deeper = true;
195
+ else {
196
+ $val = $this->get_path_value($path);
197
+ if ( !is_object($val) && ( is_array($val) || ($inherit = strpos($val, $inherit_tag)) !== false ) )
198
+ $deeper = true;
199
+ else
200
+ $deeper = false;
201
+ }
202
+ if ( $deeper && 'current' != $dir ) {
203
+ //Get Parent value (recursive)
204
+ $ex_val = ( 'parent' != $dir ) ? $this->get_container_value($member, $name, $default) : $this->get_parent_value($member, $name, $default);
205
+ //Handle inheritance
206
+ if ( is_array($val) ) {
207
+ //Combine Arrays
208
+ if ( is_array($ex_val) )
209
+ $val = array_merge($ex_val, $val);
210
+ } elseif ( $inherit !== false ) {
211
+ //Replace placeholder with inherited string
212
+ $val = str_replace($inherit_tag, $ex_val, $val);
213
+ } else {
214
+ //Default: Set parent value as value
215
+ $val = $ex_val;
216
+ }
217
+ }
218
+
219
+ return $val;
220
+ }
221
+
222
+ /**
223
+ * Search for specified member value in an object
224
+ * @param object $object Reference to object to retrieve value from
225
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
226
+ * @param string $name (optional) Value to retrieve from member
227
+ * @param mixed $default (optional) Default value to use if no value found (Default: empty string)
228
+ * @param string $dir Direction to move through hierarchy to find value @see CNR_Field_Type::get_member_value() for possible values
229
+ * @return mixed Member value if found (Default: $default)
230
+ */
231
+ function get_object_value(&$object, $member, $name = '', $default = '', $dir = 'parent') {
232
+ $ret = $default;
233
+ if ( is_object($object) && method_exists($object, 'get_member_value') )
234
+ $ret = $object->get_member_value($member, $name, $default, $dir);
235
+ return $ret;
236
+ }
237
+
238
+ /**
239
+ * Retrieve value from data member
240
+ * @param bool $top (optional) Whether to traverse through the field hierarchy to get data for field (Default: TRUE)
241
+ * @return mixed Value at specified path
242
+ */
243
+ function get_data($top = true) {
244
+ $top = !!$top;
245
+ $obj = $this;
246
+ $obj_path = array($this);
247
+ $path = array();
248
+ //Iterate through hiearchy to get top-most object
249
+ while ( !empty($obj) ) {
250
+ $new = null;
251
+ //Try to get caller first
252
+ if ( method_exists($obj, 'get_caller') ) {
253
+ $checked = true;
254
+ $new = $obj->get_caller();
255
+ }
256
+ //Try to get container if no caller found
257
+ if ( empty($new) && method_exists($obj, 'get_container') ) {
258
+ $checked = true;
259
+ $new = $obj->get_container();
260
+ }
261
+
262
+ $obj = $new;
263
+
264
+ //Stop iteration
265
+ if ( !empty($obj) ) {
266
+ //Add object to path if it is valid
267
+ $obj_path[] = $obj;
268
+ }
269
+ }
270
+
271
+ //Check each object (starting with top-most) for matching data for current field
272
+
273
+ //Reverse array
274
+ $obj_path = array_reverse($obj_path);
275
+ //Build path for data location
276
+ foreach ( $obj_path as $obj ) {
277
+ if ( $this->util->property_exists($obj, 'id') )
278
+ $path[] = $obj->id;
279
+ }
280
+
281
+ //Iterate through objects
282
+ while ( !empty($obj_path) ) {
283
+ //Get next object
284
+ $obj = array_shift($obj_path);
285
+ //Shorten path
286
+ array_shift($path);
287
+ //Check for value in object and stop iteration if matching data found
288
+ if ( ($val = $this->get_object_value($obj, 'data', $path, null, 'current')) && !is_null($val) ) {
289
+ break;
290
+ }
291
+ }
292
+
293
+ return $val;
294
+ }
295
+
296
+ /**
297
+ * Sets value in data member
298
+ * Sets value to data member itself by default
299
+ * @param mixed $value Value to set
300
+ * @param string|array $name Name of value to set (Can also be path to value)
301
+ */
302
+ function set_data($value, $name = '') {
303
+ $ref =& $this->get_path_value('data', $name);
304
+ $ref = $value;
305
+ }
306
+
307
+ /**
308
+ * Retrieve base_class property
309
+ * @return string base_class property of current class/instance object
310
+ */
311
+ static function get_base_class() {
312
+ $ret = '';
313
+ if ( isset($this) )
314
+ $ret = $this->base_class;
315
+ else {
316
+ $ret = CNR_Utilities::get_property(__CLASS__, 'base_class');
317
+ }
318
+
319
+ return $ret;
320
+ }
321
+
322
+ /**
323
+ * Sets parent object of current instance
324
+ * Parent objects must be the same object type as current instance
325
+ * @param string|object $parent Parent ID or reference
326
+ */
327
+ function set_parent($parent) {
328
+ if ( !empty($parent) ) {
329
+ //Validate parent object
330
+ if ( is_array($parent) )
331
+ $parent =& $parent[0];
332
+
333
+ //Retrieve reference object if ID was supplied
334
+ if ( is_string($parent) ) {
335
+ $parent = trim($parent);
336
+ //Check for existence of parent
337
+ $lookup = $this->base_class . 's';
338
+ if ( isset($GLOBALS[$lookup][$parent]) ) {
339
+ //Get reference to parent
340
+ $parent =& $GLOBALS[$lookup][$parent];
341
+ }
342
+ }
343
+
344
+ //Set reference to parent field type
345
+ if ( is_a($parent, $this->base_class) ) {
346
+ $this->parent =& $parent;
347
+ }
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Retrieve field type parent
353
+ * @return CNR_Field_Type Reference to parent field
354
+ */
355
+ function &get_parent() {
356
+ return $this->parent;
357
+ }
358
+
359
+ /**
360
+ * Retrieves field ID
361
+ * @param string|CNR_Field|array $field (optional) Field object or ID of field or options array
362
+ * @return string|bool Field ID, FALSE if $field is invalid
363
+ */
364
+ function get_id($field = null) {
365
+ $ret = false;
366
+
367
+ // Get options.
368
+ $num_args = func_num_args();
369
+ $options = ( $num_args > 0 && ( $last_arg = func_get_arg($num_args - 1) ) && is_array($last_arg) ) ? $last_arg : array();
370
+
371
+ // Validate field parameter.
372
+ if ( ( !is_object($field) || !is_a($field, 'cnr_field_type') ) && isset($this) ) {
373
+ $field =& $this;
374
+ }
375
+
376
+ if ( is_a($field, CNR_Field_Type::get_base_class()) )
377
+ $id = $field->id;
378
+
379
+ if ( is_string($id) )
380
+ $ret = trim($id);
381
+
382
+ // Setup options.
383
+ $options = wp_parse_args( $options, [ 'format' => null ] );
384
+ //Check if field should be formatted
385
+ if ( is_string($ret) && !empty($options['format']) ) {
386
+ //Clear format option if it is an invalid value
387
+ if ( is_bool($options['format']) || is_int($options['format']) )
388
+ $options['format'] = null;
389
+ //Setup values
390
+ $wrap = array('open' => '[', 'close' => ']');
391
+ if ( isset($options['wrap']) && is_array($options['wrap']) )
392
+ $wrap = wp_parse_args($options['wrap'], $wrap);
393
+ $wrap_trailing = ( isset($options['wrap_trailing']) ) ? !!$options['wrap_trailing'] : true;
394
+ switch ( $options['format'] ) {
395
+ case 'attr_id' :
396
+ $wrap = (array('open' => '_', 'close' => '_'));
397
+ $wrap_trailing = false;
398
+ break;
399
+ }
400
+ $c = $field->get_caller();
401
+ $field_id = array($ret);
402
+ while ( !!$c ) {
403
+ //Add ID of current field to array
404
+ if ( isset($c->id) && is_a($c, $this->base_class) )
405
+ $field_id[] = $c->id;
406
+ $c = ( method_exists($c, 'get_caller') ) ? $c->get_caller() : null;
407
+ }
408
+
409
+ //Add prefix to ID value
410
+ $field_id[] = 'attributes';
411
+
412
+ //Convert array to string
413
+ $ret = $field->prefix . $wrap['open'] . implode($wrap['close'] . $wrap['open'], array_reverse($field_id)) . ( $wrap_trailing ? $wrap['close'] : '');
414
+ }
415
+ return $ret;
416
+ }
417
+
418
+ /**
419
+ * Set object title
420
+ * @param string $title Title for object
421
+ * @param string $plural Plural form of title
422
+ */
423
+ function set_title($title = '', $plural = '') {
424
+ $this->title = strip_tags(trim($title));
425
+ if ( isset($plural) )
426
+ $this->title_plural = strip_tags(trim($plural));
427
+ }
428
+
429
+ /**
430
+ * Retrieve object title
431
+ * @param bool $plural TRUE if plural title should be retrieved, FALSE otherwise (Default: FALSE)
432
+ */
433
+ function get_title($plural = false) {
434
+ $dir = 'current';
435
+ //Singular
436
+ if ( !$plural )
437
+ return $this->get_member_value('title', '','', $dir);
438
+ //Plural
439
+ $title = $this->get_member_value('title_plural', '', '', $dir);
440
+ if ( empty($title) ) {
441
+ //Use singular title for plural base
442
+ $title = $this->get_member_value('title', '', '', $dir);
443
+ //Determine technique for making title plural
444
+ //Get last letter
445
+ if ( !empty($title) ) {
446
+ $tail = substr($title, -1);
447
+ switch ( $tail ) {
448
+ case 's' :
449
+ $title .= 'es';
450
+ break;
451
+ case 'y' :
452
+ $title = substr($title, 0, -1) . 'ies';
453
+ break;
454
+ default :
455
+ $title .= 's';
456
+ }
457
+ }
458
+ }
459
+ return $title;
460
+ }
461
+
462
+ /**
463
+ * Set object description
464
+ * @param string $description Description for object
465
+ */
466
+ function set_description($description = '') {
467
+ $this->description = strip_tags(trim($description));
468
+ }
469
+
470
+ /**
471
+ * Retrieve object description
472
+ * @return string Object description
473
+ */
474
+ function get_description() {
475
+ $dir = 'current';
476
+ return $this->get_member_value('description', '','', $dir);
477
+ return $desc;
478
+ }
479
+
480
+ /*-** Hooks **-*/
481
+
482
+ /**
483
+ * Retrieve hooks added to object
484
+ * @return array Hooks
485
+ */
486
+ function get_hooks() {
487
+ return $this->get_member_value('hooks', '', array());
488
+ }
489
+
490
+ /**
491
+ * Add hook for object
492
+ * @see add_filter() for parameter defaults
493
+ * @param $tag
494
+ * @param $function_to_add
495
+ * @param $priority
496
+ * @param $accepted_args
497
+ */
498
+ function add_hook($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
499
+ //Create new array for tag (if not already set)
500
+ if ( !isset($this->hooks[$tag]) )
501
+ $this->hooks[$tag] = array();
502
+ //Build Unique ID
503
+ if ( is_string($function_to_add) )
504
+ $id = $function_to_add;
505
+ elseif ( is_array($function_to_add) && !empty($function_to_add) )
506
+ $id = strval($function_to_add[count($function_to_add) - 1]);
507
+ else
508
+ $id = 'function_' . ( count($this->hooks[$tag]) + 1 );
509
+ //Add hook
510
+ $this->hooks[$tag][$id] = func_get_args();
511
+ }
512
+
513
+ /**
514
+ * Convenience method for adding an action for object
515
+ * @see add_filter() for parameter defaults
516
+ * @param $tag
517
+ * @param $function_to_add
518
+ * @param $priority
519
+ * @param $accepted_args
520
+ */
521
+ function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
522
+ $this->add_hook($tag, $function_to_add, $priority, $accepted_args);
523
+ }
524
+
525
+ /**
526
+ * Convenience method for adding a filter for object
527
+ * @see add_filter() for parameter defaults
528
+ * @param $tag
529
+ * @param $function_to_add
530
+ * @param $priority
531
+ * @param $accepted_args
532
+ */
533
+ function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
534
+ $this->add_hook($tag, $function_to_add, $priority, $accepted_args);
535
+ }
536
+
537
+ /*-** Dependencies **-*/
538
+
539
+ /**
540
+ * Adds dependency to object
541
+ * @param string $type Type of dependency to add (script, style)
542
+ * @param array|string $context When dependency will be added (@see CNR_Utilities::get_action() for possible contexts)
543
+ * @see wp_enqueue_script for the following of the parameters
544
+ * @param $handle
545
+ * @param $src
546
+ * @param $deps
547
+ * @param $ver
548
+ * @param $ex
549
+ */
550
+ function add_dependency($type, $context, $handle, $src = false, $deps = array(), $ver = false, $ex = false) {
551
+ $args = func_get_args();
552
+ //Remove type/context from arguments
553
+ $args = array_slice($args, 2);
554
+
555
+ //Set context
556
+ if ( !is_array($context) ) {
557
+ //Wrap single contexts in an array
558
+ if ( is_string($context) )
559
+ $context = array($context);
560
+ else
561
+ $context = array();
562
+ }
563
+ //Add file to instance property
564
+ $this->{$type}[$handle] = array('context' => $context, 'params' => $args);
565
+ }
566
+
567
+ /**
568
+ * Add script to object to be added in specified contexts
569
+ * @param array|string $context Array of contexts to add script to page
570
+ * @see wp_enqueue_script for the following of the parameters
571
+ * @param $handle
572
+ * @param $src
573
+ * @param $deps
574
+ * @param $ver
575
+ * @param $in_footer
576
+ */
577
+ function add_script( $context, $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) {
578
+ $args = func_get_args();
579
+ //Add file type to front of arguments array
580
+ array_unshift($args, 'scripts');
581
+ call_user_func_array(array(&$this, 'add_dependency'), $args);
582
+ }
583
+
584
+ /**
585
+ * Retrieve script dependencies for object
586
+ * @return array Script dependencies
587
+ */
588
+ function get_scripts() {
589
+ return $this->get_member_value('scripts', '', array());
590
+ }
591
+
592
+ /**
593
+ * Add style to object to be added in specified contexts
594
+ * @param array|string $context Array of contexts to add style to page
595
+ * @see wp_enqueue_style for the following of the parameters
596
+ * @param $handle
597
+ * @param $src
598
+ * @param $deps
599
+ * @param $ver
600
+ * @param $in_footer
601
+ */
602
+ function add_style( $handle, $src = false, $deps = array(), $ver = false, $media = false ) {
603
+ $args = func_get_args();
604
+ array_unshift($args, 'styles');
605
+ call_user_method_array('add_dependency', $this, $args);
606
+ }
607
+
608
+ /**
609
+ * Retrieve Style dependencies for object
610
+ * @return array Style dependencies
611
+ */
612
+ function get_styles() {
613
+ return $this->get_member_value('styles', '', array());
614
+ }
615
+ }
includes/class.content_type.php ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class CNR_Content_Type extends CNR_Content_Base {
3
+
4
+ /**
5
+ * Base class for instance objects
6
+ * @var string
7
+ */
8
+ var $base_class = 'cnr_content_type';
9
+
10
+ /**
11
+ * Indexed array of fields in content type
12
+ * @var array
13
+ */
14
+ var $fields = array();
15
+
16
+ /**
17
+ * Associative array of groups in conten type
18
+ * Key: Group name
19
+ * Value: object of group properties
20
+ * > description string Group description
21
+ * > location string Location of group on edit form
22
+ * > fields array Fields in group
23
+ * @var array
24
+ */
25
+ var $groups = array();
26
+
27
+ /* Constructors */
28
+
29
+ /**
30
+ * Class constructor
31
+ * @param string $id Content type ID
32
+ * @param string|bool $parent (optional) Parent to inherit properties from (Default: none)
33
+ * @param array $properties (optional) Properties to set for content type (Default: none)
34
+ */
35
+ function __construct($id = '', $parent = null, $properties = null) {
36
+ parent::__construct($id, $parent);
37
+
38
+ //Set properties
39
+ //TODO Iterate through additional arguments and set instance properties
40
+ }
41
+
42
+ /* Registration */
43
+
44
+ /**
45
+ * Registers current content type w/CNR
46
+ */
47
+ function register() {
48
+ global $cnr_content_utilities;
49
+ $cnr_content_utilities->register_content_type($this);
50
+ }
51
+
52
+ /* Getters/Setters */
53
+
54
+ /**
55
+ * Adds group to content type
56
+ * Groups are used to display related fields in the UI
57
+ * @param string $id Unique name for group
58
+ * @param string $title Group title
59
+ * @param string $description Short description of group's purpose
60
+ * @param string $location Where group will be displayed on post edit form (Default: main)
61
+ * @param array $fields (optional) ID's of existing fields to add to group
62
+ * @return object Group object
63
+ */
64
+ function &add_group($id, $title = '', $description = '', $location = 'normal', $fields = array()) {
65
+ //Create new group and set properties
66
+ $id = trim($id);
67
+ $this->groups[$id] =& $this->create_group($title, $description, $location);
68
+ //Add fields to group (if supplied)
69
+ if ( !empty($fields) && is_array($fields) )
70
+ $this->add_to_group($id, $fields);
71
+ return $this->groups[$id];
72
+ }
73
+
74
+ /**
75
+ * Remove specified group from content type
76
+ * @param string $id Group ID to remove
77
+ */
78
+ function remove_group($id) {
79
+ $id = trim($id);
80
+ if ( $this->group_exists($id) ) {
81
+ unset($this->groups[$id]);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Standardized method to create a new field group
87
+ * @param string $title Group title (used in meta boxes, etc.)
88
+ * @param string $description Short description of group's purpose
89
+ * @param string $location Where group will be displayed on post edit form (Default: main)
90
+ * @return object Group object
91
+ */
92
+ function &create_group($title = '', $description = '', $location = 'normal') {
93
+ $group = new stdClass();
94
+ $title = ( is_scalar($title) ) ? trim($title) : '';
95
+ $group->title = $title;
96
+ $description = ( is_scalar($description) ) ? trim($description) : '';
97
+ $group->description = $description;
98
+ $location = ( is_scalar($location) ) ? trim($location) : 'normal';
99
+ $group->location = $location;
100
+ $group->fields = array();
101
+ return $group;
102
+ }
103
+
104
+ /**
105
+ * Checks if group exists
106
+ * @param string $id Group name
107
+ * @return bool TRUE if group exists, FALSE otherwise
108
+ */
109
+ function group_exists($id) {
110
+ $id = trim($id);
111
+ //Check if group exists in content type
112
+ return ( !is_null($this->get_member_value('groups', $id, null)) );
113
+ }
114
+
115
+ /**
116
+ * Adds field to content type
117
+ * @param string $id Unique name for field
118
+ * @param CNR_Field_Type|string $parent Field type that this field is based on
119
+ * @param array $properties (optional) Field properties
120
+ * @param string $group (optional) Group ID to add field to
121
+ * @return CNR_Field Reference to new field
122
+ */
123
+ function &add_field($id, $parent, $properties = array(), $group = null) {
124
+ //Create new field
125
+ $id = trim(strval($id));
126
+ $field = new CNR_Field($id);
127
+ $field->set_parent($parent);
128
+ $field->set_container($this);
129
+ $field->set_properties($properties);
130
+
131
+ //Add field to content type
132
+ $this->fields[$id] =& $field;
133
+ //Add field to group
134
+ $this->add_to_group($group, $field->id);
135
+ return $field;
136
+ }
137
+
138
+ /**
139
+ * Removes field from content type
140
+ * @param string|CNR_Field $field Object or Field ID to remove
141
+ */
142
+ function remove_field($field) {
143
+ if ( $field instanceof CNR_Field_Type ) {
144
+ $field = $field->get_id();
145
+ }
146
+ if ( !is_string($field) || empty($field) )
147
+ return false;
148
+
149
+ //Remove from fields array
150
+ //$this->fields[$field] = null;
151
+ unset($this->fields[$field]);
152
+
153
+ //Remove field from groups
154
+ $this->remove_from_group($field);
155
+ }
156
+
157
+ /**
158
+ * Retrieve specified field in Content Type
159
+ * @param string $field Field ID
160
+ * @return CNR_Field Specified field
161
+ */
162
+ function &get_field($field) {
163
+ if ( $this->has_field($field) ) {
164
+ $field = trim($field);
165
+ $field = $this->get_member_value('fields', $field);
166
+ } else {
167
+ //Return empty field if no field exists
168
+ $field = new CNR_Field('');
169
+ }
170
+ return $field;
171
+ }
172
+
173
+ /**
174
+ * Checks if field exists in the content type
175
+ * @param string $field Field ID
176
+ * @return bool TRUE if field exists, FALSE otherwise
177
+ */
178
+ function has_field($field) {
179
+ return ( !is_string($field) || empty($field) || is_null($this->get_member_value('fields', $field, null)) ) ? false : true;
180
+ }
181
+
182
+ /**
183
+ * Adds field to a group in the content type
184
+ * Group is created if it does not already exist
185
+ * @param string|array $group ID of group (or group parameters if new group) to add field to
186
+ * @param string|array $fields Name or array of field(s) to add to group
187
+ */
188
+ function add_to_group($group, $fields) {
189
+ //Validate parameters
190
+ $group_id = '';
191
+ if ( !empty($group) ) {
192
+ if ( !is_array($group) ) {
193
+ $group = array($group, $group);
194
+ }
195
+
196
+ $group[0] = $group_id = trim(sanitize_title_with_dashes($group[0]));
197
+ }
198
+ if ( empty($group_id) || empty($fields) )
199
+ return false;
200
+ //Create group if it doesn't exist
201
+ if ( !$this->group_exists($group_id) ) {
202
+ call_user_func_array($this->m('add_group'), $group);
203
+ }
204
+ if ( ! is_array($fields) )
205
+ $fields = array($fields);
206
+ foreach ( $fields as $field ) {
207
+ unset($fref);
208
+ if ( ! $this->has_field($field) )
209
+ continue;
210
+ $fref =& $this->get_field($field);
211
+ //Remove field from any other group it's in (fields can only be in one group)
212
+ foreach ( array_keys($this->groups) as $group_name ) {
213
+ if ( isset($this->groups[$group_name]->fields[$fref->id]) )
214
+ unset($this->groups[$group_name]->fields[$fref->id]);
215
+ }
216
+ //Add reference to field in group
217
+ $this->groups[$group_id]->fields[$fref->id] =& $fref;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Remove field from a group
223
+ * If no group is specified, then field is removed from all groups
224
+ * @param string|CNR_Field $field Field object or ID of field to remove from group
225
+ * @param string $group (optional) Group ID to remove field from
226
+ */
227
+ function remove_from_group($field, $group = '') {
228
+ //Get ID of field to remove or stop execution if field invalid
229
+ if ( $field instanceof CNR_Field_Type ) {
230
+ $field = $field->get_id();
231
+ }
232
+ if ( !is_string($field) || empty($field) )
233
+ return false;
234
+
235
+ //Remove field from group
236
+ if ( !empty($group) ) {
237
+ //Remove field from single group
238
+ if ( ($group =& $this->get_group($group)) && isset($group->fields[$field]) ) {
239
+ unset($group->fields[$field]);
240
+ }
241
+ } else {
242
+ //Remove field from all groups
243
+ foreach ( array_keys($this->groups) as $group ) {
244
+ if ( ($group =& $this->get_group($group)) && isset($group->fields[$field]) ) {
245
+ unset($group->fields[$field]);
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Retrieve specified group
253
+ * @param string $group ID of group to retrieve
254
+ * @return object Reference to specified group
255
+ */
256
+ function &get_group($group) {
257
+ $group = trim($group);
258
+ //Create group if it doesn't already exist
259
+ if ( ! $this->group_exists($group) )
260
+ $this->add_group($group);
261
+ $group = $this->get_member_value('groups', $group);
262
+ return $group;
263
+ }
264
+
265
+ /**
266
+ * Retrieve all groups in content type
267
+ * @return array Reference to group objects
268
+ */
269
+ function &get_groups() {
270
+ $groups = $this->get_member_value('groups');
271
+ return $groups;
272
+ }
273
+
274
+ /**
275
+ * Output fields in a group
276
+ * @param string $group ID of Group to output
277
+ * @return string Group output
278
+ */
279
+ function build_group($group) {
280
+ $out = array();
281
+ $classnames = (object) array(
282
+ 'multi' => 'multi_field',
283
+ 'single' => 'single_field',
284
+ 'elements' => 'has_elements'
285
+ );
286
+
287
+ //Stop execution if group does not exist
288
+ if ( $this->group_exists($group) && $group =& $this->get_group($group) ) {
289
+ $group_fields = ( count($group->fields) > 1 ) ? $classnames->multi : $classnames->single . ( ( ( $fs = array_keys($group->fields) ) && ( $f =& $group->fields[$fs[0]] ) && ( $els = $f->get_member_value('elements', '', null) ) && !empty($els) ) ? '_' . $classnames->elements : '' );
290
+ $classname = array('cnr_attributes_wrap', $group_fields);
291
+ $out[] = '<div class="' . implode(' ', $classname) . '">'; //Wrap all fields in group
292
+
293
+ //Build layout for each field in group
294
+ foreach ( array_keys($group->fields) as $field_id ) {
295
+ /**
296
+ * CNR_Field_Type
297
+ */
298
+ $field =& $group->fields[$field_id];
299
+ $field->set_caller($this);
300
+ //Start field output
301
+ $id = 'cnr_field_' . $field->get_id();
302
+ $class = array('cnr_attribute_wrap');
303
+ //If single field in group, check if field title matches group
304
+ if ( count($group->fields) == 1 && $group->title == $field->get_property('label') )
305
+ $class[] = 'group_field_title';
306
+ //Add flag to indicate that field was loaded on page
307
+ $inc = 'cnr[fields_loaded][' . $field->get_id() . ']';
308
+ $out[] = '<input type="hidden" id="' . $inc . '" name="' . $inc . '" value="1" />';
309
+ $out[] = '<div id="' . $id . '_wrap" class="' . implode(' ', $class) . '">';
310
+ //Build field layout
311
+ $out[] = $field->build_layout();
312
+ //end field output
313
+ $out[] = '</div>';
314
+ $field->clear_caller();
315
+ }
316
+ $out[] = '</div>'; //Close fields container
317
+ //Add description if exists
318
+ if ( !empty($group->description) )
319
+ $out[] = '<p class="cnr_group_description">' . $group->description . '</p>';
320
+ }
321
+
322
+ //Return group output
323
+ return implode($out);
324
+ }
325
+
326
+ /**
327
+ * Set data for a field
328
+ * @param string|CNR_Field $field Reference or ID of Field to set data for
329
+ * @param mixed $value Data to set
330
+ */
331
+ function set_data($field, $value = '') {
332
+ if ( 1 == func_num_args() && is_array($field) )
333
+ $this->data = $field;
334
+ else {
335
+ if ( $field instanceof CNR_Field_Type ) {
336
+ $field = $field->get_id();
337
+ }
338
+ if ( !is_string($field) || empty($field) )
339
+ return false;
340
+ $this->data[$field] = $value;
341
+ }
342
+ }
343
+
344
+ /*-** Admin **-*/
345
+
346
+ /**
347
+ * Adds meta boxes for post's content type
348
+ * Each group in content type is a separate meta box
349
+ * @param string $type Type of item meta boxes are being build for (post, page, link)
350
+ * @param string $context Location of meta box (normal, advanced, side)
351
+ * @param object $post Post object
352
+ */
353
+ function admin_do_meta_boxes($type, $context, $post) {
354
+ //Add post data to content type
355
+ global $cnr_content_utilities;
356
+ $this->set_data($cnr_content_utilities->get_item_data($post));
357
+
358
+ //Get Groups
359
+ $groups = array_keys($this->get_groups());
360
+ $priority = 'default';
361
+ //Iterate through groups and add meta box if it fits the context (location)
362
+ foreach ( $groups as $group_id ) {
363
+ $group =& $this->get_group($group_id);
364
+ if ( $context == $group->location && count($group->fields) ) {
365
+ //Format ID for meta box
366
+ $meta_box_id = $this->prefix . '_group_' . $group_id;
367
+ $group_args = array( 'group' => $group_id );
368
+ add_meta_box($meta_box_id, $group->title, $this->m('admin_build_meta_box'), $type, $context, $priority, $group_args);
369
+ }
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Outputs group fields for a meta box
375
+ * @param object $post Post object
376
+ * @param array $box Meta box properties
377
+ */
378
+ function admin_build_meta_box($post, $box) {
379
+ //Stop execution if group not specified
380
+ if ( !isset($box['args']['group']) )
381
+ return false;
382
+
383
+ //Get ID of group to output
384
+ $group_id =& $box['args']['group'];
385
+
386
+ $output = array();
387
+ $output[] = '<div class="cnr_group_wrap">';
388
+ $output[] = $this->build_group($group_id);
389
+ $output[] = '</div>';
390
+
391
+ //Output group content to screen
392
+ echo implode($output);
393
+ }
394
+
395
+ /**
396
+ * Retrieves type ID formatted as a meta value
397
+ * @return string
398
+ */
399
+ function get_meta_value() {
400
+ return serialize(array($this->id));
401
+ }
402
+ }
includes/class.content_utilities.php ADDED
@@ -0,0 +1,1248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Utilities for Content Type functionality
4
+ * @package Cornerstone
5
+ * @subpackage Content Types
6
+ * @author Archetyped
7
+ */
8
+ class CNR_Content_Utilities extends CNR_Base {
9
+
10
+ /**
11
+ * Array of hooks called
12
+ * @var array
13
+ */
14
+ var $hooks_processed = array();
15
+
16
+ /**
17
+ * Initialize content type functionality
18
+ */
19
+ function init() {
20
+ $this->register_hooks();
21
+ }
22
+
23
+ /**
24
+ * Registers hooks for content types
25
+ * @todo 2010-07-30: Check hooks for 3.0 compatibility
26
+ */
27
+ function register_hooks() {
28
+ //Register types
29
+ add_action('init', $this->m('register_types'));
30
+ add_action('init', $this->m('add_hooks'), 11);
31
+
32
+ //Enqueue scripts for fields in current post type
33
+ add_action('admin_enqueue_scripts', $this->m('enqueue_files'));
34
+
35
+ //Add menus
36
+ //add_action('admin_menu', $this->m('admin_menu'));
37
+
38
+ //Build UI on post edit form
39
+ add_action('do_meta_boxes', $this->m('admin_do_meta_boxes'), 10, 3);
40
+
41
+ //Get edit link for items
42
+ //add_filter('get_edit_post_link', $this->m('get_edit_item_url'), 10, 3);
43
+
44
+ //add_action('edit_form_advanced', $this->m('admin_page_edit_form'));
45
+
46
+ //Save Field data/Content type
47
+ add_action('save_post', $this->m('save_item_data'), 10, 2);
48
+
49
+ //Modify post query for content type compatibility
50
+ add_action('pre_get_posts', $this->m('pre_get_posts'), 20);
51
+ }
52
+
53
+ /**
54
+ * Initialize fields and content types
55
+ */
56
+ function register_types() {
57
+ //Global variables
58
+ global $cnr_field_types, $cnr_content_types;
59
+
60
+ /* Field Types */
61
+
62
+ //Base
63
+ $base = new CNR_Field_Type('base');
64
+ $base->set_description('Default Element');
65
+ $base->set_property('tag', 'span');
66
+ $base->set_property('class', '', 'attr');
67
+ $base->set_layout('form', '<{tag} name="{field_name}" id="{field_id}" {properties ref_base="root" group="attr"} />');
68
+ $base->set_layout('label', '<label for="{field_id}">{label}</label>');
69
+ $base->set_layout('display', '{data format="display"}');
70
+ $this->register_field($base);
71
+
72
+ //Base closed
73
+ $base_closed = new CNR_Field_Type('base_closed');
74
+ $base_closed->set_parent('base');
75
+ $base_closed->set_description('Default Element (Closed Tag)');
76
+ $base_closed->set_layout('form_start', '<{tag} id="{field_id}" name="{field_name}" {properties ref_base="root" group="attr"}>');
77
+ $base_closed->set_layout('form_end', '</{tag}>');
78
+ $base_closed->set_layout('form', '{form_start ref_base="layout"}{data}{form_end ref_base="layout"}');
79
+ $this->register_field($base_closed);
80
+
81
+ //Input
82
+ $input = new CNR_Field_Type('input');
83
+ $input->set_parent('base');
84
+ $input->set_description('Default Input Element');
85
+ $input->set_property('tag', 'input');
86
+ $input->set_property('type', 'text', 'attr');
87
+ $input->set_property('value', CNR_Field::USES_DATA, 'attr');
88
+ $this->register_field($input);
89
+
90
+ //Text input
91
+ $text = new CNR_Field_Type('text', 'input');
92
+ $text->set_description('Text Box');
93
+ $text->set_property('size', 15, 'attr');
94
+ $text->set_property('label');
95
+ $text->set_layout('form', '{label ref_base="layout"} {inherit}');
96
+ $this->register_field($text);
97
+
98
+ //Checkbox
99
+ $checkbox = new CNR_Field_Type('checkbox', 'input');
100
+ $checkbox->set_description('Checkbox');
101
+ $checkbox->set_property('type', 'checkbox', 'attr');
102
+ $checkbox->set_property('label');
103
+ $checkbox->set_property('checked', '', 'attr');
104
+ $checkbox->set_layout('form', '{inherit} {label ref_base="layout"}');
105
+ $this->register_field($checkbox);
106
+
107
+ //Textarea
108
+ $ta = new CNR_Field_Type('textarea', 'base_closed');
109
+ $ta->set_property('tag', 'textarea');
110
+ $ta->set_property('cols', 40, 'attr');
111
+ $ta->set_property('rows', 3, 'attr');
112
+ $this->register_field($ta);
113
+
114
+ //Rich Text
115
+ $rt = new CNR_Field_Type('richtext', 'textarea');
116
+ $rt->set_layout('form', '<div class="rt_container">{rich_editor}</div>');
117
+ $this->register_field($rt);
118
+
119
+ //Location
120
+ $location = new CNR_Field_Type('location');
121
+ $location->set_description('Geographic Coordinates');
122
+ $location->set_element('latitude', 'text', array( 'size' => 3, 'label' => 'Latitude' ));
123
+ $location->set_element('longitude', 'text', array( 'size' => 3, 'label' => 'Longitude' ));
124
+ $location->set_layout('form', '<span>{latitude ref_base="elements"}</span>, <span>{longitude ref_base="elements"}</span>');
125
+ $this->register_field($location);
126
+
127
+ //Phone
128
+ $phone = new CNR_Field_Type('phone');
129
+ $phone->set_description('Phone Number');
130
+ $phone->set_element('area', 'text', array( 'size' => 3 ));
131
+ $phone->set_element('prefix', 'text', array( 'size' => 3 ));
132
+ $phone->set_element('suffix', 'text', array( 'size' => 4 ));
133
+ $phone->set_layout('form', '({area ref_base="elements"}) {prefix ref_base="elements"} - {suffix ref_base="elements"}');
134
+ $this->register_field($phone);
135
+
136
+ //Hidden
137
+ $hidden = new CNR_Field_Type('hidden');
138
+ $hidden->set_parent('input');
139
+ $hidden->set_description('Hidden Field');
140
+ $hidden->set_property('type', 'hidden');
141
+ $this->register_field($hidden);
142
+
143
+ //Span
144
+ $span = new CNR_Field_Type('span');
145
+ $span->set_description('Inline wrapper');
146
+ $span->set_parent('base_closed');
147
+ $span->set_property('tag', 'span');
148
+ $span->set_property('value', 'Hello there!');
149
+ $this->register_field($span);
150
+
151
+ //Select
152
+ $select = new CNR_Field_Type('select');
153
+ $select->set_description('Select tag');
154
+ $select->set_parent('base_closed');
155
+ $select->set_property('tag', 'select');
156
+ $select->set_property('tag_option', 'option');
157
+ $select->set_property('options', array());
158
+ $select->set_layout('form', '{label ref_base="layout"} {form_start ref_base="layout"}{loop data="properties.options" layout="option" layout_data="option_data"}{form_end ref_base="layout"}');
159
+ $select->set_layout('option', '<{tag_option} value="{data_ext id="option_value"}">{data_ext id="option_text"}</{tag_option}>');
160
+ $select->set_layout('option_data', '<{tag_option} value="{data_ext id="option_value"}" selected="selected">{data_ext id="option_text"}</{tag_option}>');
161
+ $this->register_field($select);
162
+
163
+ //Enable plugins to modify (add, remove, etc.) field types
164
+ do_action_ref_array('cnr_register_field_types', array(&$cnr_field_types));
165
+
166
+ //Content Types
167
+
168
+ //Enable plugins to add/remove content types
169
+ do_action_ref_array('cnr_register_content_types', array(&$cnr_content_types));
170
+
171
+ //Enable plugins to modify content types after they have all been registered
172
+ do_action_ref_array('cnr_content_types_registered', array(&$cnr_content_types));
173
+ }
174
+
175
+ /**
176
+ * Add content type to global array of content types
177
+ * @param CNR_Content_Type $ct Content type to register
178
+ * @global array $cnr_content_types Content types array
179
+ */
180
+ function register_content_type(&$ct) {
181
+ //Add content type to CNR array
182
+ if ( $this->is_content_type($ct) && !empty($ct->id) ) {
183
+ global $cnr_content_types;
184
+ $cnr_content_types[$ct->id] =& $ct;
185
+ }
186
+ //WP Post Type Registration
187
+ global $wp_post_types;
188
+ if ( !empty($ct->id) && !isset($wp_post_types[$ct->id]) )
189
+ register_post_type($ct->id, $this->build_post_type_args($ct));
190
+ }
191
+
192
+ /**
193
+ * Generates arguments array for WP Post Type Registration
194
+ * @param CNR_Content_Type $ct Content type being registered
195
+ * @return array Arguments array
196
+ * @todo Enable custom taxonomies
197
+ */
198
+ function build_post_type_args(&$ct) {
199
+ //Setup labels
200
+
201
+ //Build labels
202
+ $labels = array (
203
+ 'name' => _( $ct->get_title(true) ),
204
+ 'singular_name' => _( $ct->get_title(false) ),
205
+ 'all_items' => sprintf( _( 'All %s' ), $ct->get_title(true) ),
206
+ );
207
+
208
+ //Action labels
209
+ $item_actions = array(
210
+ 'add_new' => 'Add New %s',
211
+ 'edit' => 'Edit %s',
212
+ 'new' => 'New %s',
213
+ 'view' => 'View %s',
214
+ 'search' => array('Search %s', true),
215
+ 'not_found' => array('No %s found', true, false),
216
+ 'not_found_in_trash' => array('No %s found in Trash', true, false)
217
+ );
218
+
219
+ foreach ( $item_actions as $key => $val ) {
220
+ $excluded = false;
221
+ $plural = false;
222
+ if ( is_array($val) ) {
223
+ if ( count($val) > 1 && true == $val[1] ) {
224
+ $plural = true;
225
+ }
226
+ if ( count($val) > 2 && false == $val[2] )
227
+ $excluded = true;
228
+ $val = $val[0];
229
+ }
230
+ $title = ( $plural ) ? $labels['name'] : $labels['singular_name'];
231
+ if ( $excluded )
232
+ $item = $key;
233
+ else {
234
+ $item = $key . '_item' . ( ( $plural ) ? 's' : '' );
235
+ }
236
+ $labels[$item] = sprintf($val, $title);
237
+ }
238
+
239
+ //Setup args
240
+ $args = array (
241
+ 'labels' => $labels,
242
+ 'description' => $ct->get_description(),
243
+ 'public' => true,
244
+ 'capability_type' => 'post',
245
+ 'rewrite' => array( 'slug' => strtolower($labels['name']) ),
246
+ 'has_archive' => true,
247
+ 'hierarchical' => false,
248
+ 'menu_position' => 5,
249
+ 'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions'),
250
+ 'taxonomies' => get_object_taxonomies('post'),
251
+ 'show_in_rest' => true,
252
+ );
253
+
254
+ return $args;
255
+ }
256
+
257
+ /**
258
+ * Add field type to global array of field types
259
+ * @param CNR_Field_Type $field Field to register
260
+ *
261
+ * @global array $cnr_field_types Field types array
262
+ */
263
+ function register_field(&$field) {
264
+ if ( $this->is_field($field) && !empty($field->id) ) {
265
+ global $cnr_field_types;
266
+ $cnr_field_types[$field->id] =& $field;
267
+ }
268
+ }
269
+
270
+ /*-** Helpers **-*/
271
+
272
+ /**
273
+ * Checks whether an object is a valid content type instance
274
+ * @param obj $ct Object to evaluate
275
+ * @return bool TRUE if object is a valid content type instance, FALSE otherwise
276
+ */
277
+ function is_content_type(&$ct) {
278
+ return is_a($ct, 'cnr_content_type');
279
+ }
280
+
281
+ /**
282
+ * Checks whether an object is a valid field instance
283
+ * @param obj $field Object to evaluate
284
+ * @return bool TRUE if object is a valid field instance, FALSE otherwise
285
+ */
286
+ function is_field(&$field) {
287
+ return is_a($field, 'cnr_field_type');
288
+ }
289
+
290
+ /*-** Handlers **-*/
291
+
292
+ /**
293
+ * Modifies query parameters to include custom content types
294
+ * Adds custom content types to default post query so these items are retrieved as well
295
+ * @param WP_Query $q Reference to WP_Query object being used to perform posts query
296
+ * @see WP_Query for reference
297
+ */
298
+ function pre_get_posts($q) {
299
+ $pt =& $q->query_vars['post_type'];
300
+ /* Do not continue processing if:
301
+ * > In admin section
302
+ * > Not main query (or CNR-initiated query)
303
+ * > Single object requested
304
+ * > More than one post type is already specified
305
+ * > Post type other than 'post' is supplied
306
+ */
307
+ if ( is_admin()
308
+ || ( !$q->is_main_query() && !isset($q->query_vars[$this->get_prefix()]) )
309
+ || $q->is_singular()
310
+ || ( is_array($pt)
311
+ && ( count($pt) > 1
312
+ || 'post' != $pt[0] )
313
+ )
314
+ || !in_array($pt, array('post', null))
315
+ ) {
316
+ return false;
317
+ }
318
+
319
+ $default_types = $this->get_default_post_types();
320
+ $custom_types = array_diff(array_keys($this->get_types()), $default_types);
321
+ if ( !count($custom_types) )
322
+ return false;
323
+ //Wrap post type in array
324
+ if ( empty($pt) || is_null($pt) )
325
+ $pt = array('post');
326
+ if ( !is_array($pt) )
327
+ $pt = array($pt);
328
+ //Add custom types to query
329
+ foreach ( $custom_types as $type ) {
330
+ $pt[] = $type;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Retrieves current context (content type, action)
336
+ * @return array Content Type and Action of current request
337
+ */
338
+ function get_context() {
339
+ $post = false;
340
+ if ( isset($GLOBALS['post']) && !is_null($GLOBALS['post']) )
341
+ $post = $GLOBALS['post'];
342
+ elseif ( isset($_REQUEST['post_id']) )
343
+ $post = $_REQUEST['post_id'];
344
+ elseif ( isset($_REQUEST['post']) )
345
+ $post = $_REQUEST['post'];
346
+ elseif ( isset($_REQUEST['post_type']) )
347
+ $post = $_REQUEST['post_type'];
348
+ //Get action
349
+ $action = $this->util->get_action();
350
+ if ( empty($post) )
351
+ $post = $this->get_page_type();
352
+ //Get post's content type
353
+ $ct =& $this->get_type($post);
354
+
355
+ return array(&$ct, $action);
356
+ }
357
+
358
+ /**
359
+ * Enqueues files for fields in current content type
360
+ * @param string $page Current context
361
+ */
362
+ function enqueue_files($page = null) {
363
+ list($ct, $action) = $this->get_context();
364
+ $file_types = array('scripts' => 'script', 'styles' => 'style');
365
+ //Get content type fields
366
+ foreach ( $ct->fields as $field ) {
367
+ //Enqueue scripts/styles for each field
368
+ foreach ( $file_types as $type => $func_base ) {
369
+ $deps = $field->{"get_$type"}();
370
+ foreach ( $deps as $handle => $args ) {
371
+ //Confirm context
372
+ if ( in_array('all', $args['context']) || in_array($page, $args['context']) || in_array($action, $args['context']) ) {
373
+ $this->enqueue_file($func_base, $args['params']);
374
+ }
375
+ }
376
+ }
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Add plugin hooks for fields used in current request
382
+ */
383
+ function add_hooks() {
384
+ list($ct, $action) = $this->get_context();
385
+ //Iterate through content type fields and add hooks from fields
386
+ foreach ( $ct->fields as $field ) {
387
+ //Iterate through hooks added to field
388
+ $hooks = $field->get_hooks();
389
+ foreach ( $hooks as $tag => $callback ) {
390
+ //Iterate through function callbacks added to tag
391
+ foreach ( $callback as $id => $args ) {
392
+ //Check if hook/function was already processed
393
+ if ( isset($this->hooks_processed[$tag][$id]) )
394
+ continue;
395
+ //Add hook/function to list of processed hooks
396
+ if ( !isset($this->hooks_processed[$tag]) || !is_array($this->hooks_processed[$tag]) )
397
+ $this->hooks_processed[$tag] = array($id => true);
398
+ //Add hook to WP
399
+ call_user_func_array('add_filter', $args);
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Enqueues files
407
+ * @param string $type Type of file to enqueue (script or style)
408
+ * @param array $args (optional) Arguments to pass to enqueue function
409
+ */
410
+ function enqueue_file($type = 'script', $args = array()) {
411
+ $func = 'wp_enqueue_' . $type;
412
+ if ( function_exists($func) ) {
413
+ call_user_func_array($func, $args);
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Add admin menus for content types
419
+ * @deprecated Not needed for 3.0+
420
+ */
421
+ function admin_menu() {
422
+ global $cnr_content_types;
423
+
424
+ $pos = 21;
425
+ foreach ( $cnr_content_types as $id => $type ) {
426
+ if ( $this->is_default_post_type($id) )
427
+ continue;
428
+ $page = $this->get_admin_page_file($id);
429
+ $callback = $this->m('admin_page');
430
+ $access = 8;
431
+ $pos += 1;
432
+ $title = $type->get_title(true);
433
+ if ( !empty($title) ) {
434
+ //Main menu
435
+ add_menu_page($type->get_title(true), $type->get_title(true), $access, $page, $callback, '', $pos);
436
+ //Edit
437
+ add_submenu_page($page, __('Edit ' . $type->get_title(true)), __('Edit'), $access, $page, $callback);
438
+ $hook = get_plugin_page_hookname($page, $page);
439
+ add_action('load-' . $hook, $this->m('admin_menu_load_plugin'));
440
+ //Add
441
+ $page_add = $this->get_admin_page_file($id, 'add');
442
+ add_submenu_page($page, __('Add New ' . $type->get_title()), __('Add New'), $access, $page_add, $callback);
443
+ $hook = get_plugin_page_hook($page_add, $page);
444
+ add_action('load-' . $hook, $this->m('admin_menu_load_plugin'));
445
+ //Hook for additional menus
446
+ $menu_hook = 'cnr_admin_menu_type';
447
+ //Type specific
448
+ do_action_ref_array($menu_hook . '_' . $id, array(&$type));
449
+ //General
450
+ do_action_ref_array($menu_hook, array(&$type));
451
+ }
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Load data for plugin admin page prior to admin-header.php is loaded
457
+ * Useful for enqueueing scripts/styles, etc.
458
+ */
459
+ function admin_menu_load_plugin() {
460
+ //Get Action
461
+ global $editing, $post, $post_ID, $p;
462
+ $action = $this->util->get_action();
463
+ if ( isset($_GET['delete_all']) )
464
+ $action = 'delete_all';
465
+ if ( isset($_GET['action']) && 'edit' == $_GET['action'] && ! isset($_GET['bulk_edit']))
466
+ $action = 'manage';
467
+ switch ( $action ) {
468
+ case 'delete_all' :
469
+ case 'edit' :
470
+ //Handle bulk actions
471
+ //Redirect to edit.php for processing
472
+
473
+ //Build query string
474
+ $qs = $_GET;
475
+ unset($qs['page']);
476
+ $edit_uri = admin_url('edit.php') . '?' . build_query($qs);
477
+ wp_redirect($edit_uri);
478
+ break;
479
+ case 'edit-item' :
480
+ wp_enqueue_script('admin_comments');
481
+ enqueue_comment_hotkeys_js();
482
+ //Get post being edited
483
+ if ( empty($_GET['post']) ) {
484
+ wp_redirect("post.php"); //TODO redirect to appropriate manage page
485
+ exit();
486
+ }
487
+ $post_ID = $p = (int) $_GET['post'];
488
+ $post = get_post($post_ID);
489
+ if ( !current_user_can('edit_post', $post_ID) )
490
+ wp_die( __('You are not allowed to edit this item') );
491
+
492
+ if ( $last = wp_check_post_lock($post->ID) ) {
493
+ add_action('admin_notices', '_admin_notice_post_locked');
494
+ } else {
495
+ wp_set_post_lock($post->ID);
496
+ $locked = true;
497
+ }
498
+ //Continue on to add case
499
+ case 'add' :
500
+ $editing = true;
501
+ wp_enqueue_script('autosave');
502
+ wp_enqueue_script('post');
503
+ if ( user_can_richedit() )
504
+ wp_enqueue_script('editor');
505
+ add_thickbox();
506
+ wp_enqueue_script('media-upload');
507
+ wp_enqueue_script('word-count');
508
+ add_action( 'admin_print_footer_scripts', 'wp_tiny_mce', 25 );
509
+ wp_enqueue_script('quicktags');
510
+ wp_enqueue_script($this->add_prefix('edit_form'), $this->util->get_file_url('js/lib.admin.edit_form.js'), array('jquery', 'postbox'), false, true);
511
+ break;
512
+ default :
513
+ wp_enqueue_script( $this->add_prefix('inline-edit-post') );
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Build admin page file name for the specified post type
519
+ * @param string|CNR_Content_Type $type Content type ID or object
520
+ * @param string $action Action to build file name for
521
+ * @param bool $sep_action Whether action should be a separate query variable (Default: false)
522
+ * @return string Admin page file name
523
+ */
524
+ function get_admin_page_file($type, $action = '', $sep_action = false) {
525
+ if ( isset($type->id) )
526
+ $type = $type->id;
527
+ $page = $this->add_prefix('post_type_' . $type);
528
+ if ( !empty($action) ) {
529
+ if ( $sep_action )
530
+ $page .= '&action=';
531
+ else
532
+ $page .= '-';
533
+
534
+ $page .= $action;
535
+ }
536
+ return $page;
537
+ }
538
+
539
+ /**
540
+ * Determine content type based on URL query variables
541
+ * Uses $_GET['page'] variable to determine content type
542
+ * @return string Content type of page (NULL if no type defined by page)
543
+ */
544
+ function get_page_type() {
545
+ $type = null;
546
+ //Extract type from query variable
547
+ if ( isset($_GET['page']) ) {
548
+ $type = $_GET['page'];
549
+ $prefix = $this->add_prefix('post_type_');
550
+ //Remove plugin page prefix
551
+ if ( ($pos = strpos($type, $prefix)) === 0 )
552
+ $type = substr($type, strlen($prefix));
553
+ //Remove action (if present)
554
+ if ( ($pos = strrpos($type, '-')) && $pos !== false )
555
+ $type = substr($type, 0, $pos);
556
+ }
557
+ return $type;
558
+ }
559
+
560
+ /**
561
+ * Populate administration page for content type
562
+ */
563
+ function admin_page() {
564
+ $prefix = $this->add_prefix('post_type_');
565
+ if ( strpos($_GET['page'], $prefix) !== 0 )
566
+ return false;
567
+
568
+ //Get action
569
+ $action = $this->util->get_action('manage');
570
+ //Get content type
571
+ $type =& $this->get_type($this->get_page_type());
572
+ global $title, $parent_file, $submenu_file;
573
+ $title = $type->get_title(true);
574
+ //$parent_file = $prefix . $type->id;
575
+ //$submenu_file = $parent_file;
576
+
577
+ switch ( $action ) {
578
+ case 'edit-item' :
579
+ case 'add' :
580
+ $this->admin_page_edit($type, $action);
581
+ break;
582
+ default :
583
+ $this->admin_page_manage($type, $action);
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Queries content items for admin management pages
589
+ * Also retrieves available post status for specified content type
590
+ * @see wp_edit_posts_query
591
+ * @param CNR_Content_Type|string $type Content type instance or ID
592
+ * @return array All item statuses and Available item status
593
+ */
594
+ function admin_manage_query($type = 'post') {
595
+ global $wp_query;
596
+ $q = array();
597
+ //Get post type
598
+ if ( ! is_a($type, 'CNR_Content_Type') ) {
599
+ $type = $this->get_type($type);
600
+ }
601
+ $q = array('post_type' => $type->id);
602
+ $g = $_GET;
603
+ //Date
604
+ $q['m'] = isset($g['m']) ? (int) $g['m'] : 0;
605
+ //Category
606
+ $q['cat'] = isset($g['cat']) ? (int) $g['cat'] : 0;
607
+ $post_stati = array( // array( adj, noun )
608
+ 'publish' => array(_x('Published', 'post'), __('Published posts'), _n_noop('Published <span class="count">(%s)</span>', 'Published <span class="count">(%s)</span>')),
609
+ 'future' => array(_x('Scheduled', 'post'), __('Scheduled posts'), _n_noop('Scheduled <span class="count">(%s)</span>', 'Scheduled <span class="count">(%s)</span>')),
610
+ 'pending' => array(_x('Pending Review', 'post'), __('Pending posts'), _n_noop('Pending Review <span class="count">(%s)</span>', 'Pending Review <span class="count">(%s)</span>')),
611
+ 'draft' => array(_x('Draft', 'post'), _x('Drafts', 'manage posts header'), _n_noop('Draft <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>')),
612
+ 'private' => array(_x('Private', 'post'), __('Private posts'), _n_noop('Private <span class="count">(%s)</span>', 'Private <span class="count">(%s)</span>')),
613
+ 'trash' => array(_x('Trash', 'post'), __('Trash posts'), _n_noop('Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>')),
614
+ );
615
+
616
+ $post_stati = apply_filters('post_stati', $post_stati);
617
+
618
+ $avail_post_stati = get_available_post_statuses('post');
619
+
620
+ //Status
621
+ if ( isset($g['post_status']) && in_array( $g['post_status'], array_keys($post_stati) ) ) {
622
+ $q['post_status'] = $g['post_status'];
623
+ $q['perm'] = 'readable';
624
+ } else {
625
+ unset($q['post_status']);
626
+ }
627
+
628
+ //Order
629
+ if ( isset($q['post_status']) && 'pending' === $q['post_status'] ) {
630
+ $q['order'] = 'ASC';
631
+ $q['orderby'] = 'modified';
632
+ } elseif ( isset($q['post_status']) && 'draft' === $q['post_status'] ) {
633
+ $q['order'] = 'DESC';
634
+ $q['orderby'] = 'modified';
635
+ } else {
636
+ $q['order'] = 'DESC';
637
+ $q['orderby'] = 'date';
638
+ }
639
+
640
+ //Pagination
641
+ $posts_per_page = (int) get_user_option( 'edit_per_page', 0, false );
642
+ if ( empty( $posts_per_page ) || $posts_per_page < 1 )
643
+ $posts_per_page = 15;
644
+ if ( isset($g['paged']) && (int) $g['paged'] > 1 )
645
+ $q['paged'] = (int) $g['paged'];
646
+ $q['posts_per_page'] = apply_filters( 'edit_posts_per_page', $posts_per_page );
647
+ //Search
648
+ $q[s] = ( isset($g['s']) ) ? $g[s] : '';
649
+ $wp_query->query($q);
650
+
651
+ return array($post_stati, $avail_post_stati);
652
+ }
653
+
654
+ /**
655
+ * Counts the number of items in the specified content type
656
+ * @see wp_count_posts
657
+ * @param CNR_Content_Type|string $type Content Type instance or ID
658
+ * @param string $perm Permission level for items (e.g. readable)
659
+ * @return array Associative array of item counts by post status (published, draft, etc.)
660
+ */
661
+ function count_posts( $type, $perm = '' ) {
662
+ global $wpdb;
663
+
664
+ $user = wp_get_current_user();
665
+
666
+ if ( !is_a($type, 'CNR_Content_Type') )
667
+ $type = $this->get_type($type);
668
+ $type_val = $type->get_meta_value();
669
+ $type = $type->id;
670
+ $cache_key = $type;
671
+
672
+ //$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
673
+ $query = "SELECT p.post_status, COUNT( * ) as num_posts FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} m ON m.post_id = p.id WHERE m.meta_key = '" . $this->get_type_meta_key() . "' AND m.meta_value = '$type_val'";
674
+ if ( 'readable' == $perm && is_user_logged_in() ) {
675
+ //TODO enable check for custom post types "read_private_{$type}s"
676
+ if ( !current_user_can("read_private_posts") ) {
677
+ $cache_key .= '_' . $perm . '_' . $user->ID;
678
+ $query .= " AND (p.post_status != 'private' OR ( p.post_author = '$user->ID' AND p.post_status = 'private' ))";
679
+ }
680
+ }
681
+ $query .= ' GROUP BY p.post_status';
682
+
683
+ $count = wp_cache_get($cache_key, 'counts');
684
+ if ( false !== $count )
685
+ return $count;
686
+
687
+ $count = $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
688
+
689
+ $stats = array( 'publish' => 0, 'private' => 0, 'draft' => 0, 'pending' => 0, 'future' => 0, 'trash' => 0 );
690
+ foreach( (array) $count as $row_num => $row ) {
691
+ $stats[$row['post_status']] = $row['num_posts'];
692
+ }
693
+
694
+ $stats = (object) $stats;
695
+ wp_cache_set($cache_key, $stats, 'counts');
696
+
697
+ return $stats;
698
+ }
699
+
700
+ /**
701
+ * Builds management page for items of a specific custom content type
702
+ * @param CNR_Content_Type $type Content Type to manage
703
+ * @param string $action Current action
704
+ *
705
+ * @global string $title
706
+ * @global string $parent_file
707
+ * @global string $plugin_page
708
+ * @global string $page_hook
709
+ * @global WP_User $current_user
710
+ * @global WP_Query $wp_query
711
+ * @global wpdb $wpdb
712
+ * @global WP_Locale $wp_locale
713
+ */
714
+ function admin_page_manage($type, $action) {
715
+ if ( !current_user_can('edit_posts') )
716
+ wp_die(__('You do not have sufficient permissions to access this page.'));
717
+
718
+ global $title, $parent_file, $plugin_page, $page_hook, $current_user, $wp_query, $wpdb, $wp_locale;
719
+ $title = __('Edit ' . $type->get_title(true));
720
+ $admin_path = ABSPATH . 'wp-admin/';
721
+
722
+ //Pagination
723
+ if ( ! isset($_GET['paged']) )
724
+ $_GET['paged'] = 1;
725
+
726
+ $add_url = $this->get_admin_page_url($type->id, 'add');
727
+ $is_trash = isset($_GET['post_status']) && $_GET['post_status'] == 'trash';
728
+ //User posts
729
+ $user_posts = false;
730
+ if ( !current_user_can('edit_others_posts') ) {
731
+ $user_posts_count = $wpdb->get_var( $wpdb->prepare("SELECT COUNT(1) FROM $wpdb->posts p JOIN $wpdb->postmeta m ON m.post_id = p.id WHERE m.meta_key = '_cnr_post_type' AND m.meta_value = %s AND p.post_status != 'trash' AND p.post_author = %d", $type->get_meta_value(), $current_user->ID) );
732
+ $user_posts = true;
733
+ if ( $user_posts_count && empty($_GET['post_status']) && empty($_GET['all_posts']) && empty($_GET['author']) )
734
+ $_GET['author'] = $current_user->ID;
735
+ }
736
+ //Get content type items
737
+ list($post_stati, $avail_post_stati) = $this->admin_manage_query($type->id);
738
+ ?>
739
+ <div class="wrap">
740
+ <?php screen_icon('edit'); ?>
741
+ <h2><?php echo esc_html( $title ); ?> <a href="<?php echo $add_url; ?>" class="button add-new-h2"><?php echo esc_html_x('Add New', 'post'); ?></a> <?php
742
+ if ( isset($_GET['s']) && $_GET['s'] )
743
+ printf( '<span class="subtitle">' . __('Search results for &#8220;%s&#8221;') . '</span>', esc_html( get_search_query() ) ); ?>
744
+ </h2>
745
+ <?php /* Action messages here: saved, trashed, etc. */ ?>
746
+ <form id="posts-filter" action="<?php echo admin_url('admin.php'); ?>" method="get">
747
+ <?php if ( isset($_GET['page']) ) { ?>
748
+ <input type="hidden" name="page" id="page" value="<?php esc_attr_e($_GET['page']); ?>" />
749
+ <?php } ?>
750
+ <ul class="subsubsub">
751
+ <?php
752
+ /* Status links */
753
+ if ( empty($locked_post_status) ) :
754
+ $status_links = array();
755
+ $num_posts = $this->count_posts($type, 'readable');
756
+ $class = '';
757
+ $allposts = '';
758
+ $curr_page = $_SERVER['PHP_SELF'] . '?page=' . $_GET['page'];
759
+ if ( $user_posts ) {
760
+ if ( isset( $_GET['author'] ) && ( $_GET['author'] == $current_user->ID ) )
761
+ $class = ' class="current"';
762
+ $status_links[] = "<li><a href='$curr_page&author=$current_user->ID'$class>" . sprintf( _nx( 'My Posts <span class="count">(%s)</span>', 'My Posts <span class="count">(%s)</span>', $user_posts_count, 'posts' ), number_format_i18n( $user_posts_count ) ) . '</a>';
763
+ $allposts = '?all_posts=1';
764
+ }
765
+
766
+ $total_posts = array_sum( (array) $num_posts ) - $num_posts->trash;
767
+ $class = empty($class) && empty($_GET['post_status']) ? ' class="current"' : '';
768
+ $status_links[] = "<li><a href='$curr_page{$allposts}'$class>" . sprintf( _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $total_posts, 'posts' ), number_format_i18n( $total_posts ) ) . '</a>';
769
+
770
+ foreach ( $post_stati as $status => $label ) {
771
+ $class = '';
772
+
773
+ if ( !in_array( $status, $avail_post_stati ) )
774
+ continue;
775
+
776
+ if ( empty( $num_posts->$status ) )
777
+ continue;
778
+
779
+ if ( isset($_GET['post_status']) && $status == $_GET['post_status'] )
780
+ $class = ' class="current"';
781
+
782
+ $status_links[] = "<li><a href='$curr_page&post_status=$status'$class>" . sprintf( _n( $label[2][0], $label[2][1], $num_posts->$status ), number_format_i18n( $num_posts->$status ) ) . '</a>';
783
+ }
784
+ echo implode( " |</li>\n", $status_links ) . '</li>';
785
+ unset( $status_links );
786
+ endif;
787
+ ?>
788
+ </ul>
789
+ <p class="search-box">
790
+ <label class="screen-reader-text" for="post-search-input"><?php _e('Search Posts', 'cornerstone'); ?>:</label>
791
+ <input type="text" id="post-search-input" name="s" value="<?php the_search_query(); ?>" />
792
+ <input type="submit" value="<?php esc_attr_e('Search Posts', 'cornerstone'); ?>" class="button" />
793
+ </p>
794
+ <?php
795
+ if ( have_posts() ) {
796
+ ?>
797
+ <div class="tablenav">
798
+ <?php
799
+ $page_links = paginate_links( array(
800
+ 'base' => add_query_arg( 'paged', '%#%' ),
801
+ 'format' => '',
802
+ 'prev_text' => __('&laquo;'),
803
+ 'next_text' => __('&raquo;'),
804
+ 'total' => $wp_query->max_num_pages,
805
+ 'current' => $_GET['paged']
806
+ ));
807
+ ?>
808
+ <div class="alignleft actions">
809
+ <select name="action">
810
+ <option value="-1" selected="selected"><?php _e('Bulk Actions', 'cornerstone'); ?></option>
811
+ <?php if ( $is_trash ) { ?>
812
+ <option value="untrash"><?php _e('Restore', 'cornerstone'); ?></option>
813
+ <?php } else { ?>
814
+ <option value="edit"><?php _e('Edit', 'cornerstone'); ?></option>
815
+ <?php } if ( $is_trash || !EMPTY_TRASH_DAYS ) { ?>
816
+ <option value="delete"><?php _e('Delete Permanently', 'cornerstone'); ?></option>
817
+ <?php } else { ?>
818
+ <option value="trash"><?php _e('Move to Trash', 'cornerstone'); ?></option>
819
+ <?php } ?>
820
+ </select>
821
+ <input type="submit" value="<?php esc_attr_e('Apply', 'cornerstone'); ?>" name="doaction" id="doaction" class="button-secondary action" />
822
+ <?php wp_nonce_field('bulk-posts'); ?>
823
+
824
+ <?php // view filters
825
+ if ( !is_singular() ) {
826
+ $arc_query = "SELECT DISTINCT YEAR(post_date) AS yyear, MONTH(post_date) AS mmonth FROM $wpdb->posts p JOIN $wpdb->postmeta m ON m.post_id = p.ID WHERE m.meta_key = '" . $this->get_type_meta_key() . "' AND m.meta_value = '" . $type->get_meta_value() . "' ORDER BY post_date DESC";
827
+
828
+ $arc_result = $wpdb->get_results( $arc_query );
829
+
830
+ $month_count = count($arc_result);
831
+
832
+ if ( $month_count && !( 1 == $month_count && 0 == $arc_result[0]->mmonth ) ) {
833
+ $m = isset($_GET['m']) ? (int)$_GET['m'] : 0;
834
+ ?>
835
+ <select name='m'>
836
+ <option<?php selected( $m, 0 ); ?> value='0'><?php _e('Show all dates', 'cornerstone'); ?></option>
837
+ <?php
838
+ foreach ($arc_result as $arc_row) {
839
+ if ( $arc_row->yyear == 0 )
840
+ continue;
841
+ $arc_row->mmonth = zeroise( $arc_row->mmonth, 2 );
842
+
843
+ if ( $arc_row->yyear . $arc_row->mmonth == $m )
844
+ $default = ' selected="selected"';
845
+ else
846
+ $default = '';
847
+
848
+ echo "<option$default value='" . esc_attr("$arc_row->yyear$arc_row->mmonth") . "'>";
849
+ echo $wp_locale->get_month($arc_row->mmonth) . " $arc_row->yyear";
850
+ echo "</option>\n";
851
+ }
852
+ ?>
853
+ </select>
854
+ <?php }
855
+
856
+ $dropdown_options = array('show_option_all' => __('View all categories'), 'hide_empty' => 0, 'hierarchical' => 1,
857
+ 'show_count' => 0, 'orderby' => 'name', 'selected' => $cat);
858
+ wp_dropdown_categories($dropdown_options);
859
+ do_action('restrict_manage_posts');
860
+ ?>
861
+ <input type="submit" id="post-query-submit" value="<?php esc_attr_e('Filter', 'cornerstone'); ?>" class="button-secondary" />
862
+ <?php }
863
+
864
+ if ( $is_trash && current_user_can('edit_others_posts') ) { ?>
865
+ <input type="submit" name="delete_all" id="delete_all" value="<?php esc_attr_e('Empty Trash', 'cornerstone'); ?>" class="button-secondary apply" />
866
+ <?php } ?>
867
+ </div>
868
+
869
+ <?php if ( $page_links ) { ?>
870
+ <div class="tablenav-pages"><?php $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of %s' ) . '</span>%s',
871
+ number_format_i18n( ( $_GET['paged'] - 1 ) * $wp_query->query_vars['posts_per_page'] + 1 ),
872
+ number_format_i18n( min( $_GET['paged'] * $wp_query->query_vars['posts_per_page'], $wp_query->found_posts ) ),
873
+ number_format_i18n( $wp_query->found_posts ),
874
+ $page_links
875
+ ); echo $page_links_text; ?></div>
876
+ <?php } //page links ?>
877
+ <div class="clear"></div>
878
+ </div>
879
+ <?php
880
+ include ($admin_path . 'edit-post-rows.php');
881
+ } else { //have_posts() ?>
882
+ <div class="clear"></div>
883
+ <p><?php
884
+ if ( $is_trash )
885
+ _e('No posts found in the trash', 'cornerstone');
886
+ else
887
+ _e('No posts found', 'cornerstone');
888
+ ?></p>
889
+ <?php } ?>
890
+ </form>
891
+ <?php inline_edit_row('post'); ?>
892
+ <div id="ajax-response"></div>
893
+ <br class="clear" />
894
+ </div>
895
+ <?php
896
+ }
897
+
898
+ /**
899
+ * Build admin edit page for custom type item
900
+ * @param CNR_Content_Type $type Content type being edited
901
+ * @param string $action Current action (add, edit, manage, etc.)
902
+ */
903
+ function admin_page_edit($type, $action) {
904
+ global $title, $hook_suffix, $parent_file, $screen_layout_columns, $post, $post_ID, $p;
905
+ $screen_layout_columns = 2;
906
+ //TODO Add default icon for content type
907
+ $parent_file = 'edit.php'; //Makes screen_icon() use edit icon on post edit form
908
+ switch ( $action ) {
909
+ case 'edit-item' :
910
+ $title = 'Edit';
911
+ $post = get_post_to_edit($post_ID);
912
+ break;
913
+ default :
914
+ $title = 'Add New';
915
+ $post = get_default_post_to_edit();
916
+ break;
917
+ }
918
+ $title = __($title . ' ' . $type->get_title());
919
+ $admin_path = ABSPATH . 'wp-admin/';
920
+ include ($admin_path . 'edit-form-advanced.php');
921
+ }
922
+
923
+ /**
924
+ * Adds hidden field declaring content type on post edit form
925
+ * @deprecated no longer needed for WP 3.0+
926
+ */
927
+ function admin_page_edit_form() {
928
+ global $post, $plugin_page;
929
+ if ( empty($post) || !$post->ID ) {
930
+ $type = $this->get_type($post);
931
+ if ( ! empty($type) && ! empty($type->id) ) {
932
+ ?>
933
+ <input type="hidden" name="cnr[content_type]" id="cnr[content_type]" value="<?php echo $type->id; ?>" />
934
+ <?php
935
+ }
936
+ }
937
+ }
938
+
939
+ /**
940
+ * Adds meta boxes for post's content type
941
+ * Each group in content type is a separate meta box
942
+ * @param string $type Type of item meta boxes are being build for (post, page, link)
943
+ * @param string $context Location of meta box (normal, advanced, side)
944
+ * @param object $post Post object
945
+ */
946
+ function admin_do_meta_boxes($type, $context, $post) {
947
+ //Validate $type. Should be 'post','page', or a custom post type for our purposes
948
+ if ( in_array($type, array_merge(array_keys($this->get_types()), array('post', 'page'))) ) {
949
+ //Get content type definition
950
+ $ct =& $this->get_type($post);
951
+ //Pass processing to content type instance
952
+ $ct->admin_do_meta_boxes($type, $context, $post);
953
+ }
954
+ }
955
+
956
+ /**
957
+ * Saves field data submitted for current post
958
+ * @param int $post_id ID of current post
959
+ * @param object $post Post object
960
+ */
961
+ function save_item_data($post_id, $post) {
962
+ if ( empty($post_id) || empty($post) || !isset($_POST['cnr']) || !is_array($_POST['cnr']) )
963
+ return false;
964
+ $pdata = $_POST['cnr'];
965
+
966
+ if ( isset($pdata['attributes']) && is_array($pdata['attributes']) && isset($pdata['fields_loaded']) && is_array($pdata['fields_loaded']) ) {
967
+
968
+ $prev_data = (array) $this->get_item_data($post_id);
969
+
970
+ //Remove loaded fields from prev data
971
+ $prev_data = array_diff_key($prev_data, $pdata['fields_loaded']);
972
+
973
+ //Get current field data
974
+ $curr_data = $pdata['attributes'];
975
+
976
+ //Merge arrays together (new data overwrites old data)
977
+ if ( is_array($prev_data) && is_array($curr_data) ) {
978
+ $curr_data = array_merge($prev_data, $curr_data);
979
+ }
980
+
981
+ //Save to database
982
+ update_post_meta($post_id, $this->get_fields_meta_key(), $curr_data);
983
+ }
984
+ //Save content type
985
+ if ( isset($_POST['cnr']['content_type']) ) {
986
+ $type = $_POST['cnr']['content_type'];
987
+ $saved_type = get_post_meta($post_id, $this->get_type_meta_key(), true);
988
+ if ( is_array($saved_type) )
989
+ $saved_type = implode($saved_type);
990
+ if ( $type != $saved_type ) {
991
+ //Continue processing if submitted content type is different from previously-saved content type (or no type was previously set)
992
+ update_post_meta($post_id, $this->get_type_meta_key(), array($type));
993
+ }
994
+ }
995
+ }
996
+
997
+ /*-** Helpers **-*/
998
+
999
+ /**
1000
+ * Get array of default post types
1001
+ * @return array Default post types
1002
+ */
1003
+ function get_default_post_types() {
1004
+ return array('post', 'page', 'attachment', 'revision', 'nav_menu');
1005
+ }
1006
+
1007
+ /**
1008
+ * Checks if post's post type is a standard WP post type
1009
+ * @param mixed $post_type Post type (default) or post ID/object to evaluate
1010
+ * @see CNR_Content_Utilities::get_type() for possible parameter values
1011
+ * @return bool TRUE if post is default type, FALSE if it is a custom type
1012
+ */
1013
+ function is_default_post_type($post_type) {
1014
+ if ( !is_string($post_type) ) {
1015
+ $post_type = $this->get_type($post_type);
1016
+ $post_type = $post_type->id;
1017
+ }
1018
+ return in_array($post_type, $this->get_default_post_types());
1019
+ }
1020
+
1021
+ /**
1022
+ * Checks if specified content type has been defined
1023
+ * @param string|CNR_Content_Type $type Content type ID or object
1024
+ * @return bool TRUE if content type exists, FALSE otherwise
1025
+ *
1026
+ * @uses array $cnr_content_types
1027
+ */
1028
+ function type_exists($type) {
1029
+ global $cnr_content_types;
1030
+ if ( ! is_scalar($type) ) {
1031
+ if ( is_a($type, 'CNR_Content_Type') )
1032
+ $type = $type->id;
1033
+ else
1034
+ $type = null;
1035
+ }
1036
+ return ( isset($cnr_content_types[$type]) );
1037
+ }
1038
+
1039
+ /**
1040
+ * Retrieves content type definition for specified content item (post, page, etc.)
1041
+ * If content type does not exist, a new instance object will be created and returned
1042
+ * > New content types are automatically registered (since we are looking for registered types when using this method)
1043
+ * @param string|object $item Post object, or item type (string)
1044
+ * @return CNR_Content_Type Reference to matching content type, empty content type if no matching type exists
1045
+ *
1046
+ * @uses array $cnr_content_types
1047
+ */
1048
+ function &get_type($item) {
1049
+ //Return immediately if $item is a content type instance
1050
+ if ( is_a($item, 'CNR_Content_Type') )
1051
+ return $item;
1052
+
1053
+ $type = null;
1054
+
1055
+ if ( is_string($item) )
1056
+ $type = $item;
1057
+
1058
+ if ( !$this->type_exists($type) ) {
1059
+ $post = $item;
1060
+
1061
+ //Check if $item is a post (object or ID)
1062
+ if ( $this->util->check_post($post) && isset($post->post_type) ) {
1063
+ $type = $post->post_type;
1064
+ }
1065
+ }
1066
+ global $cnr_content_types;
1067
+ if ( $this->type_exists($type) ) {
1068
+ //Retrieve content type from global array
1069
+ $type =& $cnr_content_types[$type];
1070
+ } else {
1071
+ //Create new empty content type if it does not already exist
1072
+ $type = new CNR_Content_Type($type);
1073
+ //Automatically register newly initialized content type if it extends an existing WP post type
1074
+ if ( $this->is_default_post_type($type->id) )
1075
+ $type->register();
1076
+ }
1077
+
1078
+ return $type;
1079
+ }
1080
+
1081
+ /**
1082
+ * Retrieve content types
1083
+ * @return Reference to content types array
1084
+ */
1085
+ function &get_types() {
1086
+ return $GLOBALS['cnr_content_types'];
1087
+ }
1088
+
1089
+ /**
1090
+ * Retrieve meta key for post fields
1091
+ * @return string Fields meta key
1092
+ */
1093
+ function get_fields_meta_key() {
1094
+ return $this->util->make_meta_key('fields');
1095
+ }
1096
+
1097
+ /**
1098
+ * Retrieve meta key for post type
1099
+ * @return string Post type meta key
1100
+ */
1101
+ function get_type_meta_key() {
1102
+ return $this->util->make_meta_key('post_type');
1103
+ }
1104
+
1105
+ /**
1106
+ * Checks if post contains specified field data
1107
+ * @param Object $post (optional) Post to check data for
1108
+ * @param string $field (optional) Field ID to check for
1109
+ * @return bool TRUE if data exists, FALSE otherwise
1110
+ */
1111
+ function has_item_data($item = null, $field = null) {
1112
+ $ret = $this->get_item_data($item, $field, 'raw', null);
1113
+ if ( is_scalar($ret) )
1114
+ return ( !empty($ret) || $ret === 0 );
1115
+ if ( is_array($ret) ) {
1116
+ foreach ( $ret as $key => $val ) {
1117
+ if ( !empty($val) || $val === 0 )
1118
+ return true;
1119
+ }
1120
+ }
1121
+ return false;
1122
+ }
1123
+
1124
+ /**
1125
+ * Retrieve specified field data from content item (e.g. post)
1126
+ * Usage Examples:
1127
+ * get_item_data($post_id, 'field_id')
1128
+ * - Retrieves field_id data from global $post object
1129
+ * - Field data is formatted using 'display' layout of field
1130
+ *
1131
+ * get_item_data($post_id, 'field_id', 'raw')
1132
+ * - Retrieves field_id data from global $post object
1133
+ * - Raw field data is returned (no formatting)
1134
+ *
1135
+ * get_item_data($post_id, 'field_id', 'display', $post_id)
1136
+ * - Retrieves field_id data from post matching $post_id
1137
+ * - Field data is formatted using 'display' layout of field
1138
+ *
1139
+ * get_item_data($post_id, 'field_id', null)
1140
+ * - Retrieves field_id data from post matching $post_id
1141
+ * - Field data is formatted using 'display' layout of field
1142
+ * - The default layout is used when no valid layout is specified
1143
+ *
1144
+ * get_item_data($post_id)
1145
+ * - Retrieves full data array from post matching $post_id
1146
+ *
1147
+ * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used)
1148
+ * @param string $field ID of field to retrieve
1149
+ * @param string $layout(optional) Layout to use when returning field data (Default: display)
1150
+ * @param array $attr (optional) Additional attributes to pass along to field object (e.g. for building layout, etc.)
1151
+ * @see CNR_Field_Type::build_layout for more information on attribute usage
1152
+ * @return mixed Specified field data
1153
+ */
1154
+ function get_item_data($item = null, $field = null, $layout = null, $default = '', $attr = null) {
1155
+ $ret = $default;
1156
+
1157
+ //Get item
1158
+ $item = get_post($item);
1159
+
1160
+ if ( !isset($item->ID) )
1161
+ return $ret;
1162
+
1163
+ //Get item data
1164
+ $data = get_post_meta($item->ID, $this->get_fields_meta_key(), true);
1165
+
1166
+ //Get field data
1167
+
1168
+ //Set return value to data if no field specified
1169
+ if ( empty($field) || !is_string($field) )
1170
+ $ret = $data;
1171
+ //Stop if no valid field specified
1172
+ if ( !isset($data[$field]) ) {
1173
+ //TODO Check $item object to see if specified field exists (e.g. title, post_status, etc.)
1174
+ return $ret;
1175
+ }
1176
+
1177
+ $ret = $data[$field];
1178
+
1179
+ //Initialize layout value
1180
+ $layout_def = 'display';
1181
+
1182
+ if ( !is_scalar($layout) || empty($layout) )
1183
+ $layout = $layout_def;
1184
+
1185
+ $layout = strtolower($layout);
1186
+
1187
+ //Check if raw data requested
1188
+ if ( 'raw' == $layout )
1189
+ return $ret;
1190
+
1191
+ /* Build specified layout */
1192
+
1193
+ //Get item's content type
1194
+ $ct =& $this->get_type($item);
1195
+ $ct->set_data($data);
1196
+
1197
+ //Get field definition
1198
+ $fdef =& $ct->get_field($field);
1199
+
1200
+ //Validate layout
1201
+ if ( !$fdef->has_layout($layout) )
1202
+ $layout = $layout_def;
1203
+
1204
+ //Build layout
1205
+ $fdef->set_caller($ct);
1206
+ $ret = $fdef->build_layout($layout, $attr);
1207
+ $fdef->clear_caller();
1208
+
1209
+ //Return formatted value
1210
+ return $ret;
1211
+ }
1212
+
1213
+ /**
1214
+ * Prints an item's field data
1215
+ * @see CNR_Content_Utilities::get_item_data() for more information
1216
+ * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used)
1217
+ * @param string $field ID of field to retrieve
1218
+ * @param string $layout(optional) Layout to use when returning field data (Default: display)
1219
+ * @param mixed $default (optional) Default value to return in case of errors, etc.
1220
+ * @param array $attr Additional attributes to pass to field
1221
+ */
1222
+ function the_item_data($item = null, $field = null, $layout = null, $default = '', $attr = null) {
1223
+ echo apply_filters('cnr_the_item_data', $this->get_item_data($item, $field, $layout, $default, $attr), $item, $field, $layout, $default, $attr);
1224
+ }
1225
+
1226
+ /**
1227
+ * Build Admin URL for specified post type
1228
+ * @param string|CNR_Content_Type $type Content type ID or object
1229
+ * @param string $action Action to build URL for
1230
+ * @param bool $sep_action Whether action should be a separate query variable (Default: false)
1231
+ * @return string Admin page URL
1232
+ */
1233
+ function get_admin_page_url($type, $action = '', $sep_action = false) {
1234
+ $url = admin_url('admin.php');
1235
+ $url .= '?page=' . $this->get_admin_page_file($type, $action, $sep_action);
1236
+ return $url;
1237
+ }
1238
+
1239
+ function get_edit_item_url($edit_url, $item_id, $context) {
1240
+ //Get post type
1241
+ $type = $this->get_type($item_id);
1242
+ if ( ! $this->is_default_post_type($type->id) && $this->type_exists($type) ) {
1243
+ $edit_url = $this->get_admin_page_url($type, 'edit-item', true) . '&post=' . $item_id;
1244
+ }
1245
+
1246
+ return $edit_url;
1247
+ }
1248
+ }
includes/class.feeds.php ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Core properties/methods for feed management
5
+ * @package Cornerstone
6
+ * @subpackage Feeds
7
+ * @author Archetyped
8
+ * @uses CNR_Post
9
+ */
10
+ class CNR_Feeds extends CNR_Base {
11
+
12
+ /**
13
+ * Constructor
14
+ */
15
+ function __construct() {
16
+ parent::__construct();
17
+ }
18
+
19
+ /* Methods */
20
+
21
+ /**
22
+ * Registers plugin hooks
23
+ */
24
+ function register_hooks() {
25
+ // add_action('template_redirect', $this->m('feed_redirect'));
26
+ // add_filter('get_wp_title_rss', $this->m('get_title'));
27
+ // add_filter('get_bloginfo_rss', $this->m('get_description'), 10, 2);
28
+ // add_filter('the_title_rss', $this->m('get_item_title'), 9);
29
+ add_filter('the_content_feed', $this->m('get_item_content'));
30
+ add_filter('the_excerpt_rss', $this->m('get_item_content'));
31
+
32
+ //Feed links (header)
33
+ /*
34
+ if ( $p = has_action('wp_head', 'feed_links_extra') )
35
+ remove_action('wp_head', 'feed_links_extra', $p);
36
+ add_action('wp_head', $this->m('feed_links_extra'));
37
+ */
38
+ }
39
+
40
+ /**
41
+ * Customize feed links (header) for sections
42
+ * @uses feed_links_extra() To generate additional feed links
43
+ */
44
+ function feed_links_extra() {
45
+ $args = array();
46
+ if ( is_page() ) {
47
+ //Add custom feed title for section
48
+ $args['singletitle'] = __('%1$s %2$s %3$s Feed');
49
+ //Make sure feed is processed
50
+ $cb = create_function('', 'return true;');
51
+ $tag = 'pings_open';
52
+ $priority = 99;
53
+ add_filter($tag, $cb, $priority);
54
+ }
55
+ feed_links_extra($args);
56
+
57
+ //Remove filter after section feed link has been generated
58
+ if ( is_page() ) {
59
+ remove_filter($tag, $cb, $priority);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Customizes feed to use for certain requests
65
+ * Example: Pages (Sections) should load the normal feed template (instead of the comments feed template)
66
+ */
67
+ function feed_redirect() {
68
+ if ( is_page() && is_feed() ) {
69
+ //Turn off comments feed for sections
70
+ global $wp_query;
71
+ $wp_query->is_comment_feed = false;
72
+ //Enable child content retrieval for section feeds
73
+ $m = $this->m('get_children');
74
+ $feed = get_query_var('feed');
75
+ if ('feed' == $feed)
76
+ $feed = get_default_feed();
77
+ $hook = ('rdf' == $feed) ? 'rdf_header' : $feed . "_head";
78
+ add_action($hook, $m);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Retrieves a section's child content for output in a feed
84
+ */
85
+ static function get_children() {
86
+ if ( is_page() && is_feed() ) {
87
+ global $wp_query;
88
+ //Get children of current page
89
+ $children =& CNR_Post::get_children();
90
+
91
+ //Set retrieved posts to global $wp_query object
92
+ $wp_query->posts = $children->posts;
93
+ $wp_query->current_post = $children->current;
94
+ $wp_query->post_count = $children->count;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Sets feed title for sections
100
+ * @param string $title Title passed from 'get_wp_title_rss' hook
101
+ * @return string Updated title
102
+ */
103
+ function get_title($title) {
104
+ $sep = '&#8250;';
105
+ remove_filter('get_wp_title_rss', $this->m('get_title'));
106
+ $title = get_wp_title_rss($sep);
107
+ add_filter('get_wp_title_rss', $this->m('get_title'));
108
+ return $title;
109
+ }
110
+
111
+ /**
112
+ * Adds description to feed
113
+ * Specifically, adds feed descriptions for sections
114
+ *
115
+ * @param string $description Description passed from 'get_bloginfo_rss' hook
116
+ * @param string $show Specifies data to retrieve
117
+ * @see get_bloginfo() For the list of possible values to display.
118
+ * @return string Modified feed description
119
+ */
120
+ function get_description($description, $show = '') {
121
+ global $post;
122
+ if ( is_feed() && is_page() && 'description' == $show && strlen($post->post_content) > 0 ) {
123
+ //Get section's own description (if exists)
124
+ $description = convert_chars($post->post_content);
125
+ }
126
+ return $description;
127
+ }
128
+
129
+ /**
130
+ * Sets title for feed items
131
+ * Specifies what section an item is in if the feed is not specifically for that section
132
+ *
133
+ * @param string $title Post title passed from 'the_title_rss' hook
134
+ * @return string Updated post title
135
+ */
136
+ function get_item_title($title) {
137
+ if ( is_feed() && !is_page() ) {
138
+ //Get item's section
139
+ $section = CNR_Post::get_section(null, 'post_title');
140
+ $title = "$section &#8250; $title"; //Section precedes post title
141
+ }
142
+ return $title;
143
+ }
144
+
145
+ /**
146
+ * Modifies post content/excerpt for feed items
147
+ * @param string $content Post content
148
+ * @return string Updated post content
149
+ */
150
+ function get_item_content($content = '') {
151
+ global $post;
152
+
153
+ //Skip processing in the following scenarios
154
+ // > Request is not feed
155
+ // > Current post requires a password
156
+ if ( !is_feed() || post_password_required() ) {
157
+ return $content;
158
+ }
159
+
160
+ //Add post thumbnail
161
+ if ( has_post_thumbnail() ) {
162
+ $content = get_the_post_thumbnail(null, 'large') . $content;
163
+ }
164
+
165
+ return $content;
166
+ }
167
+
168
+ /**
169
+ * Generates feed links based on current request
170
+ * @return string Feed links (HTML)
171
+ */
172
+ function get_links() {
173
+ $text = array();
174
+ $links = array();
175
+ $link_template = '<a class="link_feed" rel="alternate" href="%1$s" title="%3$s">%2$s</a>';
176
+ //Page specific feeds
177
+ if ( is_page() || is_single() ) {
178
+ global $post, $wpdb;
179
+ $is_p = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE ID = $post->ID");
180
+ if ( $is_p ) {
181
+ $href = get_post_comments_feed_link($post->ID);
182
+ if ( is_page() )
183
+ $title = __('Subscribe to this Section');
184
+ else
185
+ $title = __('Subscribe to Comments');
186
+ $links[$href] = $title;
187
+ }
188
+ } elseif ( is_search() ) {
189
+ $links[get_search_feed_link()] = __('Subscribe to this Search');
190
+ } elseif ( is_tag() ) {
191
+ $links[get_tag_feed_link( get_query_var('tag_id') )] = __('Subscribe to this Tag');
192
+ } elseif ( is_category() ) {
193
+ $links[get_category_feed_link( get_query_var('cat') )] = __('Subscribe to this Topic');
194
+ }
195
+
196
+ //Sitewide feed
197
+ $title = ( !empty($links) ) ? __('Subscribe to All updates') : __('Subscribe to Updates');
198
+ $links[get_feed_link()] = $title;
199
+ foreach ($links as $href => $title) {
200
+ $text[] = sprintf($link_template, $href, $title, esc_attr($title));
201
+ }
202
+ $text = implode(' or ', $text);
203
+ return $text;
204
+ }
205
+
206
+ /**
207
+ * Outputs feed links based on current page
208
+ * @see get_links()
209
+ * @return void
210
+ */
211
+ function the_links() {
212
+ echo $this->get_links();
213
+ }
214
+ }
includes/class.field.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <?php
2
+ class CNR_Field extends CNR_Field_Type {
3
+
4
+ }
includes/class.field_type.php ADDED
@@ -0,0 +1,698 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Content Type - Field Types
4
+ * Stores properties for a specific field
5
+ * @package Cornerstone
6
+ * @subpackage Content Types
7
+ * @author Archetyped
8
+ */
9
+ class CNR_Field_Type extends CNR_Content_Base {
10
+ /* Properties */
11
+
12
+ const USES_DATA = '{data}';
13
+
14
+ /**
15
+ * Base class name
16
+ * @var string
17
+ */
18
+ var $base_class = 'cnr_field_type';
19
+
20
+ /**
21
+ * @var array Array of Field types that make up current Field type
22
+ */
23
+ var $elements = array();
24
+
25
+ /**
26
+ * Structure: Property names stored as keys in group
27
+ * Root
28
+ * -> Group Name
29
+ * -> Property Name => Null
30
+ * Reason: Faster searching over large arrays
31
+ * @var array Groupings of Properties
32
+ */
33
+ var $property_groups = array();
34
+
35
+ /**
36
+ * @var array Field type layouts
37
+ */
38
+ var $layout = array();
39
+
40
+ /**
41
+ * @var CNR_Field_Type Parent field type (reference)
42
+ */
43
+ var $parent = null;
44
+
45
+ /**
46
+ * Object that field is in
47
+ * @var CNR_Field|CNR_Field_Type|CNR_Content_Type
48
+ */
49
+ var $container = null;
50
+
51
+ /**
52
+ * Object that called field
53
+ * Used to determine field hierarchy/nesting
54
+ * @var CNR_Field|CNR_Field_Type|CNR_Content_Type
55
+ */
56
+ var $caller = null;
57
+
58
+ /**
59
+ * Constructor
60
+ */
61
+ function __construct($id = '', $parent = null) {
62
+ parent::__construct($id);
63
+
64
+ $this->id = $id;
65
+ $this->set_parent($parent);
66
+ }
67
+
68
+ /* Getters/Setters */
69
+
70
+ /**
71
+ * Search for specified member value in field's container object (if exists)
72
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
73
+ * @param string $name Value to retrieve from member
74
+ * @return mixed Member value if found (Default: empty string)
75
+ */
76
+ function get_container_value($member, $name = '', $default = '') {
77
+ $container =& $this->get_container();
78
+ return $this->get_object_value($container, $member, $name, $default, 'container');
79
+ }
80
+
81
+ /**
82
+ * Search for specified member value in field's container object (if exists)
83
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
84
+ * @param string $name Value to retrieve from member
85
+ * @return mixed Member value if found (Default: empty string)
86
+ */
87
+ function get_caller_value($member, $name = '', $default = '') {
88
+ $caller =& $this->get_caller();
89
+ return $this->get_object_value($caller, $member, $name, $default, 'caller');
90
+ }
91
+
92
+ /**
93
+ * Sets reference to container object of current field
94
+ * Reference is cleared if no valid object is passed to method
95
+ * @param object $container
96
+ */
97
+ function set_container(&$container) {
98
+ if ( !empty($container) && is_object($container) ) {
99
+ //Set as param as container for current field
100
+ $this->container =& $container;
101
+ } else {
102
+ //Clear container member if argument is invalid
103
+ $this->clear_container();
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Clears reference to container object of current field
109
+ */
110
+ function clear_container() {
111
+ $this->container = null;
112
+ }
113
+
114
+ /**
115
+ * Retrieves reference to container object of current field
116
+ * @return object Reference to container object
117
+ */
118
+ function &get_container() {
119
+ $ret = null;
120
+ if ( $this->has_container() )
121
+ $ret =& $this->container;
122
+ return $ret;
123
+ }
124
+
125
+ /**
126
+ * Checks if field has a container reference
127
+ * @return bool TRUE if field is contained, FALSE otherwise
128
+ */
129
+ function has_container() {
130
+ return !empty($this->container);
131
+ }
132
+
133
+ /**
134
+ * Sets reference to calling object of current field
135
+ * Any existing reference is cleared if no valid object is passed to method
136
+ * @param object $caller Calling object
137
+ */
138
+ function set_caller(&$caller) {
139
+ if ( !empty($caller) && is_object($caller) )
140
+ $this->caller =& $caller;
141
+ else
142
+ $this->clear_caller();
143
+ }
144
+
145
+ /**
146
+ * Clears reference to calling object of current field
147
+ */
148
+ function clear_caller() {
149
+ unset($this->caller);
150
+ }
151
+
152
+ /**
153
+ * Retrieves reference to caller object of current field
154
+ * @return object Reference to caller object
155
+ */
156
+ function &get_caller() {
157
+ $ret = null;
158
+ if ( $this->has_caller() )
159
+ $ret =& $this->caller;
160
+ return $ret;
161
+ }
162
+
163
+ /**
164
+ * Checks if field has a caller reference
165
+ * @return bool TRUE if field is called by another field, FALSE otherwise
166
+ */
167
+ function has_caller() {
168
+ return !empty($this->caller);
169
+ }
170
+
171
+ /**
172
+ * Add/Set a property on the field definition
173
+ * @param string $name Name of property
174
+ * @param mixed $value Default value for property
175
+ * @param string|array $group Group(s) property belongs to
176
+ * @return boolean TRUE if property is successfully added to field type, FALSE otherwise
177
+ */
178
+ function set_property($name, $value = '', $group = null) {
179
+ //Do not add if property name is not a string
180
+ if ( !is_string($name) )
181
+ return false;
182
+ //Create property array
183
+ $prop_arr = array();
184
+ $prop_arr['value'] = $value;
185
+ //Add to properties array
186
+ $this->properties[$name] = $value;
187
+ //Add property to specified groups
188
+ if ( !empty($group) ) {
189
+ $this->set_group_property($group, $name);
190
+ }
191
+ return true;
192
+ }
193
+
194
+ /**
195
+ * Sets multiple properties on field type at once
196
+ * @param array $properties Properties. Each element is an array containing the arguments to set a new property
197
+ * @return boolean TRUE if successful, FALSE otherwise
198
+ */
199
+ function set_properties($properties) {
200
+ if ( !is_array($properties) )
201
+ return false;
202
+ foreach ( $properties as $name => $val) {
203
+ $this->set_property($name, $val);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Retreives property from field type
209
+ * @param string $name Name of property to retrieve
210
+ * @return mixed Specified Property if exists (Default: Empty string)
211
+ */
212
+ function get_property($name) {
213
+ $val = $this->get_member_value('properties', $name);
214
+ return $val;
215
+ }
216
+
217
+ /**
218
+ * Adds Specified Property to a Group
219
+ * @param string|array $group Group(s) to add property to
220
+ * @param string $property Property to add to group
221
+ */
222
+ function set_group_property($group, $property) {
223
+ if ( is_string($group) && isset($this->property_groups[$group][$property]) )
224
+ return;
225
+ if ( !is_array($group) ) {
226
+ $group = array($group);
227
+ }
228
+
229
+ foreach ($group as $g) {
230
+ $g = trim($g);
231
+ //Initialize group if it doesn't already exist
232
+ if ( !isset($this->property_groups[$g]) )
233
+ $this->property_groups[$g] = array();
234
+
235
+ //Add property to group
236
+ $this->property_groups[$g][$property] = null;
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Retrieve property group
242
+ * @param string $group Group to retrieve
243
+ * @return array Array of properties in specified group
244
+ */
245
+ function get_group($group) {
246
+ return $this->get_member_value('property_groups', $group, array());
247
+ }
248
+
249
+ /**
250
+ * Sets an element for the field type
251
+ * @param string $name Name of element
252
+ * @param CNR_Field_Type $type Reference of field type to use for element
253
+ * @param array $properties Properties for element (passed as keyed associative array)
254
+ * @param string $id_prop Name of property to set $name to (e.g. ID, etc.)
255
+ */
256
+ function set_element($name, $type, $properties = array(), $id_prop = 'id') {
257
+ $name = trim(strval($name));
258
+ if ( empty($name) )
259
+ return false;
260
+ //Create new field for element
261
+ $el = new CNR_Field($name, $type);
262
+ //Set container to current field instance
263
+ $el->set_container($this);
264
+ //Add properties to element
265
+ $el->set_properties($properties);
266
+ //Save element to current instance
267
+ $this->elements[$name] =& $el;
268
+ }
269
+
270
+ /**
271
+ * Add a layout to the field
272
+ * @param string $name Name of layout
273
+ * @param string $value Layout text
274
+ */
275
+ function set_layout($name, $value = '') {
276
+ if ( !is_string($name) )
277
+ return false;
278
+ $name = trim($name);
279
+ $this->layout[$name] = $value;
280
+ return true;
281
+ }
282
+
283
+ /**
284
+ * Retrieve specified layout
285
+ * @param string $name Layout name
286
+ * @param bool $parse_nested (optional) Whether nested layouts should be expanded in retreived layout or not (Default: TRUE)
287
+ * @return string Specified layout text
288
+ */
289
+ function get_layout($name = 'form', $parse_nested = true) {
290
+ //Retrieve specified layout (use $name value if no layout by that name exists)
291
+ $layout = $this->get_member_value('layout', $name, $name);
292
+
293
+ //Find all nested layouts in current layout
294
+ if ( !empty($layout) && !!$parse_nested ) {
295
+ $ph = $this->get_placeholder_defaults();
296
+
297
+ while ($ph->match = $this->parse_layout($layout, $ph->pattern_layout)) {
298
+ //Iterate through the different types of layout placeholders
299
+ foreach ($ph->match as $tag => $instances) {
300
+ //Iterate through instances of a specific type of layout placeholder
301
+ foreach ($instances as $instance) {
302
+ //Get nested layout
303
+ $nested_layout = $this->get_member_value($instance);
304
+
305
+ //Replace layout placeholder with retrieved item data
306
+ if ( !empty($nested_layout) )
307
+ $layout = str_replace($ph->start . $instance['match'] . $ph->end, $nested_layout, $layout);
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ return $layout;
314
+ }
315
+
316
+ /**
317
+ * Checks if specified layout exists
318
+ * Finds layout if it exists in current object or any of its parents
319
+ * @param string $layout Name of layout to check for
320
+ * @return bool TRUE if layout exists, FALSE otherwise
321
+ */
322
+ function has_layout($layout) {
323
+ $ret = false;
324
+ if ( is_string($layout) && ($layout = trim($layout)) && !empty($layout) ) {
325
+ $layout = $this->get_member_value('layout', $layout, false);
326
+ if ( $layout !== false )
327
+ $ret = true;
328
+ }
329
+
330
+ return $ret;
331
+ }
332
+
333
+ /**
334
+ * Checks if layout content is valid
335
+ * Layouts need to have placeholders to be valid
336
+ * @param string $layout_content Layout content (markup)
337
+ * @return bool TRUE if layout is valid, FALSE otherwise
338
+ */
339
+ function is_valid_layout($layout_content) {
340
+ $ph = $this->get_placeholder_defaults();
341
+ return preg_match($ph->pattern_general, $layout_content);
342
+ }
343
+
344
+ /**
345
+ * Parse field layout with a regular expression
346
+ * @param string $layout Layout data
347
+ * @param string $search Regular expression pattern to search layout for
348
+ * @return array Associative array containing all of the regular expression matches in the layout data
349
+ * Array Structure:
350
+ * root => placeholder tags
351
+ * => Tag instances (array)
352
+ * 'tag' => (string) tag name
353
+ * 'match' => (string) placeholder match
354
+ * 'attributes' => (array) attributes
355
+ */
356
+ function parse_layout($layout, $search) {
357
+ $ph_xml = '';
358
+ $parse_match = '';
359
+ $ph_root_tag = 'ph_root_element';
360
+ $ph_start_xml = '<';
361
+ $ph_end_xml = ' />';
362
+ $ph_wrap_start = '<' . $ph_root_tag . '>';
363
+ $ph_wrap_end = '</' . $ph_root_tag . '>';
364
+ $parse_result = false;
365
+
366
+ //Find all nested layouts in layout
367
+ $match_value = preg_match_all($search, $layout, $parse_match, PREG_PATTERN_ORDER);
368
+
369
+ if ($match_value !== false && $match_value > 0) {
370
+ $parse_result = array();
371
+ //Get all matched elements
372
+ $parse_match = $parse_match[1];
373
+
374
+ //Build XML string from placeholders
375
+ foreach ($parse_match as $ph) {
376
+ $ph_xml .= $ph_start_xml . $ph . $ph_end_xml . ' ';
377
+ }
378
+ $ph_xml = $ph_wrap_start . $ph_xml . $ph_wrap_end;
379
+ //Parse XML data
380
+ $ph_prs = xml_parser_create();
381
+ xml_parser_set_option($ph_prs, XML_OPTION_SKIP_WHITE, 1);
382
+ xml_parser_set_option($ph_prs, XML_OPTION_CASE_FOLDING, 0);
383
+ $ret = xml_parse_into_struct($ph_prs, $ph_xml, $parse_result['values'], $parse_result['index']);
384
+ xml_parser_free($ph_prs);
385
+
386
+ //Build structured array with all parsed data
387
+
388
+ unset($parse_result['index'][$ph_root_tag]);
389
+
390
+ //Build structured array
391
+ $result = array();
392
+ foreach ($parse_result['index'] as $tag => $instances) {
393
+ $result[$tag] = array();
394
+ //Instances
395
+ foreach ($instances as $instance) {
396
+ //Skip instance if it doesn't exist in parse results
397
+ if (!isset($parse_result['values'][$instance]))
398
+ continue;
399
+
400
+ //Stop processing instance if a previously-saved instance with the same options already exists
401
+ foreach ($result[$tag] as $tag_match) {
402
+ if ($tag_match['match'] == $parse_match[$instance - 1])
403
+ continue 2;
404
+ }
405
+
406
+ //Init instance data array
407
+ $inst_data = array();
408
+
409
+ //Add Tag to array
410
+ $inst_data['tag'] = $parse_result['values'][$instance]['tag'];
411
+
412
+ //Add instance data to array
413
+ $inst_data['attributes'] = (isset($parse_result['values'][$instance]['attributes'])) ? $inst_data['attributes'] = $parse_result['values'][$instance]['attributes'] : '';
414
+
415
+ //Add match to array
416
+ $inst_data['match'] = $parse_match[$instance - 1];
417
+
418
+ //Add to result array
419
+ $result[$tag][] = $inst_data;
420
+ }
421
+ }
422
+ $parse_result = $result;
423
+ }
424
+
425
+ return $parse_result;
426
+ }
427
+
428
+ /**
429
+ * Retrieves default properties to use when evaluating layout placeholders
430
+ * @return object Object with properties for evaluating layout placeholders
431
+ */
432
+ function get_placeholder_defaults() {
433
+ $ph = new stdClass();
434
+ $ph->start = '{';
435
+ $ph->end = '}';
436
+ $ph->reserved = array('ref' => 'ref_base');
437
+ $ph->pattern_general = '/' . $ph->start . '([a-zA-Z0-9_].*?)' . $ph->end . '/i';
438
+ $ph->pattern_layout = '/' . $ph->start . '([a-zA-Z0-9].*?\s+' . $ph->reserved['ref'] . '="layout.*?".*?)' . $ph->end . '/i';
439
+ return $ph;
440
+ }
441
+
442
+ /**
443
+ * Builds HTML for a field based on its properties
444
+ * @param array $field Field properties (id, field, etc.)
445
+ * @param array data Additional data for current field
446
+ */
447
+ function build_layout($layout = 'form', $data = null) {
448
+ $out_default = '';
449
+
450
+ /* Layout */
451
+
452
+ //Get base layout
453
+ $out = $this->get_layout($layout);
454
+
455
+ //Only parse valid layouts
456
+ if ( $this->is_valid_layout($out) ) {
457
+ //Parse Layout
458
+ $ph = $this->get_placeholder_defaults();
459
+
460
+ //Search layout for placeholders
461
+ while ( $ph->match = $this->parse_layout($out, $ph->pattern_general) ) {
462
+ //Iterate through placeholders (tag, id, etc.)
463
+ foreach ( $ph->match as $tag => $instances ) {
464
+ //Iterate through instances of current placeholder
465
+ foreach ( $instances as $instance ) {
466
+ //Process value based on placeholder name
467
+ $target_property = apply_filters('cnr_process_placeholder_' . $tag, '', $this, $instance, $layout, $data);
468
+
469
+ //Process value using default processors (if necessary)
470
+ if ( '' == $target_property ) {
471
+ $target_property = apply_filters('cnr_process_placeholder', $target_property, $this, $instance, $layout, $data);
472
+ }
473
+
474
+ //Clear value if value not a string
475
+ if ( !is_scalar($target_property) ) {
476
+ $target_property = '';
477
+ }
478
+ //Replace layout placeholder with retrieved item data
479
+ $out = str_replace($ph->start . $instance['match'] . $ph->end, $target_property, $out);
480
+ }
481
+ }
482
+ }
483
+ } else {
484
+ $out = $out_default;
485
+ }
486
+
487
+ /* Return generated value */
488
+
489
+ return $out;
490
+ }
491
+
492
+ /*-** Static Methods **-*/
493
+
494
+ /**
495
+ * Returns indacator to use field data (in layouts, property values, etc.)
496
+ */
497
+ function uses_data() {
498
+ return self::USES_DATA;
499
+ }
500
+
501
+ /**
502
+ * Register a function to handle a placeholder
503
+ * Multiple handlers may be registered for a single placeholder
504
+ * Basically a wrapper function to facilitate adding hooks for placeholder processing
505
+ * @uses add_filter()
506
+ * @param string $placeholder Name of placeholder to add handler for (Using 'all' will set the function as a handler for all placeholders
507
+ * @param callback $handler Function to set as a handler
508
+ * @param int $priority (optional) Priority of handler
509
+ */
510
+ static function register_placeholder_handler($placeholder, $handler, $priority = 10) {
511
+ if ( 'all' == $placeholder )
512
+ $placeholder = '';
513
+ else
514
+ $placeholder = '_' . $placeholder;
515
+
516
+ add_filter('cnr_process_placeholder' . $placeholder, $handler, $priority, 5);
517
+ }
518
+
519
+ /**
520
+ * Default placeholder processing
521
+ * To be executed when current placeholder has not been handled by another handler
522
+ * @param string $ph_output Value to be used in place of placeholder
523
+ * @param CNR_Field $field Field containing placeholder
524
+ * @param array $placeholder Current placeholder
525
+ * @see CNR_Field::parse_layout for structure of $placeholder array
526
+ * @param string $layout Layout to build
527
+ * @param array $data Extended data for field
528
+ * @return string Value to use in place of current placeholder
529
+ */
530
+ static function process_placeholder_default($ph_output, $field, $placeholder, $layout, $data) {
531
+ //Validate parameters before processing
532
+ if ( empty($ph_output) && is_a($field, 'CNR_Field_Type') && is_array($placeholder) ) {
533
+ //Build path to replacement data
534
+ $ph_output = $field->get_member_value($placeholder);
535
+
536
+ //Check if value is group (properties, etc.)
537
+ //All groups must have additional attributes (beyond reserved attributes) that define how items in group are used
538
+ if (is_array($ph_output)
539
+ && !empty($placeholder['attributes'])
540
+ && is_array($placeholder['attributes'])
541
+ && ($ph = $field->get_placeholder_defaults())
542
+ && $attribs = array_diff(array_keys($placeholder['attributes']), array_values($ph->reserved))
543
+ ) {
544
+ /* Targeted property is an array, but the placeholder contains additional options on how property is to be used */
545
+
546
+ //Find items matching criteria in $ph_output
547
+ //Check for group criteria
548
+ //TODO: Implement more robust/flexible criteria handling (2010-03-11: Currently only processes property groups)
549
+ if ( 'properties' == $placeholder['tag'] && ($prop_group = $field->get_group($placeholder['attributes']['group'])) && !empty($prop_group) ) {
550
+ /* Process group */
551
+ $group_out = array();
552
+ //Iterate through properties in group and build string
553
+ foreach ( $prop_group as $prop_key => $prop_val ) {
554
+ $group_out[] = $prop_key . '="' . $field->get_property($prop_key) . '"';
555
+ }
556
+ $ph_output = implode(' ', $group_out);
557
+ }
558
+ } elseif ( is_object($ph_output) && is_a($ph_output, $field->base_class) ) {
559
+ /* Targeted property is actually a nested field */
560
+ //Set caller to current field
561
+ $ph_output->set_caller($field);
562
+ //Build layout for nested element
563
+ $ph_output = $ph_output->build_layout($layout);
564
+ }
565
+ }
566
+
567
+ return $ph_output;
568
+ }
569
+
570
+ /**
571
+ * Build Field ID attribute
572
+ * @see CNR_Field_Type::process_placeholder_default for parameter descriptions
573
+ * @return string Placeholder output
574
+ */
575
+ static function process_placeholder_id($ph_output, $field, $placeholder, $layout, $data) {
576
+ //Get attributes
577
+ $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_id'));
578
+ return $field->get_id($args);
579
+ }
580
+
581
+ /**
582
+ * Build Field name attribute
583
+ * Name is formatted as an associative array for processing by PHP after submission
584
+ * @see CNR_Field_Type::process_placeholder_default for parameter descriptions
585
+ * @return string Placeholder output
586
+ */
587
+ static function process_placeholder_name($ph_output, $field, $placeholder, $layout, $data) {
588
+ //Get attributes
589
+ $args = wp_parse_args($placeholder['attributes'], array('format' => 'default'));
590
+ return $field->get_id($args);
591
+ }
592
+
593
+ /**
594
+ * Retrieve data for field
595
+ * @see CNR_Field_Type::process_placeholder_default for parameter descriptions
596
+ * @return string Placeholder output
597
+ */
598
+ static function process_placeholder_data($ph_output, $field, $placeholder, $layout) {
599
+ $val = $field->get_data();
600
+ if ( !is_null($val) ) {
601
+ $ph_output = $val;
602
+ $attr =& $placeholder['attributes'];
603
+ //Get specific member in value (e.g. value from a specific field element)
604
+ if ( isset($attr['element']) && is_array($ph_output) && ( $el = $attr['element'] ) && isset($ph_output[$el]) )
605
+ $ph_output = $ph_output[$el];
606
+ if ( isset($attr['format']) && 'display' == $attr['format'] )
607
+ $ph_output = nl2br($ph_output);
608
+ }
609
+
610
+ //Return data
611
+ return $ph_output;
612
+ }
613
+
614
+ /**
615
+ * Loops over data to build field output
616
+ * Options:
617
+ * data - Dot-delimited path in field that contains data to loop through
618
+ * layout - Name of layout to use for each data item in loop
619
+ * layout_data - Name of layout to use for data item that matches previously-saved field data
620
+ * @see CNR_Field_Type::process_placeholder_default for parameter descriptions
621
+ * @return string Placeholder output
622
+ */
623
+ static function process_placeholder_loop($ph_output, $field, $placeholder, $layout, $data) {
624
+ //Setup loop options
625
+ $attr_defaults = array (
626
+ 'layout' => '',
627
+ 'layout_data' => null,
628
+ 'data' => ''
629
+ );
630
+
631
+ $attr = wp_parse_args($placeholder['attributes'], $attr_defaults);
632
+
633
+ if ( is_null($attr['layout_data']) ) {
634
+ $attr['layout_data'] =& $attr['layout'];
635
+ }
636
+
637
+ //Get data for loop
638
+ $path = explode('.', $attr['data']);
639
+ $loop_data = $field->get_member_value($path);
640
+ /*if ( isset($loop_data['value']) )
641
+ $loop_data = $loop_data['value'];
642
+ */
643
+ $out = array();
644
+
645
+ //Get field data
646
+ $data = $field->get_data();
647
+
648
+ //Iterate over data and build output
649
+ if ( is_array($loop_data) && !empty($loop_data) ) {
650
+ foreach ( $loop_data as $value => $label ) {
651
+ //Load appropriate layout based on field value
652
+ $layout = ( ($data === 0 && $value === $data) xor $data == $value ) ? $attr['layout_data'] : $attr['layout'];
653
+ //Stop processing if no valid layout is returned
654
+ if ( empty($layout) )
655
+ continue;
656
+ //Prep extended field data
657
+ $data_ext = array('option_value' => $value, 'option_text' => $label);
658
+ $out[] = $field->build_layout($layout, $data_ext);
659
+ }
660
+ }
661
+
662
+ //Return output
663
+ return implode($out);
664
+ }
665
+
666
+ /**
667
+ * Returns specified value from extended data array for field
668
+ * @see CNR_Field_Type::process_placeholder_default for parameter descriptions
669
+ * @return string Placeholder output
670
+ */
671
+ static function process_placeholder_data_ext($ph_output, $field, $placeholder, $layout, $data) {
672
+ if ( isset($placeholder['attributes']['id']) && ($key = $placeholder['attributes']['id']) && isset($data[$key]) ) {
673
+ $ph_output = strval($data[$key]);
674
+ }
675
+
676
+ return $ph_output;
677
+ }
678
+
679
+ /**
680
+ * WP Editor
681
+ * @see CNR_Field_Type::process_placeholder_default for parameter descriptions
682
+ * @return string Placeholder output
683
+ */
684
+ static function process_placeholder_rich_editor($ph_output, $field, $placeholder, $layout, $data) {
685
+ $id = $field->get_id( array (
686
+ 'format' => 'attr_id'
687
+ ));
688
+ $settings = array (
689
+ 'textarea_name' => $field->get_id( array (
690
+ 'format' => 'default'
691
+ ))
692
+ );
693
+ ob_start();
694
+ wp_editor($field->get_data(), $id, $settings);
695
+ $out = ob_get_clean();
696
+ return $out;
697
+ }
698
+ }
includes/class.media.php ADDED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Core properties/methods for Media management
5
+ * @package Cornerstone
6
+ * @subpackage Media
7
+ * @author Archetyped
8
+ */
9
+ class CNR_Media extends CNR_Base {
10
+
11
+ var $var_field = 'field';
12
+
13
+ var $var_action = 'action';
14
+
15
+ /**
16
+ * Constructor
17
+ */
18
+ function __construct() {
19
+ parent::__construct();
20
+ $this->var_field = $this->add_prefix($this->var_field);
21
+ $this->var_action = $this->add_prefix($this->var_action);
22
+ }
23
+
24
+ /* Methods */
25
+
26
+ function register_hooks() {
27
+ parent::register_hooks();
28
+
29
+ //Register media placeholder handler
30
+ cnr_register_placeholder_handler('media', $this->m('content_type_process_placeholder_media'));
31
+
32
+ //Register field types
33
+ add_action('cnr_register_field_types', $this->m('register_field_types'));
34
+
35
+ //Register/Modify content types
36
+ // add_action('cnr_register_content_types', $this->m('register_content_types'));
37
+
38
+ //Register handler for custom media requests
39
+ add_action('media_upload_cnr_field_media', $this->m('field_upload_media'));
40
+
41
+ //Display 'Set as...' button in media item box
42
+ add_filter('attachment_fields_to_edit', $this->m('attachment_fields_to_edit'), 11, 2);
43
+
44
+ //Add form fields to upload form (to pass query vars along with form submission)
45
+ add_action('pre-html-upload-ui', $this->m('attachment_html_upload_ui'));
46
+
47
+ //Display additional meta data for media item (dimensions, etc.)
48
+ //add_filter('media_meta', $this->m('media_meta'), 10, 2);
49
+
50
+ //Modifies media upload query vars so that request is routed through plugin
51
+ add_filter('admin_url', $this->m('media_upload_url'), 10, 2);
52
+
53
+ //Adds admin menus for content types
54
+ add_action('cnr_admin_menu_type', $this->m('type_admin_menu'));
55
+
56
+ //Modify tabs in upload popup for fields
57
+ add_filter('media_upload_tabs', $this->m('field_upload_tabs'));
58
+ }
59
+
60
+ /**
61
+ * Register media-specific field types
62
+ */
63
+ function register_field_types($field_types) {
64
+ /**
65
+ * @var CNR_Content_Utilities
66
+ */
67
+ global $cnr_content_utilities;
68
+
69
+ // Media (Base)
70
+ $media = new CNR_Field_Type('media', 'base_closed');
71
+ $media->set_description('Media Item');
72
+ $media->set_property('title', 'Select Media');
73
+ $media->set_property('button','Select Media');
74
+ $media->set_property('remove', 'Remove Media');
75
+ $media->set_property('set_as', 'media');
76
+ $media->set_layout('form', '{media}');
77
+ $media->set_layout('display', '{media format="display"}');
78
+ $media->set_layout('display_url', '{media format="display" type="url"}');
79
+ $media->add_script( array('add', 'edit-item', 'post-new.php', 'post.php', 'media-upload-popup'), $this->add_prefix('script_media'), $this->util->get_file_url('js/lib.media.js'), array($this->add_prefix('core'), $this->add_prefix('admin')));
80
+ $cnr_content_utilities->register_field($media);
81
+
82
+ // Image
83
+ $image = new CNR_Field_Type('image', 'media');
84
+ $image->set_description('Image');
85
+ $image->set_parent('media');
86
+ $image->set_property('title', 'Select Image');
87
+ $image->set_property('button', 'Select Image');
88
+ $image->set_property('remove', 'Remove Image');
89
+ $image->set_property('set_as', 'image');
90
+ $cnr_content_utilities->register_field($image);
91
+ }
92
+
93
+ /**
94
+ * Media placeholder handler
95
+ * @param string $ph_output Value to be used in place of placeholder
96
+ * @param CNR_Field $field Field containing placeholder
97
+ * @param array $placeholder Current placeholder @see CNR_Field::parse_layout for structure of $placeholder array
98
+ * @param string $layout Layout to build
99
+ * @param array $data Extended data for field (Default: null)
100
+ * @return string Value to use in place of current placeholder
101
+ */
102
+ function content_type_process_placeholder_media($ph_output, $field, $placeholder, $layout, $data) {
103
+ global $post_ID, $temp_ID;
104
+ $attr_default = array('format' => 'form', 'type' => 'html', 'id' => '', 'class' => '');
105
+ $attr = wp_parse_args($placeholder['attributes'], $attr_default);
106
+ //Get media ID
107
+ $post_media = $field->get_data();
108
+
109
+ //Format output based on placeholder attribute
110
+ switch ( strtolower($attr['format']) ) {
111
+ case 'form':
112
+ $uploading_iframe_ID = (int) (0 == $post_ID ? $temp_ID : $post_ID);
113
+ $media_upload_iframe_src = "media-upload.php";
114
+ $media_id = $field->get_id(array('format' => true));
115
+ $media_name = $media_id;
116
+ $query = array (
117
+ 'post_id' => $uploading_iframe_ID,
118
+ 'type' => $this->add_prefix('field_media'),
119
+ $this->var_action => 'true',
120
+ $this->var_field => $media_id,
121
+ 'cnr_set_as' => $field->get_property('set_as'),
122
+ 'TB_iframe' => 'true'
123
+ );
124
+ $media_upload_iframe_src = apply_filters('image_upload_iframe_src', $media_upload_iframe_src . '?' . http_build_query($query));
125
+
126
+ //Get Attachment media URL
127
+ $post_media_valid = get_post($post_media);
128
+ $post_media_valid = ( isset($post_media_valid->post_type) && 'attachment' == $post_media_valid->post_type ) ? true : false;
129
+
130
+ //Start output
131
+ ob_start();
132
+ ?>
133
+ <div id="<?php echo "$media_name-data"; ?>" class="media_data">
134
+ <?php
135
+ if ($post_media_valid) {
136
+ //Add media preview
137
+ ?>
138
+ {media format="display" id="<?php echo "$media_name-frame"; ?>" class="media_frame"}
139
+ {media format="display" type="link" target="_blank" id="<?php echo "$media_name-link"; ?>" class="media_link"}
140
+ <input type="hidden" name="<?php echo "$media_name"?>" id="<?php echo "$media_name"?>" value="<?php echo $post_media ?>" />
141
+ <?php
142
+ }
143
+ ?>
144
+ </div>
145
+ <?php
146
+ //Add media action options (upload, remove, etc.)
147
+ ?>
148
+ <div class="actions buttons">
149
+ <a href="<?php echo "$media_upload_iframe_src" ?>" id="<?php echo "$media_name-lnk"?>" class="thickbox button" title="{title}" onclick="return false;">{button}</a>
150
+ <span id="<?php echo "$media_name-options"?>" class="options <?php if (!$post_media_valid) : ?> options-default <?php endif; ?>">
151
+ or <a href="#" title="Remove media" class="del-link" id="<?php echo "$media_name-option_remove"?>" onclick="CNR.media.doAction(this); return false;">{remove}</a>
152
+ <span id="<?php echo "$media_name-remove_confirmation"?>" class="confirmation remove-confirmation confirmation-default">Are you sure? <a href="#" id="<?php echo "$media_name-remove"?>" class="delete" onclick="return CNR.media.doAction(this);">Remove</a> or <a href="#" id="<?php echo "$media_name-remove_cancel"?>" onclick="return CNR.media.doAction(this);">Cancel</a></span>
153
+ </span>
154
+ </div>
155
+ <?php
156
+ //Output content
157
+ $ph_output = ob_get_clean();
158
+ break;
159
+ case 'display':
160
+ //Add placeholder attributes to attributes from function call
161
+
162
+ //Remove attributes used by system
163
+ $type = $attr['type'];
164
+ $attr_system = array('format', 'type');
165
+ foreach ( $attr_system as $key ) {
166
+ unset($attr[$key]);
167
+ }
168
+ $data = wp_parse_args($data, $attr);
169
+ $ph_output = $this->get_media_output($post_media, $type, $data);
170
+ break;
171
+ }
172
+ return $ph_output;
173
+ }
174
+
175
+ /**
176
+ * Handles upload of Post media on post edit form
177
+ * @return void
178
+ */
179
+ function field_upload_media() {
180
+ $errors = array();
181
+ $id = 0;
182
+
183
+ //Process media selection
184
+ if ( isset($_POST['setmedia']) ) {
185
+ /* Send image data to main post edit form and close popup */
186
+ //Get Attachment ID
187
+ $field_var = $this->add_prefix('field');
188
+ $args = new stdClass();
189
+ $args->id = array_shift( array_keys($_POST['setmedia']) );
190
+ $args->field = '';
191
+ if ( isset($_REQUEST['attachments'][$args->id][$this->var_field]) )
192
+ $args->field = $_REQUEST['attachments'][$args->id][$this->var_field];
193
+ elseif ( isset($_REQUEST[$this->var_field]) )
194
+ $args->field = $_REQUEST[$this->var_field];
195
+ $a =& get_post($args->id);
196
+ if ( ! empty($a) ) {
197
+ $args->url = wp_get_attachment_url($a->ID);
198
+ $args->type = get_post_mime_type($a->ID);
199
+ $icon = !wp_attachment_is_image($a->ID);
200
+ $args->preview = wp_get_attachment_image_src($a->ID, '', $icon);
201
+ $args->preview = ( ! $args->preview ) ? '' : $args->preview[0];
202
+ }
203
+ //Update parent window (JS)
204
+ $js_out = "var win = window.dialogArguments || opener || parent || top; win.CNR.media.setPostMedia(" . json_encode($args) . ");";
205
+ echo $this->util->build_script_element($js_out, 'media_upload', false);
206
+ exit;
207
+ }
208
+
209
+ //Handle HTML upload
210
+ if ( isset($_POST['html-upload']) && !empty($_FILES) ) {
211
+ $id = media_handle_upload('async-upload', $_REQUEST['post_id']);
212
+ //Clear uploaded files
213
+ unset($_FILES);
214
+ if ( is_wp_error($id) ) {
215
+ $errors['upload_error'] = $id;
216
+ $id = false;
217
+ }
218
+ }
219
+
220
+ //Display default UI
221
+
222
+ //Determine media type
223
+ $type = ( isset($_REQUEST['type']) ) ? $_REQUEST['type'] : 'cnr_field_media';
224
+ //Determine UI to use (disk or URL upload)
225
+ $upload_form = ( isset($_GET['tab']) && 'type_url' == $_GET['tab'] ) ? 'media_upload_type_url_form' : 'media_upload_type_form';
226
+ //Load UI
227
+ return wp_iframe( $upload_form, $type, $errors, $id );
228
+ }
229
+
230
+ /**
231
+ * Modifies array of form fields to display on Attachment edit form
232
+ * Array items are in the form:
233
+ * 'key' => array(
234
+ * 'label' => "Label Text",
235
+ * 'value' => Value
236
+ * )
237
+ *
238
+ * @return array Form fields to display on Attachment edit form
239
+ * @param array $form_fields Associative array of Fields to display on form (@see get_attachment_fields_to_edit())
240
+ * @param object $attachment Attachment post object
241
+ */
242
+ function attachment_fields_to_edit($form_fields, $attachment) {
243
+
244
+ if ( $this->is_custom_media() ) {
245
+ $post =& get_post($attachment);
246
+ //Clear all form fields
247
+ $form_fields = array();
248
+ //TODO Display custom buttons based on mime type defined in content type's field
249
+ $set_as = 'media';
250
+ $qvar = 'cnr_set_as';
251
+ //Get set as text from request
252
+ if ( isset($_REQUEST[$qvar]) && !empty($_REQUEST[$qvar]) )
253
+ $set_as = $_REQUEST[$qvar];
254
+ elseif ( ( strpos($_SERVER['PHP_SELF'], 'async-upload.php') !== false || isset($_POST['html-upload']) ) && ($ref = wp_get_referer()) && strpos($ref, $qvar) !== false && ($ref = parse_url($ref)) && isset($ref['query']) ) {
255
+ //Get set as text from referer (for async uploads)
256
+ $qs = array();
257
+ parse_str($ref['query'], $qs);
258
+ if ( isset($qs[$qvar]) )
259
+ $set_as = $qs[$qvar];
260
+ }
261
+ //Add "Set as Image" button to form fields array
262
+ $set_as = 'Set as ' . $set_as;
263
+ $field = array(
264
+ 'label' => '',
265
+ 'input' => 'html',
266
+ 'html' => '<input type="submit" class="button" value="' . $set_as . '" name="setmedia[' . $post->ID . ']" />'
267
+ );
268
+ $form_fields['buttons'] = $field;
269
+ //Add field ID value as hidden field (if set)
270
+ if ( isset($_REQUEST[$this->var_field]) ) {
271
+ $field = array(
272
+ 'input' => 'hidden',
273
+ 'value' => $_REQUEST[$this->var_field]
274
+ );
275
+ $form_fields[$this->var_field] = $field;
276
+ }
277
+ }
278
+ return $form_fields;
279
+ }
280
+
281
+ /**
282
+ * Checks if value represents a valid media item
283
+ * @param int|object $media Attachment ID or Object to check
284
+ * @return bool TRUE if item is media, FALSE otherwise
285
+ */
286
+ function is_media($media) {
287
+ $media =& get_post($media);
288
+ return ( ! empty($media) && 'attachment' == $media->post_type );
289
+ }
290
+
291
+ /**
292
+ * Checks whether current media upload/selection request is initiated by the plugin
293
+ */
294
+ function is_custom_media() {
295
+ $ret = false;
296
+ $action = $this->var_action;
297
+ $upload = false;
298
+ if (isset($_REQUEST[$action]))
299
+ $ret = true;
300
+ elseif (isset($_SERVER['HTTP_REFERER']) ) {
301
+ $qs = array();
302
+ $ref = parse_url($_SERVER['HTTP_REFERER']);
303
+ if ( isset($ref['query']) )
304
+ parse_str($ref['query'], $qs);
305
+ if (array_key_exists($action, $qs))
306
+ $ret = true;
307
+ }
308
+
309
+ return $ret;
310
+ }
311
+
312
+ /**
313
+ * Add HTML Media upload form
314
+ * @return void
315
+ */
316
+ function attachment_html_upload_ui() {
317
+ $vars = array ($this->var_action, $this->var_field);
318
+ foreach ( $vars as $var ) {
319
+ if ( isset($_REQUEST[$var]) )
320
+ echo '<input type="hidden" name="' . $var . '" id="' . $var . '" value="' . esc_attr($_REQUEST[$var]) . '" />';
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Adds additional media meta data to media item display
326
+ * @param object $meta Meta data to display
327
+ * @param object $post Attachment post object
328
+ * @return string Meta data to display
329
+ */
330
+ function media_meta($meta, $post) {
331
+ if ($this->is_custom_media() && wp_attachment_is_image($post->ID)) {
332
+ //Get attachment image info
333
+ $img = wp_get_attachment_image_src($post->ID, '');
334
+ if (is_array($img) && count($img) > 2) {
335
+ //Add image dimensions to output
336
+ $meta .= sprintf('<div>%dpx&nbsp;&times;&nbsp;%dpx</div>', $img[1], $img[2]);
337
+ }
338
+ }
339
+ return $meta;
340
+ }
341
+
342
+ /**
343
+ * Modifies media upload URL to work with CNR attachments
344
+ * @param string $url Full admin URL
345
+ * @param string $path Path part of URL
346
+ * @return string Modified media upload URL
347
+ */
348
+ function media_upload_url($url, $path) {
349
+ if (strpos($path, 'media-upload.php') === 0) {
350
+ //Get query vars
351
+ $qs = parse_url($url);
352
+ $qs = ( isset($qs['query']) ) ? $qs['query'] : '';
353
+ $q = array();
354
+ parse_str($qs, $q);
355
+ //Check for tab variable
356
+ if (isset($q['tab'])) {
357
+ //Replace tab value
358
+ $q['cnr_tab'] = $q['tab'];
359
+ $q['tab'] = 'type';
360
+ }
361
+ //Rebuild query string
362
+ $qs_upd = build_query($q);
363
+ //Update query string on URL
364
+ $url = str_replace($qs, $qs_upd, $url);
365
+ }
366
+ return $url;
367
+ }
368
+
369
+ /*-** Field-Specific **-*/
370
+
371
+ /**
372
+ * Removes URL tab from media upload popup for fields
373
+ * Fields currently only support media stored @ website
374
+ * @param array $default_tabs Media upload tabs
375
+ * @see media_upload_tabs() for full $default_tabs array
376
+ * @return array Modified tabs array
377
+ */
378
+ function field_upload_tabs($default_tabs) {
379
+ if ( $this->is_custom_media() )
380
+ unset($default_tabs['type_url']);
381
+ return $default_tabs;
382
+ }
383
+
384
+ /*-** Post Attachments **-*/
385
+
386
+ /**
387
+ * Retrieves matching attachments for post
388
+ * @param object|int $post Post object or Post ID (Default: current global post)
389
+ * @param array $args (optional) Associative array of query arguments
390
+ * @see get_posts() for query arguments
391
+ * @return array|bool Array of post attachments (FALSE on failure)
392
+ */
393
+ function post_get_attachments($post = null, $args = '', $filter_special = true) {
394
+ if (!$this->util->check_post($post))
395
+ return false;
396
+ global $wpdb;
397
+
398
+ //Default arguments
399
+ $defaults = array(
400
+ 'post_type' => 'attachment',
401
+ 'post_parent' => (int) $post->ID,
402
+ 'numberposts' => -1
403
+ );
404
+
405
+ $args = wp_parse_args($args, $defaults);
406
+
407
+ //Get attachments
408
+ $attachments = get_children($args);
409
+
410
+ //Filter special attachments
411
+ if ( $filter_special ) {
412
+ $start = '[';
413
+ $end = ']';
414
+ $removed = false;
415
+ foreach ( $attachments as $i => $a ) {
416
+ if ( $start == substr($a->post_title, 0, 1) && $end == substr($a->post_title, -1) ) {
417
+ unset($attachments[$i]);
418
+ $removed = true;
419
+ }
420
+ }
421
+ if ( $removed )
422
+ $attachments = array_values($attachments);
423
+ }
424
+
425
+ //Return attachments
426
+ return $attachments;
427
+ }
428
+
429
+ /**
430
+ * Retrieve the attachment's path
431
+ * Path = Full URL to attachment - site's base URL
432
+ * Useful for filesystem operations (e.g. file_exists(), etc.)
433
+ * @param object|id $post Attachment object or ID
434
+ * @return string Attachment path
435
+ */
436
+ function get_attachment_path($post = null) {
437
+ if (!$this->util->check_post($post))
438
+ return '';
439
+ //Get Attachment URL
440
+ $url = wp_get_attachment_url($post->ID);
441
+ //Replace with absolute path
442
+ $path = str_replace(get_bloginfo('wpurl') . '/', ABSPATH, $url);
443
+ return $path;
444
+ }
445
+
446
+ /**
447
+ * Retrieves filesize of an attachment
448
+ * @param obj|int $post (optional) Attachment object or ID (uses global $post object if parameter not provided)
449
+ * @param bool $formatted (optional) Whether or not filesize should be formatted (kb/mb, etc.) (Default: TRUE)
450
+ * @return int|string Filesize in bytes (@see filesize()) or as formatted string based on parameters
451
+ */
452
+ function get_attachment_filesize($post = null, $formatted = true) {
453
+ $size = 0;
454
+ if (!$this->util->check_post($post))
455
+ return $size;
456
+ //Get path to attachment
457
+ $path = $this->get_attachment_path($post);
458
+ //Get file size
459
+ if (file_exists($path))
460
+ $size = filesize($path);
461
+ if ($size > 0 && $formatted) {
462
+ $size = (int) $size;
463
+ $label = 'b';
464
+ $format = "%s%s";
465
+ //Format file size
466
+ if ($size >= 1024 && $size < 102400) {
467
+ $label = 'kb';
468
+ $size = intval($size/1024);
469
+ }
470
+ elseif ($size >= 102400) {
471
+ $label = 'mb';
472
+ $size = round(($size/1024)/1024, 1);
473
+ }
474
+ $size = sprintf($format, $size, $label);
475
+ }
476
+
477
+ return $size;
478
+ }
479
+
480
+ /**
481
+ * Prints the attachment's filesize
482
+ * @param obj|int $post (optional) Attachment object or ID (uses global $post object if parameter not provided)
483
+ * @param bool $formatted (optional) Whether or not filesize should be formatted (kb/mb, etc.) (Default: TRUE)
484
+ */
485
+ function the_attachment_filesize($post = null, $formatted = true) {
486
+ echo $this->get_attachment_filesize($post, $formatted);
487
+ }
488
+
489
+ /**
490
+ * Build output for media item
491
+ * Based on media type and output type parameter
492
+ * @param int|obj $media Media object or ID
493
+ * @param string $type (optional) Output type (Default: source URL)
494
+ * @return string Media output
495
+ */
496
+ function get_media_output($media, $type = 'url', $attr = array()) {
497
+ $ret = '';
498
+ $media =& get_post($media);
499
+ //Continue processing valid media items
500
+ if ( $this->is_media($media) ) {
501
+ //URL - Same for all attachments
502
+ if ( 'url' == $type ) {
503
+ $ret = wp_get_attachment_url($media->ID);
504
+ } elseif ( 'link' == $type ) {
505
+ $ret = $this->get_link($media, $attr);
506
+ } else {
507
+ //Determine media type
508
+ $mime = get_post_mime_type($media);
509
+ $mime_main = substr($mime, 0, strpos($mime, '/'));
510
+
511
+ //Pass to handler for media type + output type
512
+ $handler = implode('_', array('get', $mime_main, 'output'));
513
+ if ( method_exists($this, $handler))
514
+ $ret = $this->{$handler}($media, $type, $attr);
515
+ else {
516
+ //Default output if no handler exists
517
+ $ret = $this->get_image_output($media, $type, $attr);
518
+ }
519
+ }
520
+ }
521
+
522
+
523
+ return apply_filters($this->add_prefix('get_media_output'), $ret, $media, $type);
524
+ }
525
+
526
+ /**
527
+ * Build HTML for displaying media
528
+ * Output based on media type (image, video, etc.)
529
+ * @param int|obj $media (Media object or ID)
530
+ * @return string HTML for media
531
+ */
532
+ function get_media_html($media) {
533
+ $out = '';
534
+ return $out;
535
+ }
536
+
537
+ function get_link($media, $attr = array()) {
538
+ $ret = '';
539
+ $media =& get_post($media);
540
+ if ( $this->is_media($media) ) {
541
+ $attr['href'] = wp_get_attachment_url($media->ID);
542
+ $text = ( isset($attr['text']) ) ? $attr['text'] : basename($attr['href']);
543
+ unset($attr['text']);
544
+ //Build attribute string
545
+ $attr = wp_parse_args($attr, array('href' => ''));
546
+ $attr_string = $this->util->build_attribute_string($attr);
547
+ $ret = '<a ' . $attr_string . '>' . $text . '</a>';
548
+ }
549
+ return $ret;
550
+ }
551
+
552
+ /**
553
+ * Builds output for image attachments
554
+ * @param int|obj $media Media object or ID
555
+ * @param string $type Output type
556
+ * @return string Image output
557
+ */
558
+ function get_image_output($media, $type = 'html', $attr = array()) {
559
+ $ret = '';
560
+ $icon = !wp_attachment_is_image($media->ID);
561
+
562
+ //Get image properties
563
+ $attr = wp_parse_args($attr, array('alt' => trim(strip_tags( $media->post_excerpt ))));
564
+ list($attr['src'], $attribs['width'], $attribs['height']) = wp_get_attachment_image_src($media->ID, '', $icon);
565
+
566
+ switch ( $type ) {
567
+ case 'html' :
568
+ //Remove attributes that must not be empty
569
+ $attr_nonempty = array('id');
570
+ foreach ( $attr_nonempty as $key ) {
571
+ if ( !isset($attr[$key]) || empty($attr[$key]) )
572
+ unset($attr[$key]);
573
+ }
574
+ $attr_str = $this->util->build_attribute_string($attr);
575
+ $ret = '<img ' . $attr_str . ' />';
576
+ break;
577
+ }
578
+
579
+ return $ret;
580
+ }
581
+
582
+ /**
583
+ * Build HTML IMG element of an Image
584
+ * @param array $image Array of image properties
585
+ * 0: Source URI
586
+ * 1: Width
587
+ * 2: Height
588
+ * @return string HTML IMG element of specified image
589
+ */
590
+ function get_image_html($image, $attributes = '') {
591
+ $ret = '';
592
+ if (is_array($image) && count($image) >= 3) {
593
+ //Build attribute string
594
+ if (is_array($attributes)) {
595
+ $attribs = '';
596
+ $attr_format = '%s="%s"';
597
+ foreach ($attributes as $attr => $val) {
598
+ $attribs .= sprintf($attr_format, $attr, esc_attr($val));
599
+ }
600
+ $attributes = $attribs;
601
+ }
602
+ $format = '<img src="%1$s" width="%2$d" height="%3$d" ' . $attributes . ' />';
603
+ $ret = sprintf($format, $image[0], $image[1], $image[2]);
604
+ }
605
+ return $ret;
606
+ }
607
+
608
+ /**
609
+ * Registers admin menus for content types
610
+ * @param CNR_Content_Type $type Content Type Instance
611
+ *
612
+ * @global CNR_Content_Utilities $cnr_content_utilities
613
+ */
614
+ function type_admin_menu($type) {
615
+ global $cnr_content_utilities;
616
+ $u =& $cnr_content_utilities;
617
+
618
+ //Add Menu
619
+ $parent_page = $u->get_admin_page_file($type->id);
620
+ $menu_page = $u->get_admin_page_file($type->id, 'extra');
621
+ $this->util->add_submenu_page($parent_page, __('Extra Menu'), __('Extra Menu'), 8, $menu_page, $this->m('type_admin_page'));
622
+ }
623
+
624
+ function type_admin_page() {
625
+ global $title;
626
+ ?>
627
+ <div class="wrap">
628
+ <?php screen_icon('edit'); ?>
629
+ <h2><?php esc_html_e($title); ?></h2>
630
+ <p>
631
+ This is an extra menu for a specific content type added via a plugin hook
632
+ </p>
633
+ </div>
634
+ <?php
635
+ }
636
+ }
includes/class.post.php ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package Cornerstone
4
+ * @subpackage Posts
5
+ * @author Archetyped
6
+ *
7
+ */
8
+ class CNR_Post extends CNR_Base {
9
+
10
+ /*-** Properties **-*/
11
+
12
+ /**
13
+ * Script files
14
+ * @see CNR_Base::client_files
15
+ * @var array
16
+ */
17
+ var $scripts = array (
18
+ 'posts' => array (
19
+ 'file' => 'js/lib.posts.js',
20
+ 'deps' => '[core]',
21
+ 'context' => 'admin'
22
+ ),
23
+ );
24
+
25
+ /**
26
+ * Default title separator
27
+ * @var string
28
+ */
29
+ var $title_sep = '&lsaquo;';
30
+
31
+ /*-** Initialization **-*/
32
+
33
+ function register_hooks() {
34
+ parent::register_hooks();
35
+
36
+ //Template
37
+ // add_filter('wp_title', $this->m('page_title'), 11, 3);
38
+
39
+ //Admin
40
+ add_action('admin_head', $this->m('admin_set_title'), 11);
41
+ }
42
+
43
+ /**
44
+ * Gets entire parent tree of post as an array
45
+ *
46
+ * Array order is from top level to immediate post parent
47
+ * @static
48
+ * @param object $post Post to get path for
49
+ * @param string $prop Property to retrieve from parents. If specified, array will contain only this property from parents
50
+ * @param $depth Unused
51
+ * @return array of Post Objects/Properties
52
+ */
53
+ static function get_parents($post, $prop = '', $depth = '') {
54
+ $parents = get_post_ancestors($post = get_post($post, OBJECT, ''));
55
+ if ( is_object($post) && !empty($parents) && ('id' != strtolower(trim($prop))) ) {
56
+ //Retrieve post data for parents if full data or property other than post ID is required
57
+ $args = array(
58
+ 'include' => $parents,
59
+ 'post_type' => 'any',
60
+ );
61
+ $ancestors = get_posts($args);
62
+
63
+ //Sort array in ancestor order
64
+ $temp_parents = array();
65
+ foreach ($ancestors as $ancestor) {
66
+ //Get index of ancestor
67
+ $i = array_search($ancestor->ID, $parents);
68
+ if ( false === $i )
69
+ continue;
70
+ //Insert post at index
71
+ $temp_parents[$i] = $ancestor;
72
+ }
73
+
74
+ if ( !empty($temp_parents) )
75
+ $parents = $temp_parents;
76
+ }
77
+ //Reverse Array (to put top level parent at beginning of array)
78
+ $parents = array_reverse($parents);
79
+ return $parents;
80
+ }
81
+
82
+ /**
83
+ * Get the IDs of a collection of posts
84
+ * @return array IDs of Posts passed to function
85
+ * @param array $posts Array of Post objects
86
+ */
87
+ function get_ids($posts) {
88
+ $callback = create_function('$post', 'return $post->ID;');
89
+ $arr_ids = array_map($callback, $posts);
90
+ return $arr_ids;
91
+ }
92
+
93
+ /*-** Children **-*/
94
+
95
+ /**
96
+ * Gets children posts of specified page and stores them for later use
97
+ * This method hooks into 'the_posts' filter to retrieve child posts for any single page retrieved by WP
98
+ * @param int|object $post ID or Object of Post to get children for
99
+ * @return CNR_Post_Query $posts Posts array (required by 'the_posts' filter)
100
+ *
101
+ * @global WP_Query $wp_query
102
+ */
103
+ function &get_children($post = null) {
104
+ //Global variables
105
+ global $wp_query;
106
+ $children = new CNR_Post_Query();
107
+ if ( empty($post) && !empty($wp_query->posts) )
108
+ $post = $wp_query->posts[0];
109
+
110
+ if ( is_object($post) )
111
+ $post = $post->ID;
112
+ if ( is_numeric($post) )
113
+ $post = (int) $post;
114
+ else
115
+ return $children;
116
+
117
+ //Get children posts of page
118
+ if ( $post ) {
119
+ //Set arguments to retrieve children posts of current page
120
+ $limit = ( is_feed() ) ? get_option('posts_per_rss') : get_option('posts_per_page');
121
+ $offset = ( is_paged() ) ? ( (get_query_var('paged') - 1) * $limit ) : 0;
122
+ $c_args = array(
123
+ 'post_parent' => $post,
124
+ 'numberposts' => $limit,
125
+ 'offset' => $offset
126
+ );
127
+
128
+ //Create post query object
129
+ $children->set_arg($c_args);
130
+
131
+ //Get children posts
132
+ $children->get();
133
+ }
134
+
135
+ return $children;
136
+ }
137
+
138
+ /*-** Post Metadata **-*/
139
+
140
+ /**
141
+ * Retrieves the post's section data
142
+ * @param string $data (optional) Section data to return (Default: full section object)
143
+ * Possible values:
144
+ * NULL Full section post object
145
+ * Column name Post column data (if exists)
146
+ *
147
+ * @return mixed post's section data (Default: ID value)
148
+ */
149
+ static function get_section($post = null, $data = null) {
150
+ $p = get_post($post);
151
+ $retval = 0;
152
+ if ( is_object($p) && isset($p->post_parent) )
153
+ $retval = intval($p->post_parent);
154
+
155
+ //Get specified section data for posts with valid parents
156
+ if ( $retval > 0 ) {
157
+ if ( !empty($data) ) {
158
+ $retval = get_post_field($data, $retval);
159
+ } else {
160
+ $retval = get_post($retval);
161
+ }
162
+ }
163
+
164
+ return $retval;
165
+ }
166
+
167
+ /**
168
+ * Prints the post's section data
169
+ * @uses CNR_Post::get_section()
170
+ * @param string $type (optional) Type of data to return (Default: ID)
171
+ */
172
+ static function the_section($post = null, $data = 'ID') {
173
+ if ( empty($data) )
174
+ $data = 'ID';
175
+ echo CNR_Post::get_section($post, $data);
176
+ }
177
+
178
+ /*-** Admin **-*/
179
+
180
+ function admin_set_title() {
181
+ global $post;
182
+
183
+ if ( !$post )
184
+ return false;
185
+
186
+ $obj = new stdClass();
187
+ //Section title
188
+ $sec = $this->get_section($post);
189
+ if ( $sec )
190
+ $obj->item_section = get_the_title($sec);
191
+ //Separator
192
+ $obj->title_sep = $this->page_title_get_sep();
193
+ $this->util->extend_client_object('posts', $obj, true);
194
+ }
195
+
196
+ function page_title_get_sep($pad = true) {
197
+ $sep = $this->title_sep;
198
+ if ( $pad )
199
+ $sep = ' ' . trim($sep) . ' ';
200
+ return $sep;
201
+ }
202
+
203
+ /*-** Template **-*/
204
+
205
+ /**
206
+ * Builds page title for current request
207
+ * Adds subtitle to title
208
+ * Filter called by `wp_title` hook
209
+ * @param $title
210
+ * @param $sep
211
+ * @param $seplocation
212
+ * @return string Title text
213
+ */
214
+ function page_title_get($title, $sep = '', $seplocation = '') {
215
+ global $post;
216
+
217
+ $sep = $this->page_title_get_sep();
218
+
219
+ if ( is_single() ) {
220
+ //Append section name to post title
221
+ $ptitle = get_the_title();
222
+ $ptitle_pos = ( $ptitle ) ? strpos($title, $ptitle) : false;
223
+ if ( $ptitle_pos !== false ) {
224
+ //Get section
225
+ if ( ( $sec = $this->get_section($post) ) ) {
226
+ //Append section name to post title only once
227
+ $title = substr_replace($ptitle, $ptitle . $sep . get_the_title($sec), $ptitle_pos, strlen($ptitle)) . substr($title, strlen($ptitle));
228
+ }
229
+ }
230
+ }
231
+
232
+ //Return new title
233
+ return $title;
234
+ }
235
+
236
+ /**
237
+ * Builds page title for current request
238
+ * Filter called by `wp_title` hook
239
+ * @param $title
240
+ * @param $sep
241
+ * @param $seplocation
242
+ * @return string Title text
243
+ * @uses CNR::page_title_get()
244
+ */
245
+ function page_title($title, $sep = '', $seplocation = '') {
246
+ return $this->page_title_get($title, $sep, $seplocation);
247
+ }
248
+ }
includes/class.post_query.php ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package Cornerstone
4
+ * @subpackage Posts
5
+ * @author Archetyped
6
+ *
7
+ * Represents a collection of posts in a query
8
+ * Handles navigating through post collection, reporting status, etc.
9
+ *
10
+ */
11
+ class CNR_Post_Query extends CNR_Base {
12
+
13
+ /*-** Variables **-*/
14
+
15
+ var $scripts = array (
16
+ 'posts' => array (
17
+ 'file' => 'js/lib.posts.js',
18
+ 'deps' => '[core]',
19
+ 'context' => 'admin'
20
+ ),
21
+ );
22
+
23
+ /**
24
+ * Holds posts
25
+ * @var array
26
+ */
27
+ var $posts;
28
+
29
+ /**
30
+ * IDs of posts in $posts
31
+ * @var array
32
+ */
33
+ var $post_ids;
34
+
35
+ /**
36
+ * whether or not object contains posts
37
+ * @var bool
38
+ */
39
+ var $has;
40
+
41
+ /**
42
+ * Index of post in current iteration
43
+ * @var int
44
+ */
45
+ var $current;
46
+
47
+ /**
48
+ * Total number of posts in object
49
+ * @var int
50
+ */
51
+ var $count;
52
+
53
+ /**
54
+ * Total number of matching posts found in DB
55
+ * All found posts may not have been returned in current query though (paging, etc.)
56
+ * @var int
57
+ */
58
+ var $found = 0;
59
+
60
+ /**
61
+ * Query arguments
62
+ * @var array
63
+ */
64
+ var $args;
65
+
66
+ /**
67
+ * Argument to be used during query to identify request
68
+ * Prefix added during init
69
+ * @see init()
70
+ * @var string
71
+ */
72
+ var $arg_fetch = 'fetch';
73
+
74
+ /**
75
+ * TRUE if posts have been fetched, FALSE otherwise
76
+ * @var bool
77
+ */
78
+ var $fetched;
79
+
80
+ function __construct( $args = null ) {
81
+ parent::__construct();
82
+
83
+ parent::init();
84
+ //Init properties
85
+ $this->init();
86
+
87
+ //Set arguments
88
+ if ( !empty($args) && is_array($args) ) {
89
+ $this->args = wp_parse_args($args, $this->args);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Initializes object properties with default values
95
+ * @return void
96
+ */
97
+ function init() {
98
+ $this->posts = array();
99
+ $this->post_ids = array();
100
+ $this->has = false;
101
+ $this->current = -1;
102
+ $this->count = 0;
103
+ $this->found = 0;
104
+ $this->arg_fetch = $this->add_prefix($this->arg_fetch);
105
+ $this->args = array($this->arg_fetch => true, $this->get_prefix() => true);
106
+ $this->fetched = false;
107
+ }
108
+
109
+ /**
110
+ * Set argument value
111
+ * @param string $arg Argument name
112
+ * @param mixed $value Argument value
113
+ */
114
+ function set_arg($arg, $value = null) {
115
+ if ( is_scalar($arg) ) { //Single argument (key/value) pair
116
+ $this->args[$arg] = $value;
117
+ } elseif ( is_array($arg) ) { //Multiple arguments
118
+ $this->args = wp_parse_args($arg, $this->args);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Retrieve argument value
124
+ * @param string $arg Argument name
125
+ * @return mixed Argument value
126
+ */
127
+ function get_arg($arg) {
128
+ return ( $this->arg_isset($arg) ) ? $this->args[$arg] : null;
129
+ }
130
+
131
+ /**
132
+ * Checks if an argument is set in the object
133
+ * @param string $arg Argument name
134
+ * @return bool TRUE if argument is set, FALSE otherwise
135
+ */
136
+ function arg_isset($arg) {
137
+ return ( isset($this->args[$arg]) );
138
+ }
139
+
140
+ /**
141
+ * Gets posts matching parameters and stores them in object
142
+ *
143
+ * @param int $limit (optional) Maximum number of posts to retrieve (Default: -1 = All matching posts)
144
+ * @param array $args (optional) Additional arguments to use in post query
145
+ * @return array Retrieved posts
146
+ */
147
+ function get( $limit = null, $args = null ) {
148
+ //Global variables
149
+ global $wp_query;
150
+
151
+ //Clear previously retrieved post data
152
+ $this->unload();
153
+
154
+ //Determine section
155
+ $p_arg = 'post_parent';
156
+ if ( ! $this->arg_isset($p_arg) ) {
157
+ $parent = null;
158
+ if ( is_page() ) {
159
+ $parent = $wp_query->get_queried_object_id();
160
+ }
161
+ if ( !! $parent )
162
+ $this->set_arg($p_arg, $parent);
163
+ }
164
+
165
+ //Check if parent is valid post ID
166
+ if ((int)$parent < 1) {
167
+ //Get featured posts from all sections if no valid parent is set
168
+ $parent = null;
169
+ }
170
+
171
+ //Set query args
172
+ if ( !empty($args) )
173
+ $this->args = wp_parse_args($args, $this->args);
174
+ //Set post limit
175
+ if ( is_numeric($limit) )
176
+ $limit = intval($limit);
177
+ if ( ! $limit && !$this->arg_isset('numberposts') )
178
+ $limit = ( is_feed() ) ? get_option('posts_per_rss') : get_option('posts_per_page');
179
+ if ( $limit > 0 )
180
+ $this->set_arg('numberposts', $limit);
181
+
182
+ //Set offset (pagination)
183
+ if ( !is_feed() ) {
184
+ $c_page = $wp_query->get('paged');
185
+ $offset = ( $c_page > 0 ) ? $c_page - 1 : 0;
186
+ $offset = $limit * $offset;
187
+ $this->set_arg('offset', $offset);
188
+ }
189
+
190
+ //Retrieve posts
191
+ $filter = 'found_posts';
192
+ $f_callback = $this->m('set_found');
193
+ $action = 'parse_query';
194
+ $a_callback = $this->m('set_found_flag');
195
+
196
+ //Add filter to populate found property during query
197
+ add_filter($filter, $f_callback);
198
+ add_action($action, $a_callback);
199
+ //Get posts
200
+ $posts = get_posts($this->args);
201
+ //Remove filter after query has completed
202
+ remove_action($action, $a_callback);
203
+ remove_filter($filter, $f_callback);
204
+ //Save retrieved posts to array
205
+ $this->load($posts);
206
+ //Return retrieved posts so that array may be manipulated further if desired
207
+ return $this->posts;
208
+ }
209
+
210
+ /**
211
+ * Sets object properties with post data
212
+ *
213
+ * @param array $posts Array of post objects
214
+ * @return void
215
+ */
216
+ function load( $posts = null ) {
217
+ $this->fetched = true;
218
+ if ( !empty($posts) ) {
219
+ $this->posts = $posts;
220
+ $this->has = true;
221
+ $this->count = count($this->posts);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Resets object properties to allow for new data to be saved
227
+ * @return void
228
+ */
229
+ function unload() {
230
+ //Temporarily save properties that should persist
231
+ $_args = $this->args;
232
+
233
+ //Initialize object properties
234
+ $this->init();
235
+
236
+ //Restore persistent properties
237
+ $this->args = $_args;
238
+ }
239
+
240
+ /**
241
+ * Sets number of found posts in object's query
242
+ * @link `found_posts` hook
243
+ * @see WP_Query::get_posts()
244
+ * @param int $num_found
245
+ */
246
+ function set_found($num_found) {
247
+ $this->found = $num_found;
248
+ }
249
+
250
+ /**
251
+ * Modifies query parameters to allow `found_posts` hook to be called
252
+ * Unsets `no_found_rows` query parameter set in WP 3.1
253
+ * @see WP_Query::parse_query()
254
+ * @link `parse_query` action hook
255
+ * @param WP_Query $q Query instance object
256
+ */
257
+ function set_found_flag(&$q) {
258
+ if ( isset($q->query_vars[$this->arg_fetch]) ) {
259
+ $q->query_vars['no_found_rows'] = false;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Makes sure query was run prior
265
+ * @return void
266
+ */
267
+ function confirm_fetched() {
268
+ if ( !$this->fetched )
269
+ $this->get();
270
+ }
271
+
272
+ /**
273
+ * Returns number of matching posts found in DB
274
+ * May not necessarily match number of posts contained in object (due to post limits, pagination, etc.)
275
+ * @return int Number of posts found
276
+ */
277
+ function found() {
278
+ $this->confirm_fetched();
279
+ return $this->found;
280
+ }
281
+
282
+ /**
283
+ * Checks whether posts related to this object are available in the current context
284
+ *
285
+ * If no accessible posts are found, current post (section) is set as global post variable
286
+ *
287
+ * @see 'the_posts' filter
288
+ * @see get_children()
289
+ *
290
+ * @param bool $fetch Whether posts should be fetched if they have not yet been retrieved
291
+ * @return boolean TRUE if section contains children, FALSE otherwise
292
+ * Note: Will also return FALSE if section contains children, but all children have been previously accessed
293
+ *
294
+ * @global WP_Query $wp_query
295
+ * @global obj $post
296
+ */
297
+ function has( $fetch = true ) {
298
+ global $wp_query, $post, $more;
299
+
300
+ $this->confirm_fetched();
301
+
302
+ //Check if any posts on current page were retrieved
303
+ //If posts are found, make sure there are more posts
304
+ if ( $this->count > 0 && ( $this->current < $this->count - 1 ) ) {
305
+ return true;
306
+ }
307
+
308
+ //Reset current post position if all posts have been processed
309
+ $this->rewind();
310
+ $wp_query->in_the_loop = false;
311
+ //If no posts were found (or the last post has been previously loaded),
312
+ //load previous post back into global post variable
313
+ $i = ( $wp_query->current_post >= 0 ) ? $wp_query->current_post : 0;
314
+ if ( count($wp_query->posts) ) {
315
+ $post = $wp_query->posts[ $i ];
316
+ setup_postdata($post);
317
+ }
318
+
319
+ if ( is_single() || is_page() )
320
+ $more = 1;
321
+ return false;
322
+ }
323
+
324
+ /**
325
+ * Loads next post into global $post variable for use in the loop
326
+ * Allows use of WP template tags
327
+ * @return void
328
+ *
329
+ * @global obj $post Post object
330
+ */
331
+ function next() {
332
+ global $post, $more, $wp_query;
333
+
334
+ if ( $this->has() ) {
335
+ $wp_query->in_the_loop = true;
336
+ //Increment post position
337
+ $this->current++;
338
+
339
+ //Load post into global post variable
340
+ $post = $this->posts[ $this->current ];
341
+
342
+ setup_postdata($post);
343
+ $more = 0;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Resets position of current post
349
+ * Allows for multiple loops over $posts array
350
+ * @return void
351
+ */
352
+ function rewind() {
353
+ $this->current = -1;
354
+ }
355
+
356
+ /**
357
+ * Gets index of current post
358
+ * @return int Index position of current post
359
+ */
360
+ function current() {
361
+ return $this->current;
362
+ }
363
+
364
+ /**
365
+ * Returns number of posts in object
366
+ * @return int number of posts
367
+ */
368
+ function count() {
369
+ $this->confirm_fetched();
370
+ return $this->count;
371
+ }
372
+
373
+ /**
374
+ * Gets the number of pages needed to list all found posts
375
+ * @return int Total number of pages
376
+ */
377
+ function max_num_pages() {
378
+ $this->confirm_fetched();
379
+ $posts_per_page = $this->get_arg('numberposts');
380
+ if ( ! $posts_per_page )
381
+ $posts_per_page = get_option('posts_per_page');
382
+ return ceil( $this->found / $posts_per_page );
383
+ }
384
+
385
+ /**
386
+ * Checks if current post is the first post
387
+ * @return bool TRUE if current post is the first post in array, FALSE otherwise
388
+ */
389
+ function is_first() {
390
+ $this->confirm_fetched();
391
+ return ( 0 == $this->current() );
392
+ }
393
+
394
+ /**
395
+ * Checks if current featured post is the last item in the post array
396
+ * @return bool TRUE if item is the last featured item, FALSE otherwise
397
+ */
398
+ function is_last() {
399
+ $this->confirm_fetched();
400
+ return ($this->current == $this->count - 1) ? true : false;
401
+ }
402
+
403
+ /**
404
+ * @param int $post (optional) ID of post to check for existence in the object's posts array (uses global $post object if no value passed)
405
+ * @return bool TRUE if post is in posts array
406
+ */
407
+ function contains( $post = null ) {
408
+ $this->confirm_fetched();
409
+ //Use argument value if it is an integer
410
+ if ( is_numeric($post) && intval($post) > 0 ) {
411
+ //Cast to object and set ID property (for later use)
412
+ $post = (object) $post;
413
+ $post->ID = $post->scalar;
414
+ }
415
+ //Otherwise check if argument is valid post
416
+ elseif ( !$this->util->check_post($post) ) {
417
+ return false;
418
+ }
419
+
420
+ //Check for existence of post ID in posts array
421
+ return in_array($post->ID, $this->get_ids());
422
+ }
423
+
424
+ /**
425
+ * Retrieve IDs of all retrieved posts
426
+ */
427
+ function get_ids() {
428
+ $this->confirm_fetched();
429
+
430
+ if ( $this->has && empty($this->post_ids) ) {
431
+ //Build array of post ids in array
432
+ foreach ($this->posts as $post) {
433
+ $this->post_ids[] = $post->ID;
434
+ }
435
+ }
436
+
437
+ return $this->post_ids;
438
+ }
439
+ }
includes/class.structure.php ADDED
@@ -0,0 +1,815 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Core properties/methods for Media management
5
+ * @package Cornerstone
6
+ * @subpackage Media
7
+ * @author Archetyped
8
+ * @uses CNR_Post
9
+ */
10
+ class CNR_Structure extends CNR_Base {
11
+
12
+ /* Properties */
13
+
14
+ /**
15
+ * Postname token
16
+ * @var string
17
+ */
18
+ var $tok_post = '%postname%';
19
+
20
+ /**
21
+ * Post Path token
22
+ * @var string
23
+ */
24
+ var $tok_path = '%postpath%';
25
+
26
+ /**
27
+ * Custom post permalink structure
28
+ * @var string
29
+ */
30
+ var $permalink_structure = null;
31
+
32
+ /**
33
+ * ID of old permalink structure
34
+ * Used as base of meta key
35
+ * @see __construct() Value prefixed
36
+ * @var string
37
+ */
38
+ var $permalink_structure_old = 'permastruct_old';
39
+
40
+ /**
41
+ * Name of field used for setting post parent
42
+ * Prefix added in constructor
43
+ * @var string
44
+ */
45
+ var $field_parent = 'post_parent';
46
+
47
+ /**
48
+ * Name of post being created/edited
49
+ * @var string
50
+ */
51
+ var $data_insert_post_name = null;
52
+
53
+ /**
54
+ * Indicates whether process is currently occurring
55
+ * Used to determine whether to skip hook handlers, etc.
56
+ * @var bool
57
+ */
58
+ var $status_processing = false;
59
+
60
+ /**
61
+ * Column name on posts management page
62
+ * @var string
63
+ */
64
+ var $management_column = array( 'name' => 'section', 'title' => 'Section' );
65
+
66
+ /**
67
+ * Client scripts
68
+ * @var array
69
+ */
70
+ var $scripts = array(
71
+ 'structure' => array (
72
+ 'file' => 'js/lib.structure.js',
73
+ 'deps' => array('jquery', '[core]'),
74
+ 'context' => 'admin'
75
+ ),
76
+ 'structure_permalink' => array (
77
+ 'file' => 'js/lib.structure.admin.js',
78
+ 'deps' => array('jquery', '[admin]','[structure]'),
79
+ 'context' => 'admin_page_options-permalink'
80
+ )
81
+ );
82
+
83
+ /* Constructor */
84
+
85
+ /**
86
+ * Constructor
87
+ */
88
+ function __construct() {
89
+ parent::__construct();
90
+ $this->add_prefix_ref($this->field_parent);
91
+ $this->add_prefix_ref($this->permalink_structure_old);
92
+ $this->permalink_structure = $this->util->normalize_path($this->tok_path, $this->tok_post, array(true, true));
93
+ $this->add_prefix_ref($this->management_column['name']);
94
+ }
95
+
96
+ /* Methods */
97
+
98
+ function register_hooks() {
99
+ parent::register_hooks();
100
+
101
+ //Rewrite Rules
102
+ add_filter('rewrite_rules_array', $this->m('rewrite_rules_array'));
103
+
104
+ //Request
105
+ add_filter('post_rewrite_rules', $this->m('post_rewrite_rules'));
106
+
107
+ //Query
108
+ add_action('pre_get_posts', $this->m('pre_get_posts'));
109
+
110
+ //Permalink
111
+ add_filter('post_link', $this->m('post_link'), 10, 3);
112
+ add_filter('post_type_link', $this->m('post_link'), 10, 4);
113
+
114
+ //Navigation
115
+ add_filter('wp_nav_menu_objects', $this->m('nav_menu_objects'), 10, 2);
116
+
117
+ //Admin
118
+ add_action('admin_print_scripts', $this->m('admin_print_scripts'), 30);
119
+ add_action('update_option_permalink_structure', $this->m('update_permastruct'), 10, 3);
120
+ //Edit
121
+ //Add meta boxes
122
+ add_action('do_meta_boxes', $this->m('admin_post_sidebar'), 1, 3);
123
+ //Process post creation/updating
124
+ add_filter('wp_insert_post_data', $this->m('admin_post_insert_data'), 10, 2);
125
+ add_action('save_post', $this->m('admin_post_save'), 10, 2);
126
+ add_action('delete_post', $this->m('admin_post_delete'), 10, 1);
127
+ //Management
128
+ add_action('restrict_manage_posts', $this->m('admin_restrict_manage_posts'));
129
+ add_action('parse_request', $this->m('admin_manage_posts_filter_section'));
130
+ add_filter('manage_posts_columns', $this->m('admin_manage_posts_columns'));
131
+ add_action('manage_posts_custom_column', $this->m('admin_manage_posts_custom_column'), 10, 2);
132
+ add_action('quick_edit_custom_box', $this->m('admin_quick_edit_custom_box'), 10, 2);
133
+ add_action('bulk_edit_custom_box', $this->m('admin_bulk_edit_custom_box'), 10, 2);
134
+ }
135
+
136
+ /**
137
+ * Flushes rewrite rules
138
+ */
139
+ function reset() {
140
+ global $wp_rewrite;
141
+ //Rebuild URL Rewrite rules
142
+ $wp_rewrite->flush_rules();
143
+ }
144
+
145
+ /**
146
+ * Plugin activation routines
147
+ * @global WP_Rewrite $wp_rewrite
148
+ */
149
+ function activate() {
150
+ global $wp_rewrite;
151
+ //Update permalink structure
152
+ $struct = get_option($this->permalink_structure_old, null);
153
+ if ( $struct )
154
+ $wp_rewrite->set_permalink_structure($this->permalink_structure);
155
+ else {
156
+ delete_option($this->permalink_structure_old);
157
+ }
158
+ $this->reset();
159
+ }
160
+
161
+ /**
162
+ * Plugin deactivation routines
163
+ * @uses WP_Rewrite $wp_rewrite to Update permalink structure
164
+ */
165
+ function deactivate() {
166
+ //Reset permalink structure
167
+ if ( $this->using_post_permastruct() ) {
168
+ global $wp_rewrite;
169
+ $permastruct = get_option($this->permalink_structure_old, '');
170
+ $wp_rewrite->set_permalink_structure($permastruct);
171
+ //Save permastruct setting
172
+ update_option($this->permalink_structure_old, true);
173
+ } else {
174
+ delete_option($this->permalink_structure_old);
175
+ }
176
+ //Flush Rules
177
+ $this->reset();
178
+ }
179
+
180
+ /**
181
+ * Returns formatted query variable for use in post requests
182
+ * @return string Custom query variable
183
+ *
184
+ * @global WP_Rewrite $wp_rewrite
185
+ */
186
+ function get_query_var() {
187
+ static $qvar = '';
188
+
189
+ //Retrieve query var used for page queries
190
+ if ( empty($qvar) ) {
191
+ global $wp_rewrite;
192
+ //Get page permastruct
193
+ $page_tag = $wp_rewrite->get_page_permastruct();
194
+
195
+ //Extract tag for page
196
+ $page_tag = str_replace($wp_rewrite->index, '', $page_tag);
197
+
198
+ //Get query var for tag
199
+ if ( ($idx = array_search($page_tag, $wp_rewrite->rewritecode)) !== false ) {
200
+ $qvar = trim($wp_rewrite->queryreplace[$idx], '=');
201
+ }
202
+ }
203
+
204
+ return $qvar;
205
+ }
206
+
207
+ /**
208
+ * Checks if custom permalink structure is currently in use
209
+ * @return bool TRUE if custom permalink structure is in use, FALSE otherwise
210
+ *
211
+ * @global WP_Rewrite $wp_rewrite
212
+ */
213
+ function using_post_permastruct() {
214
+ global $wp_rewrite;
215
+ return ( $wp_rewrite->using_permalinks() && get_option('permalink_structure') == $this->permalink_structure );
216
+ }
217
+
218
+ /**
219
+ * Save old permastruct when custom one set
220
+ * @uses `update_option_permalink_structure` Action hook
221
+ * @see update_option()
222
+ * @see register_hooks() Initialized
223
+ * @param string $option Option name
224
+ * @param string $old Previous permalink structure value
225
+ * @param string $new New permalink structure value
226
+ * @return void
227
+ */
228
+ function update_permastruct($old, $new) {
229
+ //Validate request
230
+ if ( $old == $new || ( $new != $this->permalink_structure && $old != $this->permalink_structure ) )
231
+ return false;
232
+ //Save old permastruct
233
+ if ( $new == $this->permalink_structure)
234
+ update_option($this->permalink_structure_old, $old);
235
+ elseif ( $old == $this->permalink_structure )
236
+ delete_option($this->permalink_structure_old);
237
+ }
238
+
239
+ /**
240
+ * Returns path to post based on site structure
241
+ * @return string Path to post enclosed in '/' (forward slashes)
242
+ * Example: /path/to/post/
243
+ * @param object $post Post object
244
+ */
245
+ function get_path($post, $sample = false) {
246
+ //Get post parents
247
+ $parents = CNR_Post::get_parents($post);
248
+ $sep = '/';
249
+ $path = $sep;
250
+ //Modify parent for sample permalink request
251
+ if ( $sample ) {
252
+ $this->util->check_post($post);
253
+ $parent = get_post_meta($post->ID, $this->get_parent_meta_key(), true);
254
+
255
+ if ( $parent && $parent = get_post($parent) ) {
256
+ if ( !empty($parents) ) {
257
+ //Remove last element
258
+ array_pop($parents);
259
+ }
260
+ //Add new parent
261
+ $parents[] = $parent;
262
+ }
263
+ }
264
+ //Build path
265
+ foreach ($parents as $post_parent) {
266
+ $path .= $post_parent->post_name . $sep;
267
+ }
268
+ return $path;
269
+ }
270
+
271
+ /**
272
+ * Modifies post permalink to reflect position of post in site structure
273
+ * Example: baseurl/section-name/post-name/
274
+ *
275
+ * @param string $permalink Current permalink url for post
276
+ * @param object|int $post Post object or Post ID
277
+ * @param bool $leavename Whether to leave post name
278
+ * @return string
279
+ *
280
+ * @global WP_Rewrite $wp_rewrite
281
+ * @global WP_Query $wp_query
282
+ *
283
+ * @see get_permalink
284
+ * @see get_post_permalink
285
+ *
286
+ * @todo Enable redirect_canonical functionality
287
+ *
288
+ */
289
+ function post_link($permalink, $post, $leavename = false, $sample = false) {
290
+ global $wp_query;
291
+
292
+ /* Stop processing immediately if:
293
+ * Custom permalink structure is not activated by user
294
+ * Post data is not a valid post
295
+ * Post has no name (e.g. drafts)
296
+ * Custom post type NOT in a section
297
+ */
298
+ if ( !$this->using_post_permastruct()
299
+ || ( !$this->util->check_post($post) )
300
+ || ( empty($post->post_name) && empty($post->post_title) )
301
+ || ( 'draft' == $post->post_status && empty($post->post_name) && !$sample )
302
+ || ( 'inherit' == $post->post_status )
303
+ || ( !in_array($post->post_type, array('post', 'page', 'attachment', 'revision', 'nav_menu')) && empty($post->post_parent) ) )
304
+ return $permalink;
305
+
306
+ //Get base data
307
+ $base = get_bloginfo('url');
308
+ $name = '';
309
+
310
+ //Set mode
311
+ $sample = ( isset($post->filter) && 'sample' == $post->filter ) ? true : $sample;
312
+
313
+ //Get post slug
314
+
315
+ //Sample
316
+ if ( $sample ) {
317
+ /**
318
+ * Sample permalink
319
+ * Use permalink placeholder if sample permalink is being generated
320
+ * @see get_sample_permalink()
321
+ */
322
+ $name = '%postname%';
323
+
324
+ //Remove temporary parent if not valid for current request
325
+ if ( !defined('DOING_AJAX') || !DOING_AJAX || !isset($_POST['action']) || 'sample-permalink' != $_POST['action'] ) {
326
+ delete_post_meta($post->ID, $this->get_parent_meta_key());
327
+ }
328
+ }
329
+ //Use existing name
330
+ elseif ( !empty($post->post_name) ) {
331
+ $name = $post->post_name;
332
+ }
333
+ //Build name from title (if not yet set)
334
+ if ( empty($name) ) {
335
+ $post->post_status = 'publish';
336
+ $name = sanitize_title($name ? $name : $post->post_title, $post->ID);
337
+ $name = wp_unique_post_slug($name, $post->ID, $post->post_status, $post->post_type, $post->post_parent);
338
+ }
339
+
340
+ //Get post path
341
+ $path = $this->get_path($post, $sample);
342
+
343
+ //Set permalink (Add trailing slash)
344
+ $permalink = trailingslashit($base . $path . $name);
345
+
346
+ return $permalink;
347
+ }
348
+
349
+ /**
350
+ * Resets certain query properties before post retrieval
351
+ * Checks if request is for a post (using value from pagename query var) and adjusts query to retrieve the post instead of a page
352
+ * @return void
353
+ * @param WP_Query $q Reference to global <tt>$wp_query</tt> variable
354
+ *
355
+ * @global wpdb $wpdb
356
+ */
357
+ function pre_get_posts($q) {
358
+ //Do not process query if custom post permastruct is not in use
359
+ if ( !$this->using_post_permastruct() )
360
+ return;
361
+ $qvar = $this->get_query_var();
362
+ $qv =& $q->query_vars;
363
+
364
+ //Stop processing if custom query variable is not present in current query
365
+ if ( empty($qvar) || !isset($qv[$qvar]) || empty($qv[$qvar]) ) {
366
+ return;
367
+ }
368
+ global $wpdb;
369
+
370
+ $qval = $qv[$qvar];
371
+
372
+ //Get last segment
373
+ $slug = array_reverse( explode('/', $qval) );
374
+ if ( is_array($slug) && !empty($slug) )
375
+ $slug = $slug[0];
376
+ else
377
+ return;
378
+
379
+ //Determine if query is for page or post
380
+ $type = $wpdb->get_var($wpdb->prepare("SELECT post_type FROM $wpdb->posts WHERE post_name = %s LIMIT 1", $slug));
381
+ if ( empty($type) )
382
+ return;
383
+
384
+ //Adjust query if requested item is not a page
385
+ if ( 'page' != $type ) {
386
+ $new_var = 'name';
387
+ $qval = $slug;
388
+ //Set new query var
389
+ $qv[$new_var] = $qval;
390
+ unset($qv[$qvar]);
391
+ //Set post type
392
+ $qv['post_type'] = $type;
393
+ //Reparse query variables
394
+ $q->parse_query($qv);
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Modifies post rewrite rules when using custom permalink structure
400
+ * Removes all post rewrite rules since we are modifying page rewrite rules to process the request
401
+ * @param array $r Post rewrite rules from WP_Rewrite::rewrite_rules
402
+ * @return array Modified post rewrite rules
403
+ */
404
+ function post_rewrite_rules($r) {
405
+ if ( $this->using_post_permastruct() )
406
+ $r = array();
407
+ return $r;
408
+ }
409
+
410
+ /**
411
+ * Modifies rewrite rules array
412
+ * Removes unnecessary paged permalink structure for Pages/Content Types (/pagename/[0-9]/)
413
+ * - Conflicts with /pagename/postname/ permalink structure
414
+ * @param array $rules Generated rewrite rules
415
+ * @return array Modified rewrite rules
416
+ */
417
+ function rewrite_rules_array($rules) {
418
+ $subpattern_old = '(/[0-9]+)?/?$';
419
+ $subpattern_new = '(/)?$';
420
+ $qvar = '&page=';
421
+ $rules_temp = array();
422
+
423
+ //Find rules containing subpattern
424
+ $patterns = array_keys($rules);
425
+
426
+ foreach ( $patterns as $idx => $patt ) {
427
+ $rule = '';
428
+ //Check if pattern contains subpattern
429
+ if ( strpos($patt, $subpattern_old) !== false && strpos($rules[$patt], $qvar) !== false ) {
430
+ //Generate new pattern
431
+ $rule = str_replace($subpattern_old, $subpattern_new, $patt);
432
+ } else {
433
+ $rule = $patt;
434
+ }
435
+ //Add rule to temp array
436
+ $rules_temp[$rule] = $rules[$patt];
437
+ }
438
+
439
+ //Return modified rewrite rules
440
+ return $rules_temp;
441
+ }
442
+
443
+ /**
444
+ * Performs additional processing on nav menu objects
445
+ * > Adds additional classes to menu items based on current request
446
+ * @see wp_nav_menu()
447
+ * @uses `wp_nav_menu_objects` filter hook to modify items
448
+ * @param array $menu_items Sorted menu items
449
+ * @param object $args Arguments passed to function
450
+ * @return array Menu items array
451
+ */
452
+ function nav_menu_objects($menu_items, $args) {
453
+ $class_base = 'current-page-';
454
+ $class_ancestor = $class_base . 'ancestor';
455
+ $class_parent = $class_base . 'parent';
456
+ //Get current item
457
+ if ( is_singular()
458
+ && ( $item = get_queried_object() )
459
+ && !empty($item->post_type)
460
+ && !is_post_type_hierarchical($item->post_type) //Only process non-hierarchical post types
461
+ ) {
462
+ //Get ancestors of current item
463
+ $ancestors = get_ancestors($item->ID, $item->post_type);
464
+
465
+ //Loop through menu items and add classes to ancestors of current item
466
+ foreach ( $menu_items as $key => $m_item ) {
467
+ //Only process menu items representing posts/pages
468
+ if ( isset($m_item->type) && 'post_type' == $m_item->type && in_array($m_item->object_id, $ancestors) ) {
469
+ //Add ancestor class to item
470
+ if ( !is_array($m_item->classes) )
471
+ $m_item->classes = array();
472
+ $m_item->classes[] = $class_ancestor;
473
+ //Check if menu item is parent of current item
474
+ if ( $item->post_parent == $m_item->object_id )
475
+ $m_item->classes[] = $class_parent;
476
+ //Filter duplicate classes
477
+ $m_item->classes = array_unique($m_item->classes);
478
+ //Update menu array
479
+ $menu_items[$key] = $m_item;
480
+ }
481
+ }
482
+ }
483
+
484
+ return $menu_items;
485
+ }
486
+
487
+ /**
488
+ * Sets post parent
489
+ * @param obj|int $post Post object or ID
490
+ * @param obj|int $parent Parent post object or ID
491
+ * @param bool $temp (optional) Whether parent should be temporarily set (Default: NO)
492
+ */
493
+ function set_parent($post, $parent = null, $temp = false) {
494
+ /* Validation */
495
+ //Post
496
+ if ( !$this->util->check_post($post) || ( is_null($parent) && !isset($post->post_parent) ) )
497
+ return false;
498
+ //Default parent
499
+ if ( is_null($parent) )
500
+ $parent = $post->post_parent;
501
+ //Request
502
+ if ( 0 != $parent && ( !$this->util->check_post($parent) || !isset($parent->ID) || $post->ID == $parent->ID ) )
503
+ return false;
504
+
505
+ if ( is_object($parent) ) {
506
+ $parent = $parent->ID;
507
+ }
508
+
509
+ /* Set Parent */
510
+
511
+ //Temporary
512
+ if ( $temp ) {
513
+ update_post_meta($post->ID, $this->get_parent_meta_key(), $parent);
514
+ }
515
+ //Standard
516
+ else {
517
+ //Remove temporary parent
518
+ delete_post_meta($post->ID, $this->get_parent_meta_key());
519
+ }
520
+ }
521
+
522
+ /*-** Admin **-*/
523
+
524
+ /**
525
+ * Outputs structure data to client
526
+ * @param string $page Current page
527
+ */
528
+ function admin_print_scripts() {
529
+ $out = array();
530
+ $opts = array (
531
+ 'using_permastruct' => $this->using_post_permastruct(),
532
+ 'permalink_structure' => $this->permalink_structure,
533
+ 'field_parent' => $this->field_parent
534
+ );
535
+
536
+ $out[] = $this->util->extend_client_object('structure.options', $opts);
537
+
538
+ if ( $this->util->is_context($this->scripts->structure_permalink->context) ) {
539
+ $opts_admin = array (
540
+ 'label' => __('Structured', $this->get_prefix()),
541
+ 'example' => get_option('home') . '/section-name/sample-post/'
542
+ );
543
+
544
+ $out[] = $this->util->extend_client_object('structure.admin.options', $opts_admin);
545
+ }
546
+
547
+ //Output data
548
+ echo $this->util->build_script_element(implode(PHP_EOL, $out), 'structure_options');
549
+ }
550
+
551
+ /**
552
+ * Set post parent for current post
553
+ * If custom field is present in $postarr, use value to set post's parent
554
+ * Post parent is set in WP posts table (as post_parent value) in calling function
555
+ * @see $this->admin_post_save() Saves parent as metadata for post
556
+ * @see wp_insert_post()
557
+ * @uses $this->field_parent as field name to check for
558
+ * @param array $data Post data (restricted default columns only)
559
+ * @param array $postarr Post data passed to wp_insert_post() and parsed with defaults
560
+ * @return array Modified post data
561
+ */
562
+ function admin_post_insert_data($data, $postarr) {
563
+ if ( !in_array($data['post_type'], array('revision', 'page')) && !$this->is_processing() ) {
564
+ //Check for custom field and validate value
565
+ if ( isset($postarr[$this->field_parent]) ) {
566
+ $parent_val = intval($postarr[$this->field_parent]);
567
+ //If field is set, set as parent
568
+ if ( $parent_val >= 0 )
569
+ $data['post_parent'] = $parent_val;
570
+ //Set post name
571
+ // $this->data_insert_post_name = $data['post_name'];
572
+ } else {
573
+ //If field is not set, remove metadata (if previously set)
574
+ // $this->data_insert_post_name = null;
575
+ }
576
+ }
577
+ //Return updated post data
578
+ return $data;
579
+ }
580
+
581
+ /**
582
+ * Save post parent as metadata
583
+ * Metadata used for backwards compatibility and future-proofing
584
+ * @param int $post_ID Saved post ID
585
+ * @param obj $post Saved post object
586
+ */
587
+ function admin_post_save($post_ID, $post) {
588
+ $parent = null;
589
+ $temp = false;
590
+
591
+ //Autosave
592
+ if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE && $pid = wp_is_post_autosave($post) ) {
593
+ //Get temporary parent ID
594
+ $parent = ( isset($_POST['parent_id']) ) ? $_POST['parent_id'] : 0;
595
+ //Save temporary parent as metadata
596
+ $temp = true;
597
+ $post_ID = $post->post_parent;
598
+ }
599
+
600
+ $this->set_parent($post_ID, $parent, $temp);
601
+ }
602
+
603
+ /**
604
+ * Move children of deleted section to parent
605
+ * Process only pages
606
+ * @uses $wpdb
607
+ * @param int $post_ID ID of deleted post
608
+ */
609
+ function admin_post_delete($post_ID) {
610
+ static $p = null;
611
+ //Only set if post type is page
612
+ $page = get_post($post_ID);
613
+ if ( 'page' != $page->post_type )
614
+ return false;
615
+ if ( is_null($p) ) {
616
+ //Set static var and stop processing
617
+ $p = $post_ID;
618
+ }
619
+ elseif ( $p != $post_ID ) {
620
+ //Clear static var
621
+ $p = null;
622
+ }
623
+ else {
624
+ global $wpdb;
625
+ //Move children
626
+ $page = get_post($post_ID);
627
+ $children_ids = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_parent = %d", $post_ID));
628
+ $parent_data = array( 'post_parent' => $page->post_parent );
629
+ $parent_where = array( 'post_parent' => $page->ID );
630
+ $wpdb->update($wpdb->posts, $parent_data, $parent_where);
631
+
632
+ //Clear child caches & update postmeta
633
+ foreach ( $children_ids as $child ) {
634
+ $this->set_parent($child, $page->post_parent);
635
+ clean_post_cache($child);
636
+ }
637
+
638
+ //Clear static var
639
+ $p = null;
640
+ }
641
+ }
642
+
643
+ /**
644
+ * Adds meta box for section selection on post edit form
645
+ */
646
+ function admin_post_sidebar($type, $context = null, $post = null) {
647
+ $child_types = get_post_types(array('show_ui' => true, '_builtin' => false));
648
+ $child_types[] = 'post';
649
+ $side_context = 'side';
650
+ $priority = 'high';
651
+ if ( in_array($type, $child_types) && $side_context == $context )
652
+ add_meta_box($this->add_prefix('section'), __('Section'), $this->m('admin_post_sidebar_section'), $type, $context, $priority);
653
+ }
654
+
655
+ /**
656
+ * Adds Section selection box to post sidebar
657
+ * @return void
658
+ * @param object $post Post Object
659
+ */
660
+ function admin_post_sidebar_section($post) {
661
+ ?>
662
+ <div class="<?php echo $this->add_prefix('pages_dropdown'); ?>">
663
+ <?php
664
+ wp_dropdown_pages(array('exclude_tree' => $post->ID,
665
+ 'selected' => $post->post_parent,
666
+ 'name' => $this->field_parent,
667
+ 'show_option_none' => __('- No Section -'),
668
+ 'sort_column'=> 'menu_order, post_title'));
669
+ ?>
670
+ </div>
671
+ <?php
672
+ }
673
+
674
+ /**
675
+ * Adds additional options to filter posts
676
+ */
677
+ function admin_restrict_manage_posts() {
678
+ //Add to post edit only
679
+ $section_param = $this->add_prefix('section');
680
+ if ( $this->util->is_admin_management_page() ) {
681
+ $selected = ( isset($_GET[$section_param]) && is_numeric($_GET[$section_param]) ) ? $_GET[$section_param] : 0;
682
+ //Add post statuses
683
+ $options = array('name' => $section_param,
684
+ 'selected' => $selected,
685
+ 'show_option_none' => __( 'View all sections' ),
686
+ 'sort_column' => 'menu_order, post_title');
687
+ wp_dropdown_pages($options);
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Filters posts by specified section on the Manage Posts admin page
693
+ * Hooks into 'parse_request' action
694
+ * @see WP::parse_request()
695
+ * @param array $query_vars Parsed query variables
696
+ * @return array Modified query variables
697
+ */
698
+ function admin_manage_posts_filter_section($q) {
699
+ //Determine if request is coming from manage posts admin page
700
+ $var = $this->add_prefix('section');
701
+ if ( $this->util->is_admin_management_page()
702
+ && isset($_GET[$var])
703
+ && is_numeric($_GET[$var])
704
+ ) {
705
+ $q->query_vars['post_parent'] = intval($_GET[$var]);
706
+ }
707
+ }
708
+
709
+ /**
710
+ * Modifies the columns that are displayed on the Post Management Admin Page
711
+ * @param array $columns Array of columns for displaying post data on each post's row
712
+ * @return array Modified columns array
713
+ */
714
+ function admin_manage_posts_columns($columns) {
715
+ $columns[$this->management_column['name']] = __($this->management_column['title']);
716
+ return $columns;
717
+ }
718
+
719
+ /**
720
+ * Adds section name that post belongs to in custom column on Post Management admin page
721
+ * @param string $column_name Name of current custom column
722
+ * @param int $post_id ID of current post
723
+ */
724
+ function admin_manage_posts_custom_column($column_name, $post_id) {
725
+ //Only process for specific columns (stop processing otherwise)
726
+ if ( $this->management_column['name'] != $column_name )
727
+ return false;
728
+ //Get section
729
+ $section = CNR_Post::get_section($post_id);
730
+
731
+ if ( !empty($section) ) {
732
+ echo $section->post_title;
733
+ $data = array("post_$post_id" => (object) array('post_parent' => $section->ID));
734
+ echo $this->util->build_script_element($this->util->extend_client_object('posts.data', $data));
735
+ //echo '<script type="text/javascript">postData["post_' . $post_id . '"] = {"post_parent" : ' . $section->ID . '};</script>';
736
+ } else
737
+ _e('None', 'cornerstone');
738
+ }
739
+
740
+ /**
741
+ * Adds field for Section selection on the Quick Edit form for posts
742
+ * @param string $column_name Name of custom column
743
+ * @param string $type Type of current item (post, page, etc.)
744
+ */
745
+ function admin_quick_edit_custom_box($column_name, $type, $bulk = false) {
746
+ global $post;
747
+ $child_types = get_post_types(array('show_ui' => true, '_builtin' => false));
748
+ $child_types[] = 'post';
749
+ if ( $column_name == $this->add_prefix('section') && in_array($type, $child_types) ) :
750
+ ?>
751
+ <fieldset class="inline-edit-col-right">
752
+ <div class="inline-edit-col">
753
+ <div class="inline-edit-group">
754
+ <label><span class="title">Section</span></label>
755
+ <?php
756
+ $options = array('exclude_tree' => $post->ID,
757
+ 'name' => $this->field_parent,
758
+ 'show_option_none' => __('- No Section -'),
759
+ 'option_none_value' => 0,
760
+ 'show_option_no_change' => ($bulk) ? __('- No Change -') : '',
761
+ 'sort_column' => 'menu_order, post_title');
762
+ wp_dropdown_pages($options);
763
+ ?>
764
+ </div>
765
+ </div>
766
+ </fieldset>
767
+ <?php endif;
768
+ }
769
+
770
+ /**
771
+ * Adds field for Section selection on the bulk edit form for posts
772
+ * @see admin_quick_edit_custom_box()
773
+ * @param string $column_name Name of custom column
774
+ * @param string $type Type of current item (post, page, etc.)
775
+ */
776
+ function admin_bulk_edit_custom_box($column_name, $type) {
777
+ $this->admin_quick_edit_custom_box($column_name, $type, true);
778
+ }
779
+
780
+ /* Helpers */
781
+
782
+ /**
783
+ * Return key for storing post parent metadata
784
+ * @return string Meta key value
785
+ */
786
+ function get_parent_meta_key() {
787
+ return '_' . $this->field_parent;
788
+ }
789
+
790
+ /**
791
+ * Set processing flag
792
+ * @uses status_processing
793
+ * @param bool $status (optional) Set processing status
794
+ */
795
+ function start_processing($status = true) {
796
+ $this->status_processing = !!$status;
797
+ }
798
+
799
+ /**
800
+ * Unset processing flag
801
+ * @uses start_processing() to set processing flag
802
+ */
803
+ function stop_processing() {
804
+ $this->start_processing(false);
805
+ }
806
+
807
+ /**
808
+ * Check if request is currently being processed
809
+ * @uses status_processing
810
+ * return bool Processing status
811
+ */
812
+ function is_processing() {
813
+ return !!$this->status_processing;
814
+ }
815
+ }
includes/class.utilities.php ADDED
@@ -0,0 +1,1416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Utility methods
5
+ *
6
+ * @package Cornerstone
7
+ * @subpackage Utilities
8
+ * @author Archetyped
9
+ *
10
+ */
11
+ class CNR_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
+ var $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
+ /* Constructors */
38
+
39
+ function __construct(&$obj) {
40
+ if ( is_object($obj) )
41
+ $this->parent =& $obj;
42
+ }
43
+
44
+ /**
45
+ * Returns callback array to instance method
46
+ * @param object $obj Instance object
47
+ * @param string $method Name of method
48
+ * @return array Callback array
49
+ */
50
+ function &m(&$obj, $method = '') {
51
+ if ( $obj == null && isset($this) )
52
+ $obj =& $this;
53
+ $arr = array(&$obj, $method);
54
+ return $arr;
55
+ }
56
+
57
+ /* Helper Functions */
58
+
59
+ /*-** Prefix **-*/
60
+
61
+ /**
62
+ * Get valid separator
63
+ * @param string $sep (optional) Separator supplied
64
+ * @return string Separator
65
+ */
66
+ function get_sep($sep = false) {
67
+ if ( is_null($sep) )
68
+ $sep = '';
69
+ return ( is_string($sep) ) ? $sep : '_';
70
+ }
71
+
72
+ /**
73
+ * Retrieve class prefix (with separator if set)
74
+ * @param bool|string $sep Separator to append to class prefix (Default: no separator)
75
+ * @return string Class prefix
76
+ */
77
+ function get_prefix($sep = null) {
78
+ $sep = $this->get_sep($sep);
79
+ $prefix = ( !empty($this->parent->prefix) ) ? $this->parent->prefix . $sep : '';
80
+ return $prefix;
81
+ }
82
+
83
+ /**
84
+ * Check if a string is prefixed
85
+ * @param string $text Text to check for prefix
86
+ * @param string $sep (optional) Separator used
87
+ */
88
+ function has_prefix($text, $sep = null) {
89
+ return ( !empty($text) && strpos($text, $this->get_prefix($sep)) === 0 );
90
+ }
91
+
92
+ /**
93
+ * Prepend plugin prefix to some text
94
+ * @param string $text Text to add to prefix
95
+ * @param string $sep (optional) Text used to separate prefix and text
96
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
97
+ * @return string Text with prefix prepended
98
+ */
99
+ function add_prefix($text, $sep = '_', $once = true) {
100
+ if ( $once && $this->has_prefix($text, $sep) )
101
+ return $text;
102
+ return $this->get_prefix($sep) . $text;
103
+ }
104
+
105
+ /**
106
+ * Prepend uppercased plugin prefix to some text
107
+ * @param string $text Text to add to prefix
108
+ * @param string $sep (optional) Text used to separate prefix and text
109
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
110
+ * @return string Text with prefix prepended
111
+ */
112
+ function add_prefix_uc($text, $sep = '_', $once = true) {
113
+ $args = func_get_args();
114
+ $var = call_user_func_array($this->m($this, 'add_prefix'), $args);
115
+ $pre = $this->get_prefix();
116
+ return str_replace($pre . $sep, strtoupper($pre) . $sep, $var);
117
+ }
118
+
119
+ /**
120
+ * Add prefix to variable reference
121
+ * Updates actual variable rather than return value
122
+ * @uses add_prefix() to add prefix to variable
123
+ * @param string $var Variable to add prefix to
124
+ * @param string $sep (optional) Separator text
125
+ * @param bool $once (optional) Add prefix only once
126
+ * @return void
127
+ */
128
+ function add_prefix_ref(&$var, $sep = null, $once = true) {
129
+ $args = func_get_args();
130
+ $var = call_user_func_array($this->m($this, 'add_prefix'), $args);
131
+ }
132
+
133
+ /**
134
+ * Remove prefix from specified string
135
+ * @param string $text String to remove prefix from
136
+ * @param string $sep (optional) Separator used with prefix
137
+ */
138
+ function remove_prefix($text, $sep = '_') {
139
+ if ( $this->has_prefix($text,$sep) )
140
+ $text = substr($text, strlen($this->get_prefix($sep)));
141
+ return $text;
142
+ }
143
+
144
+ /**
145
+ * Returns Database prefix for plugin-related DB Tables
146
+ * @return string Database prefix
147
+ */
148
+ function get_db_prefix() {
149
+ global $wpdb;
150
+ return $wpdb->prefix . $this->get_prefix('_');
151
+ }
152
+
153
+ /*-** Client **-*/
154
+
155
+ /**
156
+ * Parses client files array
157
+ * > Adds ID property (prefixed file key)
158
+ * > Parses and validates internal dependencies
159
+ * > Converts properties array to object
160
+ * @param array $files Files array
161
+ * @return object Client files
162
+ */
163
+ function parse_client_files($files, $type = 'scripts') {
164
+ if ( is_array($files) && !empty($files) ) {
165
+ foreach ( $files as $h => $p ) {
166
+ //Defaults
167
+ $defaults = array(
168
+ 'id' => $this->add_prefix($h),
169
+ 'file' => null,
170
+ 'deps' => array(),
171
+ 'callback' => null,
172
+ 'context' => array()
173
+ );
174
+ switch ( $type ) {
175
+ case 'styles':
176
+ $defaults['media'] = 'all';
177
+ break;
178
+ default:
179
+ $defaults['in_footer'] = false;
180
+ }
181
+
182
+ //Type Validation
183
+ foreach ( $defaults as $m => $d ) {
184
+ //Check if value requires validation
185
+ if ( !is_array($d) || !isset($p[$m]) || is_array($p[$m]) )
186
+ continue;
187
+ //Wrap value in array or destroy it
188
+ if ( is_scalar($p[$m]) )
189
+ $p[$m] = array($p[$m]);
190
+ else
191
+ unset($p[$m]);
192
+ }
193
+
194
+ $p = array_merge($defaults, $p);
195
+
196
+ //Validate file
197
+ $file =& $p['file'];
198
+
199
+ //Callback
200
+ if ( is_array($file) ) {
201
+ $file = $this->m($this->parent, array_shift($file));
202
+ if ( !is_callable($file) )
203
+ $file = null;
204
+ }
205
+
206
+ //Remove invalid files
207
+ if ( empty($file) ) {
208
+ unset($files[$h]);
209
+ continue;
210
+ }
211
+
212
+ //Validate callback
213
+ $cb =& $p['callback'];
214
+ if ( !is_null($cb) ) {
215
+ if ( is_array($cb) )
216
+ $cb = $this->m($this->parent, array_shift($cb));
217
+ if ( !is_callable($cb) )
218
+ $cb = null;
219
+ }
220
+
221
+ //Format internal dependencies
222
+ foreach ( $p['deps'] as $idx => $dep ) {
223
+ if ( substr($dep, 0, 1) == '[' && substr($dep, -1, 1) == ']' ) {
224
+ $dep = trim($dep, '[]');
225
+ $p['deps'][$idx] = $this->add_prefix($dep);
226
+ }
227
+ }
228
+
229
+ //Convert properties to object
230
+ $files[$h] = (object) $p;
231
+
232
+ unset($file, $cb);
233
+ }
234
+ }
235
+ //Cast to object before returning
236
+ if ( !is_object($files) )
237
+ $files = (object) $files;
238
+ return $files;
239
+ }
240
+
241
+ /**
242
+ * Build JS client object
243
+ * @param string (optional) $path Additional object path
244
+ * @return string Client object
245
+ */
246
+ function get_client_object($path = null) {
247
+ $obj = strtoupper($this->get_prefix());
248
+ if ( !empty($path) && is_string($path) ) {
249
+ if ( 0 !== strpos($path, '[') )
250
+ $obj .= '.';
251
+ $obj .= $path;
252
+ }
253
+ return $obj;
254
+ }
255
+
256
+ /**
257
+ * Build jQuery JS expression to add data to specified client object
258
+ * @param string $obj Name of client object (Set to root object if not a valid name)
259
+ * @param mixed $data Data to add to client object
260
+ * @param bool (optional) $out Whether or not to output code (Default: false)
261
+ * @return string JS expression to extend client object
262
+ */
263
+ function extend_client_object($obj, $data = null, $out = false) {
264
+ //Validate parameters
265
+ $args = func_get_args();
266
+ switch ( count($args) ) {
267
+ case 2:
268
+ if ( !is_scalar($args[0]) ) {
269
+ if ( is_bool($args[1]) )
270
+ $out = $args[1];
271
+ } else {
272
+ break;
273
+ }
274
+ case 1:
275
+ $data = $args[0];
276
+ $obj = null;
277
+ break;
278
+ }
279
+ //Default client object
280
+ if ( !is_string($obj) || empty($obj) )
281
+ $obj = null;
282
+ //Default data
283
+ if ( is_array($data) )
284
+ $data = (object)$data;
285
+ //Build expression
286
+ if ( empty($data) || ( empty($obj) && is_scalar($data) ) ) {
287
+ $ret = '';
288
+ } else {
289
+ $ret = array();
290
+ //Validate object(s) being extended
291
+ $c_obj = $this->get_client_object($obj);
292
+ $sep = '.';
293
+ $pos = strpos($c_obj, $sep);
294
+ $start = $offset = 0;
295
+ $objs = array();
296
+ if ( false !== $pos ) {
297
+ while ( false !== $pos ) {
298
+ $objs[] = substr($c_obj, $start, $pos);
299
+ $offset = $pos + 1;
300
+ $pos = strpos($c_obj, $sep, $offset);
301
+ }
302
+ } else {
303
+ $objs[] = $c_obj;
304
+ }
305
+ $condition = 'if ( ' . implode(' && ', $objs) . ' ) ';
306
+ $ret = $condition . '$.extend(' . $c_obj . ', ' . json_encode($data) . ');';
307
+ if ( $out )
308
+ echo $this->build_script_element($ret);
309
+ }
310
+ return $ret;
311
+ }
312
+
313
+ /**
314
+ * Build client method call
315
+ * @uses get_client_object() to generate the body of the method call
316
+ * @param string $method Method name
317
+ * @param mixed Parameters to pass to method (will be JSON-encoded)
318
+ * @return string Method call
319
+ */
320
+ function call_client_method($method, $params = null) {
321
+ if ( !$method )
322
+ return '';
323
+ $params = ( !is_null($params) ) ? json_encode($params) : '';
324
+ return $this->get_client_object($method) . '(' . $params. ');';
325
+ }
326
+
327
+ /*-** WP **-*/
328
+
329
+ /**
330
+ * Checks if $post is a valid Post object
331
+ * If $post is not valid, assigns global post object to $post (if available)
332
+ * @return bool TRUE if $post is valid object by end of function processing
333
+ * @param object $post Post object to evaluate
334
+ */
335
+ function check_post(&$post) {
336
+ if (empty($post)) {
337
+ if (isset($GLOBALS['post'])) {
338
+ $post = $GLOBALS['post'];
339
+ $GLOBALS['post'] =& $post;
340
+ }
341
+ else
342
+ return false;
343
+ }
344
+ if (is_array($post))
345
+ $post = (object) $post;
346
+ elseif (is_numeric($post))
347
+ $post = get_post($post);
348
+ if (!is_object($post))
349
+ return false;
350
+ return true;
351
+ }
352
+
353
+ /* Hooks */
354
+
355
+ function do_action($tag, $arg = '') {
356
+ do_action($this->add_prefix($tag), $arg);
357
+ }
358
+
359
+ function apply_filters($tag, $value) {
360
+ apply_filters($this->add_prefix($tag), $value);
361
+ }
362
+
363
+ function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
364
+ return add_action($this->add_prefix($tag), $function_to_add, $priority, $accepted_args);
365
+ }
366
+
367
+ function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
368
+ return add_filter($this->add_prefix(tag), $function_to_add, $priority, $accepted_args);
369
+ }
370
+
371
+ /* Meta */
372
+
373
+ /**
374
+ * Retrieves post metadata for internal methods
375
+ * Metadata set internally is wrapped in an array so it is unwrapped before returned the retrieved value
376
+ * @see get_post_meta()
377
+ * @param int $post_id Post ID
378
+ * @param string $key Name of metadata to retrieve
379
+ * @param boolean $single Whether or not to retrieve single value or not
380
+ * @return mixed Retrieved post metadata
381
+ */
382
+ function post_meta_get($post_id, $key, $single = false) {
383
+ $meta_value = get_post_meta($post_id, $this->post_meta_get_key($key), $single);
384
+ if (is_array($meta_value) && count($meta_value) == 1)
385
+ $meta_value = $meta_value[0];
386
+ return $meta_value;
387
+ }
388
+
389
+ /**
390
+ * Wraps metadata in array for storage in database
391
+ * @param mixed $meta_value Value to be set as metadata
392
+ * @return array Wrapped metadata value
393
+ */
394
+ function post_meta_prepare_value($meta_value) {
395
+ return array($meta_value);
396
+ }
397
+
398
+ /**
399
+ * Adds Metadata for a post to database
400
+ * For internal methods
401
+ * @see add_post_meta
402
+ * @param $post_id
403
+ * @param $meta_key
404
+ * @param $meta_value
405
+ * @param $unique
406
+ * @return boolean Result of operation
407
+ */
408
+ function post_meta_add($post_id, $meta_key, $meta_value, $unique = false) {
409
+ $meta_value = $this->post_meta_value_prepare($meta_value);
410
+ return add_post_meta($post_id, $meta_key, $meta_value, $unique);
411
+ }
412
+
413
+ /**
414
+ * Updates post metadata for internal data/methods
415
+ * @see update_post_meta()
416
+ * @param $post_id
417
+ * @param $meta_key
418
+ * @param $meta_value
419
+ * @param $prev_value
420
+ * @return boolean Result of operation
421
+ */
422
+ function post_meta_update($post_id, $meta_key, $meta_value, $prev_value = '') {
423
+ $meta_value = $this->post_meta_prepare_value($meta_value);
424
+ return update_post_meta($post_id, $meta_key, $meta_value, $prev_value);
425
+ }
426
+
427
+ /**
428
+ * Builds postmeta key for custom data set by plugin
429
+ * @param string $key Base key name
430
+ * @return string Formatted postmeta key
431
+ */
432
+ function post_meta_get_key($key) {
433
+ $sep = '_';
434
+ if ( strpos($key, $sep . $this->prefix) !== 0 ) {
435
+ $key_base = func_get_args();
436
+ if ( !empty($key_base) ) {
437
+ $key = array_merge((array)$this->prefix, $key_base);
438
+ return $sep . implode($sep, $key);
439
+ }
440
+ }
441
+
442
+ return $key;
443
+ }
444
+
445
+ /**
446
+ * Creates a meta key for storing post meta data
447
+ * Prefixes standard prefixed text with underscore to hide meta data on post edit forms
448
+ * @param string $text Text to use as base of meta key
449
+ * @return string Formatted meta key
450
+ */
451
+ function make_meta_key($text = '') {
452
+ return '_' . $this->add_prefix($text);
453
+ }
454
+
455
+ /*-** Request **-*/
456
+
457
+ /**
458
+ * Checks if the currently executing file matches specified file name
459
+ * @param string $filename Filename to check for
460
+ * @return bool TRUE if current page matches specified filename, FALSE otherwise
461
+ */
462
+ function is_file( $filename ) {
463
+ return ( $filename == basename( $_SERVER['SCRIPT_NAME'] ) );
464
+ }
465
+
466
+ /**
467
+ * Checks whether the current page is a management page
468
+ * @return bool TRUE if current page is a management page, FALSE otherwise
469
+ */
470
+ function is_admin_management_page() {
471
+ return ( is_admin()
472
+ && ( $this->is_file('edit.php')
473
+ || ( $this->is_file('admin.php')
474
+ && isset($_GET['page'])
475
+ && strpos($_GET['page'], $this->get_prefix()) === 0 )
476
+ )
477
+ );
478
+ }
479
+
480
+ /* Context */
481
+
482
+ /**
483
+ * Retrieve context for current request
484
+ * @return array Context
485
+ */
486
+ function get_context() {
487
+ //Context
488
+ static $ctx = null;
489
+ if ( !is_array($ctx) ) {
490
+ //Standard
491
+ $ctx = array($this->build_context());
492
+ //Action
493
+ $action = $this->get_action();
494
+ if ( !empty($action) )
495
+ $ctx[] = $this->build_context('action', $action);
496
+ //Admin page
497
+ if ( is_admin() ) {
498
+ global $pagenow;
499
+ $pg = $this->strip_file_extension($pagenow);
500
+ $ctx[] = $this->build_context('page', $pg);
501
+ if ( !empty($action) )
502
+ $ctx[] = $this->build_context('page', $pg, 'action', $action);
503
+ }
504
+ //User
505
+ $u = wp_get_current_user();
506
+ $ctx[] = $this->build_context('user', ( $u->ID ) ? 'registered' : 'guest', false);
507
+ }
508
+
509
+ return $ctx;
510
+ }
511
+
512
+ /**
513
+ * Builds context from multiple components
514
+ * Usage:
515
+ * > $prefix can be omitted and context strings can be added as needed
516
+ * > Multiple context strings may be passed to be joined together
517
+ *
518
+ * @param string (optional) $context Variable number of components to add to context
519
+ * @param bool (optional) $prefix Whether or not to prefix context with request type (public or admin) [Default: TRUE]
520
+ * @return string Context
521
+ */
522
+ function build_context($context = null, $prefix = true) {
523
+ $args = func_get_args();
524
+ //Get prefix option
525
+ if ( !empty($args) ) {
526
+ $prefix = ( is_bool($args[count($args) - 1]) ) ? array_pop($args) : true;
527
+ }
528
+
529
+ //Validate
530
+ $context = array_filter($args, 'is_string');
531
+ $sep = '_';
532
+
533
+ //Context Prefix
534
+ if ( $prefix )
535
+ array_unshift($context, ( is_admin() ) ? 'admin' : 'public' );
536
+ return implode($sep, $context);
537
+ }
538
+
539
+ /**
540
+ * Check if context exists in current request
541
+ * @param string $context Context to check for
542
+ * @return bool TRUE if context exists FALSE otherwise
543
+ */
544
+ function is_context($context) {
545
+ $ret = false;
546
+ if ( is_scalar($context) )
547
+ $context = array($context);
548
+ if ( is_array($context) && !empty($context) ) {
549
+ $ictx = array_intersect($this->get_context(), $context);
550
+ if ( !empty($ictx) )
551
+ $ret = true;
552
+ }
553
+ return $ret;
554
+ }
555
+
556
+ /**
557
+ * Joins and normalizes the slashes in the paths passed to method
558
+ * All forward/back slashes are converted to forward slashes
559
+ * Multiple path segments can be passed as additional argments
560
+ * @param string $path Path to normalize
561
+ * @param bool|array $trailing_slash (optional) Whether or not normalized path should have a trailing slash or not (Default: FALSE)
562
+ * If array is passed, first index is trailing, second is leading slash
563
+ * If multiple path segments are passed, $trailing_slash will be the LAST parameter (default value used if omitted)
564
+ */
565
+ function normalize_path($path, $trailing_slash = false) {
566
+ $sl_f = '/';
567
+ $sl_b = '\\';
568
+ $parts = func_get_args();
569
+ //Slash defaults (trailing, leading);
570
+ $slashes = array(false, true);
571
+ if ( func_num_args() > 1 ) {
572
+ //Get last argument
573
+ $arg_last = $parts[count($parts) - 1];
574
+ if ( is_bool($arg_last) ) {
575
+ $arg_last = array($arg_last);
576
+ }
577
+
578
+ if ( is_array($arg_last) && count($arg_last) > 0 && is_bool($arg_last[0]) ) {
579
+ //Remove slash paramter from args array
580
+ array_pop($parts);
581
+ //Normalize slashes options
582
+ if ( isset($arg_last[0]) )
583
+ $slashes[0] = $arg_last[0];
584
+ if ( isset($arg_last[1]) )
585
+ $slashes[1] = $arg_last[1];
586
+ }
587
+ }
588
+ //Extract to slash options local variables
589
+ list($trailing_slash, $leading_slash) = $slashes;
590
+
591
+ //Clean path segments
592
+ foreach ( $parts as $key => $part ) {
593
+ //Trim slashes/spaces
594
+ $parts[$key] = trim($part, " " . $sl_f . $sl_b);
595
+
596
+ //Verify path segment still contains value
597
+ if ( empty($parts[$key]) ) {
598
+ unset($parts[$key]);
599
+ continue;
600
+ }
601
+ }
602
+
603
+ //Join path parts together
604
+ $parts = implode($sl_b, $parts);
605
+ $parts = str_replace($sl_b, $sl_f, $parts);
606
+ //Add trailing slash (if necessary)
607
+ if ( $trailing_slash )
608
+ $parts .= $sl_f;
609
+ //Add leading slash (if necessary)
610
+ $regex = '#^.+:[\\/]#';
611
+ if ( $leading_slash && !preg_match($regex, $parts) ) {
612
+ $parts = $sl_f . $parts;
613
+ }
614
+ return $parts;
615
+ }
616
+
617
+ /**
618
+ * Returns URL of file (assumes that it is in plugin directory)
619
+ * @param string $file name of file get URL
620
+ * @return string File URL
621
+ */
622
+ function get_file_url($file) {
623
+ if ( is_string($file) && '' != trim($file) ) {
624
+ $file = str_replace(' ', '%20', $this->normalize_path($this->get_url_base(), $file));
625
+ }
626
+ return $file;
627
+ }
628
+
629
+ /**
630
+ * Returns path to plugin file
631
+ * @param string $file file name
632
+ * @return string File path
633
+ */
634
+ function get_file_path($file) {
635
+ if ( is_string($file) && '' != trim($file) ) {
636
+ $file = $this->normalize_path($this->get_path_base(), $file);
637
+ }
638
+ return $file;
639
+ }
640
+
641
+ function get_plugin_file_path($file, $trailing_slash = false) {
642
+ if ( is_string($file) && '' != trim($file) )
643
+ $file = $this->normalize_path($this->get_plugin_base(), $file, $trailing_slash);
644
+ return $file;
645
+ }
646
+
647
+ /**
648
+ * Retrieves file extension
649
+ * @param string $file file name/path
650
+ * @param bool (optional) $lowercase Whether lowercase extension should be returned (Default: TRUE)
651
+ * @return string File's extension
652
+ */
653
+ function get_file_extension($file, $lowercase = true) {
654
+ $ret = '';
655
+ $sep = '.';
656
+ if ( ( $rpos = strrpos($file, $sep) ) !== false )
657
+ $ret = substr($file, $rpos + 1);
658
+ if ( $lowercase )
659
+ $ret = strtolower($ret);
660
+ return $ret;
661
+ }
662
+
663
+ /**
664
+ * Checks if file has specified extension
665
+ * @uses get_file_extension()
666
+ * @param string $file File name/path
667
+ * @param string|array $extension File ending(s) to check $file for
668
+ * @param bool (optional) Whether check should be case senstive or not (Default: FALSE)
669
+ * @return bool TRUE if file has extension
670
+ */
671
+ function has_file_extension($file, $extension, $case_sensitive = false) {
672
+ if ( !is_array($extension) )
673
+ $extension = array(strval($extension));
674
+ if ( !$case_sensitive ) {
675
+ //Normalize extensions
676
+ $extension = array_map('strtolower', $extension);
677
+ }
678
+ return ( in_array($this->get_file_extension($file, !$case_sensitive), $extension) ) ? true : false;
679
+ }
680
+
681
+ /**
682
+ * Removes file extension from file name
683
+ * The extension is the text following the last period ('.') in the file name
684
+ * @uses get_file_extension()
685
+ * @param string $file File name
686
+ * @return string File name without extension
687
+ */
688
+ function strip_file_extension($file) {
689
+ $ext = $this->get_file_extension($file);
690
+ if ( !empty($ext) ) {
691
+ $file = substr($file, 0, (strlen($ext) + 1) * -1);
692
+ }
693
+ return $file;
694
+ }
695
+
696
+ /**
697
+ * Retrieve base URL for plugin-specific files
698
+ * @uses get_plugin_base()
699
+ * @uses normalize_path()
700
+ * @return string Base URL
701
+ */
702
+ function get_url_base() {
703
+ static $url_base = '';
704
+ if ( '' == $url_base ) {
705
+ $url_base = $this->normalize_path(plugins_url(), $this->get_plugin_base());
706
+ }
707
+ return $url_base;
708
+ }
709
+
710
+ /**
711
+ * Retrieve plugin's base path
712
+ * @uses WP_PLUGIN_DIR
713
+ * @uses get_plugin_base()
714
+ * @uses normalize_path()
715
+ * @return string Base path
716
+ */
717
+ function get_path_base() {
718
+ static $path_base = '';
719
+ if ( '' == $path_base ) {
720
+ $path_base = $this->normalize_path(WP_PLUGIN_DIR, $this->get_plugin_base());
721
+ }
722
+ return $path_base;
723
+ }
724
+
725
+ /**
726
+ * Retrieve plugin's base directory
727
+ * @uses WP_PLUGIN_DIR
728
+ * @uses normalize_path()
729
+ * @return string Base directory
730
+ */
731
+ function get_plugin_base($trim = false) {
732
+ static $plugin_dir = '';
733
+ if ( '' == $plugin_dir ) {
734
+ $plugin_dir = str_replace($this->normalize_path(WP_PLUGIN_DIR), '', $this->normalize_path(dirname(dirname(__FILE__))));
735
+ }
736
+ if ( $trim )
737
+ $plugin_dir = trim($plugin_dir, ' \/');
738
+ return $plugin_dir;
739
+ }
740
+
741
+ /**
742
+ * Retrieve plugin's base file path
743
+ * @uses get_path_base()
744
+ * @uses get_file_path()
745
+ * @return string Base file path
746
+ */
747
+ function get_plugin_base_file() {
748
+ static $file = '';
749
+ if ( empty($file) ) {
750
+ $dir = @ opendir($this->get_path_base());
751
+ if ( $dir ) {
752
+ while ( ($ftemp = readdir($dir)) !== false ) {
753
+ //Only process PHP files
754
+ $ftemp = $this->get_file_path($ftemp);
755
+ if ( !$this->has_file_extension($ftemp, 'php') || !is_readable($ftemp) )
756
+ continue;
757
+ //Check for data
758
+ $data = get_file_data($ftemp, $this->plugin_headers);
759
+ if ( !empty($data['Name']) ) {
760
+ //Set base file
761
+ $file = $ftemp;
762
+ break;
763
+ }
764
+ }
765
+ }
766
+ @closedir($dir);
767
+ }
768
+ //Return
769
+ return $file;
770
+ }
771
+
772
+ /**
773
+ * Retrieve plugin's internal name
774
+ * Internal name is used by WP core
775
+ * @uses get_plugin_base_file()
776
+ * @uses plugin_basename()
777
+ * @return string Internal plugin name
778
+ */
779
+ function get_plugin_base_name() {
780
+ static $name = false;
781
+ if ( !$name ) {
782
+ $file = $this->get_plugin_base_file();
783
+ $name = plugin_basename($file);
784
+ }
785
+ return $name;
786
+ }
787
+
788
+ /**
789
+ * Retrieve plugin info
790
+ * Parses info comment in main plugin file
791
+ * @uses get_plugin_base_file()
792
+ */
793
+ function get_plugin_info($field = '') {
794
+ static $data = array();
795
+ $ret = '';
796
+ //Get plugin data
797
+ if ( empty($data) ) {
798
+ $file = $this->get_plugin_base_file();
799
+ $data = get_file_data($file, $this->plugin_headers);
800
+ }
801
+ //Return specified field
802
+ if ( !empty($field) ) {
803
+ if ( isset($data[$field]) )
804
+ $ret = $data[$field];
805
+ } else {
806
+ $ret = $data;
807
+ }
808
+ return $ret;
809
+ }
810
+
811
+ /**
812
+ * Retrieve plugin version
813
+ * @uses get_plugin_info()
814
+ * @param bool $strip_desc Strip any additional version text
815
+ * @return string Plugin version
816
+ */
817
+ function get_plugin_version($strip_desc = true) {
818
+ static $v = '';
819
+ //Retrieve version
820
+ if ( empty($v) ) {
821
+ $field = 'Version';
822
+ $v = $this->get_plugin_info($field);
823
+ }
824
+ //Format
825
+ $ret = $v;
826
+ if ( $strip_desc ) {
827
+ $ret = explode(' ', $ret, 2);
828
+ $ret = $ret[0];
829
+ }
830
+ //Return
831
+ return $ret;
832
+ }
833
+
834
+ /**
835
+ * Retrieve plugin textdomain (for localization)
836
+ * @return string
837
+ */
838
+ function get_plugin_textdomain() {
839
+ static $dom = '';
840
+ if ( empty($dom) )
841
+ $dom = $this->get_plugin_base(true);
842
+ return $dom;
843
+ }
844
+
845
+ /**
846
+ * Retrieve current action based on URL query variables
847
+ * @param mixed $default (optional) Default action if no action exists
848
+ * @return string Current action
849
+ */
850
+ function get_action($default = null) {
851
+ $action = '';
852
+
853
+ //Check if action is set in URL
854
+ if ( isset($_GET['action']) )
855
+ $action = $_GET['action'];
856
+ //Otherwise, Determine action based on plugin plugin admin page suffix
857
+ elseif ( isset($_GET['page']) && ($pos = strrpos($_GET['page'], '-')) && $pos !== false && ( $pos != count($_GET['page']) - 1 ) )
858
+ $action = trim(substr($_GET['page'], $pos + 1), '-_');
859
+
860
+ //Determine action for core admin pages
861
+ if ( ! isset($_GET['page']) || empty($action) ) {
862
+ $actions = array(
863
+ 'add' => array('page-new', 'post-new'),
864
+ 'edit-item' => array('page', 'post'),
865
+ 'edit' => array('edit', 'edit-pages')
866
+ );
867
+ $page = basename($_SERVER['SCRIPT_NAME'], '.php');
868
+
869
+ foreach ( $actions as $act => $pages ) {
870
+ if ( in_array($page, $pages) ) {
871
+ $action = $act;
872
+ break;
873
+ }
874
+ }
875
+ }
876
+ if ( empty($action) )
877
+ $action = $default;
878
+ return $action;
879
+ }
880
+
881
+ /*-** General **-*/
882
+
883
+ /**
884
+ * Checks if last parameter sent to a function is an array of options and returns it
885
+ * Calling function should use `func_get_args()` and pass the value to this method
886
+ * @param array $args Parameters passed to calling function
887
+ * @return array Options array (Default: empty array)
888
+ */
889
+ function func_get_options($args) {
890
+ $r = array();
891
+ if ( is_array($args) && !empty($args) ) {
892
+ $last = count($args) - 1;
893
+ if ( is_array($args[$last]) )
894
+ $r = $args[$last];
895
+ }
896
+ return $r;
897
+ }
898
+
899
+ /**
900
+ * Checks if a property exists in a class or object
901
+ * (Compatibility method for PHP 4
902
+ * @param mixed $class Class or object to check
903
+ * @param string $property Name of property to look for in $class
904
+ */
905
+ function property_exists($class, $property) {
906
+ if ( !is_object($class) && !is_array($class) )
907
+ return false;
908
+ if ( function_exists('property_exists') && is_object($class) ) {
909
+ return property_exists($class, $property);
910
+ } else {
911
+ return array_key_exists($property, $class);
912
+ }
913
+ }
914
+
915
+ /**
916
+ * Retrieve specified property from object or array
917
+ * @param object|array $obj Object or array to get property from
918
+ * @param string $property Property name to retrieve
919
+ * @return mixed Property value
920
+ */
921
+ static function &get_property($obj, $property) {
922
+ $property = trim($property);
923
+ //Object
924
+ if ( is_object($obj) )
925
+ return $obj->{$property};
926
+ //Array
927
+ if ( is_array($obj) )
928
+ return $obj[$property];
929
+ //Class
930
+ if ( is_string($obj) && class_exists($obj) ) {
931
+ $cvars = get_class_vars($obj);
932
+ if ( isset($cvars[$property]) )
933
+ return $cvars[$property];
934
+ }
935
+ }
936
+
937
+ /**
938
+ * Remap array members based on a
939
+ * mapping of source/destination keys
940
+ * @param array $properties Associative array of properties
941
+ * @param array $map Source/Destination mapping
942
+ * > Key: Source member
943
+ * > Val: Destination member
944
+ * @param bool $overwrite If TRUE, source value will be set in destination regardless of whether member already exists or not
945
+ * @return array Remapped properties
946
+ */
947
+ function array_remap($arr, $map = array(), $overwrite = false) {
948
+ if ( !empty($map) && is_array($map) ) {
949
+ //Iterate through mappings
950
+ foreach ( $map as $from => $to ) {
951
+ if ( !array_key_exists($from, $arr) )
952
+ continue;
953
+ $move = $overwrite;
954
+ //Only remap if parent property doesn't already exist in array
955
+ if ( !array_key_exists($to, $arr) )
956
+ $move = true;
957
+ if ( $move ) {
958
+ //Move member value to new key
959
+ $arr[$to] = $arr[$from];
960
+ //Remove source member
961
+ unset($arr[$from]);
962
+ }
963
+ }
964
+ }
965
+ //Return remapped properties
966
+ return $arr;
967
+ }
968
+
969
+ function array_filter_keys($arr, $keys) {
970
+ if ( is_array($arr) && !empty($arr) && is_array($keys) && !empty($keys) ) {
971
+ foreach ( $keys as $rem ) {
972
+ if ( array_key_exists($rem, $arr) )
973
+ unset($arr[$rem]);
974
+ }
975
+ }
976
+
977
+ return $arr;
978
+ }
979
+
980
+ /**
981
+ * Insert an item into an array at the specified position
982
+ * @param mixed $item Item to insert into array
983
+ * @param int $pos Index position to insert item into array
984
+ * @return array Modified array
985
+ */
986
+ function array_insert($array, $item, $pos = null) {
987
+ array_splice($array, $pos, 0, $item);
988
+ return $array;
989
+ }
990
+
991
+ /**
992
+ * Merges 1 or more arrays together
993
+ * Methodology
994
+ * - Set first parameter as base array
995
+ * - All other parameters will be merged into base array
996
+ * - Iterate through other parameters (arrays)
997
+ * - Skip all non-array parameters
998
+ * - Iterate though key/value pairs of current array
999
+ * - Merge item in base array with current item based on key name
1000
+ * - If the current item's value AND the corresponding item in the base array are BOTH arrays, recursively merge the the arrays
1001
+ * - If the current item's value OR the corresponding item in the base array is NOT an array, current item overwrites base item
1002
+ * @param array Variable number of arrays
1003
+ * @param array $arr1 Default array
1004
+ * @return array Merged array
1005
+ */
1006
+ function array_merge_recursive_distinct($arr1) {
1007
+ //Get all arrays passed to function
1008
+ $args = func_get_args();
1009
+ if ( empty($args) )
1010
+ return false;
1011
+ //Return empty array if first parameter is not an array
1012
+ if ( !is_array($args[0]) )
1013
+ return array();
1014
+ //Set first array as base array
1015
+ $merged = $args[0];
1016
+ //Iterate through arrays to merge
1017
+ $arg_length = count($args);
1018
+ for ( $x = 1; $x < $arg_length; $x++ ) {
1019
+ //Skip if argument is not an array (only merge arrays)
1020
+ if ( !is_array($args[$x]) )
1021
+ continue;
1022
+ //Iterate through argument items
1023
+ foreach ( $args[$x] as $key => $val ) {
1024
+ //Generate key for numeric indexes
1025
+ if ( is_int($key) ) {
1026
+ //Add new item to merged array
1027
+ $merged[] = null;
1028
+ //Get key of new item
1029
+ $key = array_pop(array_keys($merged));
1030
+ }
1031
+ if ( !isset($merged[$key]) || !is_array($merged[$key]) || !is_array($val) ) {
1032
+ $merged[$key] = $val;
1033
+ } elseif ( is_array($merged[$key]) && is_array($val) ) {
1034
+ $merged[$key] = $this->array_merge_recursive_distinct($merged[$key], $val);
1035
+ }
1036
+ }
1037
+ }
1038
+ return $merged;
1039
+ }
1040
+
1041
+ /**
1042
+ * Replaces string value in one array with the value of the matching element in a another array
1043
+ *
1044
+ * @param string $search Text to search for in array
1045
+ * @param array $arr_replace Array to use for replacing values
1046
+ * @param array $arr_subject Array to search for specified value
1047
+ * @return array Searched array with replacements made
1048
+ */
1049
+ function array_replace_recursive($search, $arr_replace, $arr_subject) {
1050
+ foreach ($arr_subject as $key => $val) {
1051
+ //Skip element if key does not exist in the replacement array
1052
+ if (!isset($arr_replace[$key]))
1053
+ continue;
1054
+ //If element values for both arrays are strings, replace text
1055
+ if (is_string($val) && strpos($val, $search) !== false && is_string($arr_replace[$key]))
1056
+ $arr_subject[$key] = str_replace($search, $arr_replace[$key], $val);
1057
+ //If value in both arrays are arrays, recursively replace text
1058
+ if (is_array($val) && is_array($arr_replace[$key]))
1059
+ $arr_subject[$key] = $this->array_replace_recursive($search, $arr_replace[$key], $val);
1060
+ }
1061
+
1062
+ return $arr_subject;
1063
+ }
1064
+
1065
+ /**
1066
+ * Checks if item at specified path in array is set
1067
+ * @param array $arr Array to check for item
1068
+ * @param array $path Array of segments that form path to array (each array item is a deeper dimension in the array)
1069
+ * @return boolean TRUE if item is set in array, FALSE otherwise
1070
+ */
1071
+ function array_item_isset(&$arr, &$path) {
1072
+ $f_path = $this->get_array_path($path);
1073
+ return eval('return isset($arr' . $f_path . ');');
1074
+ }
1075
+
1076
+ /**
1077
+ * Returns value of item at specified path in array
1078
+ * @param array $arr Array to get item from
1079
+ * @param array $path Array of segments that form path to array (each array item is a deeper dimension in the array)
1080
+ * @return mixed Value of item in array (Default: empty string)
1081
+ */
1082
+ function &get_array_item(&$arr, &$path) {
1083
+ $item = '';
1084
+ if ($this->array_item_isset($arr, $path)) {
1085
+ eval('$item =& $arr' . $this->get_array_path($path) . ';');
1086
+ }
1087
+ return $item;
1088
+ }
1089
+
1090
+ /**
1091
+ * Build formatted string based on array values
1092
+ * Array values in formatted string will be ordered by index order
1093
+ * @param array $attribute Values to build string with
1094
+ * @param string $format (optional) Format name (Default: Multidimensional array representation > ['value1']['value2']['value3'], etc.)
1095
+ * @return string Formatted string based on array values
1096
+ */
1097
+ function get_array_path($attribute = '', $format = null) {
1098
+ //Formatted value
1099
+ $fmtd = '';
1100
+ if (!empty($attribute)) {
1101
+ //Make sure attribute is array
1102
+ if (!is_array($attribute)) {
1103
+ $attribute = array($attribute);
1104
+ }
1105
+ //Format attribute
1106
+ $format = strtolower($format);
1107
+ switch ($format) {
1108
+ case 'id':
1109
+ $fmtd = array_shift($attribute) . '[' . implode('][', $attribute) . ']';
1110
+ break;
1111
+ case 'metadata':
1112
+ case 'attribute':
1113
+ //Join segments
1114
+ $delim = '_';
1115
+ $fmtd = implode($delim, $attribute);
1116
+ //Replace white space and repeating delimiters
1117
+ $fmtd = str_replace(' ', $delim, $fmtd);
1118
+ while (strpos($fmtd, $delim.$delim) !== false)
1119
+ $fmtd = str_replace($delim.$delim, $delim, $fmtd);
1120
+ //Prefix formatted value with delimeter for metadata keys
1121
+ if ('metadata' == $format)
1122
+ $fmtd = $delim . $fmtd;
1123
+ break;
1124
+ case 'path':
1125
+ case 'post':
1126
+ default:
1127
+ $fmtd = '["' . implode('"]["', $attribute) . '"]';
1128
+ }
1129
+ }
1130
+ return $fmtd;
1131
+ }
1132
+
1133
+ /**
1134
+ * Builds array of path elements based on arguments
1135
+ * Each item in path array represents a deeper level in structure path is for (object, array, filesystem, etc.)
1136
+ * @param array|string Value to add to the path
1137
+ * @return array 1-dimensional array of path elements
1138
+ */
1139
+ function build_path() {
1140
+ $path = array();
1141
+ $args = func_get_args();
1142
+
1143
+ //Iterate through parameters and build path
1144
+ foreach ( $args as $arg ) {
1145
+ if ( empty($arg) )
1146
+ continue;
1147
+
1148
+ if (is_array($arg)) {
1149
+ //Recurse through array items to pull out any more arrays
1150
+ foreach ($arg as $key => $val) {
1151
+ $path = array_merge($path, $this->build_path($val));
1152
+ }
1153
+ //$path = array_merge($path, array_values($arg));
1154
+ } elseif ( is_scalar($arg) ) {
1155
+ $path[] = $arg;
1156
+ }
1157
+ }
1158
+
1159
+ return $path;
1160
+ }
1161
+
1162
+ /**
1163
+ * Parse string of attributes into array
1164
+ * For XML/XHTML tag attributes
1165
+ * @param string $txt Attribute text (Can be full tag or just attributes)
1166
+ * @return array Attributes as associative array
1167
+ */
1168
+ function parse_attribute_string($txt, $defaults = array()) {
1169
+ $txt = trim($txt, ' >');
1170
+ $matches = $attr = array();
1171
+ //Strip tag
1172
+ if ( $txt[0] == '<' && ($s = strpos($txt, ' ')) && $s !== false ) {
1173
+ $txt = trim(substr($txt, $s + 1));
1174
+ }
1175
+ //Parse attributes
1176
+ $rgx = "/\b(\w+.*?)=([\"'])(.*?)\\2(?:\s|$)/i";
1177
+ preg_match_all($rgx, $txt, $matches);
1178
+ if ( count($matches) > 3 ) {
1179
+ foreach ( $matches[1] as $sub_idx => $val ) {
1180
+ if ( isset($matches[3][$sub_idx]) )
1181
+ $attr[trim($val)] = trim($matches[3][$sub_idx]);
1182
+ }
1183
+ }
1184
+ //Destroy parsing vars
1185
+ unset($txt, $matches);
1186
+
1187
+ return array_merge($defaults, $attr);
1188
+ }
1189
+
1190
+ /**
1191
+ * Builds attribute string for HTML element
1192
+ * @param array $attr Attributes
1193
+ * @return string Formatted attribute string
1194
+ */
1195
+ function build_attribute_string($attr) {
1196
+ $ret = '';
1197
+ if ( is_object($attr) )
1198
+ $attr = (array) $attr;
1199
+ if ( is_array($attr) ) {
1200
+ array_map('esc_attr', $attr);
1201
+ $attr_str = array();
1202
+ foreach ( $attr as $key => $val ) {
1203
+ $attr_str[] = $key . '="' . $val . '"';
1204
+ }
1205
+ $ret = implode(' ', $attr_str);
1206
+ }
1207
+ return $ret;
1208
+ }
1209
+
1210
+ /**
1211
+ * Generate external stylesheet element
1212
+ * @param $url Stylesheet URL
1213
+ * @return string Stylesheet element
1214
+ */
1215
+ function build_stylesheet_element($url = '') {
1216
+ $attributes = array('href' => $url, 'type' => 'text/css', 'rel' => 'stylesheet');
1217
+ return $this->build_html_element(array('tag' => 'link', 'wrap' => false, 'attributes' => $attributes));
1218
+ }
1219
+
1220
+ function build_script_element($content = '', $id = '', $wrap_jquery = true, $wait_doc_ready = false) {
1221
+ //Stop processing invalid content
1222
+ if ( empty($content) || !is_string($content) )
1223
+ return '';
1224
+ $attributes = array('type' => 'text/javascript');
1225
+ $start = array('/* <![CDATA[ */');
1226
+ $end = array('/* ]]> */');
1227
+ if ( $wrap_jquery ) {
1228
+ $start[] = '(function($){';
1229
+ $end[] = '})(jQuery);';
1230
+
1231
+ //Add event handler (if necessary)
1232
+ if ( $wait_doc_ready ) {
1233
+ $start[] = '$(document).ready(function(){';
1234
+ $end[] = '})';
1235
+ }
1236
+ }
1237
+
1238
+ //Reverse order of end values
1239
+ $end = array_reverse($end);
1240
+ $content = implode('', array_merge($start, array($content), $end));
1241
+ if ( is_string($id) && !empty($id) ) {
1242
+ $attributes['id'] = $this->add_prefix($id);
1243
+ }
1244
+ return $this->build_html_element(array('tag' => 'script', 'content' => $content, 'attributes' => $attributes)) . PHP_EOL;
1245
+ }
1246
+
1247
+ /**
1248
+ * Generate external script element
1249
+ * @param $url Script URL
1250
+ * @return string Script element
1251
+ */
1252
+ function build_ext_script_element($url = '') {
1253
+ $attributes = array('src' => $url, 'type' => 'text/javascript');
1254
+ return $this->build_html_element(array('tag' => 'script', 'attributes' => $attributes));
1255
+ }
1256
+
1257
+ /**
1258
+ * Generate HTML element based on values
1259
+ * @param $args Element arguments
1260
+ * @return string Generated HTML element
1261
+ */
1262
+ function build_html_element($args) {
1263
+ $defaults = array(
1264
+ 'tag' => 'span',
1265
+ 'wrap' => true,
1266
+ 'content' => '',
1267
+ 'attributes' => array()
1268
+ );
1269
+ $el_start = '<';
1270
+ $el_end = '>';
1271
+ $el_close = '/';
1272
+ extract(wp_parse_args($args, $defaults), EXTR_SKIP);
1273
+ $content = trim($content);
1274
+
1275
+ if ( !$wrap && strlen($content) > 0 )
1276
+ $wrap = true;
1277
+
1278
+ $attributes = $this->build_attribute_string($attributes);
1279
+ if ( strlen($attributes) > 0 )
1280
+ $attributes = ' ' . $attributes;
1281
+
1282
+ $ret = $el_start . $tag . $attributes;
1283
+
1284
+ if ( $wrap )
1285
+ $ret .= $el_end . $content . $el_start . $el_close . $tag;
1286
+ else
1287
+ $ret .= ' ' . $el_close;
1288
+
1289
+ $ret .= $el_end;
1290
+ return $ret;
1291
+ }
1292
+
1293
+ /*-** Admin **-*/
1294
+
1295
+ /**
1296
+ * Add submenu page in the admin menu
1297
+ * Adds ability to set the position of the page in the menu
1298
+ * @see add_submenu_page (Wraps functionality)
1299
+ *
1300
+ * @param $parent
1301
+ * @param $page_title
1302
+ * @param $menu_title
1303
+ * @param $access_level
1304
+ * @param $file
1305
+ * @param $function
1306
+ * @param int $pos Index position of menu page
1307
+ *
1308
+ * @global array $submenu Admin page submenus
1309
+ */
1310
+ function add_submenu_page($parent, $page_title, $menu_title, $capability, $file, $function = '', $pos = false) {
1311
+ //Add submenu page as usual
1312
+ $args = func_get_args();
1313
+ $hookname = call_user_func_array('add_submenu_page', $args);
1314
+ if ( is_int($pos) ) {
1315
+ global $submenu;
1316
+ //Get last submenu added
1317
+ $parent = $this->get_submenu_parent_file($parent);
1318
+ if ( isset($submenu[$parent]) ) {
1319
+ $subs =& $submenu[$parent];
1320
+
1321
+ //Make sure menu isn't already in the desired position
1322
+ if ( $pos <= ( count($subs) - 1 ) ) {
1323
+ //Get submenu that was just added
1324
+ $sub = array_pop($subs);
1325
+ //Insert into desired position
1326
+ if ( 0 == $pos ) {
1327
+ array_unshift($subs, $sub);
1328
+ } else {
1329
+ $top = array_slice($subs, 0, $pos);
1330
+ $bottom = array_slice($subs, $pos);
1331
+ array_push($top, $sub);
1332
+ $subs = array_merge($top, $bottom);
1333
+ }
1334
+ }
1335
+ }
1336
+ }
1337
+
1338
+ return $hookname;
1339
+ }
1340
+
1341
+ /**
1342
+ * Remove admin submenu
1343
+ * @param string $parent Submenu parent file
1344
+ * @param string $file Submenu file name
1345
+ * @return int|null Index of removed submenu (NULL if submenu not found)
1346
+ *
1347
+ * @global array $submenu
1348
+ * @global array $_registered_pages
1349
+ */
1350
+ function remove_submenu_page($parent, $file) {
1351
+ global $submenu, $_registered_pages;
1352
+ $ret = null;
1353
+
1354
+ $parent = $this->get_submenu_parent_file($parent);
1355
+ $file = plugin_basename($file);
1356
+ $file_index = 2;
1357
+
1358
+ //Find submenu
1359
+ if ( isset($submenu[$parent]) ) {
1360
+ $subs =& $submenu[$parent];
1361
+ for ($x = 0; $x < count($subs); $x++) {
1362
+ if ( $subs[$x][$file_index] == $file ) {
1363
+ //Remove matching submenu
1364
+ $hookname = get_plugin_page_hookname($file, $parent);
1365
+ remove_all_actions($hookname);
1366
+ unset($_registered_pages[$hookname]);
1367
+ unset($subs[$x]);
1368
+ $subs = array_values($subs);
1369
+ //Set index and stop processing
1370
+ $ret = $x;
1371
+ break;
1372
+ }
1373
+ }
1374
+ }
1375
+
1376
+ return $ret;
1377
+ }
1378
+
1379
+ /**
1380
+ * Replace a submenu page
1381
+ * Adds a submenu page in the place of an existing submenu page that has the same $file value
1382
+ *
1383
+ * @param $parent
1384
+ * @param $page_title
1385
+ * @param $menu_title
1386
+ * @param $access_level
1387
+ * @param $file
1388
+ * @param $function
1389
+ * @return string Hookname
1390
+ *
1391
+ * @global array $submenu
1392
+ */
1393
+ function replace_submenu_page($parent, $page_title, $menu_title, $access_level, $file, $function = '') {
1394
+ global $submenu;
1395
+ //Remove matching submenu (if exists)
1396
+ $pos = $this->remove_submenu_page($parent, $file);
1397
+ //Insert submenu page
1398
+ $hookname = $this->add_submenu_page($parent, $page_title, $menu_title, $access_level, $file, $function, $pos);
1399
+ return $hookname;
1400
+ }
1401
+
1402
+ /**
1403
+ * Retrieves parent file for submenu
1404
+ * @param string $parent Parent file
1405
+ * @return string Formatted parent file name
1406
+ *
1407
+ * @global array $_wp_real_parent_file;
1408
+ */
1409
+ function get_submenu_parent_file($parent) {
1410
+ global $_wp_real_parent_file;
1411
+ $parent = plugin_basename($parent);
1412
+ if ( isset($_wp_real_parent_file[$parent]) )
1413
+ $parent = $_wp_real_parent_file[$parent];
1414
+ return $parent;
1415
+ }
1416
+ }
js/lib.admin.edit_form.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ var page = ( adminpage ) ? adminpage : 'post';
2
+ jQuery(document).ready( function($) {
3
+ if ( postboxes && postboxes.add_postbox_toggles )
4
+ postboxes.add_postbox_toggles(page);
5
+ });
js/lib.admin.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @package Cornerstone
3
+ * @subpackage Admin
4
+ * @author Archetyped
5
+ */
6
+
7
+ (function ($) {
8
+
9
+ if ( CNR && CNR.extend ) CNR.extend('admin', {});
10
+
11
+ })(jQuery);
js/lib.core.js ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Core
3
+ * @package Cornerstone
4
+ * @author Archetyped
5
+ */
6
+
7
+ var CNR = {};
8
+
9
+ (function($){
10
+ /* Quick Hide */
11
+
12
+ $('html').addClass('js');
13
+
14
+ /* Classes */
15
+
16
+ /* CNR */
17
+ CNR = {
18
+ prefix: 'cnr',
19
+ context: [], //Context
20
+ options: { //Options
21
+ },
22
+
23
+ extend: function(member, data) {
24
+ if ( $.type(member) == 'string' && $.isPlainObject(data) ) {
25
+ //Add initial member
26
+ var obj = {};
27
+ obj[member] = $.extend({}, data);
28
+ $.extend(this, obj);
29
+
30
+ if ( member in this ) {
31
+ //Add additional objects
32
+ var args = ( arguments.length > 2 ) ? [].slice.apply(arguments).slice(2) : [];
33
+ args.unshift(this[member]);
34
+ //Add base properties
35
+ args.push({
36
+ base: CNR,
37
+ parent: this,
38
+ extend: this.extend
39
+ });
40
+ $.extend.apply($, args);
41
+ }
42
+ }
43
+ },
44
+
45
+ /* Prefix */
46
+
47
+ /**
48
+ * Retrieve valid separator
49
+ * If supplied argument is not a valid separator, use default separator
50
+ * @param string (optional) sep Separator text
51
+ * @return string Separator text
52
+ */
53
+ get_sep: function(sep) {
54
+ if ( typeof sep == 'undefined' || sep == null )
55
+ sep = '';
56
+
57
+ return ( $.type(sep) == 'string' ) ? sep : '_';
58
+ },
59
+
60
+ /**
61
+ * Retrieve prefix
62
+ * @param string (optional) sep Separator text
63
+ * @return string Prefix (with separator if specified)
64
+ */
65
+ get_prefix: function(sep) {
66
+ return ( this.prefix && this.prefix.length ) ? this.prefix + this.get_sep(sep) : '';
67
+ },
68
+
69
+ /**
70
+ * Check if string is prefixed
71
+ */
72
+ has_prefix: function(val, sep) {
73
+ return ( $.type(val) == 'string' && val.length && val.indexOf(this.get_prefix(sep)) === 0 );
74
+ },
75
+
76
+ /**
77
+ * Add Prefix to value
78
+ * @param string val Value to add prefix to
79
+ * @param string sep (optional) Separator (Default: `_`)
80
+ * @param bool (optional) once If text should only be prefixed once (Default: true)
81
+ */
82
+ add_prefix: function(val, sep, once) {
83
+ if ( typeof sep == 'undefined' )
84
+ sep = '_';
85
+ once = ( typeof once == 'undefined' ) ? true : !!once;
86
+ if ( once && this.has_prefix(val, sep) )
87
+ return val;
88
+ return this.get_prefix(sep) + val;
89
+ }
90
+ }
91
+
92
+ /* Utilities */
93
+ CNR.extend('util', {
94
+ /**
95
+ * Return formatted string
96
+ */
97
+ sprintf: function() {
98
+ var format = '',
99
+ params = [];
100
+ if (arguments.length < 1)
101
+ return format;
102
+ if (arguments.length == 1) {
103
+ format = arguments[0];
104
+ return format;
105
+ }
106
+ params = arguments.slice(1);
107
+ return format;
108
+ },
109
+
110
+ /* Request */
111
+
112
+ /**
113
+ * Retrieve valid context
114
+ * @return array Context
115
+ */
116
+ get_context: function() {
117
+ //Valid context
118
+ if ( !$.isArray(this.base.context) )
119
+ this.base.context = [];
120
+ //Return context
121
+ return this.base.context;
122
+ },
123
+
124
+ /**
125
+ * Check if a context exists in current request
126
+ * If multiple contexts are supplied, result will be TRUE if at least ONE context exists
127
+ *
128
+ * @param string|array ctx Context to check for
129
+ * @return bool TRUE if context exists, FALSE otherwise
130
+ */
131
+ is_context: function(ctx) {
132
+ var ret = false;
133
+ //Validate context
134
+ if ( typeof ctx == 'string' )
135
+ ctx = [ctx];
136
+ if ( $.isArray(ctx) && this.arr_intersect(this.get_context(), ctx).length ) {
137
+ ret = true;
138
+ }
139
+ return ret;
140
+ },
141
+
142
+ /* DOM */
143
+
144
+ /**
145
+ * Strip selector (ID, class, etc.)
146
+ * @param string id_value Original element
147
+ */
148
+ esc_selector: function(id_value) {
149
+ return id_value.toString().replace(/(\[|\])/gi, '\\$1');
150
+ },
151
+
152
+ /**
153
+ * Find common elements of 2 arrays
154
+ * @param array arr1 First array
155
+ * @param array arr2 Second array
156
+ * @return array Elements common to both arrays
157
+ */
158
+ arr_intersect: function(arr1, arr2) {
159
+ var ret = [];
160
+ if ( arr1 == arr2 ) {
161
+ return arr2;
162
+ }
163
+ if ( !$.isArray(arr2) || !arr2.length || !arr1.length ) {
164
+ return ret;
165
+ }
166
+ //Compare elements in arrays
167
+ var a1;
168
+ var a2;
169
+ var val;
170
+ if ( arr1.length < arr2.length ) {
171
+ a1 = arr1;
172
+ a2 = arr2;
173
+ } else {
174
+ a1 = arr2;
175
+ a2 = arr1;
176
+ }
177
+
178
+ for ( var x = 0; x < a1.length; x++ ) {
179
+ //Add mutual elements into intersection array
180
+ val = a1[x];
181
+ if ( a2.indexOf(val) != -1 && ret.indexOf(val) == -1 )
182
+ ret.push(val);
183
+ }
184
+
185
+ //Return intersection results
186
+ return ret;
187
+ }
188
+ });
189
+
190
+ })(jQuery); //END CNR init
js/lib.media.js ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Media
3
+ * @package Cornerstone
4
+ * @author Archetyped
5
+ */
6
+
7
+ (function($) {
8
+
9
+ if ( CNR && CNR.extend ) CNR.extend('media', {
10
+ /**
11
+ * Convert array-based element IDs to standalone IDs
12
+ * @param string el_id Element ID
13
+ * @return string Converted ID
14
+ */
15
+ convertId : function (el_id) {
16
+ return 'cnr_field_' + el_id.replace('cnr[attributes]', '').replace('][', '_').replace('[', '').replace(']', '')
17
+ },
18
+ /**
19
+ * Set selected media as value of specified post field
20
+ * @param object a Media arguments
21
+ * Arguments
22
+ * id: Attachment ID
23
+ * field: Field ID
24
+ * url: Source URL
25
+ * type: Mime type
26
+ * preview: URL of preview image (file type icon, etc.)
27
+ */
28
+ setPostMedia : function (a) {
29
+ if ( typeof(a) != 'undefined' ) {
30
+ var selContainer = '#' + CNR.util.esc_selector(a.field) + '-data';
31
+ var container = $(selContainer);
32
+ var mediaElName = CNR.util.esc_selector(a.field);
33
+ //Set Media ID
34
+ var mediaId = $(container).find("#" + mediaElName);
35
+ if ( mediaId.length == 0 ) {
36
+ mediaId = $('<input />').attr({
37
+ type: 'hidden',
38
+ id: a.field,
39
+ name: a.field
40
+ });
41
+ $(container).prepend(mediaId);
42
+ }
43
+ $(mediaId).attr('value', a.id);
44
+
45
+ //Add source file link
46
+ media_el = $(container).find('#' + mediaElName + '-link');
47
+ if ( media_el.length == 0 ) {
48
+ //Build link
49
+ media_el = $('<a>').attr({
50
+ title: 'Media Link',
51
+ 'class': 'media_link',
52
+ 'target': '_blank',
53
+ id: a.field + '-link'
54
+ });
55
+ $(container).prepend(media_el);
56
+ }
57
+ $(media_el).attr('href', a.url).text( a.url.substr(a.url.lastIndexOf('/') + 1) );
58
+
59
+ //Build preview based on media type
60
+ var media_el;
61
+ if ( a.preview.length > 0 ) {
62
+ media_el = $(container).find("#" + mediaElName + '-frame');
63
+ if (media_el.length == 0) {
64
+ media_el = $("<img />").attr({
65
+ title: 'Media Preview',
66
+ alt: 'Media Preview',
67
+ 'class': 'media_frame',
68
+ id: a.field + '-frame'
69
+ });
70
+ $(container).prepend(media_el);
71
+ }
72
+ $(media_el).attr('src', a.preview);
73
+ }
74
+
75
+ //Show Media Options
76
+ var opts = $('#' + mediaElName + '-options').removeClass('options-default');
77
+ //Hide confirmation options
78
+ opts.find('.confirmation').addClass('confirmation-default');
79
+ }
80
+ //Close popup
81
+ tb_remove();
82
+ },
83
+ /**
84
+ * Execute an action
85
+ * Uses attributes (class, etc.) of triggering element to determine action to execute
86
+ * @param {Object} el Element that triggered the action
87
+ */
88
+ doAction : function (el) {
89
+ if (!el.id || el.id.length < 1)
90
+ return false;
91
+ var sep = '-';
92
+ //Get Element Parts
93
+ var parts = el.id.split(sep);
94
+ //Base
95
+ var base = (parts.length > 2) ? parts.slice(0, parts.length - 1).join(sep) : parts[0];
96
+ //Action
97
+ var action = parts[parts.length - 1];
98
+ var actEl;
99
+ var getEl = function (ident) {
100
+ ident = (typeof(ident) != 'undefined' && ident.length > 0) ? sep + ident : '';
101
+ return $('#' + CNR.util.esc_selector(base) + ident);
102
+ };
103
+ switch (action) {
104
+ case 'option_remove':
105
+ actEl = getEl('remove_confirmation').removeClass('confirmation-default');
106
+ break;
107
+ case 'remove_cancel':
108
+ getEl('remove_confirmation').addClass('confirmation-default');
109
+ break;
110
+ case 'remove':
111
+ //Remove Image Frame
112
+ getEl('data').empty();
113
+ //Reset Image ID
114
+ getEl().attr('value', 0);
115
+ //Hide remove options
116
+ $($(el).parents('.options').get(0)).addClass('options-default');
117
+ //Hide confirmation options
118
+ $(el).parents('.confirmation').addClass('confirmation-default');
119
+ break;
120
+ }
121
+
122
+ return false;
123
+ }
124
+ });
125
+ })(jQuery);
js/lib.posts.inline_edit.js ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+ if ( inlineEditPost && CNR && CNR.posts ) {
3
+ //Move init method to guarantee execution order
4
+ inlineEditPost.initSaved = inlineEditPost.init;
5
+ inlineEditPost.init = function() {};
6
+
7
+ //Extend inlineEditPost object
8
+ CNR.posts.extend('inline_edit', inlineEditPost, {
9
+ field_parent: '',
10
+
11
+ init: function() {
12
+ this.field_parent = this.base.add_prefix('post_parent');
13
+ var t = this;
14
+ //Execute default init method
15
+ if ( inlineEditPost.initSaved ) {
16
+ inlineEditPost.initSaved();
17
+ }
18
+ //Unbind quick edit click events
19
+ $('.wp-list-table tbody').off( 'click', '.editinline' );
20
+ //Bind new quick edit click handler
21
+ $('.wp-list-table tbody').on( 'click', '.editinline',
22
+ function() {
23
+ t.editHandler(this);
24
+ return false;
25
+ }
26
+ );
27
+ var qeRow = $('#inline-edit');
28
+ $('a.save', qeRow).click(function() { return t.save(this); });
29
+ $('td', qeRow).keydown(function(e) { if (e.which == 13) { return t.save(this); } });
30
+ //Restore original init method for future use
31
+ if ( inlineEditPost.initSaved ) {
32
+ inlineEditPost.init = inlineEditPost.initSaved;
33
+ }
34
+ },
35
+
36
+ save: function(id) {
37
+ var t = this, post_parent;
38
+ //Update post data
39
+ if (typeof(id) == 'object') {
40
+ id = t.getId(id);
41
+ }
42
+ if (id) {
43
+ //Get post parent
44
+ post_parent = $('#edit-' + id + ' #' + t.field_parent + ' option:selected');
45
+ if (post_parent.length) {
46
+ //Set post parent in postData
47
+ this.parent.set_data(id, 'post_parent', post_parent.val());
48
+ }
49
+ }
50
+ return true;
51
+ },
52
+
53
+ editHandler: function(id) {
54
+ this.preEdit(id);
55
+ inlineEditPost.edit(id);
56
+ this.postEdit(id);
57
+ },
58
+
59
+ preEdit: function(id) {
60
+ var t = this, post_id, section_select, parent_id;
61
+ if (typeof(id) == 'object') {
62
+ id = t.getId(id);
63
+ }
64
+ //Get master section selection
65
+ section_select = $('#inline-edit #' + t.field_parent);
66
+ //Get Parent ID
67
+ if ( section_select.length && t.parent.has_data(id, 'post_parent') ) {
68
+ parent_id = t.parent.get_data(id, 'post_parent');
69
+ //Set selected
70
+ $('option[value=' + parent_id + ']', section_select).get(0).defaultSelected = true;
71
+ }
72
+ },
73
+
74
+ postEdit: function(id) {
75
+ $('#inline-edit #' + this.field_parent + ' option').removeAttr('selected');
76
+ }
77
+ });
78
+ }
79
+
80
+ if ( CNR && CNR.admin && CNR.posts.inline_edit && CNR.posts.inline_edit.init ) {
81
+ $(document).ready(function() { CNR.posts.inline_edit.init(); });
82
+ }
83
+
84
+ })(jQuery);
js/lib.posts.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+
3
+ if ( !CNR || !CNR.extend )
4
+ return false;
5
+
6
+ CNR.extend('posts', {
7
+ data: {},
8
+
9
+ item_section: false,
10
+ title_sep: '',
11
+
12
+ get_data_id: function(id) {
13
+ var pre = 'post_';
14
+ if ( id.indexOf(pre) !== 0 )
15
+ id = pre + id;
16
+ return id;
17
+ },
18
+
19
+ has_data: function(id, prop) {
20
+ var ret = false;
21
+ id = this.get_data_id(id);
22
+ if ( id in this.data ) {
23
+ ret = true;
24
+ if ( $.type(prop) == 'string' )
25
+ ret = ( prop in this.data[id] ) ? true : false;
26
+ }
27
+ return ret;
28
+ },
29
+
30
+ get_data: function(id, prop) {
31
+ var ret = '';
32
+ id = this.get_data_id(id);
33
+ if ( this.has_data(id) && prop in this.data[id] ) {
34
+ ret = this.data[id][prop];
35
+ }
36
+ return ret;
37
+ },
38
+
39
+ set_data: function(id, prop, val) {
40
+ //Validate args
41
+ if ( $.type(id) != 'string' || $.type(prop) != 'string' || typeof val == 'undefined' )
42
+ return false;
43
+ //Create data object (if necessary)
44
+ id = this.get_data_id(id);
45
+ if ( ! this.has_data(id) )
46
+ this.data[id] = {};
47
+ //Set data
48
+ this.data[id][prop] = val;
49
+ },
50
+
51
+ set_wpseo_title: function() {
52
+ if ( typeof wpseo_title_template == 'undefined' || !this.item_section )
53
+ return false;
54
+ var ph_title = '%%title%%';
55
+ wpseo_title_template = wpseo_title_template.replace(ph_title, ph_title + this.title_sep + this.item_section);
56
+ }
57
+ });
58
+
59
+ $(document).ready(function() {
60
+ CNR.posts.set_wpseo_title();
61
+ })
62
+
63
+ })(jQuery);
js/lib.structure.admin.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Structure Administration
3
+ * @package Cornerstone
4
+ * @subpackage Structure
5
+ */
6
+
7
+ (function($) {
8
+
9
+ if ( CNR && CNR.structure && CNR.structure.extend ) CNR.structure.extend('admin', {
10
+ parent: CNR.structure,
11
+ options: {},
12
+
13
+ manage_option: function() {
14
+ //Continue processing if permalink option has been defined
15
+ if ( this.options && this.options.label && this.options.example && this.parent.options.permalink_structure ) {
16
+ var o = this.options;
17
+ var po = this.parent.options;
18
+ //Get options table
19
+ var opts_list = $('.wrap .form-table tbody:first');
20
+ var rows = $(opts_list).find('tr');
21
+ //Insert custom option
22
+ if (rows.length) {
23
+ var last = $(rows).get(rows.length - 1);
24
+ var option = $(opts_list).find('tr:first').clone();
25
+ var input = $(option).find('input');
26
+ //Input
27
+ $(input).val(po.permalink_structure);
28
+ if (po.permalink_structure == $('#permalink_structure').val()) {
29
+ $(input).attr('checked', 'checked');
30
+ } else {
31
+ $(input).removeAttr('checked');
32
+ }
33
+ //Label
34
+ var label = $(option).find('label');
35
+ //Move input element out of label element
36
+ $(label).text(o.label).prepend(input);
37
+ //Example text
38
+ $(option).find('td code').text(o.example);
39
+ //Insert element before last option in table
40
+ $(last).before($(option));
41
+ }
42
+ }
43
+ }
44
+ });
45
+
46
+ if ( CNR.structure.admin.manage_option )
47
+ $(document).ready(function() {CNR.structure.admin.manage_option();});
48
+
49
+ }) (jQuery);
js/lib.structure.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @package Cornerstone
3
+ * @subpackage Structure
4
+ * @author Archetyped
5
+ */
6
+
7
+ (function ($) {
8
+
9
+ if ( CNR && CNR.extend ) CNR.extend('structure', {
10
+ options: { //Options
11
+ using_permastruct: false
12
+ },
13
+
14
+ /**
15
+ * Init
16
+ */
17
+ init: function() {
18
+ if ( this.base.util.is_context(['admin_page_post-new', 'admin_page_post']) ) {
19
+ this.setup_item_edit();
20
+ }
21
+ },
22
+
23
+ /**
24
+ * Setup item edit form
25
+ */
26
+ setup_item_edit: function() {
27
+ var id = 'parent_id',
28
+ sel = '#' + id;
29
+ //Check for parent element
30
+ if ( !$(sel).length ) {
31
+ //Add parent element
32
+ var el_par = $('<input type="hidden" />').attr({
33
+ 'id': id
34
+ }).appendTo('body');
35
+ }
36
+
37
+ //Setup parent field
38
+ if ( this.options.field_parent && $('#' + this.options.field_parent).length ) {
39
+ var f_par = $('#' + this.options.field_parent);
40
+ //Define event handler
41
+ var setParent = function(p) {
42
+ if ( typeof p == 'undefined' || p.toString().length == 0 ) {
43
+ p = 0;
44
+ }
45
+ $(sel).val(p);
46
+ };
47
+ //Set initial value
48
+ setParent();
49
+ //Add event handler
50
+ $(f_par).change(function(e) {
51
+ //Set parent
52
+ setParent($(this).val());
53
+ //Autosave (if possible)
54
+ if ( $('#title').val().length > 0 && window.delayed_autosave ) {
55
+ $('#edit-slug-box').html('');
56
+ autosaveLast = '';
57
+ delayed_autosave();
58
+ }
59
+ });
60
+ }
61
+
62
+ }
63
+ });
64
+
65
+ if ( CNR.structure.init ) {
66
+ $(document).ready(function() {
67
+ CNR.structure.init();
68
+ });
69
+ }
70
+
71
+ })(jQuery);
load.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /* Constants */
4
+
5
+ if ( !defined('CNR_DEV') ) {
6
+ define('CNR_DEV', ( isset( $_REQUEST['cnr_dev'] ) && !!$_REQUEST['cnr_dev'] ) );
7
+ }
8
+
9
+ /* Class Management */
10
+
11
+ /**
12
+ * Class loading handler
13
+ * @param string $classname Class to load
14
+ */
15
+ function cnr_autoload($classname) {
16
+ $prefix = 'cnr_';
17
+ $cls = strtolower($classname);
18
+ //Remove prefix
19
+ if ( 0 !== strpos($cls, $prefix) ) {
20
+ return false;
21
+ }
22
+ //Format class for filename
23
+ $fn = 'class.' . substr($cls, strlen($prefix)) . '.php';
24
+ //Build path
25
+ $path = dirname(__FILE__) . '/' . "includes/" . $fn;
26
+ //Load file
27
+ if ( is_readable($path) ) {
28
+ require $path;
29
+ }
30
+ }
31
+
32
+ // Register autoloader
33
+ spl_autoload_register('cnr_autoload');
34
+
35
+ /* Load Assets */
36
+ $path = dirname(__FILE__) . '/';
37
+ require_once $path . 'controller.php';
38
+ require_once $path . 'functions.php';
39
+
40
+ /* Variables */
41
+
42
+ //Global content type variables
43
+ if ( !isset($GLOBALS['cnr_content_types']) )
44
+ $GLOBALS['cnr_content_types'] = array();
45
+ if ( !isset($GLOBALS['cnr_field_types']) )
46
+ $GLOBALS['cnr_field_types'] = array();
47
+
48
+ /* Init */
49
+ $GLOBALS['cnr_content_utilities'] = new CNR_Content_Utilities();
50
+ $GLOBALS['cnr_content_utilities']->init();
51
+
52
+ /* Hooks */
53
+
54
+ // Register Default placeholder handlers
55
+ cnr_register_placeholder_handler('all', array('CNR_Field_Type', 'process_placeholder_default'), 11);
56
+ cnr_register_placeholder_handler('field_id', array('CNR_Field_Type', 'process_placeholder_id'));
57
+ cnr_register_placeholder_handler('field_name', array('CNR_Field_Type', 'process_placeholder_name'));
58
+ cnr_register_placeholder_handler('data', array('CNR_Field_Type', 'process_placeholder_data'));
59
+ cnr_register_placeholder_handler('loop', array('CNR_Field_Type', 'process_placeholder_loop'));
60
+ cnr_register_placeholder_handler('data_ext', array('CNR_Field_Type', 'process_placeholder_data_ext'));
61
+ cnr_register_placeholder_handler('rich_editor', array('CNR_Field_Type', 'process_placeholder_rich_editor'));
62
+
63
+ /* Start */
64
+
65
+ $GLOBALS['cnr'] = new Cornerstone();
main.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Cornerstone
4
+ *
5
+ * @package Cornerstone
6
+ * @author Archetyped <support@archetyped.com>
7
+ * @copyright 2020 Archetyped
8
+ *
9
+ * Plugin Name: Cornerstone
10
+ * Plugin URI: http://archetyped.com/tools/cornerstone/
11
+ * Description: Enhanced content management for WordPress
12
+ * Version: 0.7.7
13
+ * Requires at least: 5.3
14
+ * Text Domain: cornerstone
15
+ * Author: Archetyped
16
+ * Author URI: http://archetyped.com
17
+ * Support URI: https://github.com/archetyped/cornerstone/wiki/Reporting-Issues
18
+ */
19
+
20
+ $cnr = null;
21
+ /**
22
+ * Initialize CNR
23
+ */
24
+ function cnr_init() {
25
+ $path = dirname(__FILE__) . '/';
26
+ require_once $path . 'load.php';
27
+ }
28
+
29
+ add_action('init', 'cnr_init', 1);
package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cornerstone",
3
+ "version": "0.7.7",
4
+ "title": "Cornerstone",
5
+ "description": "Enhanced content management for WordPress",
6
+ "author": "Archetyped <support@archetyped.com>",
7
+ "license": "GPLv2",
8
+ "private": true,
9
+ "devDependencies": {
10
+ "grunt": "^0.4.5",
11
+ "grunt-contrib-jshint": "^0.11.2",
12
+ "grunt-contrib-uglify": "^0.9.1",
13
+ "grunt-contrib-watch": "^0.6.1",
14
+ "grunt-phplint": "0.0.5",
15
+ "grunt-sass": "^1.0.0",
16
+ "jshint-stylish": "^2.0.1",
17
+ "load-grunt-tasks": "^3.2.0",
18
+ "time-grunt": "^1.2.1"
19
+ }
20
+ }
readme.txt ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Cornerstone ===
2
+ Contributors: Archetyped
3
+ Donate: https://gum.co/cnr-donate
4
+ Tags: cornerstone, cms, content, management, system, structure, organization, sections
5
+ Plugin Link: http://archetyped.com/tools/cornerstone/
6
+ Requires at least: 5.3
7
+ Tested up to: 5.3
8
+ Stable tag: trunk
9
+
10
+ Enhanced content management for WordPress
11
+
12
+ == Description ==
13
+ Cornerstone makes WordPress practical for **any type of site** by enhancing its content management capabilities. Too long have we had to resort to hacks like using categories in menus to build a pseudo site structure (you know what I'm talking about).
14
+
15
+ Cornerstone enhances your WordPress site in several ways. One of the most useful features is one that allows WordPress to be used for sites that go beyond just blogging-- **Posts in Sections**. Create a section, add posts to it, they show up when visitors navigate to that section. Simple as that. It really is, but because you're awesome [here's a tutorial on how to do it](http://archetyped.com/know/how-to-organize-posts-in-sections-in-wordpress-with-cornerstone/).
16
+
17
+ ### Thanks for the Support!
18
+
19
+ The support from the users that love Cornerstone is huge. You can support Cornerstone's future development and help to make it even better by donating or even just by sending me a nice message :)
20
+
21
+ [Donate to Cornerstone](https://gum.co/cnr-donate)
22
+
23
+ ### Features
24
+
25
+ * ''NEW: Content Types'' - Add custom fields to default (posts, pages, etc.) and custom (events, properties, etc.) post types
26
+ * Posts in Sections (see above for more info. Why are you reading from the bottom up?)
27
+ * Structured permalinks - post permalinks are based on the section they are in (e.g. ``/section-name/post-name/``)
28
+ * RSS for Sections - Let users subscribe to and receive updates for specific sections on the site.
29
+
30
+ ### Next Up
31
+
32
+ * Template functionality - enhanced page titles, featured content, etc.
33
+ * And more, which is where your feedback comes in.
34
+
35
+ [Plugin home page](http://archetyped.com/tools/cornerstone/)
36
+
37
+ == Installation ==
38
+
39
+ Install and Activate the plugin from the WordPress.org Plugins Repository
40
+
41
+ #### Tutorials
42
+
43
+ * [How to Organize Posts in Sections with Cornerstone](http://archetyped.com/know/how-to-organize-posts-in-sections-in-wordpress-with-cornerstone/)
44
+ * [Cornerstone: Creating Custom Content Types in WordPress](http://archetyped.com/know/cornerstone-creating-custom-content-types-in-wordpress/)
45
+ * [Cornerstone: Using Custom Field Data in WordPress](http://archetyped.com/know/cornerstone-using-custom-field-data-in-wordpress/)
46
+
47
+ == Upgrade Notice ==
48
+
49
+ No upgrade notices
50
+
51
+ == Frequently Asked Questions ==
52
+
53
+ Post your questions/comments at [Cornerstone's official issue tracker](https://github.com/archetyped/cornerstone/wiki/Reporting-Issues).
54
+
55
+ == Screenshots ==
56
+
57
+ 1. Set a post's section on the post edit form
58
+ 1. Manage posts by section on post management page
59
+ 1. Quickly modify a post's section
60
+
61
+ == Changelog ==
62
+
63
+ = 0.7.7 =
64
+
65
+ * Fix: Spelling.
66
+
67
+ = 0.7.6 =
68
+
69
+ * Add: Block editor support for Custom Post Types (CPTs).
70
+ * Update: WordPress 5.3 Compatibility confirmed.
71
+ * Update: WordPress minimum version requirement (5.3).
72
+ * Fix: Plugin initialization.
73
+ * Fix: Display/Save custom field data in post/page editor.
74
+ * Fix: Display/Save section in Quick Editor.
75
+ * Optimize: Code cleanup.
76
+
77
+ = 0.7.5 =
78
+
79
+ * Update: Use plugin-specific text domain for localized strings
80
+
81
+ = 0.7.4 =
82
+
83
+ * Add: Text Domain header for translations
84
+
85
+ = 0.7.3 =
86
+
87
+ * Fix: Registration of Media field types
88
+ * Update: Confirm compatibility with WordPress v4.3.1
89
+
90
+ = 0.7.2 =
91
+
92
+ * Update: WordPress 4.2.3 support
93
+ * Optimize: Autoload classes
94
+ * Optimize: Remove legacy PHP code
95
+
96
+ = 0.7.1 =
97
+
98
+ * Update: WordPress 3.8 support
99
+ * Update: Readme content (description, features, links)
100
+
101
+ = 0.7 =
102
+
103
+ * Update: WordPress 3.6 support
104
+ * Update: Remove deprecated jQuery code
105
+ * Update: Remove legacy shortcode code
106
+ * Optimize: Custom content types only added to main and CNR-initiated queries (Props Andy Owen)
107
+ * Optimize: Remove legacy files/code
108
+ * Optimize: Permalink structure reset when plugin activated/deactivated
109
+ * Optimize: Post Editor: Update permalink preview when section changed
110
+ * Optimize: Improved client-side file loading
111
+ * Optimize: Encapsulate client-side code
112
+ * Fix: Custom content types not displayed in sections (Unflappable Andy)
113
+
114
+ = 0.6 =
115
+
116
+ * Add: Content type support
117
+ * Add: Media support
118
+ * Add: Display tagline in home page title
119
+ * Optimize: Internal performance
120
+ * Optimize: Code cleanup
121
+ * Fix: Section not always saved
122
+ * Fix: Section posts fetched prior to checking posts' properties
123
+ * Fix: Pagination compatibility for WordPress 3.1+
124
+ * Fix: Various bug fixes
125
+
126
+ = 0.5.1b =
127
+
128
+ * Structure
129
+ * Fix: Section column duplication bug
130
+
131
+ = 0.5b =
132
+
133
+ * Public beta
screenshot-1.png ADDED
Binary file
screenshot-2.png ADDED
Binary file
screenshot-3.png ADDED
Binary file