Unyson - Version 2.5.1

Version Description

  • Fixed #1062, #1278, #1292, #1293, #1310, #1295, #767, #1322, #1323, #1321, #1054, #1309, #1347, #2777093, #1355, #1354, #1379, #1394, #1391, #1403
  • Fixes for WP 4.5 BackboneJS & UnderscoreJS latest version
Download this release

Release Info

Developer Unyson
Plugin Icon 128x128 Unyson
Version 2.5.1
Comparing to
See all releases

Code changes from version 2.5.0 to 2.5.1

Files changed (60) hide show
  1. CONTRIBUTING.md +0 -25
  2. README.md +0 -88
  3. framework/LICENSE +674 -674
  4. framework/bootstrap-helpers.php +130 -130
  5. framework/bootstrap.php +103 -101
  6. framework/core/Fw.php +91 -91
  7. framework/core/class-fw-manifest.php +509 -509
  8. framework/core/components/backend.php +2075 -2069
  9. framework/core/components/extensions.php +661 -655
  10. framework/core/components/extensions/class-fw-extension-default.php +11 -11
  11. framework/core/components/extensions/manager/available-extensions.php +273 -273
  12. framework/core/components/extensions/manager/class--fw-extensions-manager.php +3219 -3201
  13. framework/core/components/extensions/manager/includes/class--fw-extensions-delete-upgrader-skin.php +28 -28
  14. framework/core/components/extensions/manager/includes/class--fw-extensions-install-upgrader-skin.php +28 -28
  15. framework/core/components/extensions/manager/includes/parsedown/LICENSE.txt +19 -19
  16. framework/core/components/extensions/manager/includes/parsedown/Parsedown.php +1527 -1527
  17. framework/core/components/extensions/manager/static/extension-page.css +22 -22
  18. framework/core/components/extensions/manager/static/extension-page.js +2 -2
  19. framework/core/components/extensions/manager/static/extensions-page.css +236 -236
  20. framework/core/components/extensions/manager/static/extensions-page.js +107 -107
  21. framework/core/components/extensions/manager/static/unyson-font-icon/fonts/icomoon.svg +10 -10
  22. framework/core/components/extensions/manager/static/unyson-font-icon/style.css +28 -28
  23. framework/core/components/extensions/manager/views/delete-form.php +54 -54
  24. framework/core/components/extensions/manager/views/extension-page-header.php +50 -50
  25. framework/core/components/extensions/manager/views/extension.php +360 -360
  26. framework/core/components/extensions/manager/views/extensions-page.php +218 -218
  27. framework/core/components/extensions/manager/views/install-form.php +51 -51
  28. framework/core/components/theme.php +211 -211
  29. framework/core/extends/class-fw-container-type.php +232 -232
  30. framework/core/extends/class-fw-extension.php +507 -507
  31. framework/core/extends/class-fw-option-type.php +397 -385
  32. framework/core/extends/interface-fw-option-handler.php +12 -12
  33. framework/extensions/blog/class-fw-extension-blog.php +89 -89
  34. framework/extensions/blog/manifest.php +13 -13
  35. framework/extensions/update/class-fw-extension-update.php +982 -941
  36. framework/extensions/update/config.php +9 -9
  37. framework/extensions/update/extensions/github-update/class-fw-extension-github-update.php +443 -443
  38. framework/extensions/update/extensions/github-update/manifest.php +5 -5
  39. framework/extensions/update/includes/classes/class--fw-ext-update-extensions-list-table.php +128 -128
  40. framework/extensions/update/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php +36 -36
  41. framework/extensions/update/includes/classes/class--fw-ext-update-framework-upgrader-skin.php +33 -33
  42. framework/extensions/update/includes/classes/class--fw-ext-update-theme-upgrader-skin.php +33 -33
  43. framework/extensions/update/includes/extends/class-fw-ext-update-service.php +105 -105
  44. framework/extensions/update/manifest.php +10 -10
  45. framework/extensions/update/static.php +14 -14
  46. framework/extensions/update/static/css/admin-update-page.css +2 -2
  47. framework/extensions/update/views/updates-list.php +113 -113
  48. framework/helpers/class-fw-access-key.php +43 -43
  49. framework/helpers/class-fw-cache.php +282 -294
  50. framework/helpers/class-fw-dumper.php +123 -123
  51. framework/helpers/class-fw-flash-messages.php +312 -312
  52. framework/helpers/class-fw-form.php +585 -585
  53. framework/helpers/class-fw-request.php +90 -90
  54. framework/helpers/class-fw-resize.php +177 -177
  55. framework/helpers/class-fw-session.php +36 -36
  56. framework/helpers/class-fw-wp-filesystem.php +312 -312
  57. framework/helpers/class-fw-wp-list-table.php +968 -968
  58. framework/helpers/class-fw-wp-meta.php +113 -113
  59. framework/helpers/class-fw-wp-option.php +84 -84
  60. framework/helpers/database.php +597 -751
CONTRIBUTING.md DELETED
@@ -1,25 +0,0 @@
1
- # How to contribute
2
-
3
- ## Getting Started
4
-
5
- * Make sure you have a [GitHub account](https://github.com/signup/free)
6
- * Submit a ticket for your issue, assuming one does not already exist.
7
- * Clearly describe the issue including steps to reproduce when it is a bug.
8
- * Make sure you fill in the earliest version that you know has the issue.
9
-
10
- ## Making Changes
11
-
12
- * Fork the repository on GitHub.
13
- * Make the changes to your forked repository.
14
- * **Ensure you stick to the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/php/).**
15
- * Ensure you use LF line endings - no crazy windows line endings. :)
16
- * When committing, reference your issue (#1234) and include a note about the fix.
17
- * Push the changes to your fork and submit a pull request on the master branch of the Unyson repository. Existing maintenance branches will be maintained of by Unyson developers.
18
-
19
- At this point you're waiting on us to merge your pull request. We'll review all pull requests, and make suggestions and changes if necessary.
20
-
21
- # Additional Resources
22
-
23
- * [General GitHub documentation](http://help.github.com/)
24
- * [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
25
- * [Unyson Docs](http://manual.unyson.io/)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md DELETED
@@ -1,88 +0,0 @@
1
- # [Unyson](https://wordpress.org/plugins/unyson/) Framework [ ![Download](https://static.md/c1cea378c63f839fbede18687134423f.jpeg) ](https://github.com/ThemeFuse/Unyson/releases/latest)
2
-
3
- [Unyson](http://unyson.io/) is a framework for [WordPress](http://wordpress.org/) that facilitates the development of a theme.
4
-
5
- This framework was created from the ground up by the team behind [ThemeFuse](http://themefuse.com/) from the desire to empower developers to build outstanding WordPress themes fast and easy.
6
-
7
- If you are not a developer, please use the [Unyson plugin page](https://wordpress.org/plugins/unyson/) on WordPress.org.
8
-
9
- [![Build Status](https://travis-ci.org/ThemeFuse/Unyson.svg?branch=master)](https://travis-ci.org/ThemeFuse/Unyson) [![Stories in Ready](https://badge.waffle.io/ThemeFuse/Unyson.svg?label=ready&title=Ready)](http://waffle.io/ThemeFuse/Unyson)
10
-
11
- ## Table of contents
12
-
13
- * [Installation](#installation)
14
- * [Bug reports](#bug-reports)
15
- * [Documentation](#documentation)
16
- * [Copyright and license](#copyright-and-license)
17
- * [Contributing to Unyson](#contributing)
18
- * [Extensions](#extensions)
19
-
20
- ## Installation
21
-
22
- 1. [Download](https://github.com/ThemeFuse/Unyson/releases/latest) the latest stable release
23
- 2. Extract the archive to the `/wp-content/plugins/unyson/` directory
24
- 3. Activate the Unyson plugin through the 'Plugins' menu in WordPress
25
- 4. Configure the plugin by going to the Unyson menu that appears in your admin menu
26
-
27
- ## Bug reports
28
-
29
- We strive to make Unyson Development to be awesome and user friendly, though sometimes it's impossible to avoid bugs.
30
- A bug means "something is broken" or is not working as it should.
31
-
32
- In order to offer you an effective support and fix for an issue, please follow the below guidelines before submitting a bug report:
33
-
34
- #### Explore Known Issues
35
-
36
- Has your issue already been reported? Check the [Issues page](https://github.com/ThemeFuse/Unyson/issues).
37
-
38
- If your issue has already been reported, great! It will be reviewed in an upcoming release.
39
-
40
- #### Submitting a Bug Report
41
-
42
- You can report the issue via [Issues page](https://github.com/ThemeFuse/Unyson/issues).
43
- A good bug report includes full details to easily understand the issue you are having.
44
-
45
- ## Documentation
46
-
47
- Unyson's documentation is available on http://manual.unyson.io/.
48
-
49
- You can help us improve the documentation by contributing to this [Github repository](https://github.com/ThemeFuse/Unyson-Documentation).
50
-
51
- ## Copyright and license
52
-
53
- Code and documentation copyright 2014 ThemeFuse LTD. Code released under [the GPL license](https://github.com/ThemeFuse/Unyson/blob/master/framework/LICENSE). Docs released under [Creative Commons](https://github.com/ThemeFuse/Unyson-Documentation/blob/master/LICENSE).
54
-
55
- ## Contributing
56
-
57
- Developers can contribute to the source code. Please read our [contributor guidelines](https://github.com/ThemeFuse/Unyson/blob/master/CONTRIBUTING.md) for more information how you can do this.
58
-
59
- Translators can contribute new languages to Unyson through [Transifex](https://www.transifex.com/projects/p/unyson/).
60
-
61
- Theme developers can test the compatibility of their themes with new extensions updates before they are going to be released on [Unyson Extensions Approval](https://github.com/ThemeFuse/Unyson-Extensions-Approval).
62
-
63
- ## Extensions
64
-
65
- If you have a bug report or feature request related to a specific extension, follow one of the links below:
66
-
67
- * [Page Builder](https://github.com/ThemeFuse/Unyson-PageBuilder-Extension)
68
- * [Shortcodes](https://github.com/ThemeFuse/Unyson-Shortcodes-Extension)
69
- * [Mega Menu](https://github.com/ThemeFuse/Unyson-MegaMenu-Extension)
70
- * [Sidebars](https://github.com/ThemeFuse/Unyson-Sidebars-Extension)
71
- * [Sliders](https://github.com/ThemeFuse/Unyson-Sliders-Extension)
72
- * [Portfolio](https://github.com/ThemeFuse/Unyson-Portfolio-Extension)
73
- * [Backup & Demo Content](https://github.com/ThemeFuse/Unyson-Backups-Extension)
74
- * [SEO](https://github.com/ThemeFuse/Unyson-SEO-Extension)
75
- * [Forms](https://github.com/ThemeFuse/Unyson-Forms-Extension)
76
- * [Feedback](https://github.com/ThemeFuse/Unyson-Feedback-Extension)
77
- * [Breadcrumbs](https://github.com/ThemeFuse/Unyson-Breadcrumbs-Extension)
78
- * [Events](https://github.com/ThemeFuse/Unyson-Events-Extension)
79
- * [Update](https://github.com/ThemeFuse/Unyson-Update-Extension)
80
- * [Analytics](https://github.com/ThemeFuse/Unyson-Analytics-Extension)
81
- * [Builder](https://github.com/ThemeFuse/Unyson-Builder-Extension)
82
- * [Mailer](https://github.com/ThemeFuse/Unyson-Mailer-Extension)
83
- * [Social](https://github.com/ThemeFuse/Unyson-Social-Extension)
84
- * [Population Methods](https://github.com/ThemeFuse/Unyson-PopulationMethods-Extension)
85
- * [Blog Posts](https://github.com/ThemeFuse/Unyson-Blog-Extension)
86
- * [Learning](https://github.com/ThemeFuse/Unyson-Learning-Extension)
87
- * [Translation](https://github.com/ThemeFuse/Unyson-Translation-Extension)
88
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
framework/LICENSE CHANGED
@@ -1,674 +1,674 @@
1
- GNU GENERAL PUBLIC LICENSE
2
- Version 3, 29 June 2007
3
-
4
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
- Everyone is permitted to copy and distribute verbatim copies
6
- of this license document, but changing it is not allowed.
7
-
8
- Preamble
9
-
10
- The GNU General Public License is a free, copyleft license for
11
- software and other kinds of works.
12
-
13
- The licenses for most software and other practical works are designed
14
- to take away your freedom to share and change the works. By contrast,
15
- the GNU General Public License is intended to guarantee your freedom to
16
- share and change all versions of a program--to make sure it remains free
17
- software for all its users. We, the Free Software Foundation, use the
18
- GNU General Public License for most of our software; it applies also to
19
- any other work released this way by its authors. You can apply it to
20
- your programs, too.
21
-
22
- When we speak of free software, we are referring to freedom, not
23
- price. Our General Public Licenses are designed to make sure that you
24
- have the freedom to distribute copies of free software (and charge for
25
- them if you wish), that you receive source code or can get it if you
26
- want it, that you can change the software or use pieces of it in new
27
- free programs, and that you know you can do these things.
28
-
29
- To protect your rights, we need to prevent others from denying you
30
- these rights or asking you to surrender the rights. Therefore, you have
31
- certain responsibilities if you distribute copies of the software, or if
32
- you modify it: responsibilities to respect the freedom of others.
33
-
34
- For example, if you distribute copies of such a program, whether
35
- gratis or for a fee, you must pass on to the recipients the same
36
- freedoms that you received. You must make sure that they, too, receive
37
- or can get the source code. And you must show them these terms so they
38
- know their rights.
39
-
40
- Developers that use the GNU GPL protect your rights with two steps:
41
- (1) assert copyright on the software, and (2) offer you this License
42
- giving you legal permission to copy, distribute and/or modify it.
43
-
44
- For the developers' and authors' protection, the GPL clearly explains
45
- that there is no warranty for this free software. For both users' and
46
- authors' sake, the GPL requires that modified versions be marked as
47
- changed, so that their problems will not be attributed erroneously to
48
- authors of previous versions.
49
-
50
- Some devices are designed to deny users access to install or run
51
- modified versions of the software inside them, although the manufacturer
52
- can do so. This is fundamentally incompatible with the aim of
53
- protecting users' freedom to change the software. The systematic
54
- pattern of such abuse occurs in the area of products for individuals to
55
- use, which is precisely where it is most unacceptable. Therefore, we
56
- have designed this version of the GPL to prohibit the practice for those
57
- products. If such problems arise substantially in other domains, we
58
- stand ready to extend this provision to those domains in future versions
59
- of the GPL, as needed to protect the freedom of users.
60
-
61
- Finally, every program is threatened constantly by software patents.
62
- States should not allow patents to restrict development and use of
63
- software on general-purpose computers, but in those that do, we wish to
64
- avoid the special danger that patents applied to a free program could
65
- make it effectively proprietary. To prevent this, the GPL assures that
66
- patents cannot be used to render the program non-free.
67
-
68
- The precise terms and conditions for copying, distribution and
69
- modification follow.
70
-
71
- TERMS AND CONDITIONS
72
-
73
- 0. Definitions.
74
-
75
- "This License" refers to version 3 of the GNU General Public License.
76
-
77
- "Copyright" also means copyright-like laws that apply to other kinds of
78
- works, such as semiconductor masks.
79
-
80
- "The Program" refers to any copyrightable work licensed under this
81
- License. Each licensee is addressed as "you". "Licensees" and
82
- "recipients" may be individuals or organizations.
83
-
84
- To "modify" a work means to copy from or adapt all or part of the work
85
- in a fashion requiring copyright permission, other than the making of an
86
- exact copy. The resulting work is called a "modified version" of the
87
- earlier work or a work "based on" the earlier work.
88
-
89
- A "covered work" means either the unmodified Program or a work based
90
- on the Program.
91
-
92
- To "propagate" a work means to do anything with it that, without
93
- permission, would make you directly or secondarily liable for
94
- infringement under applicable copyright law, except executing it on a
95
- computer or modifying a private copy. Propagation includes copying,
96
- distribution (with or without modification), making available to the
97
- public, and in some countries other activities as well.
98
-
99
- To "convey" a work means any kind of propagation that enables other
100
- parties to make or receive copies. Mere interaction with a user through
101
- a computer network, with no transfer of a copy, is not conveying.
102
-
103
- An interactive user interface displays "Appropriate Legal Notices"
104
- to the extent that it includes a convenient and prominently visible
105
- feature that (1) displays an appropriate copyright notice, and (2)
106
- tells the user that there is no warranty for the work (except to the
107
- extent that warranties are provided), that licensees may convey the
108
- work under this License, and how to view a copy of this License. If
109
- the interface presents a list of user commands or options, such as a
110
- menu, a prominent item in the list meets this criterion.
111
-
112
- 1. Source Code.
113
-
114
- The "source code" for a work means the preferred form of the work
115
- for making modifications to it. "Object code" means any non-source
116
- form of a work.
117
-
118
- A "Standard Interface" means an interface that either is an official
119
- standard defined by a recognized standards body, or, in the case of
120
- interfaces specified for a particular programming language, one that
121
- is widely used among developers working in that language.
122
-
123
- The "System Libraries" of an executable work include anything, other
124
- than the work as a whole, that (a) is included in the normal form of
125
- packaging a Major Component, but which is not part of that Major
126
- Component, and (b) serves only to enable use of the work with that
127
- Major Component, or to implement a Standard Interface for which an
128
- implementation is available to the public in source code form. A
129
- "Major Component", in this context, means a major essential component
130
- (kernel, window system, and so on) of the specific operating system
131
- (if any) on which the executable work runs, or a compiler used to
132
- produce the work, or an object code interpreter used to run it.
133
-
134
- The "Corresponding Source" for a work in object code form means all
135
- the source code needed to generate, install, and (for an executable
136
- work) run the object code and to modify the work, including scripts to
137
- control those activities. However, it does not include the work's
138
- System Libraries, or general-purpose tools or generally available free
139
- programs which are used unmodified in performing those activities but
140
- which are not part of the work. For example, Corresponding Source
141
- includes interface definition files associated with source files for
142
- the work, and the source code for shared libraries and dynamically
143
- linked subprograms that the work is specifically designed to require,
144
- such as by intimate data communication or control flow between those
145
- subprograms and other parts of the work.
146
-
147
- The Corresponding Source need not include anything that users
148
- can regenerate automatically from other parts of the Corresponding
149
- Source.
150
-
151
- The Corresponding Source for a work in source code form is that
152
- same work.
153
-
154
- 2. Basic Permissions.
155
-
156
- All rights granted under this License are granted for the term of
157
- copyright on the Program, and are irrevocable provided the stated
158
- conditions are met. This License explicitly affirms your unlimited
159
- permission to run the unmodified Program. The output from running a
160
- covered work is covered by this License only if the output, given its
161
- content, constitutes a covered work. This License acknowledges your
162
- rights of fair use or other equivalent, as provided by copyright law.
163
-
164
- You may make, run and propagate covered works that you do not
165
- convey, without conditions so long as your license otherwise remains
166
- in force. You may convey covered works to others for the sole purpose
167
- of having them make modifications exclusively for you, or provide you
168
- with facilities for running those works, provided that you comply with
169
- the terms of this License in conveying all material for which you do
170
- not control copyright. Those thus making or running the covered works
171
- for you must do so exclusively on your behalf, under your direction
172
- and control, on terms that prohibit them from making any copies of
173
- your copyrighted material outside their relationship with you.
174
-
175
- Conveying under any other circumstances is permitted solely under
176
- the conditions stated below. Sublicensing is not allowed; section 10
177
- makes it unnecessary.
178
-
179
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
-
181
- No covered work shall be deemed part of an effective technological
182
- measure under any applicable law fulfilling obligations under article
183
- 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
- similar laws prohibiting or restricting circumvention of such
185
- measures.
186
-
187
- When you convey a covered work, you waive any legal power to forbid
188
- circumvention of technological measures to the extent such circumvention
189
- is effected by exercising rights under this License with respect to
190
- the covered work, and you disclaim any intention to limit operation or
191
- modification of the work as a means of enforcing, against the work's
192
- users, your or third parties' legal rights to forbid circumvention of
193
- technological measures.
194
-
195
- 4. Conveying Verbatim Copies.
196
-
197
- You may convey verbatim copies of the Program's source code as you
198
- receive it, in any medium, provided that you conspicuously and
199
- appropriately publish on each copy an appropriate copyright notice;
200
- keep intact all notices stating that this License and any
201
- non-permissive terms added in accord with section 7 apply to the code;
202
- keep intact all notices of the absence of any warranty; and give all
203
- recipients a copy of this License along with the Program.
204
-
205
- You may charge any price or no price for each copy that you convey,
206
- and you may offer support or warranty protection for a fee.
207
-
208
- 5. Conveying Modified Source Versions.
209
-
210
- You may convey a work based on the Program, or the modifications to
211
- produce it from the Program, in the form of source code under the
212
- terms of section 4, provided that you also meet all of these conditions:
213
-
214
- a) The work must carry prominent notices stating that you modified
215
- it, and giving a relevant date.
216
-
217
- b) The work must carry prominent notices stating that it is
218
- released under this License and any conditions added under section
219
- 7. This requirement modifies the requirement in section 4 to
220
- "keep intact all notices".
221
-
222
- c) You must license the entire work, as a whole, under this
223
- License to anyone who comes into possession of a copy. This
224
- License will therefore apply, along with any applicable section 7
225
- additional terms, to the whole of the work, and all its parts,
226
- regardless of how they are packaged. This License gives no
227
- permission to license the work in any other way, but it does not
228
- invalidate such permission if you have separately received it.
229
-
230
- d) If the work has interactive user interfaces, each must display
231
- Appropriate Legal Notices; however, if the Program has interactive
232
- interfaces that do not display Appropriate Legal Notices, your
233
- work need not make them do so.
234
-
235
- A compilation of a covered work with other separate and independent
236
- works, which are not by their nature extensions of the covered work,
237
- and which are not combined with it such as to form a larger program,
238
- in or on a volume of a storage or distribution medium, is called an
239
- "aggregate" if the compilation and its resulting copyright are not
240
- used to limit the access or legal rights of the compilation's users
241
- beyond what the individual works permit. Inclusion of a covered work
242
- in an aggregate does not cause this License to apply to the other
243
- parts of the aggregate.
244
-
245
- 6. Conveying Non-Source Forms.
246
-
247
- You may convey a covered work in object code form under the terms
248
- of sections 4 and 5, provided that you also convey the
249
- machine-readable Corresponding Source under the terms of this License,
250
- in one of these ways:
251
-
252
- a) Convey the object code in, or embodied in, a physical product
253
- (including a physical distribution medium), accompanied by the
254
- Corresponding Source fixed on a durable physical medium
255
- customarily used for software interchange.
256
-
257
- b) Convey the object code in, or embodied in, a physical product
258
- (including a physical distribution medium), accompanied by a
259
- written offer, valid for at least three years and valid for as
260
- long as you offer spare parts or customer support for that product
261
- model, to give anyone who possesses the object code either (1) a
262
- copy of the Corresponding Source for all the software in the
263
- product that is covered by this License, on a durable physical
264
- medium customarily used for software interchange, for a price no
265
- more than your reasonable cost of physically performing this
266
- conveying of source, or (2) access to copy the
267
- Corresponding Source from a network server at no charge.
268
-
269
- c) Convey individual copies of the object code with a copy of the
270
- written offer to provide the Corresponding Source. This
271
- alternative is allowed only occasionally and noncommercially, and
272
- only if you received the object code with such an offer, in accord
273
- with subsection 6b.
274
-
275
- d) Convey the object code by offering access from a designated
276
- place (gratis or for a charge), and offer equivalent access to the
277
- Corresponding Source in the same way through the same place at no
278
- further charge. You need not require recipients to copy the
279
- Corresponding Source along with the object code. If the place to
280
- copy the object code is a network server, the Corresponding Source
281
- may be on a different server (operated by you or a third party)
282
- that supports equivalent copying facilities, provided you maintain
283
- clear directions next to the object code saying where to find the
284
- Corresponding Source. Regardless of what server hosts the
285
- Corresponding Source, you remain obligated to ensure that it is
286
- available for as long as needed to satisfy these requirements.
287
-
288
- e) Convey the object code using peer-to-peer transmission, provided
289
- you inform other peers where the object code and Corresponding
290
- Source of the work are being offered to the general public at no
291
- charge under subsection 6d.
292
-
293
- A separable portion of the object code, whose source code is excluded
294
- from the Corresponding Source as a System Library, need not be
295
- included in conveying the object code work.
296
-
297
- A "User Product" is either (1) a "consumer product", which means any
298
- tangible personal property which is normally used for personal, family,
299
- or household purposes, or (2) anything designed or sold for incorporation
300
- into a dwelling. In determining whether a product is a consumer product,
301
- doubtful cases shall be resolved in favor of coverage. For a particular
302
- product received by a particular user, "normally used" refers to a
303
- typical or common use of that class of product, regardless of the status
304
- of the particular user or of the way in which the particular user
305
- actually uses, or expects or is expected to use, the product. A product
306
- is a consumer product regardless of whether the product has substantial
307
- commercial, industrial or non-consumer uses, unless such uses represent
308
- the only significant mode of use of the product.
309
-
310
- "Installation Information" for a User Product means any methods,
311
- procedures, authorization keys, or other information required to install
312
- and execute modified versions of a covered work in that User Product from
313
- a modified version of its Corresponding Source. The information must
314
- suffice to ensure that the continued functioning of the modified object
315
- code is in no case prevented or interfered with solely because
316
- modification has been made.
317
-
318
- If you convey an object code work under this section in, or with, or
319
- specifically for use in, a User Product, and the conveying occurs as
320
- part of a transaction in which the right of possession and use of the
321
- User Product is transferred to the recipient in perpetuity or for a
322
- fixed term (regardless of how the transaction is characterized), the
323
- Corresponding Source conveyed under this section must be accompanied
324
- by the Installation Information. But this requirement does not apply
325
- if neither you nor any third party retains the ability to install
326
- modified object code on the User Product (for example, the work has
327
- been installed in ROM).
328
-
329
- The requirement to provide Installation Information does not include a
330
- requirement to continue to provide support service, warranty, or updates
331
- for a work that has been modified or installed by the recipient, or for
332
- the User Product in which it has been modified or installed. Access to a
333
- network may be denied when the modification itself materially and
334
- adversely affects the operation of the network or violates the rules and
335
- protocols for communication across the network.
336
-
337
- Corresponding Source conveyed, and Installation Information provided,
338
- in accord with this section must be in a format that is publicly
339
- documented (and with an implementation available to the public in
340
- source code form), and must require no special password or key for
341
- unpacking, reading or copying.
342
-
343
- 7. Additional Terms.
344
-
345
- "Additional permissions" are terms that supplement the terms of this
346
- License by making exceptions from one or more of its conditions.
347
- Additional permissions that are applicable to the entire Program shall
348
- be treated as though they were included in this License, to the extent
349
- that they are valid under applicable law. If additional permissions
350
- apply only to part of the Program, that part may be used separately
351
- under those permissions, but the entire Program remains governed by
352
- this License without regard to the additional permissions.
353
-
354
- When you convey a copy of a covered work, you may at your option
355
- remove any additional permissions from that copy, or from any part of
356
- it. (Additional permissions may be written to require their own
357
- removal in certain cases when you modify the work.) You may place
358
- additional permissions on material, added by you to a covered work,
359
- for which you have or can give appropriate copyright permission.
360
-
361
- Notwithstanding any other provision of this License, for material you
362
- add to a covered work, you may (if authorized by the copyright holders of
363
- that material) supplement the terms of this License with terms:
364
-
365
- a) Disclaiming warranty or limiting liability differently from the
366
- terms of sections 15 and 16 of this License; or
367
-
368
- b) Requiring preservation of specified reasonable legal notices or
369
- author attributions in that material or in the Appropriate Legal
370
- Notices displayed by works containing it; or
371
-
372
- c) Prohibiting misrepresentation of the origin of that material, or
373
- requiring that modified versions of such material be marked in
374
- reasonable ways as different from the original version; or
375
-
376
- d) Limiting the use for publicity purposes of names of licensors or
377
- authors of the material; or
378
-
379
- e) Declining to grant rights under trademark law for use of some
380
- trade names, trademarks, or service marks; or
381
-
382
- f) Requiring indemnification of licensors and authors of that
383
- material by anyone who conveys the material (or modified versions of
384
- it) with contractual assumptions of liability to the recipient, for
385
- any liability that these contractual assumptions directly impose on
386
- those licensors and authors.
387
-
388
- All other non-permissive additional terms are considered "further
389
- restrictions" within the meaning of section 10. If the Program as you
390
- received it, or any part of it, contains a notice stating that it is
391
- governed by this License along with a term that is a further
392
- restriction, you may remove that term. If a license document contains
393
- a further restriction but permits relicensing or conveying under this
394
- License, you may add to a covered work material governed by the terms
395
- of that license document, provided that the further restriction does
396
- not survive such relicensing or conveying.
397
-
398
- If you add terms to a covered work in accord with this section, you
399
- must place, in the relevant source files, a statement of the
400
- additional terms that apply to those files, or a notice indicating
401
- where to find the applicable terms.
402
-
403
- Additional terms, permissive or non-permissive, may be stated in the
404
- form of a separately written license, or stated as exceptions;
405
- the above requirements apply either way.
406
-
407
- 8. Termination.
408
-
409
- You may not propagate or modify a covered work except as expressly
410
- provided under this License. Any attempt otherwise to propagate or
411
- modify it is void, and will automatically terminate your rights under
412
- this License (including any patent licenses granted under the third
413
- paragraph of section 11).
414
-
415
- However, if you cease all violation of this License, then your
416
- license from a particular copyright holder is reinstated (a)
417
- provisionally, unless and until the copyright holder explicitly and
418
- finally terminates your license, and (b) permanently, if the copyright
419
- holder fails to notify you of the violation by some reasonable means
420
- prior to 60 days after the cessation.
421
-
422
- Moreover, your license from a particular copyright holder is
423
- reinstated permanently if the copyright holder notifies you of the
424
- violation by some reasonable means, this is the first time you have
425
- received notice of violation of this License (for any work) from that
426
- copyright holder, and you cure the violation prior to 30 days after
427
- your receipt of the notice.
428
-
429
- Termination of your rights under this section does not terminate the
430
- licenses of parties who have received copies or rights from you under
431
- this License. If your rights have been terminated and not permanently
432
- reinstated, you do not qualify to receive new licenses for the same
433
- material under section 10.
434
-
435
- 9. Acceptance Not Required for Having Copies.
436
-
437
- You are not required to accept this License in order to receive or
438
- run a copy of the Program. Ancillary propagation of a covered work
439
- occurring solely as a consequence of using peer-to-peer transmission
440
- to receive a copy likewise does not require acceptance. However,
441
- nothing other than this License grants you permission to propagate or
442
- modify any covered work. These actions infringe copyright if you do
443
- not accept this License. Therefore, by modifying or propagating a
444
- covered work, you indicate your acceptance of this License to do so.
445
-
446
- 10. Automatic Licensing of Downstream Recipients.
447
-
448
- Each time you convey a covered work, the recipient automatically
449
- receives a license from the original licensors, to run, modify and
450
- propagate that work, subject to this License. You are not responsible
451
- for enforcing compliance by third parties with this License.
452
-
453
- An "entity transaction" is a transaction transferring control of an
454
- organization, or substantially all assets of one, or subdividing an
455
- organization, or merging organizations. If propagation of a covered
456
- work results from an entity transaction, each party to that
457
- transaction who receives a copy of the work also receives whatever
458
- licenses to the work the party's predecessor in interest had or could
459
- give under the previous paragraph, plus a right to possession of the
460
- Corresponding Source of the work from the predecessor in interest, if
461
- the predecessor has it or can get it with reasonable efforts.
462
-
463
- You may not impose any further restrictions on the exercise of the
464
- rights granted or affirmed under this License. For example, you may
465
- not impose a license fee, royalty, or other charge for exercise of
466
- rights granted under this License, and you may not initiate litigation
467
- (including a cross-claim or counterclaim in a lawsuit) alleging that
468
- any patent claim is infringed by making, using, selling, offering for
469
- sale, or importing the Program or any portion of it.
470
-
471
- 11. Patents.
472
-
473
- A "contributor" is a copyright holder who authorizes use under this
474
- License of the Program or a work on which the Program is based. The
475
- work thus licensed is called the contributor's "contributor version".
476
-
477
- A contributor's "essential patent claims" are all patent claims
478
- owned or controlled by the contributor, whether already acquired or
479
- hereafter acquired, that would be infringed by some manner, permitted
480
- by this License, of making, using, or selling its contributor version,
481
- but do not include claims that would be infringed only as a
482
- consequence of further modification of the contributor version. For
483
- purposes of this definition, "control" includes the right to grant
484
- patent sublicenses in a manner consistent with the requirements of
485
- this License.
486
-
487
- Each contributor grants you a non-exclusive, worldwide, royalty-free
488
- patent license under the contributor's essential patent claims, to
489
- make, use, sell, offer for sale, import and otherwise run, modify and
490
- propagate the contents of its contributor version.
491
-
492
- In the following three paragraphs, a "patent license" is any express
493
- agreement or commitment, however denominated, not to enforce a patent
494
- (such as an express permission to practice a patent or covenant not to
495
- sue for patent infringement). To "grant" such a patent license to a
496
- party means to make such an agreement or commitment not to enforce a
497
- patent against the party.
498
-
499
- If you convey a covered work, knowingly relying on a patent license,
500
- and the Corresponding Source of the work is not available for anyone
501
- to copy, free of charge and under the terms of this License, through a
502
- publicly available network server or other readily accessible means,
503
- then you must either (1) cause the Corresponding Source to be so
504
- available, or (2) arrange to deprive yourself of the benefit of the
505
- patent license for this particular work, or (3) arrange, in a manner
506
- consistent with the requirements of this License, to extend the patent
507
- license to downstream recipients. "Knowingly relying" means you have
508
- actual knowledge that, but for the patent license, your conveying the
509
- covered work in a country, or your recipient's use of the covered work
510
- in a country, would infringe one or more identifiable patents in that
511
- country that you have reason to believe are valid.
512
-
513
- If, pursuant to or in connection with a single transaction or
514
- arrangement, you convey, or propagate by procuring conveyance of, a
515
- covered work, and grant a patent license to some of the parties
516
- receiving the covered work authorizing them to use, propagate, modify
517
- or convey a specific copy of the covered work, then the patent license
518
- you grant is automatically extended to all recipients of the covered
519
- work and works based on it.
520
-
521
- A patent license is "discriminatory" if it does not include within
522
- the scope of its coverage, prohibits the exercise of, or is
523
- conditioned on the non-exercise of one or more of the rights that are
524
- specifically granted under this License. You may not convey a covered
525
- work if you are a party to an arrangement with a third party that is
526
- in the business of distributing software, under which you make payment
527
- to the third party based on the extent of your activity of conveying
528
- the work, and under which the third party grants, to any of the
529
- parties who would receive the covered work from you, a discriminatory
530
- patent license (a) in connection with copies of the covered work
531
- conveyed by you (or copies made from those copies), or (b) primarily
532
- for and in connection with specific products or compilations that
533
- contain the covered work, unless you entered into that arrangement,
534
- or that patent license was granted, prior to 28 March 2007.
535
-
536
- Nothing in this License shall be construed as excluding or limiting
537
- any implied license or other defenses to infringement that may
538
- otherwise be available to you under applicable patent law.
539
-
540
- 12. No Surrender of Others' Freedom.
541
-
542
- If conditions are imposed on you (whether by court order, agreement or
543
- otherwise) that contradict the conditions of this License, they do not
544
- excuse you from the conditions of this License. If you cannot convey a
545
- covered work so as to satisfy simultaneously your obligations under this
546
- License and any other pertinent obligations, then as a consequence you may
547
- not convey it at all. For example, if you agree to terms that obligate you
548
- to collect a royalty for further conveying from those to whom you convey
549
- the Program, the only way you could satisfy both those terms and this
550
- License would be to refrain entirely from conveying the Program.
551
-
552
- 13. Use with the GNU Affero General Public License.
553
-
554
- Notwithstanding any other provision of this License, you have
555
- permission to link or combine any covered work with a work licensed
556
- under version 3 of the GNU Affero General Public License into a single
557
- combined work, and to convey the resulting work. The terms of this
558
- License will continue to apply to the part which is the covered work,
559
- but the special requirements of the GNU Affero General Public License,
560
- section 13, concerning interaction through a network will apply to the
561
- combination as such.
562
-
563
- 14. Revised Versions of this License.
564
-
565
- The Free Software Foundation may publish revised and/or new versions of
566
- the GNU General Public License from time to time. Such new versions will
567
- be similar in spirit to the present version, but may differ in detail to
568
- address new problems or concerns.
569
-
570
- Each version is given a distinguishing version number. If the
571
- Program specifies that a certain numbered version of the GNU General
572
- Public License "or any later version" applies to it, you have the
573
- option of following the terms and conditions either of that numbered
574
- version or of any later version published by the Free Software
575
- Foundation. If the Program does not specify a version number of the
576
- GNU General Public License, you may choose any version ever published
577
- by the Free Software Foundation.
578
-
579
- If the Program specifies that a proxy can decide which future
580
- versions of the GNU General Public License can be used, that proxy's
581
- public statement of acceptance of a version permanently authorizes you
582
- to choose that version for the Program.
583
-
584
- Later license versions may give you additional or different
585
- permissions. However, no additional obligations are imposed on any
586
- author or copyright holder as a result of your choosing to follow a
587
- later version.
588
-
589
- 15. Disclaimer of Warranty.
590
-
591
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
- APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
- HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
- OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
- PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
- IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
- ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
-
600
- 16. Limitation of Liability.
601
-
602
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
- WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
- THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
- GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
- USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
- DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
- PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
- EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
- SUCH DAMAGES.
611
-
612
- 17. Interpretation of Sections 15 and 16.
613
-
614
- If the disclaimer of warranty and limitation of liability provided
615
- above cannot be given local legal effect according to their terms,
616
- reviewing courts shall apply local law that most closely approximates
617
- an absolute waiver of all civil liability in connection with the
618
- Program, unless a warranty or assumption of liability accompanies a
619
- copy of the Program in return for a fee.
620
-
621
- END OF TERMS AND CONDITIONS
622
-
623
- How to Apply These Terms to Your New Programs
624
-
625
- If you develop a new program, and you want it to be of the greatest
626
- possible use to the public, the best way to achieve this is to make it
627
- free software which everyone can redistribute and change under these terms.
628
-
629
- To do so, attach the following notices to the program. It is safest
630
- to attach them to the start of each source file to most effectively
631
- state the exclusion of warranty; and each file should have at least
632
- the "copyright" line and a pointer to where the full notice is found.
633
-
634
- {one line to give the program's name and a brief idea of what it does.}
635
- Copyright (C) {year} {name of author}
636
-
637
- This program is free software: you can redistribute it and/or modify
638
- it under the terms of the GNU General Public License as published by
639
- the Free Software Foundation, either version 3 of the License, or
640
- (at your option) any later version.
641
-
642
- This program is distributed in the hope that it will be useful,
643
- but WITHOUT ANY WARRANTY; without even the implied warranty of
644
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
- GNU General Public License for more details.
646
-
647
- You should have received a copy of the GNU General Public License
648
- along with this program. If not, see <http://www.gnu.org/licenses/>.
649
-
650
- Also add information on how to contact you by electronic and paper mail.
651
-
652
- If the program does terminal interaction, make it output a short
653
- notice like this when it starts in an interactive mode:
654
-
655
- {project} Copyright (C) {year} {fullname}
656
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
- This is free software, and you are welcome to redistribute it
658
- under certain conditions; type `show c' for details.
659
-
660
- The hypothetical commands `show w' and `show c' should show the appropriate
661
- parts of the General Public License. Of course, your program's commands
662
- might be different; for a GUI interface, you would use an "about box".
663
-
664
- You should also get your employer (if you work as a programmer) or school,
665
- if any, to sign a "copyright disclaimer" for the program, if necessary.
666
- For more information on this, and how to apply and follow the GNU GPL, see
667
- <http://www.gnu.org/licenses/>.
668
-
669
- The GNU General Public License does not permit incorporating your program
670
- into proprietary programs. If your program is a subroutine library, you
671
- may consider it more useful to permit linking proprietary applications with
672
- the library. If this is what you want to do, use the GNU Lesser General
673
- Public License instead of this License. But first, please read
674
- <http://www.gnu.org/philosophy/why-not-lgpl.html>.
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must pass on to the recipients the same
36
+ freedoms that you received. You must make sure that they, too, receive
37
+ or can get the source code. And you must show them these terms so they
38
+ know their rights.
39
+
40
+ Developers that use the GNU GPL protect your rights with two steps:
41
+ (1) assert copyright on the software, and (2) offer you this License
42
+ giving you legal permission to copy, distribute and/or modify it.
43
+
44
+ For the developers' and authors' protection, the GPL clearly explains
45
+ that there is no warranty for this free software. For both users' and
46
+ authors' sake, the GPL requires that modified versions be marked as
47
+ changed, so that their problems will not be attributed erroneously to
48
+ authors of previous versions.
49
+
50
+ Some devices are designed to deny users access to install or run
51
+ modified versions of the software inside them, although the manufacturer
52
+ can do so. This is fundamentally incompatible with the aim of
53
+ protecting users' freedom to change the software. The systematic
54
+ pattern of such abuse occurs in the area of products for individuals to
55
+ use, which is precisely where it is most unacceptable. Therefore, we
56
+ have designed this version of the GPL to prohibit the practice for those
57
+ products. If such problems arise substantially in other domains, we
58
+ stand ready to extend this provision to those domains in future versions
59
+ of the GPL, as needed to protect the freedom of users.
60
+
61
+ Finally, every program is threatened constantly by software patents.
62
+ States should not allow patents to restrict development and use of
63
+ software on general-purpose computers, but in those that do, we wish to
64
+ avoid the special danger that patents applied to a free program could
65
+ make it effectively proprietary. To prevent this, the GPL assures that
66
+ patents cannot be used to render the program non-free.
67
+
68
+ The precise terms and conditions for copying, distribution and
69
+ modification follow.
70
+
71
+ TERMS AND CONDITIONS
72
+
73
+ 0. Definitions.
74
+
75
+ "This License" refers to version 3 of the GNU General Public License.
76
+
77
+ "Copyright" also means copyright-like laws that apply to other kinds of
78
+ works, such as semiconductor masks.
79
+
80
+ "The Program" refers to any copyrightable work licensed under this
81
+ License. Each licensee is addressed as "you". "Licensees" and
82
+ "recipients" may be individuals or organizations.
83
+
84
+ To "modify" a work means to copy from or adapt all or part of the work
85
+ in a fashion requiring copyright permission, other than the making of an
86
+ exact copy. The resulting work is called a "modified version" of the
87
+ earlier work or a work "based on" the earlier work.
88
+
89
+ A "covered work" means either the unmodified Program or a work based
90
+ on the Program.
91
+
92
+ To "propagate" a work means to do anything with it that, without
93
+ permission, would make you directly or secondarily liable for
94
+ infringement under applicable copyright law, except executing it on a
95
+ computer or modifying a private copy. Propagation includes copying,
96
+ distribution (with or without modification), making available to the
97
+ public, and in some countries other activities as well.
98
+
99
+ To "convey" a work means any kind of propagation that enables other
100
+ parties to make or receive copies. Mere interaction with a user through
101
+ a computer network, with no transfer of a copy, is not conveying.
102
+
103
+ An interactive user interface displays "Appropriate Legal Notices"
104
+ to the extent that it includes a convenient and prominently visible
105
+ feature that (1) displays an appropriate copyright notice, and (2)
106
+ tells the user that there is no warranty for the work (except to the
107
+ extent that warranties are provided), that licensees may convey the
108
+ work under this License, and how to view a copy of this License. If
109
+ the interface presents a list of user commands or options, such as a
110
+ menu, a prominent item in the list meets this criterion.
111
+
112
+ 1. Source Code.
113
+
114
+ The "source code" for a work means the preferred form of the work
115
+ for making modifications to it. "Object code" means any non-source
116
+ form of a work.
117
+
118
+ A "Standard Interface" means an interface that either is an official
119
+ standard defined by a recognized standards body, or, in the case of
120
+ interfaces specified for a particular programming language, one that
121
+ is widely used among developers working in that language.
122
+
123
+ The "System Libraries" of an executable work include anything, other
124
+ than the work as a whole, that (a) is included in the normal form of
125
+ packaging a Major Component, but which is not part of that Major
126
+ Component, and (b) serves only to enable use of the work with that
127
+ Major Component, or to implement a Standard Interface for which an
128
+ implementation is available to the public in source code form. A
129
+ "Major Component", in this context, means a major essential component
130
+ (kernel, window system, and so on) of the specific operating system
131
+ (if any) on which the executable work runs, or a compiler used to
132
+ produce the work, or an object code interpreter used to run it.
133
+
134
+ The "Corresponding Source" for a work in object code form means all
135
+ the source code needed to generate, install, and (for an executable
136
+ work) run the object code and to modify the work, including scripts to
137
+ control those activities. However, it does not include the work's
138
+ System Libraries, or general-purpose tools or generally available free
139
+ programs which are used unmodified in performing those activities but
140
+ which are not part of the work. For example, Corresponding Source
141
+ includes interface definition files associated with source files for
142
+ the work, and the source code for shared libraries and dynamically
143
+ linked subprograms that the work is specifically designed to require,
144
+ such as by intimate data communication or control flow between those
145
+ subprograms and other parts of the work.
146
+
147
+ The Corresponding Source need not include anything that users
148
+ can regenerate automatically from other parts of the Corresponding
149
+ Source.
150
+
151
+ The Corresponding Source for a work in source code form is that
152
+ same work.
153
+
154
+ 2. Basic Permissions.
155
+
156
+ All rights granted under this License are granted for the term of
157
+ copyright on the Program, and are irrevocable provided the stated
158
+ conditions are met. This License explicitly affirms your unlimited
159
+ permission to run the unmodified Program. The output from running a
160
+ covered work is covered by this License only if the output, given its
161
+ content, constitutes a covered work. This License acknowledges your
162
+ rights of fair use or other equivalent, as provided by copyright law.
163
+
164
+ You may make, run and propagate covered works that you do not
165
+ convey, without conditions so long as your license otherwise remains
166
+ in force. You may convey covered works to others for the sole purpose
167
+ of having them make modifications exclusively for you, or provide you
168
+ with facilities for running those works, provided that you comply with
169
+ the terms of this License in conveying all material for which you do
170
+ not control copyright. Those thus making or running the covered works
171
+ for you must do so exclusively on your behalf, under your direction
172
+ and control, on terms that prohibit them from making any copies of
173
+ your copyrighted material outside their relationship with you.
174
+
175
+ Conveying under any other circumstances is permitted solely under
176
+ the conditions stated below. Sublicensing is not allowed; section 10
177
+ makes it unnecessary.
178
+
179
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
+
181
+ No covered work shall be deemed part of an effective technological
182
+ measure under any applicable law fulfilling obligations under article
183
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
+ similar laws prohibiting or restricting circumvention of such
185
+ measures.
186
+
187
+ When you convey a covered work, you waive any legal power to forbid
188
+ circumvention of technological measures to the extent such circumvention
189
+ is effected by exercising rights under this License with respect to
190
+ the covered work, and you disclaim any intention to limit operation or
191
+ modification of the work as a means of enforcing, against the work's
192
+ users, your or third parties' legal rights to forbid circumvention of
193
+ technological measures.
194
+
195
+ 4. Conveying Verbatim Copies.
196
+
197
+ You may convey verbatim copies of the Program's source code as you
198
+ receive it, in any medium, provided that you conspicuously and
199
+ appropriately publish on each copy an appropriate copyright notice;
200
+ keep intact all notices stating that this License and any
201
+ non-permissive terms added in accord with section 7 apply to the code;
202
+ keep intact all notices of the absence of any warranty; and give all
203
+ recipients a copy of this License along with the Program.
204
+
205
+ You may charge any price or no price for each copy that you convey,
206
+ and you may offer support or warranty protection for a fee.
207
+
208
+ 5. Conveying Modified Source Versions.
209
+
210
+ You may convey a work based on the Program, or the modifications to
211
+ produce it from the Program, in the form of source code under the
212
+ terms of section 4, provided that you also meet all of these conditions:
213
+
214
+ a) The work must carry prominent notices stating that you modified
215
+ it, and giving a relevant date.
216
+
217
+ b) The work must carry prominent notices stating that it is
218
+ released under this License and any conditions added under section
219
+ 7. This requirement modifies the requirement in section 4 to
220
+ "keep intact all notices".
221
+
222
+ c) You must license the entire work, as a whole, under this
223
+ License to anyone who comes into possession of a copy. This
224
+ License will therefore apply, along with any applicable section 7
225
+ additional terms, to the whole of the work, and all its parts,
226
+ regardless of how they are packaged. This License gives no
227
+ permission to license the work in any other way, but it does not
228
+ invalidate such permission if you have separately received it.
229
+
230
+ d) If the work has interactive user interfaces, each must display
231
+ Appropriate Legal Notices; however, if the Program has interactive
232
+ interfaces that do not display Appropriate Legal Notices, your
233
+ work need not make them do so.
234
+
235
+ A compilation of a covered work with other separate and independent
236
+ works, which are not by their nature extensions of the covered work,
237
+ and which are not combined with it such as to form a larger program,
238
+ in or on a volume of a storage or distribution medium, is called an
239
+ "aggregate" if the compilation and its resulting copyright are not
240
+ used to limit the access or legal rights of the compilation's users
241
+ beyond what the individual works permit. Inclusion of a covered work
242
+ in an aggregate does not cause this License to apply to the other
243
+ parts of the aggregate.
244
+
245
+ 6. Conveying Non-Source Forms.
246
+
247
+ You may convey a covered work in object code form under the terms
248
+ of sections 4 and 5, provided that you also convey the
249
+ machine-readable Corresponding Source under the terms of this License,
250
+ in one of these ways:
251
+
252
+ a) Convey the object code in, or embodied in, a physical product
253
+ (including a physical distribution medium), accompanied by the
254
+ Corresponding Source fixed on a durable physical medium
255
+ customarily used for software interchange.
256
+
257
+ b) Convey the object code in, or embodied in, a physical product
258
+ (including a physical distribution medium), accompanied by a
259
+ written offer, valid for at least three years and valid for as
260
+ long as you offer spare parts or customer support for that product
261
+ model, to give anyone who possesses the object code either (1) a
262
+ copy of the Corresponding Source for all the software in the
263
+ product that is covered by this License, on a durable physical
264
+ medium customarily used for software interchange, for a price no
265
+ more than your reasonable cost of physically performing this
266
+ conveying of source, or (2) access to copy the
267
+ Corresponding Source from a network server at no charge.
268
+
269
+ c) Convey individual copies of the object code with a copy of the
270
+ written offer to provide the Corresponding Source. This
271
+ alternative is allowed only occasionally and noncommercially, and
272
+ only if you received the object code with such an offer, in accord
273
+ with subsection 6b.
274
+
275
+ d) Convey the object code by offering access from a designated
276
+ place (gratis or for a charge), and offer equivalent access to the
277
+ Corresponding Source in the same way through the same place at no
278
+ further charge. You need not require recipients to copy the
279
+ Corresponding Source along with the object code. If the place to
280
+ copy the object code is a network server, the Corresponding Source
281
+ may be on a different server (operated by you or a third party)
282
+ that supports equivalent copying facilities, provided you maintain
283
+ clear directions next to the object code saying where to find the
284
+ Corresponding Source. Regardless of what server hosts the
285
+ Corresponding Source, you remain obligated to ensure that it is
286
+ available for as long as needed to satisfy these requirements.
287
+
288
+ e) Convey the object code using peer-to-peer transmission, provided
289
+ you inform other peers where the object code and Corresponding
290
+ Source of the work are being offered to the general public at no
291
+ charge under subsection 6d.
292
+
293
+ A separable portion of the object code, whose source code is excluded
294
+ from the Corresponding Source as a System Library, need not be
295
+ included in conveying the object code work.
296
+
297
+ A "User Product" is either (1) a "consumer product", which means any
298
+ tangible personal property which is normally used for personal, family,
299
+ or household purposes, or (2) anything designed or sold for incorporation
300
+ into a dwelling. In determining whether a product is a consumer product,
301
+ doubtful cases shall be resolved in favor of coverage. For a particular
302
+ product received by a particular user, "normally used" refers to a
303
+ typical or common use of that class of product, regardless of the status
304
+ of the particular user or of the way in which the particular user
305
+ actually uses, or expects or is expected to use, the product. A product
306
+ is a consumer product regardless of whether the product has substantial
307
+ commercial, industrial or non-consumer uses, unless such uses represent
308
+ the only significant mode of use of the product.
309
+
310
+ "Installation Information" for a User Product means any methods,
311
+ procedures, authorization keys, or other information required to install
312
+ and execute modified versions of a covered work in that User Product from
313
+ a modified version of its Corresponding Source. The information must
314
+ suffice to ensure that the continued functioning of the modified object
315
+ code is in no case prevented or interfered with solely because
316
+ modification has been made.
317
+
318
+ If you convey an object code work under this section in, or with, or
319
+ specifically for use in, a User Product, and the conveying occurs as
320
+ part of a transaction in which the right of possession and use of the
321
+ User Product is transferred to the recipient in perpetuity or for a
322
+ fixed term (regardless of how the transaction is characterized), the
323
+ Corresponding Source conveyed under this section must be accompanied
324
+ by the Installation Information. But this requirement does not apply
325
+ if neither you nor any third party retains the ability to install
326
+ modified object code on the User Product (for example, the work has
327
+ been installed in ROM).
328
+
329
+ The requirement to provide Installation Information does not include a
330
+ requirement to continue to provide support service, warranty, or updates
331
+ for a work that has been modified or installed by the recipient, or for
332
+ the User Product in which it has been modified or installed. Access to a
333
+ network may be denied when the modification itself materially and
334
+ adversely affects the operation of the network or violates the rules and
335
+ protocols for communication across the network.
336
+
337
+ Corresponding Source conveyed, and Installation Information provided,
338
+ in accord with this section must be in a format that is publicly
339
+ documented (and with an implementation available to the public in
340
+ source code form), and must require no special password or key for
341
+ unpacking, reading or copying.
342
+
343
+ 7. Additional Terms.
344
+
345
+ "Additional permissions" are terms that supplement the terms of this
346
+ License by making exceptions from one or more of its conditions.
347
+ Additional permissions that are applicable to the entire Program shall
348
+ be treated as though they were included in this License, to the extent
349
+ that they are valid under applicable law. If additional permissions
350
+ apply only to part of the Program, that part may be used separately
351
+ under those permissions, but the entire Program remains governed by
352
+ this License without regard to the additional permissions.
353
+
354
+ When you convey a copy of a covered work, you may at your option
355
+ remove any additional permissions from that copy, or from any part of
356
+ it. (Additional permissions may be written to require their own
357
+ removal in certain cases when you modify the work.) You may place
358
+ additional permissions on material, added by you to a covered work,
359
+ for which you have or can give appropriate copyright permission.
360
+
361
+ Notwithstanding any other provision of this License, for material you
362
+ add to a covered work, you may (if authorized by the copyright holders of
363
+ that material) supplement the terms of this License with terms:
364
+
365
+ a) Disclaiming warranty or limiting liability differently from the
366
+ terms of sections 15 and 16 of this License; or
367
+
368
+ b) Requiring preservation of specified reasonable legal notices or
369
+ author attributions in that material or in the Appropriate Legal
370
+ Notices displayed by works containing it; or
371
+
372
+ c) Prohibiting misrepresentation of the origin of that material, or
373
+ requiring that modified versions of such material be marked in
374
+ reasonable ways as different from the original version; or
375
+
376
+ d) Limiting the use for publicity purposes of names of licensors or
377
+ authors of the material; or
378
+
379
+ e) Declining to grant rights under trademark law for use of some
380
+ trade names, trademarks, or service marks; or
381
+
382
+ f) Requiring indemnification of licensors and authors of that
383
+ material by anyone who conveys the material (or modified versions of
384
+ it) with contractual assumptions of liability to the recipient, for
385
+ any liability that these contractual assumptions directly impose on
386
+ those licensors and authors.
387
+
388
+ All other non-permissive additional terms are considered "further
389
+ restrictions" within the meaning of section 10. If the Program as you
390
+ received it, or any part of it, contains a notice stating that it is
391
+ governed by this License along with a term that is a further
392
+ restriction, you may remove that term. If a license document contains
393
+ a further restriction but permits relicensing or conveying under this
394
+ License, you may add to a covered work material governed by the terms
395
+ of that license document, provided that the further restriction does
396
+ not survive such relicensing or conveying.
397
+
398
+ If you add terms to a covered work in accord with this section, you
399
+ must place, in the relevant source files, a statement of the
400
+ additional terms that apply to those files, or a notice indicating
401
+ where to find the applicable terms.
402
+
403
+ Additional terms, permissive or non-permissive, may be stated in the
404
+ form of a separately written license, or stated as exceptions;
405
+ the above requirements apply either way.
406
+
407
+ 8. Termination.
408
+
409
+ You may not propagate or modify a covered work except as expressly
410
+ provided under this License. Any attempt otherwise to propagate or
411
+ modify it is void, and will automatically terminate your rights under
412
+ this License (including any patent licenses granted under the third
413
+ paragraph of section 11).
414
+
415
+ However, if you cease all violation of this License, then your
416
+ license from a particular copyright holder is reinstated (a)
417
+ provisionally, unless and until the copyright holder explicitly and
418
+ finally terminates your license, and (b) permanently, if the copyright
419
+ holder fails to notify you of the violation by some reasonable means
420
+ prior to 60 days after the cessation.
421
+
422
+ Moreover, your license from a particular copyright holder is
423
+ reinstated permanently if the copyright holder notifies you of the
424
+ violation by some reasonable means, this is the first time you have
425
+ received notice of violation of this License (for any work) from that
426
+ copyright holder, and you cure the violation prior to 30 days after
427
+ your receipt of the notice.
428
+
429
+ Termination of your rights under this section does not terminate the
430
+ licenses of parties who have received copies or rights from you under
431
+ this License. If your rights have been terminated and not permanently
432
+ reinstated, you do not qualify to receive new licenses for the same
433
+ material under section 10.
434
+
435
+ 9. Acceptance Not Required for Having Copies.
436
+
437
+ You are not required to accept this License in order to receive or
438
+ run a copy of the Program. Ancillary propagation of a covered work
439
+ occurring solely as a consequence of using peer-to-peer transmission
440
+ to receive a copy likewise does not require acceptance. However,
441
+ nothing other than this License grants you permission to propagate or
442
+ modify any covered work. These actions infringe copyright if you do
443
+ not accept this License. Therefore, by modifying or propagating a
444
+ covered work, you indicate your acceptance of this License to do so.
445
+
446
+ 10. Automatic Licensing of Downstream Recipients.
447
+
448
+ Each time you convey a covered work, the recipient automatically
449
+ receives a license from the original licensors, to run, modify and
450
+ propagate that work, subject to this License. You are not responsible
451
+ for enforcing compliance by third parties with this License.
452
+
453
+ An "entity transaction" is a transaction transferring control of an
454
+ organization, or substantially all assets of one, or subdividing an
455
+ organization, or merging organizations. If propagation of a covered
456
+ work results from an entity transaction, each party to that
457
+ transaction who receives a copy of the work also receives whatever
458
+ licenses to the work the party's predecessor in interest had or could
459
+ give under the previous paragraph, plus a right to possession of the
460
+ Corresponding Source of the work from the predecessor in interest, if
461
+ the predecessor has it or can get it with reasonable efforts.
462
+
463
+ You may not impose any further restrictions on the exercise of the
464
+ rights granted or affirmed under this License. For example, you may
465
+ not impose a license fee, royalty, or other charge for exercise of
466
+ rights granted under this License, and you may not initiate litigation
467
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
468
+ any patent claim is infringed by making, using, selling, offering for
469
+ sale, or importing the Program or any portion of it.
470
+
471
+ 11. Patents.
472
+
473
+ A "contributor" is a copyright holder who authorizes use under this
474
+ License of the Program or a work on which the Program is based. The
475
+ work thus licensed is called the contributor's "contributor version".
476
+
477
+ A contributor's "essential patent claims" are all patent claims
478
+ owned or controlled by the contributor, whether already acquired or
479
+ hereafter acquired, that would be infringed by some manner, permitted
480
+ by this License, of making, using, or selling its contributor version,
481
+ but do not include claims that would be infringed only as a
482
+ consequence of further modification of the contributor version. For
483
+ purposes of this definition, "control" includes the right to grant
484
+ patent sublicenses in a manner consistent with the requirements of
485
+ this License.
486
+
487
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
488
+ patent license under the contributor's essential patent claims, to
489
+ make, use, sell, offer for sale, import and otherwise run, modify and
490
+ propagate the contents of its contributor version.
491
+
492
+ In the following three paragraphs, a "patent license" is any express
493
+ agreement or commitment, however denominated, not to enforce a patent
494
+ (such as an express permission to practice a patent or covenant not to
495
+ sue for patent infringement). To "grant" such a patent license to a
496
+ party means to make such an agreement or commitment not to enforce a
497
+ patent against the party.
498
+
499
+ If you convey a covered work, knowingly relying on a patent license,
500
+ and the Corresponding Source of the work is not available for anyone
501
+ to copy, free of charge and under the terms of this License, through a
502
+ publicly available network server or other readily accessible means,
503
+ then you must either (1) cause the Corresponding Source to be so
504
+ available, or (2) arrange to deprive yourself of the benefit of the
505
+ patent license for this particular work, or (3) arrange, in a manner
506
+ consistent with the requirements of this License, to extend the patent
507
+ license to downstream recipients. "Knowingly relying" means you have
508
+ actual knowledge that, but for the patent license, your conveying the
509
+ covered work in a country, or your recipient's use of the covered work
510
+ in a country, would infringe one or more identifiable patents in that
511
+ country that you have reason to believe are valid.
512
+
513
+ If, pursuant to or in connection with a single transaction or
514
+ arrangement, you convey, or propagate by procuring conveyance of, a
515
+ covered work, and grant a patent license to some of the parties
516
+ receiving the covered work authorizing them to use, propagate, modify
517
+ or convey a specific copy of the covered work, then the patent license
518
+ you grant is automatically extended to all recipients of the covered
519
+ work and works based on it.
520
+
521
+ A patent license is "discriminatory" if it does not include within
522
+ the scope of its coverage, prohibits the exercise of, or is
523
+ conditioned on the non-exercise of one or more of the rights that are
524
+ specifically granted under this License. You may not convey a covered
525
+ work if you are a party to an arrangement with a third party that is
526
+ in the business of distributing software, under which you make payment
527
+ to the third party based on the extent of your activity of conveying
528
+ the work, and under which the third party grants, to any of the
529
+ parties who would receive the covered work from you, a discriminatory
530
+ patent license (a) in connection with copies of the covered work
531
+ conveyed by you (or copies made from those copies), or (b) primarily
532
+ for and in connection with specific products or compilations that
533
+ contain the covered work, unless you entered into that arrangement,
534
+ or that patent license was granted, prior to 28 March 2007.
535
+
536
+ Nothing in this License shall be construed as excluding or limiting
537
+ any implied license or other defenses to infringement that may
538
+ otherwise be available to you under applicable patent law.
539
+
540
+ 12. No Surrender of Others' Freedom.
541
+
542
+ If conditions are imposed on you (whether by court order, agreement or
543
+ otherwise) that contradict the conditions of this License, they do not
544
+ excuse you from the conditions of this License. If you cannot convey a
545
+ covered work so as to satisfy simultaneously your obligations under this
546
+ License and any other pertinent obligations, then as a consequence you may
547
+ not convey it at all. For example, if you agree to terms that obligate you
548
+ to collect a royalty for further conveying from those to whom you convey
549
+ the Program, the only way you could satisfy both those terms and this
550
+ License would be to refrain entirely from conveying the Program.
551
+
552
+ 13. Use with the GNU Affero General Public License.
553
+
554
+ Notwithstanding any other provision of this License, you have
555
+ permission to link or combine any covered work with a work licensed
556
+ under version 3 of the GNU Affero General Public License into a single
557
+ combined work, and to convey the resulting work. The terms of this
558
+ License will continue to apply to the part which is the covered work,
559
+ but the special requirements of the GNU Affero General Public License,
560
+ section 13, concerning interaction through a network will apply to the
561
+ combination as such.
562
+
563
+ 14. Revised Versions of this License.
564
+
565
+ The Free Software Foundation may publish revised and/or new versions of
566
+ the GNU General Public License from time to time. Such new versions will
567
+ be similar in spirit to the present version, but may differ in detail to
568
+ address new problems or concerns.
569
+
570
+ Each version is given a distinguishing version number. If the
571
+ Program specifies that a certain numbered version of the GNU General
572
+ Public License "or any later version" applies to it, you have the
573
+ option of following the terms and conditions either of that numbered
574
+ version or of any later version published by the Free Software
575
+ Foundation. If the Program does not specify a version number of the
576
+ GNU General Public License, you may choose any version ever published
577
+ by the Free Software Foundation.
578
+
579
+ If the Program specifies that a proxy can decide which future
580
+ versions of the GNU General Public License can be used, that proxy's
581
+ public statement of acceptance of a version permanently authorizes you
582
+ to choose that version for the Program.
583
+
584
+ Later license versions may give you additional or different
585
+ permissions. However, no additional obligations are imposed on any
586
+ author or copyright holder as a result of your choosing to follow a
587
+ later version.
588
+
589
+ 15. Disclaimer of Warranty.
590
+
591
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+ 16. Limitation of Liability.
601
+
602
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+ SUCH DAMAGES.
611
+
612
+ 17. Interpretation of Sections 15 and 16.
613
+
614
+ If the disclaimer of warranty and limitation of liability provided
615
+ above cannot be given local legal effect according to their terms,
616
+ reviewing courts shall apply local law that most closely approximates
617
+ an absolute waiver of all civil liability in connection with the
618
+ Program, unless a warranty or assumption of liability accompanies a
619
+ copy of the Program in return for a fee.
620
+
621
+ END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ {one line to give the program's name and a brief idea of what it does.}
635
+ Copyright (C) {year} {name of author}
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU General Public License as published by
639
+ the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU General Public License for more details.
646
+
647
+ You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If the program does terminal interaction, make it output a short
653
+ notice like this when it starts in an interactive mode:
654
+
655
+ {project} Copyright (C) {year} {fullname}
656
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+ This is free software, and you are welcome to redistribute it
658
+ under certain conditions; type `show c' for details.
659
+
660
+ The hypothetical commands `show w' and `show c' should show the appropriate
661
+ parts of the General Public License. Of course, your program's commands
662
+ might be different; for a GUI interface, you would use an "about box".
663
+
664
+ You should also get your employer (if you work as a programmer) or school,
665
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+ For more information on this, and how to apply and follow the GNU GPL, see
667
+ <http://www.gnu.org/licenses/>.
668
+
669
+ The GNU General Public License does not permit incorporating your program
670
+ into proprietary programs. If your program is a subroutine library, you
671
+ may consider it more useful to permit linking proprietary applications with
672
+ the library. If this is what you want to do, use the GNU Lesser General
673
+ Public License instead of this License. But first, please read
674
+ <http://www.gnu.org/philosophy/why-not-lgpl.html>.
framework/bootstrap-helpers.php CHANGED
@@ -1,130 +1,130 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Helper functions used while loading the framework
5
- */
6
-
7
- /**
8
- * Convert to Unix style directory separators
9
- */
10
- function fw_fix_path($path) {
11
- $fixed_path = untrailingslashit( str_replace(array('//', '\\'), array('/', '/'), $path) );
12
-
13
- if (empty($fixed_path) && !empty($path)) {
14
- $fixed_path = '/';
15
- }
16
-
17
- return $fixed_path;
18
- }
19
-
20
- /**
21
- * Relative path of the framework customizations directory
22
- * @param string $append
23
- * @return string
24
- */
25
- function fw_get_framework_customizations_dir_rel_path($append = '') {
26
- static $cache = null;
27
-
28
- if ($cache === null) {
29
- $cache = apply_filters('fw_framework_customizations_dir_rel_path', '/framework-customizations');
30
- }
31
-
32
- return $cache . $append;
33
- }
34
-
35
- /** Child theme related functions */
36
- {
37
- /**
38
- * Full path to the child-theme framework customizations directory
39
- * @param string $rel_path
40
- * @return null|string
41
- */
42
- function fw_get_stylesheet_customizations_directory($rel_path = '') {
43
- if (is_child_theme()) {
44
- return get_stylesheet_directory() . fw_get_framework_customizations_dir_rel_path($rel_path);
45
- } else {
46
- // check is_child_theme() before using this function
47
- return null;
48
- }
49
- }
50
-
51
- /**
52
- * URI to the child-theme framework customizations directory
53
- * @param string $rel_path
54
- * @return null|string
55
- */
56
- function fw_get_stylesheet_customizations_directory_uri($rel_path = '') {
57
- if (is_child_theme()) {
58
- return get_stylesheet_directory_uri() . fw_get_framework_customizations_dir_rel_path($rel_path);
59
- } else {
60
- // check is_child_theme() before using this function
61
- return null;
62
- }
63
- }
64
- }
65
-
66
- /** Parent theme related functions */
67
- {
68
- /**
69
- * Full path to the parent-theme framework customizations directory
70
- * @param string $rel_path
71
- * @return string
72
- */
73
- function fw_get_template_customizations_directory($rel_path = '') {
74
- static $cache = null;
75
-
76
- if ($cache === null) {
77
- $cache = get_template_directory() . fw_get_framework_customizations_dir_rel_path();
78
- }
79
-
80
- return $cache . $rel_path;
81
- }
82
-
83
- /**
84
- * URI to the parent-theme framework customizations directory
85
- * @param string $rel_path
86
- * @return string
87
- */
88
- function fw_get_template_customizations_directory_uri($rel_path = '') {
89
- static $cache = null;
90
-
91
- if ($cache === null) {
92
- $cache = get_template_directory_uri() . fw_get_framework_customizations_dir_rel_path();
93
- }
94
-
95
- return $cache . $rel_path;
96
- }
97
- }
98
-
99
- /** Framework related functions */
100
- {
101
- /**
102
- * Full path to the parent-theme/framework directory
103
- * @param string $rel_path
104
- * @return string
105
- */
106
- function fw_get_framework_directory($rel_path = '') {
107
- static $cache = null;
108
-
109
- if ($cache === null) {
110
- $cache = apply_filters('fw_framework_directory', dirname(__FILE__));
111
- }
112
-
113
- return $cache . $rel_path;
114
- }
115
-
116
- /**
117
- * URI to the parent-theme/framework directory
118
- * @param string $rel_path
119
- * @return string
120
- */
121
- function fw_get_framework_directory_uri($rel_path = '') {
122
- static $cache = null;
123
-
124
- if ($cache === null) {
125
- $cache = apply_filters('fw_framework_directory_uri', get_template_directory_uri() . '/framework');
126
- }
127
-
128
- return $cache . $rel_path;
129
- }
130
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Helper functions used while loading the framework
5
+ */
6
+
7
+ /**
8
+ * Convert to Unix style directory separators
9
+ */
10
+ function fw_fix_path($path) {
11
+ $fixed_path = untrailingslashit( str_replace(array('//', '\\'), array('/', '/'), $path) );
12
+
13
+ if (empty($fixed_path) && !empty($path)) {
14
+ $fixed_path = '/';
15
+ }
16
+
17
+ return $fixed_path;
18
+ }
19
+
20
+ /**
21
+ * Relative path of the framework customizations directory
22
+ * @param string $append
23
+ * @return string
24
+ */
25
+ function fw_get_framework_customizations_dir_rel_path($append = '') {
26
+ static $cache = null;
27
+
28
+ if ($cache === null) {
29
+ $cache = apply_filters('fw_framework_customizations_dir_rel_path', '/framework-customizations');
30
+ }
31
+
32
+ return $cache . $append;
33
+ }
34
+
35
+ /** Child theme related functions */
36
+ {
37
+ /**
38
+ * Full path to the child-theme framework customizations directory
39
+ * @param string $rel_path
40
+ * @return null|string
41
+ */
42
+ function fw_get_stylesheet_customizations_directory($rel_path = '') {
43
+ if (is_child_theme()) {
44
+ return get_stylesheet_directory() . fw_get_framework_customizations_dir_rel_path($rel_path);
45
+ } else {
46
+ // check is_child_theme() before using this function
47
+ return null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * URI to the child-theme framework customizations directory
53
+ * @param string $rel_path
54
+ * @return null|string
55
+ */
56
+ function fw_get_stylesheet_customizations_directory_uri($rel_path = '') {
57
+ if (is_child_theme()) {
58
+ return get_stylesheet_directory_uri() . fw_get_framework_customizations_dir_rel_path($rel_path);
59
+ } else {
60
+ // check is_child_theme() before using this function
61
+ return null;
62
+ }
63
+ }
64
+ }
65
+
66
+ /** Parent theme related functions */
67
+ {
68
+ /**
69
+ * Full path to the parent-theme framework customizations directory
70
+ * @param string $rel_path
71
+ * @return string
72
+ */
73
+ function fw_get_template_customizations_directory($rel_path = '') {
74
+ static $cache = null;
75
+
76
+ if ($cache === null) {
77
+ $cache = get_template_directory() . fw_get_framework_customizations_dir_rel_path();
78
+ }
79
+
80
+ return $cache . $rel_path;
81
+ }
82
+
83
+ /**
84
+ * URI to the parent-theme framework customizations directory
85
+ * @param string $rel_path
86
+ * @return string
87
+ */
88
+ function fw_get_template_customizations_directory_uri($rel_path = '') {
89
+ static $cache = null;
90
+
91
+ if ($cache === null) {
92
+ $cache = get_template_directory_uri() . fw_get_framework_customizations_dir_rel_path();
93
+ }
94
+
95
+ return $cache . $rel_path;
96
+ }
97
+ }
98
+
99
+ /** Framework related functions */
100
+ {
101
+ /**
102
+ * Full path to the parent-theme/framework directory
103
+ * @param string $rel_path
104
+ * @return string
105
+ */
106
+ function fw_get_framework_directory($rel_path = '') {
107
+ static $cache = null;
108
+
109
+ if ($cache === null) {
110
+ $cache = apply_filters('fw_framework_directory', dirname(__FILE__));
111
+ }
112
+
113
+ return $cache . $rel_path;
114
+ }
115
+
116
+ /**
117
+ * URI to the parent-theme/framework directory
118
+ * @param string $rel_path
119
+ * @return string
120
+ */
121
+ function fw_get_framework_directory_uri($rel_path = '') {
122
+ static $cache = null;
123
+
124
+ if ($cache === null) {
125
+ $cache = apply_filters('fw_framework_directory_uri', get_template_directory_uri() . '/framework');
126
+ }
127
+
128
+ return $cache . $rel_path;
129
+ }
130
+ }
framework/bootstrap.php CHANGED
@@ -1,101 +1,103 @@
1
- <?php if (!defined('ABSPATH')) die('Forbidden');
2
-
3
- if (defined('FW')) {
4
- /**
5
- * The framework is already loaded.
6
- */
7
- } else {
8
- define('FW', true);
9
-
10
- /**
11
- * Load the framework on 'after_setup_theme' action when the theme information is available
12
- * To prevent `undefined constant TEMPLATEPATH` errors when the framework is used as plugin
13
- */
14
- add_action('after_setup_theme', '_action_init_framework');
15
-
16
- function _action_init_framework() {
17
- if (did_action('fw_init')) {
18
- return;
19
- }
20
-
21
- $fw_dir = dirname(__FILE__);
22
-
23
- include $fw_dir .'/bootstrap-helpers.php';
24
-
25
- /**
26
- * Load core
27
- */
28
- {
29
- require $fw_dir .'/core/Fw.php';
30
-
31
- fw();
32
- }
33
-
34
- /**
35
- * Load helpers
36
- */
37
- foreach (
38
- array(
39
- 'meta',
40
- 'class-fw-access-key',
41
- 'class-fw-dumper',
42
- 'general',
43
- 'class-fw-wp-filesystem',
44
- 'class-fw-cache',
45
- 'class-fw-form',
46
- 'class-fw-request',
47
- 'class-fw-session',
48
- 'class-fw-wp-option',
49
- 'class-fw-wp-meta',
50
- 'database',
51
- 'class-fw-flash-messages',
52
- 'class-fw-resize',
53
- 'class-fw-wp-list-table',
54
- 'type/class-fw-type',
55
- 'type/class-fw-type-register',
56
- )
57
- as $file
58
- ) {
59
- require $fw_dir .'/helpers/'. $file .'.php';
60
- }
61
-
62
- /**
63
- * Load includes
64
- */
65
- foreach (array('hooks') as $file) {
66
- require $fw_dir .'/includes/'. $file .'.php';
67
- }
68
-
69
- /**
70
- * Init components
71
- */
72
- {
73
- $components = array(
74
- /**
75
- * Load the theme's hooks.php first, to give users the possibility to add_action()
76
- * for `extensions` and `backend` components actions that can happen while their initialization
77
- */
78
- 'theme',
79
- /**
80
- * Load extensions before backend, to give extensions the possibility to add_action()
81
- * for the `backend` component actions that can happen while its initialization
82
- */
83
- 'extensions',
84
- 'backend'
85
- );
86
-
87
- foreach ($components as $component) {
88
- fw()->{$component}->_init();
89
- }
90
-
91
- foreach ($components as $component) {
92
- fw()->{$component}->_after_components_init();
93
- }
94
- }
95
-
96
- /**
97
- * The framework is loaded
98
- */
99
- do_action('fw_init');
100
- }
101
- }
 
 
1
+ <?php if (!defined('ABSPATH')) die('Forbidden');
2
+
3
+ if (defined('FW')) {
4
+ /**
5
+ * The framework is already loaded.
6
+ */
7
+ } else {
8
+ define('FW', true);
9
+
10
+ /**
11
+ * Load the framework on 'after_setup_theme' action when the theme information is available
12
+ * To prevent `undefined constant TEMPLATEPATH` errors when the framework is used as plugin
13
+ */
14
+ add_action('after_setup_theme', '_action_init_framework');
15
+
16
+ function _action_init_framework() {
17
+ if (did_action('fw_init')) {
18
+ return;
19
+ }
20
+
21
+ do_action('fw_before_init');
22
+
23
+ $fw_dir = dirname(__FILE__);
24
+
25
+ include $fw_dir .'/bootstrap-helpers.php';
26
+
27
+ /**
28
+ * Load core
29
+ */
30
+ {
31
+ require $fw_dir .'/core/Fw.php';
32
+
33
+ fw();
34
+ }
35
+
36
+ /**
37
+ * Load helpers
38
+ */
39
+ foreach (
40
+ array(
41
+ 'meta',
42
+ 'class-fw-access-key',
43
+ 'class-fw-dumper',
44
+ 'general',
45
+ 'class-fw-wp-filesystem',
46
+ 'class-fw-cache',
47
+ 'class-fw-form',
48
+ 'class-fw-request',
49
+ 'class-fw-session',
50
+ 'class-fw-wp-option',
51
+ 'class-fw-wp-meta',
52
+ 'database',
53
+ 'class-fw-flash-messages',
54
+ 'class-fw-resize',
55
+ 'class-fw-wp-list-table',
56
+ 'type/class-fw-type',
57
+ 'type/class-fw-type-register',
58
+ )
59
+ as $file
60
+ ) {
61
+ require $fw_dir .'/helpers/'. $file .'.php';
62
+ }
63
+
64
+ /**
65
+ * Load includes
66
+ */
67
+ foreach (array('hooks') as $file) {
68
+ require $fw_dir .'/includes/'. $file .'.php';
69
+ }
70
+
71
+ /**
72
+ * Init components
73
+ */
74
+ {
75
+ $components = array(
76
+ /**
77
+ * Load the theme's hooks.php first, to give users the possibility to add_action()
78
+ * for `extensions` and `backend` components actions that can happen while their initialization
79
+ */
80
+ 'theme',
81
+ /**
82
+ * Load extensions before backend, to give extensions the possibility to add_action()
83
+ * for the `backend` component actions that can happen while its initialization
84
+ */
85
+ 'extensions',
86
+ 'backend'
87
+ );
88
+
89
+ foreach ($components as $component) {
90
+ fw()->{$component}->_init();
91
+ }
92
+
93
+ foreach ($components as $component) {
94
+ fw()->{$component}->_after_components_init();
95
+ }
96
+ }
97
+
98
+ /**
99
+ * The framework is loaded
100
+ */
101
+ do_action('fw_init');
102
+ }
103
+ }
framework/core/Fw.php CHANGED
@@ -1,91 +1,91 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Main framework class that contains everything
5
- *
6
- * Convention: All public properties should be only instances of the components (except special property: manifest)
7
- */
8
- final class _Fw
9
- {
10
- /** @var bool If already loaded */
11
- private static $loaded = false;
12
-
13
- /** @var FW_Framework_Manifest */
14
- public $manifest;
15
-
16
- /** @var _FW_Component_Extensions */
17
- public $extensions;
18
-
19
- /** @var _FW_Component_Backend */
20
- public $backend;
21
-
22
- /** @var _FW_Component_Theme */
23
- public $theme;
24
-
25
- public function __construct()
26
- {
27
- if (self::$loaded) {
28
- trigger_error('Framework already loaded', E_USER_ERROR);
29
- } else {
30
- self::$loaded = true;
31
- }
32
-
33
- $fw_dir = fw_get_framework_directory();
34
-
35
- // manifest
36
- {
37
- require $fw_dir .'/core/class-fw-manifest.php';
38
-
39
- require $fw_dir .'/manifest.php';
40
- /** @var array $manifest */
41
-
42
- $this->manifest = new FW_Framework_Manifest($manifest);
43
-
44
- add_action('fw_init', array($this, '_check_requirements'), 1);
45
- }
46
-
47
- require $fw_dir .'/core/extends/class-fw-extension.php';
48
- require $fw_dir .'/core/extends/class-fw-option-type.php';
49
- require $fw_dir .'/core/extends/class-fw-container-type.php';
50
- require $fw_dir .'/core/extends/interface-fw-option-handler.php'; // option handler (experimental)
51
-
52
- // components
53
- {
54
- require $fw_dir .'/core/components/extensions.php';
55
- $this->extensions = new _FW_Component_Extensions();
56
-
57
- require $fw_dir .'/core/components/backend.php';
58
- $this->backend = new _FW_Component_Backend();
59
-
60
- require $fw_dir .'/core/components/theme.php';
61
- $this->theme = new _FW_Component_Theme();
62
- }
63
- }
64
-
65
- /**
66
- * @internal
67
- */
68
- public function _check_requirements()
69
- {
70
- if (is_admin() && !$this->manifest->check_requirements()) {
71
- FW_Flash_Messages::add(
72
- 'fw_requirements',
73
- __('Framework requirements not met:', 'fw') .' '. $this->manifest->get_not_met_requirement_text(),
74
- 'warning'
75
- );
76
- }
77
- }
78
- }
79
-
80
- /**
81
- * @return _FW Framework instance
82
- */
83
- function fw() {
84
- static $FW = null; // cache
85
-
86
- if ($FW === null) {
87
- $FW = new _Fw();
88
- }
89
-
90
- return $FW;
91
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Main framework class that contains everything
5
+ *
6
+ * Convention: All public properties should be only instances of the components (except special property: manifest)
7
+ */
8
+ final class _Fw
9
+ {
10
+ /** @var bool If already loaded */
11
+ private static $loaded = false;
12
+
13
+ /** @var FW_Framework_Manifest */
14
+ public $manifest;
15
+
16
+ /** @var _FW_Component_Extensions */
17
+ public $extensions;
18
+
19
+ /** @var _FW_Component_Backend */
20
+ public $backend;
21
+
22
+ /** @var _FW_Component_Theme */
23
+ public $theme;
24
+
25
+ public function __construct()
26
+ {
27
+ if (self::$loaded) {
28
+ trigger_error('Framework already loaded', E_USER_ERROR);
29
+ } else {
30
+ self::$loaded = true;
31
+ }
32
+
33
+ $fw_dir = fw_get_framework_directory();
34
+
35
+ // manifest
36
+ {
37
+ require $fw_dir .'/core/class-fw-manifest.php';
38
+
39
+ require $fw_dir .'/manifest.php';
40
+ /** @var array $manifest */
41
+
42
+ $this->manifest = new FW_Framework_Manifest($manifest);
43
+
44
+ add_action('fw_init', array($this, '_check_requirements'), 1);
45
+ }
46
+
47
+ require $fw_dir .'/core/extends/class-fw-extension.php';
48
+ require $fw_dir .'/core/extends/class-fw-option-type.php';
49
+ require $fw_dir .'/core/extends/class-fw-container-type.php';
50
+ require $fw_dir .'/core/extends/interface-fw-option-handler.php'; // option handler (experimental)
51
+
52
+ // components
53
+ {
54
+ require $fw_dir .'/core/components/extensions.php';
55
+ $this->extensions = new _FW_Component_Extensions();
56
+
57
+ require $fw_dir .'/core/components/backend.php';
58
+ $this->backend = new _FW_Component_Backend();
59
+
60
+ require $fw_dir .'/core/components/theme.php';
61
+ $this->theme = new _FW_Component_Theme();
62
+ }
63
+ }
64
+
65
+ /**
66
+ * @internal
67
+ */
68
+ public function _check_requirements()
69
+ {
70
+ if (is_admin() && !$this->manifest->check_requirements()) {
71
+ FW_Flash_Messages::add(
72
+ 'fw_requirements',
73
+ __('Framework requirements not met:', 'fw') .' '. $this->manifest->get_not_met_requirement_text(),
74
+ 'warning'
75
+ );
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @return _FW Framework instance
82
+ */
83
+ function fw() {
84
+ static $FW = null; // cache
85
+
86
+ if ($FW === null) {
87
+ $FW = new _Fw();
88
+ }
89
+
90
+ return $FW;
91
+ }
framework/core/class-fw-manifest.php CHANGED
@@ -1,510 +1,510 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- abstract class FW_Manifest
4
- {
5
- /**
6
- * @var array
7
- */
8
- protected $manifest;
9
-
10
- /**
11
- * The first requirement that was not met
12
- * (that marks that the requirements are not met)
13
- *
14
- * @var array
15
- * array(
16
- * 'requirement' => 'wordpress|framework',
17
- * 'requirements' => array('min_version' => '1.2.3', ...)
18
- * )
19
- * or
20
- * array(
21
- * 'requirement' => 'extensions',
22
- * 'extension' => 'extension_name',
23
- * 'requirements' => array('min_version' => '1.2.3', ...)
24
- * )
25
- */
26
- private $not_met_requirement;
27
-
28
- /**
29
- * When an requirement that sure will not change is not met and have no sense to execute check_requirements() again
30
- * @var bool
31
- */
32
- private $not_met_is_final = false;
33
-
34
- /**
35
- * Not met requirement and skipped (not verified) requirements after $this->not_met_requirement was found
36
- * @var array
37
- */
38
- private $requirements_for_verification;
39
-
40
- private $requirements_verification_never_called = true;
41
-
42
- /**
43
- * @param array $manifest
44
- */
45
- protected function __construct(array $manifest)
46
- {
47
- $manifest = array_merge(array(
48
- 'name' => null, // title
49
- 'uri' => null,
50
- 'description' => null,
51
- 'version' => '0.0.0',
52
- 'author' => null,
53
- 'author_uri' => null,
54
-
55
- // Custom fields
56
- 'requirements' => array(),
57
- ), $manifest);
58
-
59
- /**
60
- * Merge $manifest['requirements']
61
- */
62
- {
63
- $requirements = $manifest['requirements'];
64
-
65
- $manifest['requirements'] = array();
66
-
67
- foreach ($this->get_default_requirements() as $default_requirement => $default_requirements) {
68
- $manifest['requirements'][ $default_requirement ] = isset($requirements[$default_requirement])
69
- ? array_merge(
70
- $default_requirements,
71
- $requirements[$default_requirement]
72
- )
73
- : $default_requirements;
74
- }
75
-
76
- unset($requirements);
77
- }
78
-
79
- $this->requirements_for_verification = $manifest['requirements'];
80
-
81
- $this->manifest = $manifest;
82
- }
83
-
84
- /**
85
- * @return array { 'requirement' => array('min_version' => '..', 'max_version' => '..') }
86
- */
87
- abstract protected function get_default_requirements();
88
-
89
- /**
90
- * @return bool
91
- */
92
- public function requirements_met()
93
- {
94
- if ($this->not_met_is_final) {
95
- return false;
96
- }
97
-
98
- if ($this->requirements_verification_never_called) {
99
- $this->requirements_verification_never_called = false;
100
-
101
- $this->check_requirements();
102
- }
103
-
104
- return empty($this->requirements_for_verification) && empty($this->not_met_requirement);
105
- }
106
-
107
- /**
108
- * @return bool
109
- */
110
- public function check_requirements()
111
- {
112
- if ($this->not_met_is_final) {
113
- return false;
114
- }
115
-
116
- if ($this->requirements_met()) {
117
- return true;
118
- }
119
-
120
- $this->not_met_requirement = array();
121
-
122
- global $wp_version;
123
-
124
- foreach ($this->requirements_for_verification as $requirement => $requirements) {
125
- switch ($requirement) {
126
- case 'wordpress':
127
- if (
128
- isset($requirements['min_version'])
129
- &&
130
- version_compare($wp_version, $requirements['min_version'], '<')
131
- ) {
132
- $this->not_met_requirement = array(
133
- 'requirement' => $requirement,
134
- 'requirements' => $requirements
135
- );
136
- $this->not_met_is_final = true;
137
- break 2;
138
- }
139
-
140
- if (
141
- isset($requirements['max_version'])
142
- &&
143
- version_compare($wp_version, $requirements['max_version'], '>')
144
- ) {
145
- $this->not_met_requirement = array(
146
- 'requirement' => $requirement,
147
- 'requirements' => $requirements
148
- );
149
- $this->not_met_is_final = true;
150
- break 2;
151
- }
152
-
153
- // met
154
- unset($this->requirements_for_verification[$requirement]);
155
- break;
156
- case 'framework':
157
- if (
158
- isset($requirements['min_version'])
159
- &&
160
- version_compare(fw()->manifest->get_version(), $requirements['min_version'], '<')
161
- ) {
162
- $this->not_met_requirement = array(
163
- 'requirement' => $requirement,
164
- 'requirements' => $requirements
165
- );
166
- $this->not_met_is_final = true;
167
- break 2;
168
- }
169
-
170
- if (
171
- isset($requirements['max_version'])
172
- &&
173
- version_compare(fw()->manifest->get_version(), $requirements['max_version'], '>')
174
- ) {
175
- $this->not_met_requirement = array(
176
- 'requirement' => $requirement,
177
- 'requirements' => $requirements
178
- );
179
- $this->not_met_is_final = true;
180
- break 2;
181
- }
182
-
183
- // met
184
- unset($this->requirements_for_verification[$requirement]);
185
- break;
186
- case 'extensions':
187
- $extensions =& $requirements;
188
-
189
- foreach ($extensions as $extension => $extension_requirements) {
190
- $extension_instance = fw()->extensions->get($extension);
191
-
192
- if (!$extension_instance) {
193
- /**
194
- * extension in requirements does not exists
195
- * maybe try call this method later and maybe will exist, or it really does not exists
196
- */
197
- $this->not_met_requirement = array(
198
- 'requirement' => $requirement,
199
- 'extension' => $extension,
200
- 'requirements' => $extension_requirements
201
- );
202
- break 3;
203
- }
204
-
205
- if (
206
- isset($extension_requirements['min_version'])
207
- &&
208
- version_compare($extension_instance->manifest->get_version(), $extension_requirements['min_version'], '<')
209
- ) {
210
- $this->not_met_requirement = array(
211
- 'requirement' => $requirement,
212
- 'extension' => $extension,
213
- 'requirements' => $extension_requirements
214
- );
215
- $this->not_met_is_final = true;
216
- break 3;
217
- }
218
-
219
- if (
220
- isset($extension_requirements['max_version'])
221
- &&
222
- version_compare($extension_instance->manifest->get_version(), $extension_requirements['max_version'], '>')
223
- ) {
224
- $this->not_met_requirement = array(
225
- 'requirement' => $requirement,
226
- 'extension' => $extension,
227
- 'requirements' => $extension_requirements
228
- );
229
- $this->not_met_is_final = true;
230
- break 3;
231
- }
232
-
233
- // met
234
- unset($this->requirements_for_verification[$requirement][$extension]);
235
- }
236
-
237
- if (empty($this->requirements_for_verification[$requirement])) {
238
- // all extensions requirements met
239
- unset($this->requirements_for_verification[$requirement]);
240
- }
241
- break;
242
- }
243
- }
244
-
245
- return $this->requirements_met();
246
- }
247
-
248
- public function get_version()
249
- {
250
- return $this->manifest['version'];
251
- }
252
-
253
- public function get_name()
254
- {
255
- return $this->manifest['name'];
256
- }
257
-
258
- /**
259
- * @param string $multi_key
260
- * @param mixed $default_value
261
- * @return mixed
262
- */
263
- public function get($multi_key, $default_value = null)
264
- {
265
- return fw_akg($multi_key, $this->manifest, $default_value);
266
- }
267
-
268
- /**
269
- * Call this only after check_requirements() failed
270
- * @return array
271
- */
272
- public function get_not_met_requirement()
273
- {
274
- return $this->not_met_requirement;
275
- }
276
-
277
- /**
278
- * Return user friendly requirement as text
279
- * Call this only after check_requirements() failed
280
- * @return string
281
- */
282
- public function get_not_met_requirement_text()
283
- {
284
- if (!$this->not_met_requirement) {
285
- return '';
286
- }
287
-
288
- $requirement = array();
289
-
290
- foreach ($this->not_met_requirement['requirements'] as $req_key => $req) {
291
- switch ($req_key) {
292
- case 'min_version':
293
- $requirement[] = __('minimum required version is', 'fw') .' '. $req;
294
- break;
295
- case 'max_version':
296
- $requirement[] = __('maximum required version is', 'fw') .' '. $req;
297
- break;
298
- }
299
- }
300
-
301
- $requirement = implode(' '. __('and', 'fw') .' ', $requirement);
302
-
303
- switch ($this->not_met_requirement['requirement']) {
304
- case 'wordpress':
305
- global $wp_version;
306
-
307
- $requirement = sprintf(
308
- __('Current WordPress version is %s, %s', 'fw'),
309
- $wp_version, $requirement
310
- );
311
- break;
312
- case 'framework':
313
- $requirement = sprintf(
314
- __('Current Framework version is %s, %s', 'fw'),
315
- fw()->manifest->get_version(), $requirement
316
- );
317
- break;
318
- case 'extensions':
319
- $extension = fw()->extensions->get($this->not_met_requirement['extension']);
320
-
321
- if ($extension) {
322
- $requirement = sprintf(
323
- __('Current version of the %s extension is %s, %s', 'fw'),
324
- $extension->manifest->get_name(), $extension->manifest->get_version(), $requirement
325
- );
326
- } else {
327
- if (empty($requirement)) {
328
- $requirement = sprintf(
329
- __('%s extension is required', 'fw'),
330
- ucfirst($this->not_met_requirement['extension'])
331
- );
332
- } else {
333
- $requirement = sprintf(
334
- __('%s extension is required (%s)', 'fw'),
335
- ucfirst($this->not_met_requirement['extension']), $requirement
336
- );
337
- }
338
- }
339
- break;
340
- default:
341
- $requirement = 'Unknown requirement "'. $this->not_met_requirement['requirement'] .'"';
342
- }
343
-
344
- return $requirement;
345
- }
346
- }
347
-
348
- class FW_Framework_Manifest extends FW_Manifest
349
- {
350
- public function __construct(array $manifest)
351
- {
352
- if (empty($manifest['name'])) {
353
- $manifest['name'] = __('Framework', 'fw');
354
- }
355
-
356
- parent::__construct($manifest);
357
- }
358
-
359
- protected function get_default_requirements()
360
- {
361
- return array(
362
- 'wordpress' => array(
363
- 'min_version' => '4.0',
364
- /*'max_version' => '10000.0.0',*/
365
- ),
366
- );
367
- }
368
- }
369
-
370
- class FW_Theme_Manifest extends FW_Manifest
371
- {
372
- public function __construct(array $manifest)
373
- {
374
- $manifest_defaults = array(
375
- /**
376
- * You can use this in a wp_option id,
377
- * so that option value will be different on a theme with different id.
378
- *
379
- * fixme: default value should be get_option( 'stylesheet' ) but it can't be changed now
380
- * because there can be themes that has saved Theme Settings in wp_option: 'fw_theme_settings_options:default'
381
- * changing this default value will result in Theme Settings options "reset".
382
- */
383
- 'id' => 'default',
384
- 'supported_extensions' => array(
385
- /*
386
- 'extension_name' => array(),
387
- */
388
- ),
389
- );
390
-
391
- $theme = wp_get_theme();
392
-
393
- foreach(array(
394
- 'name' => 'Name',
395
- 'uri' => 'ThemeURI',
396
- 'description' => 'Description',
397
- 'version' => 'Version',
398
- 'author' => 'Author',
399
- 'author_uri' => 'AuthorURI',
400
- ) as $manifest_key => $stylesheet_header) {
401
- $header_value = trim($theme->get($stylesheet_header));
402
-
403
- if ( is_child_theme() && $theme->parent() ) {
404
- switch ($manifest_key) {
405
- case 'version':
406
- case 'uri':
407
- case 'author':
408
- case 'author_uri':
409
- case 'license':
410
- // force parent theme value
411
- $header_value = $theme->parent()->get($stylesheet_header);
412
- break;
413
- default:
414
- if (!$header_value) {
415
- // use parent theme value only if child theme value is empty
416
- $header_value = $theme->parent()->get($stylesheet_header);
417
- }
418
- }
419
- }
420
-
421
- if ($header_value) {
422
- $manifest_defaults[$manifest_key] = $header_value;
423
- }
424
- }
425
-
426
- parent::__construct(array_merge($manifest_defaults, $manifest));
427
- }
428
-
429
- protected function get_default_requirements()
430
- {
431
- return array(
432
- 'wordpress' => array(
433
- 'min_version' => '4.0',
434
- /*'max_version' => '10000.0.0',*/
435
- ),
436
- 'framework' => array(
437
- /*'min_version' => '0.0.0',
438
- 'max_version' => '1000.0.0'*/
439
- ),
440
- 'extensions' => array(
441
- /*'extension_name' => array(
442
- 'min_version' => '0.0.0',
443
- 'max_version' => '1000.0.0'
444
- )*/
445
- )
446
- );
447
- }
448
-
449
- public function get_id()
450
- {
451
- return $this->manifest['id'];
452
- }
453
- }
454
-
455
- class FW_Extension_Manifest extends FW_Manifest
456
- {
457
- public function __construct(array $manifest)
458
- {
459
- parent::__construct($manifest);
460
-
461
- unset($manifest);
462
-
463
- // unset unnecessary keys
464
- unset($this->manifest['id']);
465
-
466
- $this->manifest = array_merge(array(
467
- /**
468
- * @type bool Display on the Extensions page or it's a hidden extension
469
- */
470
- 'display' => false,
471
- /**
472
- * @type bool If extension can exist alone
473
- * false - There is no sense for it to exist alone, it exists only when is required by some other extension.
474
- * true - Can exist alone without bothering about other extensions.
475
- */
476
- 'standalone' => false,
477
- /**
478
- * @type string Thumbnail used on the Extensions page
479
- * All framework extensions has thumbnails set in the available extensions list
480
- * but if your extension is not in that list and id located in the theme, you can set the thumbnail via this parameter
481
- */
482
- 'thumbnail' => null,
483
- ), $this->manifest);
484
- }
485
-
486
- protected function get_default_requirements()
487
- {
488
- return array(
489
- 'wordpress' => array(
490
- 'min_version' => '4.0',
491
- /*'max_version' => '10000.0.0',*/
492
- ),
493
- 'framework' => array(
494
- /*'min_version' => '0.0.0',
495
- 'max_version' => '1000.0.0'*/
496
- ),
497
- 'extensions' => array(
498
- /*'extension_name' => array(
499
- 'min_version' => '0.0.0',
500
- 'max_version' => '1000.0.0'
501
- )*/
502
- )
503
- );
504
- }
505
-
506
- public function get_required_extensions()
507
- {
508
- return $this->manifest['requirements']['extensions'];
509
- }
510
  }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ abstract class FW_Manifest
4
+ {
5
+ /**
6
+ * @var array
7
+ */
8
+ protected $manifest;
9
+
10
+ /**
11
+ * The first requirement that was not met
12
+ * (that marks that the requirements are not met)
13
+ *
14
+ * @var array
15
+ * array(
16
+ * 'requirement' => 'wordpress|framework',
17
+ * 'requirements' => array('min_version' => '1.2.3', ...)
18
+ * )
19
+ * or
20
+ * array(
21
+ * 'requirement' => 'extensions',
22
+ * 'extension' => 'extension_name',
23
+ * 'requirements' => array('min_version' => '1.2.3', ...)
24
+ * )
25
+ */
26
+ private $not_met_requirement;
27
+
28
+ /**
29
+ * When an requirement that sure will not change is not met and have no sense to execute check_requirements() again
30
+ * @var bool
31
+ */
32
+ private $not_met_is_final = false;
33
+
34
+ /**
35
+ * Not met requirement and skipped (not verified) requirements after $this->not_met_requirement was found
36
+ * @var array
37
+ */
38
+ private $requirements_for_verification;
39
+
40
+ private $requirements_verification_never_called = true;
41
+
42
+ /**
43
+ * @param array $manifest
44
+ */
45
+ protected function __construct(array $manifest)
46
+ {
47
+ $manifest = array_merge(array(
48
+ 'name' => null, // title
49
+ 'uri' => null,
50
+ 'description' => null,
51
+ 'version' => '0.0.0',
52
+ 'author' => null,
53
+ 'author_uri' => null,
54
+
55
+ // Custom fields
56
+ 'requirements' => array(),
57
+ ), $manifest);
58
+
59
+ /**
60
+ * Merge $manifest['requirements']
61
+ */
62
+ {
63
+ $requirements = $manifest['requirements'];
64
+
65
+ $manifest['requirements'] = array();
66
+
67
+ foreach ($this->get_default_requirements() as $default_requirement => $default_requirements) {
68
+ $manifest['requirements'][ $default_requirement ] = isset($requirements[$default_requirement])
69
+ ? array_merge(
70
+ $default_requirements,
71
+ $requirements[$default_requirement]
72
+ )
73
+ : $default_requirements;
74
+ }
75
+
76
+ unset($requirements);
77
+ }
78
+
79
+ $this->requirements_for_verification = $manifest['requirements'];
80
+
81
+ $this->manifest = $manifest;
82
+ }
83
+
84
+ /**
85
+ * @return array { 'requirement' => array('min_version' => '..', 'max_version' => '..') }
86
+ */
87
+ abstract protected function get_default_requirements();
88
+
89
+ /**
90
+ * @return bool
91
+ */
92
+ public function requirements_met()
93
+ {
94
+ if ($this->not_met_is_final) {
95
+ return false;
96
+ }
97
+
98
+ if ($this->requirements_verification_never_called) {
99
+ $this->requirements_verification_never_called = false;
100
+
101
+ $this->check_requirements();
102
+ }
103
+
104
+ return empty($this->requirements_for_verification) && empty($this->not_met_requirement);
105
+ }
106
+
107
+ /**
108
+ * @return bool
109
+ */
110
+ public function check_requirements()
111
+ {
112
+ if ($this->not_met_is_final) {
113
+ return false;
114
+ }
115
+
116
+ if ($this->requirements_met()) {
117
+ return true;
118
+ }
119
+
120
+ $this->not_met_requirement = array();
121
+
122
+ global $wp_version;
123
+
124
+ foreach ($this->requirements_for_verification as $requirement => $requirements) {
125
+ switch ($requirement) {
126
+ case 'wordpress':
127
+ if (
128
+ isset($requirements['min_version'])
129
+ &&
130
+ version_compare($wp_version, $requirements['min_version'], '<')
131
+ ) {
132
+ $this->not_met_requirement = array(
133
+ 'requirement' => $requirement,
134
+ 'requirements' => $requirements
135
+ );
136
+ $this->not_met_is_final = true;
137
+ break 2;
138
+ }
139
+
140
+ if (
141
+ isset($requirements['max_version'])
142
+ &&
143
+ version_compare($wp_version, $requirements['max_version'], '>')
144
+ ) {
145
+ $this->not_met_requirement = array(
146
+ 'requirement' => $requirement,
147
+ 'requirements' => $requirements
148
+ );
149
+ $this->not_met_is_final = true;
150
+ break 2;
151
+ }
152
+
153
+ // met
154
+ unset($this->requirements_for_verification[$requirement]);
155
+ break;
156
+ case 'framework':
157
+ if (
158
+ isset($requirements['min_version'])
159
+ &&
160
+ version_compare(fw()->manifest->get_version(), $requirements['min_version'], '<')
161
+ ) {
162
+ $this->not_met_requirement = array(
163
+ 'requirement' => $requirement,
164
+ 'requirements' => $requirements
165
+ );
166
+ $this->not_met_is_final = true;
167
+ break 2;
168
+ }
169
+
170
+ if (
171
+ isset($requirements['max_version'])
172
+ &&
173
+ version_compare(fw()->manifest->get_version(), $requirements['max_version'], '>')
174
+ ) {
175
+ $this->not_met_requirement = array(
176
+ 'requirement' => $requirement,
177
+ 'requirements' => $requirements
178
+ );
179
+ $this->not_met_is_final = true;
180
+ break 2;
181
+ }
182
+
183
+ // met
184
+ unset($this->requirements_for_verification[$requirement]);
185
+ break;
186
+ case 'extensions':
187
+ $extensions =& $requirements;
188
+
189
+ foreach ($extensions as $extension => $extension_requirements) {
190
+ $extension_instance = fw()->extensions->get($extension);
191
+
192
+ if (!$extension_instance) {
193
+ /**
194
+ * extension in requirements does not exists
195
+ * maybe try call this method later and maybe will exist, or it really does not exists
196
+ */
197
+ $this->not_met_requirement = array(
198
+ 'requirement' => $requirement,
199
+ 'extension' => $extension,
200
+ 'requirements' => $extension_requirements
201
+ );
202
+ break 3;
203
+ }
204
+
205
+ if (
206
+ isset($extension_requirements['min_version'])
207
+ &&
208
+ version_compare($extension_instance->manifest->get_version(), $extension_requirements['min_version'], '<')
209
+ ) {
210
+ $this->not_met_requirement = array(
211
+ 'requirement' => $requirement,
212
+ 'extension' => $extension,
213
+ 'requirements' => $extension_requirements
214
+ );
215
+ $this->not_met_is_final = true;
216
+ break 3;
217
+ }
218
+
219
+ if (
220
+ isset($extension_requirements['max_version'])
221
+ &&
222
+ version_compare($extension_instance->manifest->get_version(), $extension_requirements['max_version'], '>')
223
+ ) {
224
+ $this->not_met_requirement = array(
225
+ 'requirement' => $requirement,
226
+ 'extension' => $extension,
227
+ 'requirements' => $extension_requirements
228
+ );
229
+ $this->not_met_is_final = true;
230
+ break 3;
231
+ }
232
+
233
+ // met
234
+ unset($this->requirements_for_verification[$requirement][$extension]);
235
+ }
236
+
237
+ if (empty($this->requirements_for_verification[$requirement])) {
238
+ // all extensions requirements met
239
+ unset($this->requirements_for_verification[$requirement]);
240
+ }
241
+ break;
242
+ }
243
+ }
244
+
245
+ return $this->requirements_met();
246
+ }
247
+
248
+ public function get_version()
249
+ {
250
+ return $this->manifest['version'];
251
+ }
252
+
253
+ public function get_name()
254
+ {
255
+ return $this->manifest['name'];
256
+ }
257
+
258
+ /**
259
+ * @param string $multi_key
260
+ * @param mixed $default_value
261
+ * @return mixed
262
+ */
263
+ public function get($multi_key, $default_value = null)
264
+ {
265
+ return fw_akg($multi_key, $this->manifest, $default_value);
266
+ }
267
+
268
+ /**
269
+ * Call this only after check_requirements() failed
270
+ * @return array
271
+ */
272
+ public function get_not_met_requirement()
273
+ {
274
+ return $this->not_met_requirement;
275
+ }
276
+
277
+ /**
278
+ * Return user friendly requirement as text
279
+ * Call this only after check_requirements() failed
280
+ * @return string
281
+ */
282
+ public function get_not_met_requirement_text()
283
+ {
284
+ if (!$this->not_met_requirement) {
285
+ return '';
286
+ }
287
+
288
+ $requirement = array();
289
+
290
+ foreach ($this->not_met_requirement['requirements'] as $req_key => $req) {
291
+ switch ($req_key) {
292
+ case 'min_version':
293
+ $requirement[] = __('minimum required version is', 'fw') .' '. $req;
294
+ break;
295
+ case 'max_version':
296
+ $requirement[] = __('maximum required version is', 'fw') .' '. $req;
297
+ break;
298
+ }
299
+ }
300
+
301
+ $requirement = implode(' '. __('and', 'fw') .' ', $requirement);
302
+
303
+ switch ($this->not_met_requirement['requirement']) {
304
+ case 'wordpress':
305
+ global $wp_version;
306
+
307
+ $requirement = sprintf(
308
+ __('Current WordPress version is %s, %s', 'fw'),
309
+ $wp_version, $requirement
310
+ );
311
+ break;
312
+ case 'framework':
313
+ $requirement = sprintf(
314
+ __('Current Framework version is %s, %s', 'fw'),
315
+ fw()->manifest->get_version(), $requirement
316
+ );
317
+ break;
318
+ case 'extensions':
319
+ $extension = fw()->extensions->get($this->not_met_requirement['extension']);
320
+
321
+ if ($extension) {
322
+ $requirement = sprintf(
323
+ __('Current version of the %s extension is %s, %s', 'fw'),
324
+ $extension->manifest->get_name(), $extension->manifest->get_version(), $requirement
325
+ );
326
+ } else {
327
+ if (empty($requirement)) {
328
+ $requirement = sprintf(
329
+ __('%s extension is required', 'fw'),
330
+ ucfirst($this->not_met_requirement['extension'])
331
+ );
332
+ } else {
333
+ $requirement = sprintf(
334
+ __('%s extension is required (%s)', 'fw'),
335
+ ucfirst($this->not_met_requirement['extension']), $requirement
336
+ );
337
+ }
338
+ }
339
+ break;
340
+ default:
341
+ $requirement = 'Unknown requirement "'. $this->not_met_requirement['requirement'] .'"';
342
+ }
343
+
344
+ return $requirement;
345
+ }
346
+ }
347
+
348
+ class FW_Framework_Manifest extends FW_Manifest
349
+ {
350
+ public function __construct(array $manifest)
351
+ {
352
+ if (empty($manifest['name'])) {
353
+ $manifest['name'] = __('Framework', 'fw');
354
+ }
355
+
356
+ parent::__construct($manifest);
357
+ }
358
+
359
+ protected function get_default_requirements()
360
+ {
361
+ return array(
362
+ 'wordpress' => array(
363
+ 'min_version' => '4.0',
364
+ /*'max_version' => '10000.0.0',*/
365
+ ),
366
+ );
367
+ }
368
+ }
369
+
370
+ class FW_Theme_Manifest extends FW_Manifest
371
+ {
372
+ public function __construct(array $manifest)
373
+ {
374
+ $manifest_defaults = array(
375
+ /**
376
+ * You can use this in a wp_option id,
377
+ * so that option value will be different on a theme with different id.
378
+ *
379
+ * fixme: default value should be get_option( 'stylesheet' ) but it can't be changed now
380
+ * because there can be themes that has saved Theme Settings in wp_option: 'fw_theme_settings_options:default'
381
+ * changing this default value will result in Theme Settings options "reset".
382
+ */
383
+ 'id' => 'default',
384
+ 'supported_extensions' => array(
385
+ /*
386
+ 'extension_name' => array(),
387
+ */
388
+ ),
389
+ );
390
+
391
+ $theme = wp_get_theme();
392
+
393
+ foreach(array(
394
+ 'name' => 'Name',
395
+ 'uri' => 'ThemeURI',
396
+ 'description' => 'Description',
397
+ 'version' => 'Version',
398
+ 'author' => 'Author',
399
+ 'author_uri' => 'AuthorURI',
400
+ ) as $manifest_key => $stylesheet_header) {
401
+ $header_value = trim($theme->get($stylesheet_header));
402
+
403
+ if ( is_child_theme() && $theme->parent() ) {
404
+ switch ($manifest_key) {
405
+ case 'version':
406
+ case 'uri':
407
+ case 'author':
408
+ case 'author_uri':
409
+ case 'license':
410
+ // force parent theme value
411
+ $header_value = $theme->parent()->get($stylesheet_header);
412
+ break;
413
+ default:
414
+ if (!$header_value) {
415
+ // use parent theme value only if child theme value is empty
416
+ $header_value = $theme->parent()->get($stylesheet_header);
417
+ }
418
+ }
419
+ }
420
+
421
+ if ($header_value) {
422
+ $manifest_defaults[$manifest_key] = $header_value;
423
+ }
424
+ }
425
+
426
+ parent::__construct(array_merge($manifest_defaults, $manifest));
427
+ }
428
+
429
+ protected function get_default_requirements()
430
+ {
431
+ return array(
432
+ 'wordpress' => array(
433
+ 'min_version' => '4.0',
434
+ /*'max_version' => '10000.0.0',*/
435
+ ),
436
+ 'framework' => array(
437
+ /*'min_version' => '0.0.0',
438
+ 'max_version' => '1000.0.0'*/
439
+ ),
440
+ 'extensions' => array(
441
+ /*'extension_name' => array(
442
+ 'min_version' => '0.0.0',
443
+ 'max_version' => '1000.0.0'
444
+ )*/
445
+ )
446
+ );
447
+ }
448
+
449
+ public function get_id()
450
+ {
451
+ return $this->manifest['id'];
452
+ }
453
+ }
454
+
455
+ class FW_Extension_Manifest extends FW_Manifest
456
+ {
457
+ public function __construct(array $manifest)
458
+ {
459
+ parent::__construct($manifest);
460
+
461
+ unset($manifest);
462
+
463
+ // unset unnecessary keys
464
+ unset($this->manifest['id']);
465
+
466
+ $this->manifest = array_merge(array(
467
+ /**
468
+ * @type bool Display on the Extensions page or it's a hidden extension
469
+ */
470
+ 'display' => false,
471
+ /**
472
+ * @type bool If extension can exist alone
473
+ * false - There is no sense for it to exist alone, it exists only when is required by some other extension.
474
+ * true - Can exist alone without bothering about other extensions.
475
+ */
476
+ 'standalone' => false,
477
+ /**
478
+ * @type string Thumbnail used on the Extensions page
479
+ * All framework extensions has thumbnails set in the available extensions list
480
+ * but if your extension is not in that list and id located in the theme, you can set the thumbnail via this parameter
481
+ */
482
+ 'thumbnail' => null,
483
+ ), $this->manifest);
484
+ }
485
+
486
+ protected function get_default_requirements()
487
+ {
488
+ return array(
489
+ 'wordpress' => array(
490
+ 'min_version' => '4.0',
491
+ /*'max_version' => '10000.0.0',*/
492
+ ),
493
+ 'framework' => array(
494
+ /*'min_version' => '0.0.0',
495
+ 'max_version' => '1000.0.0'*/
496
+ ),
497
+ 'extensions' => array(
498
+ /*'extension_name' => array(
499
+ 'min_version' => '0.0.0',
500
+ 'max_version' => '1000.0.0'
501
+ )*/
502
+ )
503
+ );
504
+ }
505
+
506
+ public function get_required_extensions()
507
+ {
508
+ return $this->manifest['requirements']['extensions'];
509
+ }
510
  }
framework/core/components/backend.php CHANGED
@@ -1,2069 +1,2075 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- /**
6
- * Backend functionality
7
- */
8
- final class _FW_Component_Backend {
9
-
10
- /** @var callable */
11
- private $print_meta_box_content_callback;
12
-
13
- /** @var FW_Form */
14
- private $settings_form;
15
-
16
- private $available_render_designs = array( 'default', 'taxonomy', 'customizer' );
17
-
18
- private $default_render_design = 'default';
19
-
20
- /**
21
- * Store option types for registration, until they will be required
22
- * @var array|false
23
- * array Can have some pending option types in it
24
- * false Option types already requested and was registered, so do not use pending anymore
25
- */
26
- private $option_types_pending_registration = array();
27
-
28
- /**
29
- * Contains all option types
30
- * @var FW_Option_Type[]
31
- */
32
- private $option_types = array();
33
-
34
- /**
35
- * @var FW_Option_Type_Undefined
36
- */
37
- private $undefined_option_type;
38
-
39
- /**
40
- * Store container types for registration, until they will be required
41
- * @var array|false
42
- * array Can have some pending container types in it
43
- * false Container types already requested and was registered, so do not use pending anymore
44
- */
45
- private $container_types_pending_registration = array();
46
-
47
- /**
48
- * Contains all container types
49
- * @var FW_Container_Type[]
50
- */
51
- private $container_types = array();
52
-
53
- /**
54
- * @var FW_Container_Type_Undefined
55
- */
56
- private $undefined_container_type;
57
-
58
- private $static_registered = false;
59
-
60
- /**
61
- * @var FW_Access_Key
62
- */
63
- private $access_key;
64
-
65
- /**
66
- * @internal
67
- */
68
- public function _get_settings_page_slug() {
69
- return 'fw-settings';
70
- }
71
-
72
- private function get_current_edit_taxonomy() {
73
- static $cache_current_taxonomy_data = null;
74
-
75
- if ( $cache_current_taxonomy_data !== null ) {
76
- return $cache_current_taxonomy_data;
77
- }
78
-
79
- $result = array(
80
- 'taxonomy' => null,
81
- 'term_id' => 0,
82
- );
83
-
84
- do {
85
- if ( ! is_admin() ) {
86
- break;
87
- }
88
-
89
- // code from /wp-admin/admin.php line 110
90
- {
91
- if ( isset( $_REQUEST['taxonomy'] ) && taxonomy_exists( $_REQUEST['taxonomy'] ) ) {
92
- $taxnow = $_REQUEST['taxonomy'];
93
- } else {
94
- $taxnow = '';
95
- }
96
- }
97
-
98
- if ( empty( $taxnow ) ) {
99
- break;
100
- }
101
-
102
- $result['taxonomy'] = $taxnow;
103
-
104
- if ( empty( $_REQUEST['tag_ID'] ) ) {
105
- return $result;
106
- }
107
-
108
- // code from /wp-admin/edit-tags.php
109
- {
110
- $tag_ID = (int) $_REQUEST['tag_ID'];
111
- }
112
-
113
- $result['term_id'] = $tag_ID;
114
- } while ( false );
115
-
116
- $cache_current_taxonomy_data = $result;
117
-
118
- return $cache_current_taxonomy_data;
119
- }
120
-
121
- public function __construct() {
122
- $this->print_meta_box_content_callback = create_function( '$post,$args', 'echo $args["args"];' );
123
- }
124
-
125
- /**
126
- * @internal
127
- */
128
- public function _init() {
129
- if ( is_admin() ) {
130
- $this->settings_form = new FW_Form('fw_settings', array(
131
- 'render' => array($this, '_settings_form_render'),
132
- 'validate' => array($this, '_settings_form_validate'),
133
- 'save' => array($this, '_settings_form_save'),
134
- ));
135
- }
136
-
137
- $this->add_actions();
138
- $this->add_filters();
139
- }
140
-
141
- /**
142
- * @internal
143
- */
144
- public function _after_components_init() {}
145
-
146
- private function get_access_key()
147
- {
148
- if (!$this->access_key) {
149
- $this->access_key = new FW_Access_Key('fw_backend');
150
- }
151
-
152
- return $this->access_key;
153
- }
154
-
155
- private function add_actions() {
156
- if ( is_admin() ) {
157
- add_action('admin_menu', array($this, '_action_admin_menu'));
158
- add_action('add_meta_boxes', array($this, '_action_create_post_meta_boxes'), 10, 2);
159
- add_action('init', array($this, '_action_init'), 20);
160
- add_action('admin_enqueue_scripts', array($this, '_action_admin_register_scripts'),
161
- /**
162
- * Usually when someone register/enqueue a script/style to be used in other places
163
- * in 'admin_enqueue_scripts' actions with default (not set) priority 10, they use priority 9.
164
- * Use here priority 8, in case those scripts/styles used in actions with priority 9
165
- * are using scripts/styles registered here
166
- */
167
- 8
168
- );
169
- add_action('admin_enqueue_scripts', array($this, '_action_admin_enqueue_scripts'),
170
- /**
171
- * In case some custom defined option types are using script/styles registered
172
- * in actions with default priority 10 (make sure the enqueue is executed after register)
173
- */
174
- 11
175
- );
176
-
177
- // render and submit options from javascript
178
- {
179
- add_action('wp_ajax_fw_backend_options_render', array($this, '_action_ajax_options_render'));
180
- add_action('wp_ajax_fw_backend_options_get_values', array($this, '_action_ajax_options_get_values'));
181
- }
182
- }
183
-
184
- add_action('save_post', array($this, '_action_save_post'), 7, 3);
185
- add_action('wp_restore_post_revision', array($this, '_action_restore_post_revision'), 10, 2);
186
- add_action('_wp_put_post_revision', array($this, '_action__wp_put_post_revision'));
187
-
188
- add_action('customize_register', array($this, '_action_customize_register'), 7);
189
- }
190
-
191
- private function add_filters() {
192
- if ( is_admin() ) {
193
- add_filter('admin_footer_text', array($this, '_filter_admin_footer_text'), 11);
194
- add_filter('update_footer', array($this, '_filter_footer_version'), 11);
195
- }
196
- }
197
-
198
- /**
199
- * @param string|FW_Option_Type $option_type_class
200
- *
201
- * @internal
202
- */
203
- private function register_option_type( $option_type_class ) {
204
- if ( is_array( $this->option_types_pending_registration ) ) {
205
- // Option types never requested. Continue adding to pending
206
- $this->option_types_pending_registration[] = $option_type_class;
207
- } else {
208
- if ( is_string( $option_type_class ) ) {
209
- $option_type_class = new $option_type_class;
210
- }
211
-
212
- if ( ! is_subclass_of( $option_type_class, 'FW_Option_Type' ) ) {
213
- trigger_error( 'Invalid option type class ' . get_class( $option_type_class ), E_USER_WARNING );
214
- return;
215
- }
216
-
217
- /**
218
- * @var FW_Option_Type $option_type_class
219
- */
220
-
221
- $type = $option_type_class->get_type();
222
-
223
- if ( isset( $this->option_types[ $type ] ) ) {
224
- trigger_error( 'Option type "' . $type . '" already registered', E_USER_WARNING );
225
- return;
226
- }
227
-
228
- $this->option_types[ $type ] = $option_type_class;
229
-
230
- $this->option_types[ $type ]->_call_init($this->get_access_key());
231
- }
232
- }
233
-
234
- /**
235
- * @param string|FW_Container_Type $container_type_class
236
- *
237
- * @internal
238
- */
239
- private function register_container_type( $container_type_class ) {
240
- if ( is_array( $this->container_types_pending_registration ) ) {
241
- // Container types never requested. Continue adding to pending
242
- $this->container_types_pending_registration[] = $container_type_class;
243
- } else {
244
- if ( is_string( $container_type_class ) ) {
245
- $container_type_class = new $container_type_class;
246
- }
247
-
248
- if ( ! is_subclass_of( $container_type_class, 'FW_Container_Type' ) ) {
249
- trigger_error( 'Invalid container type class ' . get_class( $container_type_class ), E_USER_WARNING );
250
- return;
251
- }
252
-
253
- /**
254
- * @var FW_Container_Type $container_type_class
255
- */
256
-
257
- $type = $container_type_class->get_type();
258
-
259
- if ( isset( $this->container_types[ $type ] ) ) {
260
- trigger_error( 'Container type "' . $type . '" already registered', E_USER_WARNING );
261
- return;
262
- }
263
-
264
- $this->container_types[ $type ] = $container_type_class;
265
-
266
- $this->container_types[ $type ]->_call_init($this->get_access_key());
267
- }
268
- }
269
-
270
- private function register_static() {
271
- if (
272
- !doing_action('admin_enqueue_scripts')
273
- &&
274
- !did_action('admin_enqueue_scripts')
275
- ) {
276
- /**
277
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
278
- * and maybe they are used in dependencies in handles that are going to be enqueued.
279
- * So as a result some handles will not be equeued because of not registered dependecies.
280
- */
281
- return;
282
- }
283
-
284
- if ( $this->static_registered ) {
285
- return;
286
- }
287
-
288
- /**
289
- * Register styles/scripts only in admin area, on frontend it's not allowed to use styles/scripts from framework backend core
290
- * because they are meant to be used only in backend and can be changed in the future.
291
- * If you want to use a style/script from framework backend core, copy it to your theme and enqueue as a theme style/script.
292
- */
293
- if ( ! is_admin() ) {
294
- $this->static_registered = true;
295
-
296
- return;
297
- }
298
-
299
- wp_register_script(
300
- 'fw-events',
301
- fw_get_framework_directory_uri( '/static/js/fw-events.js' ),
302
- array( 'backbone' ),
303
- fw()->manifest->get_version(),
304
- true
305
- );
306
-
307
- wp_register_script(
308
- 'fw-ie-fixes',
309
- fw_get_framework_directory_uri( '/static/js/ie-fixes.js' ),
310
- array(),
311
- fw()->manifest->get_version(),
312
- true
313
- );
314
-
315
- {
316
- wp_register_style(
317
- 'qtip',
318
- fw_get_framework_directory_uri( '/static/libs/qtip/css/jquery.qtip.min.css' ),
319
- array(),
320
- fw()->manifest->get_version()
321
- );
322
- wp_register_script(
323
- 'qtip',
324
- fw_get_framework_directory_uri( '/static/libs/qtip/jquery.qtip.min.js' ),
325
- array( 'jquery' ),
326
- fw()->manifest->get_version()
327
- );
328
- }
329
-
330
- /**
331
- * Important!
332
- * Call wp_enqueue_media() before wp_enqueue_script('fw') (or using 'fw' in your script dependencies)
333
- * otherwise fw.OptionsModal won't work
334
- */
335
- {
336
- wp_register_style(
337
- 'fw',
338
- fw_get_framework_directory_uri( '/static/css/fw.css' ),
339
- array( 'qtip' ),
340
- fw()->manifest->get_version()
341
- );
342
-
343
- wp_register_script(
344
- 'fw',
345
- fw_get_framework_directory_uri( '/static/js/fw.js' ),
346
- array( 'jquery', 'fw-events', 'backbone', 'qtip' ),
347
- fw()->manifest->get_version(),
348
- true
349
- );
350
-
351
- wp_localize_script( 'fw', '_fw_localized', array(
352
- 'FW_URI' => fw_get_framework_directory_uri(),
353
- 'SITE_URI' => site_url(),
354
- 'l10n' => array(
355
- 'done' => __( 'Done', 'fw' ),
356
- 'ah_sorry' => __( 'Ah, Sorry', 'fw' ),
357
- 'save' => __( 'Save', 'fw' ),
358
- 'reset' => __( 'Reset', 'fw' ),
359
- ),
360
- ) );
361
- }
362
-
363
- {
364
- wp_register_style(
365
- 'fw-backend-options',
366
- fw_get_framework_directory_uri( '/static/css/backend-options.css' ),
367
- array( 'fw' ),
368
- fw()->manifest->get_version()
369
- );
370
-
371
- wp_register_script(
372
- 'fw-backend-options',
373
- fw_get_framework_directory_uri( '/static/js/backend-options.js' ),
374
- array( 'fw', 'fw-events', 'postbox', 'jquery-ui-tabs' ),
375
- fw()->manifest->get_version(),
376
- true
377
- );
378
-
379
- wp_localize_script( 'fw', '_fw_backend_options_localized', array(
380
- 'lazy_tabs' => fw()->theme->get_config('lazy_tabs')
381
- ) );
382
- }
383
-
384
- {
385
- wp_register_style(
386
- 'fw-selectize',
387
- fw_get_framework_directory_uri( '/static/libs/selectize/selectize.css' ),
388
- array(),
389
- fw()->manifest->get_version()
390
- );
391
- wp_register_script(
392
- 'fw-selectize',
393
- fw_get_framework_directory_uri( '/static/libs/selectize/selectize.min.js' ),
394
- array( 'jquery', 'fw-ie-fixes' ),
395
- fw()->manifest->get_version(),
396
- true
397
- );
398
- }
399
-
400
- {
401
- wp_register_script(
402
- 'fw-mousewheel',
403
- fw_get_framework_directory_uri( '/static/libs/mousewheel/jquery.mousewheel.min.js' ),
404
- array( 'jquery' ),
405
- fw()->manifest->get_version(),
406
- true
407
- );
408
- }
409
-
410
- {
411
- wp_register_style(
412
- 'fw-jscrollpane',
413
- fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.css' ),
414
- array(),
415
- fw()->manifest->get_version()
416
- );
417
- wp_register_script( 'fw-jscrollpane',
418
- fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.min.js' ),
419
- array( 'jquery', 'fw-mousewheel' ),
420
- fw()->manifest->get_version(),
421
- true
422
- );
423
- }
424
-
425
- wp_register_style(
426
- 'fw-font-awesome',
427
- fw_get_framework_directory_uri( '/static/libs/font-awesome/css/font-awesome.min.css' ),
428
- array(),
429
- fw()->manifest->get_version()
430
- );
431
-
432
- wp_register_script(
433
- 'backbone-relational',
434
- fw_get_framework_directory_uri( '/static/libs/backbone-relational/backbone-relational.js' ),
435
- array( 'backbone' ),
436
- fw()->manifest->get_version(),
437
- true
438
- );
439
-
440
- wp_register_script(
441
- 'fw-uri',
442
- fw_get_framework_directory_uri( '/static/libs/uri/URI.js' ),
443
- array(),
444
- fw()->manifest->get_version(),
445
- true
446
- );
447
-
448
- wp_register_script(
449
- 'fw-moment',
450
- fw_get_framework_directory_uri( '/static/libs/moment/moment.min.js' ),
451
- array(),
452
- fw()->manifest->get_version(),
453
- true
454
- );
455
-
456
- wp_register_script(
457
- 'fw-form-helpers',
458
- fw_get_framework_directory_uri( '/static/js/fw-form-helpers.js' ),
459
- array( 'jquery' ),
460
- fw()->manifest->get_version(),
461
- true
462
- );
463
-
464
- wp_register_style(
465
- 'fw-unycon',
466
- fw_get_framework_directory_uri( '/static/libs/unycon/unycon.css' ),
467
- array(),
468
- fw()->manifest->get_version()
469
- );
470
-
471
- $this->static_registered = true;
472
- }
473
-
474
- /**
475
- * @internal
476
- */
477
- public function _action_admin_menu() {
478
- $data = array(
479
- 'capability' => 'manage_options',
480
- 'slug' => $this->_get_settings_page_slug(),
481
- 'content_callback' => array( $this, '_print_settings_page' ),
482
- );
483
-
484
- if ( ! current_user_can( $data['capability'] ) ) {
485
- return;
486
- }
487
-
488
- if ( ! fw()->theme->locate_path('/options/settings.php') ) {
489
- return;
490
- }
491
-
492
- /**
493
- * Collect $hookname that contains $data['slug'] before the action
494
- * and skip them in verification after action
495
- */
496
- {
497
- global $_registered_pages;
498
-
499
- $found_hooknames = array();
500
-
501
- if ( ! empty( $_registered_pages ) ) {
502
- foreach ( $_registered_pages as $hookname => $b ) {
503
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
504
- $found_hooknames[ $hookname ] = true;
505
- }
506
- }
507
- }
508
- }
509
-
510
- /**
511
- * Use this action if you what to add the settings page in a custom place in menu
512
- * Usage example http://pastebin.com/gvAjGRm1
513
- */
514
- do_action( 'fw_backend_add_custom_settings_menu', $data );
515
-
516
- /**
517
- * Check if settings menu was added in the action above
518
- */
519
- {
520
- $menu_exists = false;
521
-
522
- if ( ! empty( $_registered_pages ) ) {
523
- foreach ( $_registered_pages as $hookname => $b ) {
524
- if ( isset( $found_hooknames[ $hookname ] ) ) {
525
- continue;
526
- }
527
-
528
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
529
- $menu_exists = true;
530
- break;
531
- }
532
- }
533
- }
534
- }
535
-
536
- if ( $menu_exists ) {
537
- return;
538
- }
539
-
540
- add_theme_page(
541
- __( 'Theme Settings', 'fw' ),
542
- __( 'Theme Settings', 'fw' ),
543
- $data['capability'],
544
- $data['slug'],
545
- $data['content_callback']
546
- );
547
-
548
- add_action( 'admin_menu', array( $this, '_action_admin_change_theme_settings_order' ), 9999 );
549
- }
550
-
551
- public function _filter_admin_footer_text( $html ) {
552
- if (
553
- (
554
- current_user_can( 'update_themes' )
555
- ||
556
- current_user_can( 'update_plugins' )
557
- )
558
- &&
559
- fw_current_screen_match(array(
560
- 'only' => array(
561
- array('parent_base' => fw()->extensions->manager->get_page_slug()) // Unyson Extensions page
562
- )
563
- ))
564
- ) {
565
- return ( empty( $html ) ? '' : $html . '<br/>' )
566
- . '<em>'
567
- . str_replace(
568
- array(
569
- '{wp_review_link}',
570
- '{facebook_share_link}',
571
- '{twitter_share_link}',
572
- ),
573
- array(
574
- fw_html_tag('a', array(
575
- 'target' => '_blank',
576
- 'href' => 'https://wordpress.org/support/view/plugin-reviews/unyson?filter=5#postform',
577
- ), __('leave a review', 'fw')),
578
- fw_html_tag('a', array(
579
- 'target' => '_blank',
580
- 'href' => 'https://www.facebook.com/sharer/sharer.php?'. http_build_query(array(
581
- 'u' => 'http://unyson.io',
582
- )),
583
- 'onclick' => 'return !window.open(this.href, \'Facebook\', \'width=640,height=300\')',
584
- ), __('Facebook', 'fw')),
585
- fw_html_tag('a', array(
586
- 'target' => '_blank',
587
- 'href' => 'https://twitter.com/home?'. http_build_query(array(
588
- 'status' => __('Unyson WordPress Framework is the fastest and easiest way to develop a premium theme. I highly recommend it', 'fw')
589
- .' http://unyson.io/ #UnysonWP',
590
- )),
591
- 'onclick' => 'return !window.open(this.href, \'Twitter\', \'width=640,height=430\')',
592
- ), __('Twitter', 'fw')),
593
- ),
594
- __('If you like Unyson, {wp_review_link}, share on {facebook_share_link} or {twitter_share_link}.', 'fw')
595
- )
596
- . '</em>';
597
- } else {
598
- return $html;
599
- }
600
- }
601
-
602
- /**
603
- * Print framework version in the admin footer
604
- *
605
- * @param string $html
606
- *
607
- * @return string
608
- * @internal
609
- */
610
- public function _filter_footer_version( $html ) {
611
- if ( current_user_can( 'update_themes' ) || current_user_can( 'update_plugins' ) ) {
612
- return ( empty( $html ) ? '' : $html . ' | ' ) . fw()->manifest->get_name() . ' ' . fw()->manifest->get_version();
613
- } else {
614
- return $html;
615
- }
616
- }
617
-
618
- public function _action_admin_change_theme_settings_order() {
619
- global $submenu;
620
-
621
- if ( ! isset( $submenu['themes.php'] ) ) {
622
- // probably current user doesn't have this item in menu
623
- return;
624
- }
625
-
626
- $id = $this->_get_settings_page_slug();
627
- $index = null;
628
-
629
- foreach ( $submenu['themes.php'] as $key => $sm ) {
630
- if ( $sm[2] == $id ) {
631
- $index = $key;
632
- break;
633
- }
634
- }
635
-
636
- if ( ! empty( $index ) ) {
637
- $item = $submenu['themes.php'][ $index ];
638
- unset( $submenu['themes.php'][ $index ] );
639
- array_unshift( $submenu['themes.php'], $item );
640
- }
641
- }
642
-
643
- public function _print_settings_page() {
644
- echo '<div class="wrap">';
645
-
646
- if ( fw()->theme->get_config( 'settings_form_side_tabs' ) ) {
647
- // this is needed for flash messages (admin notices) to be displayed properly
648
- echo '<h2 class="fw-hidden"></h2>';
649
- } else {
650
- echo '<h2>' . __( 'Theme Settings', 'fw' ) . '</h2><br/>';
651
- }
652
-
653
- $this->settings_form->render();
654
-
655
- echo '</div>';
656
- }
657
-
658
- /**
659
- * @param string $post_type
660
- * @param WP_Post $post
661
- */
662
- public function _action_create_post_meta_boxes( $post_type, $post ) {
663
- $options = fw()->theme->get_post_options( $post_type );
664
-
665
- if ( empty( $options ) ) {
666
- return;
667
- }
668
-
669
- $collected = array();
670
-
671
- fw_collect_options( $collected, $options, array(
672
- 'limit_option_types' => false,
673
- 'limit_container_types' => false,
674
- 'limit_level' => 1,
675
- 'info_wrapper' => true,
676
- ) );
677
-
678
- if (empty($collected)) {
679
- return;
680
- }
681
-
682
- $values = fw_get_db_post_option( $post->ID );
683
-
684
- foreach ( $collected as &$option ) {
685
- if (
686
- $option['group'] === 'container'
687
- &&
688
- $option['option']['type'] === 'box'
689
- ) { // this is a box, add it as a metabox
690
- $context = isset( $option['option']['context'] )
691
- ? $option['option']['context']
692
- : 'normal';
693
- $priority = isset( $option['option']['priority'] )
694
- ? $option['option']['priority']
695
- : 'default';
696
-
697
- add_meta_box(
698
- 'fw-options-box-' . $option['id'],
699
- empty( $option['option']['title'] ) ? ' ' : $option['option']['title'],
700
- $this->print_meta_box_content_callback,
701
- $post_type,
702
- $context,
703
- $priority,
704
- $this->render_options( $option['option']['options'], $values )
705
- );
706
- } else { // this is not a box, wrap it in auto-generated box
707
- add_meta_box(
708
- 'fw-options-box:auto-generated:'. time() .':'. fw_unique_increment(),
709
- ' ',
710
- $this->print_meta_box_content_callback,
711
- $post_type,
712
- 'normal',
713
- 'default',
714
- $this->render_options( array($option['id'] => $option['option']), $values )
715
- );
716
- }
717
- }
718
- }
719
-
720
- /**
721
- * @param object $term
722
- */
723
- public function _action_create_taxonomy_options( $term ) {
724
- $options = fw()->theme->get_taxonomy_options( $term->taxonomy );
725
-
726
- if ( empty( $options ) ) {
727
- return;
728
- }
729
-
730
- $collected = array();
731
-
732
- fw_collect_options( $collected, $options, array(
733
- 'limit_option_types' => false,
734
- 'limit_container_types' => array(), // only simple options are allowed on taxonomy edit page
735
- 'limit_level' => 1,
736
- ) );
737
-
738
- if ( empty( $collected ) ) {
739
- return;
740
- }
741
-
742
- $values = fw_get_db_term_option( $term->term_id, $term->taxonomy );
743
-
744
- // fixes word_press style: .form-field input { width: 95% }
745
- echo '<style type="text/css">.fw-option-type-radio input, .fw-option-type-checkbox input { width: auto; }</style>';
746
-
747
- echo $this->render_options( $collected, $values, array(), 'taxonomy' );
748
- }
749
-
750
- public function _action_init() {
751
- $current_edit_taxonomy = $this->get_current_edit_taxonomy();
752
-
753
- if ( $current_edit_taxonomy['taxonomy'] ) {
754
- add_action( $current_edit_taxonomy['taxonomy'] . '_edit_form_fields',
755
- array( $this, '_action_create_taxonomy_options' ), 10 );
756
- }
757
-
758
- if ( ! empty( $_POST ) ) {
759
- // is form submit
760
- add_action( 'edited_term', array( $this, '_action_term_edit' ), 10, 3 );
761
- }
762
- }
763
-
764
- /**
765
- * Experimental custom options save
766
- * @param array $options
767
- * @param array $values
768
- * @return array
769
- */
770
- private function process_options_handlers($options, $values)
771
- {
772
- $handled_values = array();
773
-
774
- foreach (
775
- fw_extract_only_options($options)
776
- as $option_id => $option
777
- ) {
778
- if (
779
- isset($option['option_handler'])
780
- &&
781
- $option['option_handler'] instanceof FW_Option_Handler
782
- ) {
783
- /*
784
- * if the option has a custom option_handler
785
- * the saving is delegated to the handler,
786
- * so it does not go to the post_meta
787
- */
788
- $option['option_handler']->save_option_value($option_id, $option, $values[$option_id]);
789
-
790
- $handled_values[$option_id] = true;
791
- }
792
- }
793
-
794
- return $handled_values;
795
- }
796
-
797
- /**
798
- * Save meta from $_POST to fw options (post meta)
799
- * @param int $post_id
800
- * @param WP_Post $post
801
- * @param bool $update
802
- */
803
- public function _action_save_post( $post_id, $post, $update ) {
804
- if (
805
- isset($_POST['post_ID'])
806
- &&
807
- intval($_POST['post_ID']) === intval($post_id)
808
- &&
809
- !empty($_POST[ FW_Option_Type::get_default_name_prefix() ]) // this happens on Quick Edit
810
- ) {
811
- /**
812
- * This happens on regular post form submit
813
- * All data from $_POST belongs this $post
814
- * so we save them in its post meta
815
- */
816
-
817
- static $post_options_save_happened = false;
818
- if ($post_options_save_happened) {
819
- /**
820
- * Prevent multiple options save for same post
821
- * It can happen from a recursion or wp_update_post() for same post id
822
- */
823
- return;
824
- } else {
825
- $post_options_save_happened = true;
826
- }
827
-
828
- $old_values = (array)fw_get_db_post_option($post_id);
829
- $current_values = fw_get_options_values_from_input(
830
- fw()->theme->get_post_options($post->post_type)
831
- );
832
-
833
- fw_set_db_post_option(
834
- $post_id,
835
- null,
836
- array_diff_key( // remove handled values
837
- $current_values,
838
- $this->process_options_handlers(
839
- fw()->theme->get_post_options($post->post_type),
840
- $current_values
841
- )
842
- )
843
- );
844
-
845
- /**
846
- * @deprecated
847
- * Use the 'fw_post_options_update' action
848
- */
849
- do_action( 'fw_save_post_options', $post_id, $post, $old_values );
850
- } elseif ($original_post_id = wp_is_post_autosave( $post_id )) {
851
- do {
852
- $parent = get_post($post->post_parent);
853
-
854
- if ( ! $parent instanceof WP_Post ) {
855
- break;
856
- }
857
-
858
- if (
859
- isset($_POST['post_ID'])
860
- &&
861
- intval($_POST['post_ID']) === intval($parent->ID)
862
- ) {} else {
863
- break;
864
- }
865
-
866
- if (empty($_POST[ FW_Option_Type::get_default_name_prefix() ])) {
867
- // this happens on Quick Edit
868
- break;
869
- }
870
-
871
- $current_values = fw_get_options_values_from_input(
872
- fw()->theme->get_post_options($parent->post_type)
873
- );
874
-
875
- fw_set_db_post_option(
876
- $post->ID,
877
- null,
878
- array_diff_key( // remove handled values
879
- $current_values,
880
- $this->process_options_handlers(
881
- fw()->theme->get_post_options($parent->post_type),
882
- $current_values
883
- )
884
- )
885
- );
886
- } while(false);
887
- } elseif ($original_post_id = wp_is_post_revision( $post_id )) {
888
- /**
889
- * Do nothing, the
890
- * - '_wp_put_post_revision'
891
- * - 'wp_restore_post_revision'
892
- * actions will handle this
893
- */
894
- } else {
895
- /**
896
- * This happens on:
897
- * - post add (auto-draft): do nothing
898
- * - revision restore: do nothing, that is handled by the 'wp_restore_post_revision' action
899
- */
900
- }
901
- }
902
-
903
- /**
904
- * @param $post_id
905
- * @param $revision_id
906
- */
907
- public function _action_restore_post_revision($post_id, $revision_id)
908
- {
909
- /**
910
- * Copy options meta from revision to post
911
- */
912
- fw_set_db_post_option(
913
- $post_id,
914
- null,
915
- (array)fw_get_db_post_option($revision_id, null, array())
916
- );
917
- }
918
-
919
- /**
920
- * @param $revision_id
921
- */
922
- public function _action__wp_put_post_revision($revision_id)
923
- {
924
- /**
925
- * Copy options meta from post to revision
926
- */
927
- fw_set_db_post_option(
928
- $revision_id,
929
- null,
930
- (array)fw_get_db_post_option(
931
- wp_is_post_revision($revision_id),
932
- null,
933
- array()
934
- )
935
- );
936
- }
937
-
938
- /**
939
- * Update all post meta `fw_option:<option-id>` with values from post options that has the 'save-in-separate-meta' parameter
940
- *
941
- * @param int $post_id
942
- *
943
- * @return bool
944
- * @deprecated since 2.5.0
945
- */
946
- public function _sync_post_separate_meta( $post_id ) {
947
- $post_type = get_post_type( $post_id );
948
-
949
- if ( ! $post_type ) {
950
- return false;
951
- }
952
-
953
- $meta_prefix = 'fw_option:';
954
-
955
- /**
956
- * Collect all options that needs to be saved in separate meta
957
- */
958
- {
959
- $options_values = fw_get_db_post_option( $post_id );
960
-
961
- $separate_meta_options = array();
962
-
963
- foreach (
964
- fw_extract_only_options( fw()->theme->get_post_options( $post_type ) )
965
- as $option_id => $option
966
- ) {
967
- if (
968
- isset( $option['save-in-separate-meta'] )
969
- &&
970
- $option['save-in-separate-meta']
971
- &&
972
- array_key_exists( $option_id, $options_values )
973
- ) {
974
- $separate_meta_options[ $meta_prefix . $option_id ] = $options_values[ $option_id ];
975
- }
976
- }
977
-
978
- unset( $options_values );
979
- }
980
-
981
- /**
982
- * Delete meta that starts with $meta_prefix
983
- */
984
- {
985
- /** @var wpdb $wpdb */
986
- global $wpdb;
987
-
988
- foreach (
989
- $wpdb->get_results(
990
- $wpdb->prepare(
991
- "SELECT meta_key " .
992
- "FROM {$wpdb->postmeta} " .
993
- "WHERE meta_key LIKE %s AND post_id = %d",
994
- $wpdb->esc_like( $meta_prefix ) . '%',
995
- $post_id
996
- )
997
- )
998
- as $row
999
- ) {
1000
- if ( array_key_exists( $row->meta_key, $separate_meta_options ) ) {
1001
- /**
1002
- * This meta exists and will be updated below.
1003
- * Do not delete for performance reasons, instead of delete->insert will be performed only update
1004
- */
1005
- continue;
1006
- } else {
1007
- // this option does not exist anymore
1008
- delete_post_meta( $post_id, $row->meta_key );
1009
- }
1010
- }
1011
- }
1012
-
1013
- foreach ( $separate_meta_options as $meta_key => $option_value ) {
1014
- fw_update_post_meta($post_id, $meta_key, $option_value );
1015
- }
1016
-
1017
- return true;
1018
- }
1019
-
1020
- public function _action_term_edit( $term_id, $tt_id, $taxonomy ) {
1021
- if ( ! isset( $_POST['action'] ) || ! isset( $_POST['taxonomy'] ) ) {
1022
- return; // this is not real term form submit, abort save
1023
- }
1024
-
1025
- if (intval(FW_Request::POST('tag_ID')) != $term_id) {
1026
- // the $_POST values belongs to another term, do not save them into this one
1027
- return;
1028
- }
1029
-
1030
- $old_values = (array) fw_get_db_term_option( $term_id, $taxonomy );
1031
-
1032
- fw_set_db_term_option(
1033
- $term_id,
1034
- $taxonomy,
1035
- null,
1036
- fw_get_options_values_from_input(
1037
- fw()->theme->get_taxonomy_options( $taxonomy )
1038
- )
1039
- );
1040
-
1041
- do_action( 'fw_save_term_options', $term_id, $taxonomy, $old_values );
1042
- }
1043
-
1044
- public function _action_admin_register_scripts() {
1045
- $this->register_static();
1046
- }
1047
-
1048
- public function _action_admin_enqueue_scripts() {
1049
- global $current_screen, $plugin_page, $post;
1050
-
1051
- /**
1052
- * Enqueue settings options static in <head>
1053
- */
1054
- {
1055
- if ( $this->_get_settings_page_slug() === $plugin_page ) {
1056
- fw()->backend->enqueue_options_static(
1057
- fw()->theme->get_settings_options()
1058
- );
1059
-
1060
- do_action( 'fw_admin_enqueue_scripts:settings' );
1061
- }
1062
- }
1063
-
1064
- /**
1065
- * Enqueue post options static in <head>
1066
- */
1067
- {
1068
- if ( 'post' === $current_screen->base && $post ) {
1069
- fw()->backend->enqueue_options_static(
1070
- fw()->theme->get_post_options( $post->post_type )
1071
- );
1072
-
1073
- do_action( 'fw_admin_enqueue_scripts:post', $post );
1074
- }
1075
- }
1076
-
1077
- /**
1078
- * Enqueue term options static in <head>
1079
- */
1080
- {
1081
- if (
1082
- 'edit-tags' === $current_screen->base
1083
- &&
1084
- $current_screen->taxonomy
1085
- &&
1086
- ! empty( $_GET['tag_ID'] )
1087
- ) {
1088
- fw()->backend->enqueue_options_static(
1089
- fw()->theme->get_taxonomy_options( $current_screen->taxonomy )
1090
- );
1091
-
1092
- do_action( 'fw_admin_enqueue_scripts:term', $current_screen->taxonomy );
1093
- }
1094
- }
1095
- }
1096
-
1097
- /**
1098
- * Render options html from input json
1099
- *
1100
- * POST vars:
1101
- * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1102
- * - values: {option_id: value, option_id: {...}, ...} // Optional // Object
1103
- * - data: {id_prefix: 'fw_options-a-b-', name_prefix: 'fw_options[a][b]'} // Optional // Object
1104
- */
1105
- public function _action_ajax_options_render() {
1106
- // options
1107
- {
1108
- if ( ! isset( $_POST['options'] ) ) {
1109
- wp_send_json_error( array(
1110
- 'message' => 'No options'
1111
- ) );
1112
- }
1113
-
1114
- $options = json_decode( FW_Request::POST( 'options' ), true );
1115
-
1116
- if ( ! $options ) {
1117
- wp_send_json_error( array(
1118
- 'message' => 'Wrong options'
1119
- ) );
1120
- }
1121
- }
1122
-
1123
- // values
1124
- {
1125
- if ( isset( $_POST['values'] ) ) {
1126
- $values = FW_Request::POST( 'values' );
1127
-
1128
- if (is_string($values)) {
1129
- $values = json_decode($values, true);
1130
- }
1131
- } else {
1132
- $values = array();
1133
- }
1134
- }
1135
-
1136
- // data
1137
- {
1138
- if ( isset( $_POST['data'] ) ) {
1139
- $data = FW_Request::POST( 'data' );
1140
- } else {
1141
- $data = array();
1142
- }
1143
- }
1144
-
1145
- {
1146
- foreach ( fw_extract_only_options( $options ) as $option_id => $option ) {
1147
- if ( ! isset( $values[ $option_id ] ) ) {
1148
- continue;
1149
- }
1150
-
1151
- /**
1152
- * Fix booleans
1153
- *
1154
- * In POST, booleans are transformed to strings: 'true' and 'false'
1155
- * Transform them back to booleans
1156
- */
1157
- do {
1158
- /**
1159
- * Detect if option is using booleans by sending it a boolean input value
1160
- * If it returns a boolean, then it works with booleans
1161
- */
1162
- if ( ! is_bool(
1163
- fw()->backend->option_type( $option['type'] )->get_value_from_input( $option, true )
1164
- ) ) {
1165
- break;
1166
- }
1167
-
1168
- if ( is_bool( $values[ $option_id ] ) ) {
1169
- // value is already boolean, does not need to fix
1170
- break;
1171
- }
1172
-
1173
- $values[ $option_id ] = ( $values[ $option_id ] === 'true' );
1174
-
1175
- continue 2;
1176
- } while(false);
1177
-
1178
- /**
1179
- * Fix integers
1180
- *
1181
- * In POST, integers are transformed to strings: '0', '1', '2', ...
1182
- * Transform them back to integers
1183
- */
1184
- do {
1185
- if (!is_numeric($values[ $option_id ])) {
1186
- // do nothing if value is not a number
1187
- break;
1188
- }
1189
-
1190
- /**
1191
- * Detect if option is using integer value by checking $option['value']
1192
- */
1193
- if ( isset($option['value']) && !is_int($option['value']) ) {
1194
- continue;
1195
- }
1196
-
1197
- $values[ $option_id ] = (float) $values[ $option_id ];
1198
-
1199
- continue 2;
1200
- } while(false);
1201
- }
1202
- }
1203
-
1204
- wp_send_json_success( array(
1205
- 'html' => fw()->backend->render_options( $options, $values, $data )
1206
- ) );
1207
- }
1208
-
1209
- /**
1210
- * Get options values from html generated with 'fw_backend_options_render' ajax action
1211
- *
1212
- * POST vars:
1213
- * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1214
- * - fw_options... // Use a jQuery "ajax form submit" to emulate real form submit
1215
- *
1216
- * Tip: Inside form html, add: <input type="hidden" name="options" value="[...json...]">
1217
- */
1218
- public function _action_ajax_options_get_values() {
1219
- // options
1220
- {
1221
- if ( ! isset( $_POST['options'] ) ) {
1222
- wp_send_json_error( array(
1223
- 'message' => 'No options'
1224
- ) );
1225
- }
1226
-
1227
- $options = json_decode( FW_Request::POST( 'options' ), true );
1228
-
1229
- if ( ! $options ) {
1230
- wp_send_json_error( array(
1231
- 'message' => 'Wrong options'
1232
- ) );
1233
- }
1234
- }
1235
-
1236
- // name_prefix
1237
- {
1238
- if ( isset( $_POST['name_prefix'] ) ) {
1239
- $name_prefix = FW_Request::POST( 'name_prefix' );
1240
- } else {
1241
- $name_prefix = FW_Option_Type::get_default_name_prefix();
1242
- }
1243
- }
1244
-
1245
- wp_send_json_success( array(
1246
- 'values' => fw_get_options_values_from_input(
1247
- $options,
1248
- FW_Request::POST( fw_html_attr_name_to_array_multi_key( $name_prefix ), array() )
1249
- )
1250
- ) );
1251
- }
1252
-
1253
- public function _settings_form_render( $data ) {
1254
- {
1255
- $this->enqueue_options_static( array() );
1256
-
1257
- wp_enqueue_script( 'fw-form-helpers' );
1258
- }
1259
-
1260
- $options = fw()->theme->get_settings_options();
1261
-
1262
- if ( empty( $options ) ) {
1263
- return $data;
1264
- }
1265
-
1266
- if ( $values = FW_Request::POST( FW_Option_Type::get_default_name_prefix() ) ) {
1267
- // This is form submit, extract correct values from $_POST values
1268
- $values = fw_get_options_values_from_input( $options, $values );
1269
- } else {
1270
- // Extract previously saved correct values
1271
- $values = fw_get_db_settings_option();
1272
- }
1273
-
1274
- $ajax_submit = fw()->theme->get_config( 'settings_form_ajax_submit' );
1275
- $side_tabs = fw()->theme->get_config( 'settings_form_side_tabs' );
1276
-
1277
- $data['attr']['class'] = 'fw-settings-form';
1278
-
1279
- if ( $side_tabs ) {
1280
- $data['attr']['class'] .= ' fw-backend-side-tabs';
1281
- }
1282
-
1283
- $data['submit']['html'] = '<!-- -->'; // is generated in view
1284
-
1285
- do_action( 'fw_settings_form_render', array(
1286
- 'ajax_submit' => $ajax_submit,
1287
- 'side_tabs' => $side_tabs,
1288
- ) );
1289
-
1290
- fw_render_view( fw_get_framework_directory( '/views/backend-settings-form.php' ), array(
1291
- 'options' => $options,
1292
- 'values' => $values,
1293
- 'reset_input_name' => '_fw_reset_options',
1294
- 'ajax_submit' => $ajax_submit,
1295
- 'side_tabs' => $side_tabs,
1296
- ), false );
1297
-
1298
- return $data;
1299
- }
1300
-
1301
- public function _settings_form_validate( $errors ) {
1302
- if ( ! current_user_can( 'manage_options' ) ) {
1303
- $errors['_no_permission'] = __( 'You have no permissions to change settings options', 'fw' );
1304
- }
1305
-
1306
- return $errors;
1307
- }
1308
-
1309
- public function _settings_form_save( $data ) {
1310
- $flash_id = 'fw_settings_form_save';
1311
- $old_values = (array) fw_get_db_settings_option();
1312
-
1313
- if ( ! empty( $_POST['_fw_reset_options'] ) ) { // The "Reset" button was pressed
1314
- fw_set_db_settings_option( null, array() );
1315
-
1316
- FW_Flash_Messages::add( $flash_id, __( 'The options were successfully reset', 'fw' ), 'success' );
1317
-
1318
- do_action( 'fw_settings_form_reset', $old_values );
1319
- } else { // The "Save" button was pressed
1320
- fw_set_db_settings_option(
1321
- null,
1322
- fw_get_options_values_from_input(
1323
- fw()->theme->get_settings_options()
1324
- )
1325
- );
1326
-
1327
- FW_Flash_Messages::add( $flash_id, __( 'The options were successfully saved', 'fw' ), 'success' );
1328
-
1329
- do_action( 'fw_settings_form_saved', $old_values );
1330
- }
1331
-
1332
- $redirect_url = fw_current_url();
1333
-
1334
- $data['redirect'] = $redirect_url;
1335
-
1336
- return $data;
1337
- }
1338
-
1339
- /**
1340
- * Render options array and return the generated HTML
1341
- *
1342
- * @param array $options
1343
- * @param array $values Correct values returned by fw_get_options_values_from_input()
1344
- * @param array $options_data {id_prefix => ..., name_prefix => ...}
1345
- * @param string $design
1346
- *
1347
- * @return string HTML
1348
- */
1349
- public function render_options( $options, $values = array(), $options_data = array(), $design = null ) {
1350
- if (empty($design)) {
1351
- $design = $this->default_render_design;
1352
- }
1353
-
1354
- if (
1355
- !doing_action('admin_enqueue_scripts')
1356
- &&
1357
- !did_action('admin_enqueue_scripts')
1358
- ) {
1359
- /**
1360
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1361
- * and maybe they are used in dependencies in handles that are going to be enqueued.
1362
- * So as a result some handles will not be equeued because of not registered dependecies.
1363
- */
1364
- } else {
1365
- /**
1366
- * register scripts and styles
1367
- * in case if this method is called before enqueue_scripts action
1368
- * and option types has some of these in their dependencies
1369
- */
1370
- $this->register_static();
1371
-
1372
- wp_enqueue_media();
1373
- wp_enqueue_style( 'fw-backend-options' );
1374
- wp_enqueue_script( 'fw-backend-options' );
1375
- }
1376
-
1377
- $collected = array();
1378
-
1379
- fw_collect_options( $collected, $options, array(
1380
- 'limit_option_types' => false,
1381
- 'limit_container_types' => false,
1382
- 'limit_level' => 1,
1383
- 'info_wrapper' => true,
1384
- ) );
1385
-
1386
- if ( empty( $collected ) ) {
1387
- return false;
1388
- }
1389
-
1390
- $html = '';
1391
-
1392
- $option = reset( $collected );
1393
-
1394
- $collected_type = array(
1395
- 'group' => $option['group'],
1396
- 'type' => $option['option']['type'],
1397
- );
1398
- $collected_type_options = array(
1399
- $option['id'] => &$option['option']
1400
- );
1401
-
1402
- while ( $collected_type_options ) {
1403
- $option = next( $collected );
1404
-
1405
- if ( $option ) {
1406
- if (
1407
- $option['group'] === $collected_type['group']
1408
- &&
1409
- $option['option']['type'] === $collected_type['type']
1410
- ) {
1411
- $collected_type_options[ $option['id'] ] = &$option['option'];
1412
- continue;
1413
- }
1414
- }
1415
-
1416
- switch ( $collected_type['group'] ) {
1417
- case 'container':
1418
- $html .= $this->container_type($collected_type['type'])->render(
1419
- $collected_type_options,
1420
- $values,
1421
- $options_data
1422
- );
1423
- break;
1424
- case 'option':
1425
- foreach ( $collected_type_options as $id => &$_option ) {
1426
- $data = $options_data; // do not change directly to not affect next loops
1427
-
1428
- $data['value'] = isset( $values[ $id ] ) ? $values[ $id ] : null;
1429
-
1430
- $html .= $this->render_option(
1431
- $id,
1432
- $_option,
1433
- $data,
1434
- $design
1435
- );
1436
- }
1437
- unset($_option);
1438
- break;
1439
- default:
1440
- $html .= '<p><em>' . __( 'Unknown collected group', 'fw' ) . ': ' . $collected_type['group'] . '</em></p>';
1441
- }
1442
-
1443
- unset( $collected_type, $collected_type_options );
1444
-
1445
- if ( $option ) {
1446
- $collected_type = array(
1447
- 'group' => $option['group'],
1448
- 'type' => $option['option']['type'],
1449
- );
1450
- $collected_type_options = array(
1451
- $option['id'] => &$option['option']
1452
- );
1453
- } else {
1454
- $collected_type_options = array();
1455
- }
1456
- }
1457
-
1458
- return $html;
1459
- }
1460
-
1461
- /**
1462
- * Enqueue options static
1463
- *
1464
- * Useful when you have dynamic options html on the page (for e.g. options modal)
1465
- * and in order to initialize that html properly, the option types scripts styles must be enqueued on the page
1466
- *
1467
- * @param array $options
1468
- */
1469
- public function enqueue_options_static( $options ) {
1470
- if (
1471
- !doing_action('admin_enqueue_scripts')
1472
- &&
1473
- !did_action('admin_enqueue_scripts')
1474
- ) {
1475
- /**
1476
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1477
- * and maybe they are used in dependencies in handles that are going to be enqueued.
1478
- * So as a result some handles will not be equeued because of not registered dependecies.
1479
- */
1480
- return;
1481
- } else {
1482
- /**
1483
- * register scripts and styles
1484
- * in case if this method is called before enqueue_scripts action
1485
- * and option types has some of these in their dependencies
1486
- */
1487
- $this->register_static();
1488
-
1489
- wp_enqueue_media();
1490
- wp_enqueue_style( 'fw-backend-options' );
1491
- wp_enqueue_script( 'fw-backend-options' );
1492
- }
1493
-
1494
- $collected = array();
1495
-
1496
- fw_collect_options( $collected, $options, array(
1497
- 'limit_option_types' => false,
1498
- 'limit_container_types' => false,
1499
- 'limit_level' => 0,
1500
- 'info_wrapper' => true,
1501
- ) );
1502
-
1503
- foreach ( $collected as &$option ) {
1504
- if ($option['group'] === 'option') {
1505
- fw()->backend->option_type($option['option']['type'])->enqueue_static($option['id'], $option['option']);
1506
- } elseif ($option['group'] === 'container') {
1507
- fw()->backend->container_type($option['option']['type'])->enqueue_static($option['id'], $option['option']);
1508
- }
1509
- }
1510
- }
1511
-
1512
- /**
1513
- * Render option enclosed in backend design
1514
- *
1515
- * @param string $id
1516
- * @param array $option
1517
- * @param array $data
1518
- * @param string $design default or taxonomy
1519
- *
1520
- * @return string
1521
- */
1522
- public function render_option( $id, $option, $data = array(), $design = null ) {
1523
- if (empty($design)) {
1524
- $design = $this->default_render_design;
1525
- }
1526
-
1527
- if (
1528
- !doing_action('admin_enqueue_scripts')
1529
- &&
1530
- !did_action('admin_enqueue_scripts')
1531
- ) {
1532
- /**
1533
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1534
- * and maybe they are used in dependencies in handles that are going to be enqueued.
1535
- * So as a result some handles will not be equeued because of not registered dependecies.
1536
- */
1537
- } else {
1538
- $this->register_static();
1539
- }
1540
-
1541
-
1542
- if ( ! in_array( $design, $this->available_render_designs ) ) {
1543
- trigger_error( 'Invalid render design specified: ' . $design, E_USER_WARNING );
1544
- $design = 'post';
1545
- }
1546
-
1547
- if ( ! isset( $data['id_prefix'] ) ) {
1548
- $data['id_prefix'] = FW_Option_Type::get_default_id_prefix();
1549
- }
1550
-
1551
- if (
1552
- isset($option['option_handler']) &&
1553
- $option['option_handler'] instanceof FW_Option_Handler
1554
- ) {
1555
-
1556
- /*
1557
- * if the option has a custom option_handler
1558
- * then the handler provides the option's value
1559
- */
1560
- $data['value'] = $option['option_handler']->get_option_value($id, $option, $data);
1561
- }
1562
-
1563
- return fw_render_view(fw_get_framework_directory('/views/backend-option-design-'. $design .'.php'), array(
1564
- 'id' => $id,
1565
- 'option' => $option,
1566
- 'data' => $data,
1567
- ) );
1568
- }
1569
-
1570
- /**
1571
- * Render a meta box
1572
- *
1573
- * @param string $id
1574
- * @param string $title
1575
- * @param string $content HTML
1576
- * @param array $other Optional elements
1577
- *
1578
- * @return string Generated meta box html
1579
- */
1580
- public function render_box( $id, $title, $content, $other = array() ) {
1581
- if ( ! function_exists( 'add_meta_box' ) ) {
1582
- trigger_error( 'Try call this method later (\'admin_init\' action), add_meta_box() function does not exists yet.',
1583
- E_USER_WARNING );
1584
-
1585
- return '';
1586
- }
1587
-
1588
- $other = array_merge( array(
1589
- 'html_before_title' => false,
1590
- 'html_after_title' => false,
1591
- 'attr' => array(),
1592
- ), $other );
1593
-
1594
- {
1595
- $placeholders = array(
1596
- 'id' => '{{meta_box_id}}',
1597
- 'title' => '{{meta_box_title}}',
1598
- 'content' => '{{meta_box_content}}',
1599
- );
1600
-
1601
- // other placeholders
1602
- {
1603
- $placeholders['html_before_title'] = '{{meta_box_html_before_title}}';
1604
- $placeholders['html_after_title'] = '{{meta_box_html_after_title}}';
1605
- $placeholders['attr'] = '{{meta_box_attr}}';
1606
- $placeholders['attr_class'] = '{{meta_box_attr_class}}';
1607
- }
1608
- }
1609
-
1610
- $cache_key = 'fw_meta_box_template';
1611
-
1612
- try {
1613
- $meta_box_template = FW_Cache::get( $cache_key );
1614
- } catch ( FW_Cache_Not_Found_Exception $e ) {
1615
- $temp_screen_id = 'fw-temp-meta-box-screen-id-' . fw_unique_increment();
1616
- $context = 'normal';
1617
-
1618
- add_meta_box(
1619
- $placeholders['id'],
1620
- $placeholders['title'],
1621
- $this->print_meta_box_content_callback,
1622
- $temp_screen_id,
1623
- $context,
1624
- 'default',
1625
- $placeholders['content']
1626
- );
1627
-
1628
- ob_start();
1629
-
1630
- do_meta_boxes( $temp_screen_id, $context, null );
1631
-
1632
- $meta_box_template = ob_get_clean();
1633
-
1634
- remove_meta_box( $id, $temp_screen_id, $context );
1635
-
1636
- // remove wrapper div, leave only meta box div
1637
- {
1638
- // <div ...>
1639
- {
1640
- $meta_box_template = str_replace(
1641
- '<div id="' . $context . '-sortables" class="meta-box-sortables">',
1642
- '',
1643
- $meta_box_template
1644
- );
1645
- }
1646
-
1647
- // </div>
1648
- {
1649
- $meta_box_template = explode( '</div>', $meta_box_template );
1650
- array_pop( $meta_box_template );
1651
- $meta_box_template = implode( '</div>', $meta_box_template );
1652
- }
1653
- }
1654
-
1655
- // add 'fw-postbox' class and some attr related placeholders
1656
- $meta_box_template = str_replace(
1657
- 'class="postbox',
1658
- $placeholders['attr'] . ' class="postbox fw-postbox' . $placeholders['attr_class'],
1659
- $meta_box_template
1660
- );
1661
-
1662
- // add html_before|after_title placeholders
1663
- {
1664
- $meta_box_template = str_replace(
1665
- '<span>' . $placeholders['title'] . '</span>',
1666
-
1667
- /**
1668
- * used <small> not <span> because there is a lot of css and js
1669
- * that thinks inside <h2 class="hndle"> there is only one <span>
1670
- * so do not brake their logic
1671
- */
1672
- '<small class="fw-html-before-title">' . $placeholders['html_before_title'] . '</small>' .
1673
- '<span>' . $placeholders['title'] . '</span>' .
1674
- '<small class="fw-html-after-title">' . $placeholders['html_after_title'] . '</small>',
1675
-
1676
- $meta_box_template
1677
- );
1678
- }
1679
-
1680
- FW_Cache::set( $cache_key, $meta_box_template );
1681
- }
1682
-
1683
- // prepare attributes
1684
- {
1685
- $attr_class = '';
1686
- if ( isset( $other['attr']['class'] ) ) {
1687
- $attr_class = ' ' . $other['attr']['class'];
1688
-
1689
- unset( $other['attr']['class'] );
1690
- }
1691
-
1692
- unset( $other['attr']['id'] );
1693
- }
1694
-
1695
- // replace placeholders with data/content
1696
- return str_replace(
1697
- array(
1698
- $placeholders['id'],
1699
- $placeholders['title'],
1700
- $placeholders['content'],
1701
- $placeholders['html_before_title'],
1702
- $placeholders['html_after_title'],
1703
- $placeholders['attr'],
1704
- $placeholders['attr_class'],
1705
- ),
1706
- array(
1707
- esc_attr( $id ),
1708
- $title,
1709
- $content,
1710
- $other['html_before_title'],
1711
- $other['html_after_title'],
1712
- fw_attr_to_html( $other['attr'] ),
1713
- esc_attr( $attr_class )
1714
- ),
1715
- $meta_box_template
1716
- );
1717
- }
1718
-
1719
- /**
1720
- * @param FW_Access_Key $access_key
1721
- * @param string|FW_Option_Type $option_type_class
1722
- *
1723
- * @internal
1724
- */
1725
- public function _register_option_type( FW_Access_Key $access_key, $option_type_class ) {
1726
- if ( $access_key->get_key() !== 'fw_option_type' ) {
1727
- trigger_error( 'Call denied', E_USER_ERROR );
1728
- }
1729
-
1730
- $this->register_option_type( $option_type_class );
1731
- }
1732
-
1733
- /**
1734
- * @param FW_Access_Key $access_key
1735
- * @param string|FW_Container_Type $container_type_class
1736
- *
1737
- * @internal
1738
- */
1739
- public function _register_container_type( FW_Access_Key $access_key, $container_type_class ) {
1740
- if ( $access_key->get_key() !== 'fw_container_type' ) {
1741
- trigger_error( 'Call denied', E_USER_ERROR );
1742
- }
1743
-
1744
- $this->register_container_type( $container_type_class );
1745
- }
1746
-
1747
- /**
1748
- * @param string $option_type
1749
- *
1750
- * @return FW_Option_Type|FW_Option_Type_Undefined
1751
- */
1752
- public function option_type( $option_type ) {
1753
- if ( is_array( $this->option_types_pending_registration ) ) {
1754
- // This method is called first time
1755
-
1756
- do_action('fw_option_types_init');
1757
-
1758
- // Register pending option types
1759
- {
1760
- $pending_option_types = $this->option_types_pending_registration;
1761
-
1762
- // clear this property, so register_option_type() will not add option types to pending anymore
1763
- $this->option_types_pending_registration = false;
1764
-
1765
- foreach ( $pending_option_types as $option_type_class ) {
1766
- $this->register_option_type( $option_type_class );
1767
- }
1768
-
1769
- unset( $pending_option_types );
1770
- }
1771
- }
1772
-
1773
- if ( isset( $this->option_types[ $option_type ] ) ) {
1774
- return $this->option_types[ $option_type ];
1775
- } else {
1776
- if ( is_admin() ) {
1777
- FW_Flash_Messages::add(
1778
- 'fw-get-option-type-undefined-' . $option_type,
1779
- sprintf( __( 'Undefined option type: %s', 'fw' ), $option_type ),
1780
- 'warning'
1781
- );
1782
- }
1783
-
1784
- if (!$this->undefined_option_type) {
1785
- require_once fw_get_framework_directory('/includes/option-types/class-fw-option-type-undefined.php');
1786
-
1787
- $this->undefined_option_type = new FW_Option_Type_Undefined();
1788
- }
1789
-
1790
- return $this->undefined_option_type;
1791
- }
1792
- }
1793
-
1794
- /**
1795
- * @param string $container_type
1796
- *
1797
- * @return FW_Container_Type|FW_Container_Type_Undefined
1798
- */
1799
- public function container_type( $container_type ) {
1800
- if ( is_array( $this->container_types_pending_registration ) ) {
1801
- // This method is called first time
1802
-
1803
- do_action('fw_container_types_init');
1804
-
1805
- // Register pending container types
1806
- {
1807
- $pending_container_types = $this->container_types_pending_registration;
1808
-
1809
- // clear this property, so register_container_type() will not add container types to pending anymore
1810
- $this->container_types_pending_registration = false;
1811
-
1812
- foreach ( $pending_container_types as $container_type_class ) {
1813
- $this->register_container_type( $container_type_class );
1814
- }
1815
-
1816
- unset( $pending_container_types );
1817
- }
1818
- }
1819
-
1820
- if ( isset( $this->container_types[ $container_type ] ) ) {
1821
- return $this->container_types[ $container_type ];
1822
- } else {
1823
- if ( is_admin() ) {
1824
- FW_Flash_Messages::add(
1825
- 'fw-get-container-type-undefined-' . $container_type,
1826
- sprintf( __( 'Undefined container type: %s', 'fw' ), $container_type ),
1827
- 'warning'
1828
- );
1829
- }
1830
-
1831
- if (!$this->undefined_container_type) {
1832
- require_once fw_get_framework_directory('/includes/container-types/class-fw-container-type-undefined.php');
1833
-
1834
- $this->undefined_container_type = new FW_Container_Type_Undefined();
1835
- }
1836
-
1837
- return $this->undefined_container_type;
1838
- }
1839
- }
1840
-
1841
- /**
1842
- * @param WP_Customize_Manager $wp_customize
1843
- * @internal
1844
- */
1845
- public function _action_customize_register($wp_customize) {
1846
- if (is_admin()) {
1847
- add_action('admin_enqueue_scripts', array($this, '_action_enqueue_customizer_static'));
1848
- }
1849
-
1850
- $this->customizer_register_options(
1851
- $wp_customize,
1852
- fw()->theme->get_customizer_options()
1853
- );
1854
- }
1855
-
1856
- /**
1857
- * @internal
1858
- */
1859
- public function _action_enqueue_customizer_static()
1860
- {
1861
- {
1862
- $options_for_enqueue = array();
1863
- $customizer_options = fw()->theme->get_customizer_options();
1864
-
1865
- /**
1866
- * In customizer options is allowed to have container with unspecified (or not existing) 'type'
1867
- * fw()->backend->enqueue_options_static() tries to enqueue both options and container static
1868
- * not existing container types will throw notices.
1869
- * To prevent that, extract and send it only options (without containers)
1870
- */
1871
- fw_collect_options($options_for_enqueue, $customizer_options);
1872
-
1873
- fw()->backend->enqueue_options_static($options_for_enqueue);
1874
-
1875
- unset($options_for_enqueue, $customizer_options);
1876
- }
1877
-
1878
- wp_enqueue_script(
1879
- 'fw-backend-customizer',
1880
- fw_get_framework_directory_uri( '/static/js/backend-customizer.js' ),
1881
- array( 'jquery', 'fw-events', 'backbone' ),
1882
- fw()->manifest->get_version(),
1883
- true
1884
- );
1885
- wp_localize_script(
1886
- 'fw-backend-customizer',
1887
- '_fw_backend_customizer_localized',
1888
- array(
1889
- 'change_timeout' => apply_filters('fw_customizer_option_change_timeout', 333),
1890
- )
1891
- );
1892
-
1893
- do_action('fw_admin_enqueue_scripts:customizer');
1894
- }
1895
-
1896
- /**
1897
- * @param WP_Customize_Manager $wp_customize
1898
- * @param array $options
1899
- * @param array $parent_data {'type':'...','id':'...'}
1900
- */
1901
- private function customizer_register_options($wp_customize, $options, $parent_data = array()) {
1902
- $collected = array();
1903
-
1904
- fw_collect_options( $collected, $options, array(
1905
- 'limit_option_types' => false,
1906
- 'limit_container_types' => false,
1907
- 'limit_level' => 1,
1908
- 'info_wrapper' => true,
1909
- ) );
1910
-
1911
- if ( empty( $collected ) ) {
1912
- return;
1913
- }
1914
-
1915
- foreach ($collected as &$opt) {
1916
- switch ($opt['group']) {
1917
- case 'container':
1918
- // Check if has container options
1919
- {
1920
- $_collected = array();
1921
-
1922
- fw_collect_options( $_collected, $opt['option']['options'], array(
1923
- 'limit_option_types' => array(),
1924
- 'limit_container_types' => false,
1925
- 'limit_level' => 1,
1926
- 'limit' => 1,
1927
- 'info_wrapper' => false,
1928
- ) );
1929
-
1930
- $has_containers = !empty($_collected);
1931
-
1932
- unset($_collected);
1933
- }
1934
-
1935
- $children_data = array(
1936
- 'group' => 'container',
1937
- 'id' => $opt['id']
1938
- );
1939
-
1940
- $args = array(
1941
- 'title' => empty($opt['option']['title'])
1942
- ? fw_id_to_title($opt['id'])
1943
- : $opt['option']['title'],
1944
- 'description' => empty($opt['option']['desc'])
1945
- ? ''
1946
- : $opt['option']['desc'],
1947
- );
1948
-
1949
- if (isset($opt['option']['wp-customizer-args']) && is_array($opt['option']['wp-customizer-args'])) {
1950
- $args = array_merge($opt['option']['wp-customizer-args'], $args);
1951
- }
1952
-
1953
- if ($has_containers) {
1954
- if ($parent_data) {
1955
- trigger_error($opt['id'] .' panel can\'t have a parent ('. $parent_data['id'] .')', E_USER_WARNING);
1956
- break;
1957
- }
1958
-
1959
- $wp_customize->add_panel($opt['id'], $args);
1960
-
1961
- $children_data['customizer_type'] = 'panel';
1962
- } else {
1963
- if ($parent_data) {
1964
- if ($parent_data['customizer_type'] === 'panel') {
1965
- $args['panel'] = $parent_data['id'];
1966
- } else {
1967
- trigger_error($opt['id'] .' section can have only panel parent ('. $parent_data['id'] .')', E_USER_WARNING);
1968
- break;
1969
- }
1970
- }
1971
-
1972
- $wp_customize->add_section($opt['id'], $args);
1973
-
1974
- $children_data['customizer_type'] = 'section';
1975
- }
1976
-
1977
- $this->customizer_register_options(
1978
- $wp_customize,
1979
- $opt['option']['options'],
1980
- $children_data
1981
- );
1982
-
1983
- unset($children_data);
1984
- break;
1985
- case 'option':
1986
- $setting_id = FW_Option_Type::get_default_name_prefix() .'['. $opt['id'] .']';
1987
-
1988
- {
1989
- $args = array(
1990
- 'label' => empty($opt['option']['label'])
1991
- ? fw_id_to_title($opt['id'])
1992
- : $opt['option']['label'],
1993
- 'description' => empty($opt['option']['desc'])
1994
- ? ''
1995
- : $opt['option']['desc'],
1996
- 'settings' => $setting_id,
1997
- );
1998
-
1999
- if (isset($opt['option']['wp-customizer-args']) && is_array($opt['option']['wp-customizer-args'])) {
2000
- $args = array_merge($opt['option']['wp-customizer-args'], $args);
2001
- }
2002
-
2003
- if ($parent_data) {
2004
- if ($parent_data['customizer_type'] === 'section') {
2005
- $args['section'] = $parent_data['id'];
2006
- } else {
2007
- trigger_error('Invalid control parent: '. $parent_data['customizer_type'], E_USER_WARNING);
2008
- break;
2009
- }
2010
- } else { // the option is not placed in a section, create a section automatically
2011
- $args['section'] = 'fw_option_auto_section_'. $opt['id'];
2012
- $wp_customize->add_section($args['section'], array(
2013
- 'title' => empty($opt['option']['label'])
2014
- ? fw_id_to_title($opt['id'])
2015
- : $opt['option']['label'],
2016
- ));
2017
- }
2018
- }
2019
-
2020
- if (!class_exists('_FW_Customizer_Setting_Option')) {
2021
- require_once fw_get_framework_directory('/includes/customizer/class--fw-customizer-setting-option.php');
2022
- }
2023
-
2024
- if (!class_exists('_FW_Customizer_Control_Option_Wrapper')) {
2025
- require_once fw_get_framework_directory('/includes/customizer/class--fw-customizer-control-option-wrapper.php');
2026
- }
2027
-
2028
- $wp_customize->add_setting(
2029
- new _FW_Customizer_Setting_Option(
2030
- $wp_customize,
2031
- $setting_id,
2032
- array(
2033
- 'default' => fw()->backend->option_type($opt['option']['type'])->get_value_from_input($opt['option'], null),
2034
- 'fw_option' => $opt['option'],
2035
- )
2036
- )
2037
- );
2038
-
2039
- $wp_customize->add_control(
2040
- new _FW_Customizer_Control_Option_Wrapper(
2041
- $wp_customize,
2042
- $opt['id'],
2043
- $args
2044
- )
2045
- );
2046
- break;
2047
- default:
2048
- trigger_error('Unknown group: '. $opt['group'], E_USER_WARNING);
2049
- }
2050
- }
2051
- }
2052
-
2053
- /**
2054
- * For e.g. an option-type was rendered using 'customizer' design,
2055
- * but inside it uses render_options() but it doesn't know the current render design
2056
- * and the options will be rendered with 'default' design.
2057
- * This method allows to specify the default design that will be used if not specified on render_options()
2058
- * @param null|string $design
2059
- * @internal
2060
- */
2061
- public function _set_default_render_design($design = null)
2062
- {
2063
- if (empty($design) || !in_array($design, $this->available_render_designs)) {
2064
- $this->default_render_design = 'default';
2065
- } else {
2066
- $this->default_render_design = $design;
2067
- }
2068
- }
2069
- }
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ /**
6
+ * Backend functionality
7
+ */
8
+ final class _FW_Component_Backend {
9
+
10
+ /** @var callable */
11
+ private $print_meta_box_content_callback;
12
+
13
+ /** @var FW_Form */
14
+ private $settings_form;
15
+
16
+ private $available_render_designs = array( 'default', 'taxonomy', 'customizer' );
17
+
18
+ private $default_render_design = 'default';
19
+
20
+ /**
21
+ * Store option types for registration, until they will be required
22
+ * @var array|false
23
+ * array Can have some pending option types in it
24
+ * false Option types already requested and was registered, so do not use pending anymore
25
+ */
26
+ private $option_types_pending_registration = array();
27
+
28
+ /**
29
+ * Contains all option types
30
+ * @var FW_Option_Type[]
31
+ */
32
+ private $option_types = array();
33
+
34
+ /**
35
+ * @var FW_Option_Type_Undefined
36
+ */
37
+ private $undefined_option_type;
38
+
39
+ /**
40
+ * Store container types for registration, until they will be required
41
+ * @var array|false
42
+ * array Can have some pending container types in it
43
+ * false Container types already requested and was registered, so do not use pending anymore
44
+ */
45
+ private $container_types_pending_registration = array();
46
+
47
+ /**
48
+ * Contains all container types
49
+ * @var FW_Container_Type[]
50
+ */
51
+ private $container_types = array();
52
+
53
+ /**
54
+ * @var FW_Container_Type_Undefined
55
+ */
56
+ private $undefined_container_type;
57
+
58
+ private $static_registered = false;
59
+
60
+ /**
61
+ * @var FW_Access_Key
62
+ */
63
+ private $access_key;
64
+
65
+ /**
66
+ * @internal
67
+ */
68
+ public function _get_settings_page_slug() {
69
+ return 'fw-settings';
70
+ }
71
+
72
+ private function get_current_edit_taxonomy() {
73
+ static $cache_current_taxonomy_data = null;
74
+
75
+ if ( $cache_current_taxonomy_data !== null ) {
76
+ return $cache_current_taxonomy_data;
77
+ }
78
+
79
+ $result = array(
80
+ 'taxonomy' => null,
81
+ 'term_id' => 0,
82
+ );
83
+
84
+ do {
85
+ if ( ! is_admin() ) {
86
+ break;
87
+ }
88
+
89
+ // code from /wp-admin/admin.php line 110
90
+ {
91
+ if ( isset( $_REQUEST['taxonomy'] ) && taxonomy_exists( $_REQUEST['taxonomy'] ) ) {
92
+ $taxnow = $_REQUEST['taxonomy'];
93
+ } else {
94
+ $taxnow = '';
95
+ }
96
+ }
97
+
98
+ if ( empty( $taxnow ) ) {
99
+ break;
100
+ }
101
+
102
+ $result['taxonomy'] = $taxnow;
103
+
104
+ if ( empty( $_REQUEST['tag_ID'] ) ) {
105
+ return $result;
106
+ }
107
+
108
+ // code from /wp-admin/edit-tags.php
109
+ {
110
+ $tag_ID = (int) $_REQUEST['tag_ID'];
111
+ }
112
+
113
+ $result['term_id'] = $tag_ID;
114
+ } while ( false );
115
+
116
+ $cache_current_taxonomy_data = $result;
117
+
118
+ return $cache_current_taxonomy_data;
119
+ }
120
+
121
+ public function __construct() {
122
+ $this->print_meta_box_content_callback = create_function( '$post,$args', 'echo $args["args"];' );
123
+ }
124
+
125
+ /**
126
+ * @internal
127
+ */
128
+ public function _init() {
129
+ if ( is_admin() ) {
130
+ $this->settings_form = new FW_Form('fw_settings', array(
131
+ 'render' => array($this, '_settings_form_render'),
132
+ 'validate' => array($this, '_settings_form_validate'),
133
+ 'save' => array($this, '_settings_form_save'),
134
+ ));
135
+ }
136
+
137
+ $this->add_actions();
138
+ $this->add_filters();
139
+ }
140
+
141
+ /**
142
+ * @internal
143
+ */
144
+ public function _after_components_init() {}
145
+
146
+ private function get_access_key()
147
+ {
148
+ if (!$this->access_key) {
149
+ $this->access_key = new FW_Access_Key('fw_backend');
150
+ }
151
+
152
+ return $this->access_key;
153
+ }
154
+
155
+ private function add_actions() {
156
+ if ( is_admin() ) {
157
+ add_action('admin_menu', array($this, '_action_admin_menu'));
158
+ add_action('add_meta_boxes', array($this, '_action_create_post_meta_boxes'), 10, 2);
159
+ add_action('init', array($this, '_action_init'), 20);
160
+ add_action('admin_enqueue_scripts', array($this, '_action_admin_register_scripts'),
161
+ /**
162
+ * Usually when someone register/enqueue a script/style to be used in other places
163
+ * in 'admin_enqueue_scripts' actions with default (not set) priority 10, they use priority 9.
164
+ * Use here priority 8, in case those scripts/styles used in actions with priority 9
165
+ * are using scripts/styles registered here
166
+ */
167
+ 8
168
+ );
169
+ add_action('admin_enqueue_scripts', array($this, '_action_admin_enqueue_scripts'),
170
+ /**
171
+ * In case some custom defined option types are using script/styles registered
172
+ * in actions with default priority 10 (make sure the enqueue is executed after register)
173
+ */
174
+ 11
175
+ );
176
+
177
+ // render and submit options from javascript
178
+ {
179
+ add_action('wp_ajax_fw_backend_options_render', array($this, '_action_ajax_options_render'));
180
+ add_action('wp_ajax_fw_backend_options_get_values', array($this, '_action_ajax_options_get_values'));
181
+ }
182
+ }
183
+
184
+ add_action('save_post', array($this, '_action_save_post'), 7, 3);
185
+ add_action('wp_restore_post_revision', array($this, '_action_restore_post_revision'), 10, 2);
186
+ add_action('_wp_put_post_revision', array($this, '_action__wp_put_post_revision'));
187
+
188
+ add_action('customize_register', array($this, '_action_customize_register'), 7);
189
+ }
190
+
191
+ private function add_filters() {
192
+ if ( is_admin() ) {
193
+ add_filter('admin_footer_text', array($this, '_filter_admin_footer_text'), 11);
194
+ add_filter('update_footer', array($this, '_filter_footer_version'), 11);
195
+ }
196
+ }
197
+
198
+ /**
199
+ * @param string|FW_Option_Type $option_type_class
200
+ *
201
+ * @internal
202
+ */
203
+ private function register_option_type( $option_type_class ) {
204
+ if ( is_array( $this->option_types_pending_registration ) ) {
205
+ // Option types never requested. Continue adding to pending
206
+ $this->option_types_pending_registration[] = $option_type_class;
207
+ } else {
208
+ if ( is_string( $option_type_class ) ) {
209
+ $option_type_class = new $option_type_class;
210
+ }
211
+
212
+ if ( ! is_subclass_of( $option_type_class, 'FW_Option_Type' ) ) {
213
+ trigger_error( 'Invalid option type class ' . get_class( $option_type_class ), E_USER_WARNING );
214
+ return;
215
+ }
216
+
217
+ /**
218
+ * @var FW_Option_Type $option_type_class
219
+ */
220
+
221
+ $type = $option_type_class->get_type();
222
+
223
+ if ( isset( $this->option_types[ $type ] ) ) {
224
+ trigger_error( 'Option type "' . $type . '" already registered', E_USER_WARNING );
225
+ return;
226
+ }
227
+
228
+ $this->option_types[ $type ] = $option_type_class;
229
+
230
+ $this->option_types[ $type ]->_call_init($this->get_access_key());
231
+ }
232
+ }
233
+
234
+ /**
235
+ * @param string|FW_Container_Type $container_type_class
236
+ *
237
+ * @internal
238
+ */
239
+ private function register_container_type( $container_type_class ) {
240
+ if ( is_array( $this->container_types_pending_registration ) ) {
241
+ // Container types never requested. Continue adding to pending
242
+ $this->container_types_pending_registration[] = $container_type_class;
243
+ } else {
244
+ if ( is_string( $container_type_class ) ) {
245
+ $container_type_class = new $container_type_class;
246
+ }
247
+
248
+ if ( ! is_subclass_of( $container_type_class, 'FW_Container_Type' ) ) {
249
+ trigger_error( 'Invalid container type class ' . get_class( $container_type_class ), E_USER_WARNING );
250
+ return;
251
+ }
252
+
253
+ /**
254
+ * @var FW_Container_Type $container_type_class
255
+ */
256
+
257
+ $type = $container_type_class->get_type();
258
+
259
+ if ( isset( $this->container_types[ $type ] ) ) {
260
+ trigger_error( 'Container type "' . $type . '" already registered', E_USER_WARNING );
261
+ return;
262
+ }
263
+
264
+ $this->container_types[ $type ] = $container_type_class;
265
+
266
+ $this->container_types[ $type ]->_call_init($this->get_access_key());
267
+ }
268
+ }
269
+
270
+ private function register_static() {
271
+ if (
272
+ !doing_action('admin_enqueue_scripts')
273
+ &&
274
+ !did_action('admin_enqueue_scripts')
275
+ ) {
276
+ /**
277
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
278
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
279
+ * So as a result some handles will not be equeued because of not registered dependecies.
280
+ */
281
+ return;
282
+ }
283
+
284
+ if ( $this->static_registered ) {
285
+ return;
286
+ }
287
+
288
+ /**
289
+ * Register styles/scripts only in admin area, on frontend it's not allowed to use styles/scripts from framework backend core
290
+ * because they are meant to be used only in backend and can be changed in the future.
291
+ * If you want to use a style/script from framework backend core, copy it to your theme and enqueue as a theme style/script.
292
+ */
293
+ if ( ! is_admin() ) {
294
+ $this->static_registered = true;
295
+
296
+ return;
297
+ }
298
+
299
+ wp_register_script(
300
+ 'fw-events',
301
+ fw_get_framework_directory_uri( '/static/js/fw-events.js' ),
302
+ array( 'backbone' ),
303
+ fw()->manifest->get_version(),
304
+ true
305
+ );
306
+
307
+ wp_register_script(
308
+ 'fw-ie-fixes',
309
+ fw_get_framework_directory_uri( '/static/js/ie-fixes.js' ),
310
+ array(),
311
+ fw()->manifest->get_version(),
312
+ true
313
+ );
314
+
315
+ {
316
+ wp_register_style(
317
+ 'qtip',
318
+ fw_get_framework_directory_uri( '/static/libs/qtip/css/jquery.qtip.min.css' ),
319
+ array(),
320
+ fw()->manifest->get_version()
321
+ );
322
+ wp_register_script(
323
+ 'qtip',
324
+ fw_get_framework_directory_uri( '/static/libs/qtip/jquery.qtip.min.js' ),
325
+ array( 'jquery' ),
326
+ fw()->manifest->get_version()
327
+ );
328
+ }
329
+
330
+ /**
331
+ * Important!
332
+ * Call wp_enqueue_media() before wp_enqueue_script('fw') (or using 'fw' in your script dependencies)
333
+ * otherwise fw.OptionsModal won't work
334
+ */
335
+ {
336
+ wp_register_style(
337
+ 'fw',
338
+ fw_get_framework_directory_uri( '/static/css/fw.css' ),
339
+ array( 'qtip' ),
340
+ fw()->manifest->get_version()
341
+ );
342
+
343
+ wp_register_script(
344
+ 'fw',
345
+ fw_get_framework_directory_uri( '/static/js/fw.js' ),
346
+ array( 'jquery', 'fw-events', 'backbone', 'qtip' ),
347
+ fw()->manifest->get_version(),
348
+ true
349
+ );
350
+
351
+ wp_localize_script( 'fw', '_fw_localized', array(
352
+ 'FW_URI' => fw_get_framework_directory_uri(),
353
+ 'SITE_URI' => site_url(),
354
+ 'l10n' => array(
355
+ 'done' => __( 'Done', 'fw' ),
356
+ 'ah_sorry' => __( 'Ah, Sorry', 'fw' ),
357
+ 'save' => __( 'Save', 'fw' ),
358
+ 'reset' => __( 'Reset', 'fw' ),
359
+ ),
360
+ ) );
361
+ }
362
+
363
+ {
364
+ wp_register_style(
365
+ 'fw-backend-options',
366
+ fw_get_framework_directory_uri( '/static/css/backend-options.css' ),
367
+ array( 'fw' ),
368
+ fw()->manifest->get_version()
369
+ );
370
+
371
+ wp_register_script(
372
+ 'fw-backend-options',
373
+ fw_get_framework_directory_uri( '/static/js/backend-options.js' ),
374
+ array( 'fw', 'fw-events', 'postbox', 'jquery-ui-tabs' ),
375
+ fw()->manifest->get_version(),
376
+ true
377
+ );
378
+
379
+ wp_localize_script( 'fw', '_fw_backend_options_localized', array(
380
+ 'lazy_tabs' => fw()->theme->get_config('lazy_tabs')
381
+ ) );
382
+ }
383
+
384
+ {
385
+ wp_register_style(
386
+ 'fw-selectize',
387
+ fw_get_framework_directory_uri( '/static/libs/selectize/selectize.css' ),
388
+ array(),
389
+ fw()->manifest->get_version()
390
+ );
391
+ wp_register_script(
392
+ 'fw-selectize',
393
+ fw_get_framework_directory_uri( '/static/libs/selectize/selectize.min.js' ),
394
+ array( 'jquery', 'fw-ie-fixes' ),
395
+ fw()->manifest->get_version(),
396
+ true
397
+ );
398
+ }
399
+
400
+ {
401
+ wp_register_script(
402
+ 'fw-mousewheel',
403
+ fw_get_framework_directory_uri( '/static/libs/mousewheel/jquery.mousewheel.min.js' ),
404
+ array( 'jquery' ),
405
+ fw()->manifest->get_version(),
406
+ true
407
+ );
408
+ }
409
+
410
+ {
411
+ wp_register_style(
412
+ 'fw-jscrollpane',
413
+ fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.css' ),
414
+ array(),
415
+ fw()->manifest->get_version()
416
+ );
417
+ wp_register_script( 'fw-jscrollpane',
418
+ fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.min.js' ),
419
+ array( 'jquery', 'fw-mousewheel' ),
420
+ fw()->manifest->get_version(),
421
+ true
422
+ );
423
+ }
424
+
425
+ wp_register_style(
426
+ 'fw-font-awesome',
427
+ fw_get_framework_directory_uri( '/static/libs/font-awesome/css/font-awesome.min.css' ),
428
+ array(),
429
+ fw()->manifest->get_version()
430
+ );
431
+
432
+ wp_register_script(
433
+ 'backbone-relational',
434
+ fw_get_framework_directory_uri( '/static/libs/backbone-relational/backbone-relational.js' ),
435
+ array( 'backbone' ),
436
+ fw()->manifest->get_version(),
437
+ true
438
+ );
439
+
440
+ wp_register_script(
441
+ 'fw-uri',
442
+ fw_get_framework_directory_uri( '/static/libs/uri/URI.js' ),
443
+ array(),
444
+ fw()->manifest->get_version(),
445
+ true
446
+ );
447
+
448
+ wp_register_script(
449
+ 'fw-moment',
450
+ fw_get_framework_directory_uri( '/static/libs/moment/moment.min.js' ),
451
+ array(),
452
+ fw()->manifest->get_version(),
453
+ true
454
+ );
455
+
456
+ wp_register_script(
457
+ 'fw-form-helpers',
458
+ fw_get_framework_directory_uri( '/static/js/fw-form-helpers.js' ),
459
+ array( 'jquery' ),
460
+ fw()->manifest->get_version(),
461
+ true
462
+ );
463
+
464
+ wp_register_style(
465
+ 'fw-unycon',
466
+ fw_get_framework_directory_uri( '/static/libs/unycon/unycon.css' ),
467
+ array(),
468
+ fw()->manifest->get_version()
469
+ );
470
+
471
+ $this->static_registered = true;
472
+ }
473
+
474
+ /**
475
+ * @internal
476
+ */
477
+ public function _action_admin_menu() {
478
+ $data = array(
479
+ 'capability' => 'manage_options',
480
+ 'slug' => $this->_get_settings_page_slug(),
481
+ 'content_callback' => array( $this, '_print_settings_page' ),
482
+ );
483
+
484
+ if ( ! current_user_can( $data['capability'] ) ) {
485
+ return;
486
+ }
487
+
488
+ if ( ! fw()->theme->locate_path('/options/settings.php') ) {
489
+ return;
490
+ }
491
+
492
+ /**
493
+ * Collect $hookname that contains $data['slug'] before the action
494
+ * and skip them in verification after action
495
+ */
496
+ {
497
+ global $_registered_pages;
498
+
499
+ $found_hooknames = array();
500
+
501
+ if ( ! empty( $_registered_pages ) ) {
502
+ foreach ( $_registered_pages as $hookname => $b ) {
503
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
504
+ $found_hooknames[ $hookname ] = true;
505
+ }
506
+ }
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Use this action if you what to add the settings page in a custom place in menu
512
+ * Usage example http://pastebin.com/gvAjGRm1
513
+ */
514
+ do_action( 'fw_backend_add_custom_settings_menu', $data );
515
+
516
+ /**
517
+ * Check if settings menu was added in the action above
518
+ */
519
+ {
520
+ $menu_exists = false;
521
+
522
+ if ( ! empty( $_registered_pages ) ) {
523
+ foreach ( $_registered_pages as $hookname => $b ) {
524
+ if ( isset( $found_hooknames[ $hookname ] ) ) {
525
+ continue;
526
+ }
527
+
528
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
529
+ $menu_exists = true;
530
+ break;
531
+ }
532
+ }
533
+ }
534
+ }
535
+
536
+ if ( $menu_exists ) {
537
+ return;
538
+ }
539
+
540
+ add_theme_page(
541
+ __( 'Theme Settings', 'fw' ),
542
+ __( 'Theme Settings', 'fw' ),
543
+ $data['capability'],
544
+ $data['slug'],
545
+ $data['content_callback']
546
+ );
547
+
548
+ add_action( 'admin_menu', array( $this, '_action_admin_change_theme_settings_order' ), 9999 );
549
+ }
550
+
551
+ public function _filter_admin_footer_text( $html ) {
552
+ if (
553
+ (
554
+ current_user_can( 'update_themes' )
555
+ ||
556
+ current_user_can( 'update_plugins' )
557
+ )
558
+ &&
559
+ fw_current_screen_match(array(
560
+ 'only' => array(
561
+ array('parent_base' => fw()->extensions->manager->get_page_slug()) // Unyson Extensions page
562
+ )
563
+ ))
564
+ ) {
565
+ return ( empty( $html ) ? '' : $html . '<br/>' )
566
+ . '<em>'
567
+ . str_replace(
568
+ array(
569
+ '{wp_review_link}',
570
+ '{facebook_share_link}',
571
+ '{twitter_share_link}',
572
+ ),
573
+ array(
574
+ fw_html_tag('a', array(
575
+ 'target' => '_blank',
576
+ 'href' => 'https://wordpress.org/support/view/plugin-reviews/unyson?filter=5#postform',
577
+ ), __('leave a review', 'fw')),
578
+ fw_html_tag('a', array(
579
+ 'target' => '_blank',
580
+ 'href' => 'https://www.facebook.com/sharer/sharer.php?'. http_build_query(array(
581
+ 'u' => 'http://unyson.io',
582
+ )),
583
+ 'onclick' => 'return !window.open(this.href, \'Facebook\', \'width=640,height=300\')',
584
+ ), __('Facebook', 'fw')),
585
+ fw_html_tag('a', array(
586
+ 'target' => '_blank',
587
+ 'href' => 'https://twitter.com/home?'. http_build_query(array(
588
+ 'status' => __('Unyson WordPress Framework is the fastest and easiest way to develop a premium theme. I highly recommend it', 'fw')
589
+ .' http://unyson.io/ #UnysonWP',
590
+ )),
591
+ 'onclick' => 'return !window.open(this.href, \'Twitter\', \'width=640,height=430\')',
592
+ ), __('Twitter', 'fw')),
593
+ ),
594
+ __('If you like Unyson, {wp_review_link}, share on {facebook_share_link} or {twitter_share_link}.', 'fw')
595
+ )
596
+ . '</em>';
597
+ } else {
598
+ return $html;
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Print framework version in the admin footer
604
+ *
605
+ * @param string $html
606
+ *
607
+ * @return string
608
+ * @internal
609
+ */
610
+ public function _filter_footer_version( $html ) {
611
+ if ( current_user_can( 'update_themes' ) || current_user_can( 'update_plugins' ) ) {
612
+ return ( empty( $html ) ? '' : $html . ' | ' ) . fw()->manifest->get_name() . ' ' . fw()->manifest->get_version();
613
+ } else {
614
+ return $html;
615
+ }
616
+ }
617
+
618
+ public function _action_admin_change_theme_settings_order() {
619
+ global $submenu;
620
+
621
+ if ( ! isset( $submenu['themes.php'] ) ) {
622
+ // probably current user doesn't have this item in menu
623
+ return;
624
+ }
625
+
626
+ $id = $this->_get_settings_page_slug();
627
+ $index = null;
628
+
629
+ foreach ( $submenu['themes.php'] as $key => $sm ) {
630
+ if ( $sm[2] == $id ) {
631
+ $index = $key;
632
+ break;
633
+ }
634
+ }
635
+
636
+ if ( ! empty( $index ) ) {
637
+ $item = $submenu['themes.php'][ $index ];
638
+ unset( $submenu['themes.php'][ $index ] );
639
+ array_unshift( $submenu['themes.php'], $item );
640
+ }
641
+ }
642
+
643
+ public function _print_settings_page() {
644
+ echo '<div class="wrap">';
645
+
646
+ if ( fw()->theme->get_config( 'settings_form_side_tabs' ) ) {
647
+ // this is needed for flash messages (admin notices) to be displayed properly
648
+ echo '<h2 class="fw-hidden"></h2>';
649
+ } else {
650
+ echo '<h2>' . __( 'Theme Settings', 'fw' ) . '</h2><br/>';
651
+ }
652
+
653
+ $this->settings_form->render();
654
+
655
+ echo '</div>';
656
+ }
657
+
658
+ /**
659
+ * @param string $post_type
660
+ * @param WP_Post $post
661
+ */
662
+ public function _action_create_post_meta_boxes( $post_type, $post ) {
663
+ $options = fw()->theme->get_post_options( $post_type );
664
+
665
+ if ( empty( $options ) ) {
666
+ return;
667
+ }
668
+
669
+ $collected = array();
670
+
671
+ fw_collect_options( $collected, $options, array(
672
+ 'limit_option_types' => false,
673
+ 'limit_container_types' => false,
674
+ 'limit_level' => 1,
675
+ 'info_wrapper' => true,
676
+ ) );
677
+
678
+ if (empty($collected)) {
679
+ return;
680
+ }
681
+
682
+ $values = fw_get_db_post_option( $post->ID );
683
+
684
+ foreach ( $collected as &$option ) {
685
+ if (
686
+ $option['group'] === 'container'
687
+ &&
688
+ $option['option']['type'] === 'box'
689
+ ) { // this is a box, add it as a metabox
690
+ $context = isset( $option['option']['context'] )
691
+ ? $option['option']['context']
692
+ : 'normal';
693
+ $priority = isset( $option['option']['priority'] )
694
+ ? $option['option']['priority']
695
+ : 'default';
696
+
697
+ add_meta_box(
698
+ 'fw-options-box-' . $option['id'],
699
+ empty( $option['option']['title'] ) ? ' ' : $option['option']['title'],
700
+ $this->print_meta_box_content_callback,
701
+ $post_type,
702
+ $context,
703
+ $priority,
704
+ $this->render_options( $option['option']['options'], $values )
705
+ );
706
+ } else { // this is not a box, wrap it in auto-generated box
707
+ add_meta_box(
708
+ 'fw-options-box:auto-generated:'. time() .':'. fw_unique_increment(),
709
+ ' ',
710
+ $this->print_meta_box_content_callback,
711
+ $post_type,
712
+ 'normal',
713
+ 'default',
714
+ $this->render_options( array($option['id'] => $option['option']), $values )
715
+ );
716
+ }
717
+ }
718
+ }
719
+
720
+ /**
721
+ * @param object $term
722
+ */
723
+ public function _action_create_taxonomy_options( $term ) {
724
+ $options = fw()->theme->get_taxonomy_options( $term->taxonomy );
725
+
726
+ if ( empty( $options ) ) {
727
+ return;
728
+ }
729
+
730
+ $collected = array();
731
+
732
+ fw_collect_options( $collected, $options, array(
733
+ 'limit_option_types' => false,
734
+ 'limit_container_types' => false,
735
+ 'limit_level' => 1,
736
+ ) );
737
+
738
+ if ( empty( $collected ) ) {
739
+ return;
740
+ }
741
+
742
+ $values = fw_get_db_term_option( $term->term_id, $term->taxonomy );
743
+
744
+ // fixes word_press style: .form-field input { width: 95% }
745
+ echo '<style type="text/css">.fw-option-type-radio input, .fw-option-type-checkbox input { width: auto; }</style>';
746
+
747
+ echo $this->render_options( $collected, $values, array(), 'taxonomy' );
748
+ }
749
+
750
+ public function _action_init() {
751
+ $current_edit_taxonomy = $this->get_current_edit_taxonomy();
752
+
753
+ if ( $current_edit_taxonomy['taxonomy'] ) {
754
+ add_action(
755
+ $current_edit_taxonomy['taxonomy'] . '_edit_form',
756
+ array( $this, '_action_create_taxonomy_options' )
757
+ );
758
+ }
759
+
760
+ if ( ! empty( $_POST ) ) {
761
+ // is form submit
762
+ add_action( 'edited_term', array( $this, '_action_term_edit' ), 10, 3 );
763
+ }
764
+ }
765
+
766
+ /**
767
+ * Experimental custom options save
768
+ * @param array $options
769
+ * @param array $values
770
+ * @return array
771
+ */
772
+ private function process_options_handlers($options, $values)
773
+ {
774
+ $handled_values = array();
775
+
776
+ foreach (
777
+ fw_extract_only_options($options)
778
+ as $option_id => $option
779
+ ) {
780
+ if (
781
+ isset($option['option_handler'])
782
+ &&
783
+ $option['option_handler'] instanceof FW_Option_Handler
784
+ ) {
785
+ /*
786
+ * if the option has a custom option_handler
787
+ * the saving is delegated to the handler,
788
+ * so it does not go to the post_meta
789
+ */
790
+ $option['option_handler']->save_option_value($option_id, $option, $values[$option_id]);
791
+
792
+ $handled_values[$option_id] = true;
793
+ }
794
+ }
795
+
796
+ return $handled_values;
797
+ }
798
+
799
+ /**
800
+ * Save meta from $_POST to fw options (post meta)
801
+ * @param int $post_id
802
+ * @param WP_Post $post
803
+ * @param bool $update
804
+ */
805
+ public function _action_save_post( $post_id, $post, $update ) {
806
+ if (
807
+ isset($_POST['post_ID'])
808
+ &&
809
+ intval($_POST['post_ID']) === intval($post_id)
810
+ &&
811
+ !empty($_POST[ FW_Option_Type::get_default_name_prefix() ]) // this happens on Quick Edit
812
+ ) {
813
+ /**
814
+ * This happens on regular post form submit
815
+ * All data from $_POST belongs this $post
816
+ * so we save them in its post meta
817
+ */
818
+
819
+ static $post_options_save_happened = false;
820
+ if ($post_options_save_happened) {
821
+ /**
822
+ * Prevent multiple options save for same post
823
+ * It can happen from a recursion or wp_update_post() for same post id
824
+ */
825
+ return;
826
+ } else {
827
+ $post_options_save_happened = true;
828
+ }
829
+
830
+ $old_values = (array)fw_get_db_post_option($post_id);
831
+ $current_values = fw_get_options_values_from_input(
832
+ fw()->theme->get_post_options($post->post_type)
833
+ );
834
+
835
+ fw_set_db_post_option(
836
+ $post_id,
837
+ null,
838
+ array_diff_key( // remove handled values
839
+ $current_values,
840
+ $this->process_options_handlers(
841
+ fw()->theme->get_post_options($post->post_type),
842
+ $current_values
843
+ )
844
+ )
845
+ );
846
+
847
+ /**
848
+ * @deprecated
849
+ * Use the 'fw_post_options_update' action
850
+ */
851
+ do_action( 'fw_save_post_options', $post_id, $post, $old_values );
852
+ } elseif ($original_post_id = wp_is_post_autosave( $post_id )) {
853
+ do {
854
+ $parent = get_post($post->post_parent);
855
+
856
+ if ( ! $parent instanceof WP_Post ) {
857
+ break;
858
+ }
859
+
860
+ if (
861
+ isset($_POST['post_ID'])
862
+ &&
863
+ intval($_POST['post_ID']) === intval($parent->ID)
864
+ ) {} else {
865
+ break;
866
+ }
867
+
868
+ if (empty($_POST[ FW_Option_Type::get_default_name_prefix() ])) {
869
+ // this happens on Quick Edit
870
+ break;
871
+ }
872
+
873
+ $current_values = fw_get_options_values_from_input(
874
+ fw()->theme->get_post_options($parent->post_type)
875
+ );
876
+
877
+ fw_set_db_post_option(
878
+ $post->ID,
879
+ null,
880
+ array_diff_key( // remove handled values
881
+ $current_values,
882
+ $this->process_options_handlers(
883
+ fw()->theme->get_post_options($parent->post_type),
884
+ $current_values
885
+ )
886
+ )
887
+ );
888
+ } while(false);
889
+ } elseif ($original_post_id = wp_is_post_revision( $post_id )) {
890
+ /**
891
+ * Do nothing, the
892
+ * - '_wp_put_post_revision'
893
+ * - 'wp_restore_post_revision'
894
+ * actions will handle this
895
+ */
896
+ } else {
897
+ /**
898
+ * This happens on:
899
+ * - post add (auto-draft): do nothing
900
+ * - revision restore: do nothing, that is handled by the 'wp_restore_post_revision' action
901
+ */
902
+ }
903
+ }
904
+
905
+ /**
906
+ * @param $post_id
907
+ * @param $revision_id
908
+ */
909
+ public function _action_restore_post_revision($post_id, $revision_id)
910
+ {
911
+ /**
912
+ * Copy options meta from revision to post
913
+ */
914
+ fw_set_db_post_option(
915
+ $post_id,
916
+ null,
917
+ (array)fw_get_db_post_option($revision_id, null, array())
918
+ );
919
+ }
920
+
921
+ /**
922
+ * @param $revision_id
923
+ */
924
+ public function _action__wp_put_post_revision($revision_id)
925
+ {
926
+ /**
927
+ * Copy options meta from post to revision
928
+ */
929
+ fw_set_db_post_option(
930
+ $revision_id,
931
+ null,
932
+ (array)fw_get_db_post_option(
933
+ wp_is_post_revision($revision_id),
934
+ null,
935
+ array()
936
+ )
937
+ );
938
+ }
939
+
940
+ /**
941
+ * Update all post meta `fw_option:<option-id>` with values from post options that has the 'save-in-separate-meta' parameter
942
+ *
943
+ * @param int $post_id
944
+ *
945
+ * @return bool
946
+ * @deprecated since 2.5.0
947
+ */
948
+ public function _sync_post_separate_meta( $post_id ) {
949
+ $post_type = get_post_type( $post_id );
950
+
951
+ if ( ! $post_type ) {
952
+ return false;
953
+ }
954
+
955
+ $meta_prefix = 'fw_option:';
956
+
957
+ /**
958
+ * Collect all options that needs to be saved in separate meta
959
+ */
960
+ {
961
+ $options_values = fw_get_db_post_option( $post_id );
962
+
963
+ $separate_meta_options = array();
964
+
965
+ foreach (
966
+ fw_extract_only_options( fw()->theme->get_post_options( $post_type ) )
967
+ as $option_id => $option
968
+ ) {
969
+ if (
970
+ isset( $option['save-in-separate-meta'] )
971
+ &&
972
+ $option['save-in-separate-meta']
973
+ &&
974
+ array_key_exists( $option_id, $options_values )
975
+ ) {
976
+ $separate_meta_options[ $meta_prefix . $option_id ] = $options_values[ $option_id ];
977
+ }
978
+ }
979
+
980
+ unset( $options_values );
981
+ }
982
+
983
+ /**
984
+ * Delete meta that starts with $meta_prefix
985
+ */
986
+ {
987
+ /** @var wpdb $wpdb */
988
+ global $wpdb;
989
+
990
+ foreach (
991
+ $wpdb->get_results(
992
+ $wpdb->prepare(
993
+ "SELECT meta_key " .
994
+ "FROM {$wpdb->postmeta} " .
995
+ "WHERE meta_key LIKE %s AND post_id = %d",
996
+ $wpdb->esc_like( $meta_prefix ) . '%',
997
+ $post_id
998
+ )
999
+ )
1000
+ as $row
1001
+ ) {
1002
+ if ( array_key_exists( $row->meta_key, $separate_meta_options ) ) {
1003
+ /**
1004
+ * This meta exists and will be updated below.
1005
+ * Do not delete for performance reasons, instead of delete->insert will be performed only update
1006
+ */
1007
+ continue;
1008
+ } else {
1009
+ // this option does not exist anymore
1010
+ delete_post_meta( $post_id, $row->meta_key );
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ foreach ( $separate_meta_options as $meta_key => $option_value ) {
1016
+ fw_update_post_meta($post_id, $meta_key, $option_value );
1017
+ }
1018
+
1019
+ return true;
1020
+ }
1021
+
1022
+ public function _action_term_edit( $term_id, $tt_id, $taxonomy ) {
1023
+ if ( ! isset( $_POST['action'] ) || ! isset( $_POST['taxonomy'] ) ) {
1024
+ return; // this is not real term form submit, abort save
1025
+ }
1026
+
1027
+ if (intval(FW_Request::POST('tag_ID')) != $term_id) {
1028
+ // the $_POST values belongs to another term, do not save them into this one
1029
+ return;
1030
+ }
1031
+
1032
+ $old_values = (array) fw_get_db_term_option( $term_id, $taxonomy );
1033
+
1034
+ fw_set_db_term_option(
1035
+ $term_id,
1036
+ $taxonomy,
1037
+ null,
1038
+ fw_get_options_values_from_input(
1039
+ fw()->theme->get_taxonomy_options( $taxonomy )
1040
+ )
1041
+ );
1042
+
1043
+ do_action( 'fw_save_term_options', $term_id, $taxonomy, $old_values );
1044
+ }
1045
+
1046
+ public function _action_admin_register_scripts() {
1047
+ $this->register_static();
1048
+ }
1049
+
1050
+ public function _action_admin_enqueue_scripts() {
1051
+ global $current_screen, $plugin_page, $post;
1052
+
1053
+ /**
1054
+ * Enqueue settings options static in <head>
1055
+ */
1056
+ {
1057
+ if ( $this->_get_settings_page_slug() === $plugin_page ) {
1058
+ fw()->backend->enqueue_options_static(
1059
+ fw()->theme->get_settings_options()
1060
+ );
1061
+
1062
+ do_action( 'fw_admin_enqueue_scripts:settings' );
1063
+ }
1064
+ }
1065
+
1066
+ /**
1067
+ * Enqueue post options static in <head>
1068
+ */
1069
+ {
1070
+ if ( 'post' === $current_screen->base && $post ) {
1071
+ fw()->backend->enqueue_options_static(
1072
+ fw()->theme->get_post_options( $post->post_type )
1073
+ );
1074
+
1075
+ do_action( 'fw_admin_enqueue_scripts:post', $post );
1076
+ }
1077
+ }
1078
+
1079
+ /**
1080
+ * Enqueue term options static in <head>
1081
+ */
1082
+ {
1083
+ if (
1084
+ 'edit-tags' === $current_screen->base
1085
+ &&
1086
+ $current_screen->taxonomy
1087
+ &&
1088
+ ! empty( $_GET['tag_ID'] )
1089
+ ) {
1090
+ fw()->backend->enqueue_options_static(
1091
+ fw()->theme->get_taxonomy_options( $current_screen->taxonomy )
1092
+ );
1093
+
1094
+ do_action( 'fw_admin_enqueue_scripts:term', $current_screen->taxonomy );
1095
+ }
1096
+ }
1097
+ }
1098
+
1099
+ /**
1100
+ * Render options html from input json
1101
+ *
1102
+ * POST vars:
1103
+ * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1104
+ * - values: {option_id: value, option_id: {...}, ...} // Optional // Object
1105
+ * - data: {id_prefix: 'fw_options-a-b-', name_prefix: 'fw_options[a][b]'} // Optional // Object
1106
+ */
1107
+ public function _action_ajax_options_render() {
1108
+ // options
1109
+ {
1110
+ if ( ! isset( $_POST['options'] ) ) {
1111
+ wp_send_json_error( array(
1112
+ 'message' => 'No options'
1113
+ ) );
1114
+ }
1115
+
1116
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1117
+
1118
+ if ( ! $options ) {
1119
+ wp_send_json_error( array(
1120
+ 'message' => 'Wrong options'
1121
+ ) );
1122
+ }
1123
+ }
1124
+
1125
+ // values
1126
+ {
1127
+ if ( isset( $_POST['values'] ) ) {
1128
+ $values = FW_Request::POST( 'values' );
1129
+
1130
+ if (is_string($values)) {
1131
+ $values = json_decode($values, true);
1132
+ }
1133
+ } else {
1134
+ $values = array();
1135
+ }
1136
+
1137
+ $values = self::_fix_POST_values(array_intersect_key($values, fw_extract_only_options($options)));
1138
+ }
1139
+
1140
+ // data
1141
+ {
1142
+ if ( isset( $_POST['data'] ) ) {
1143
+ $data = FW_Request::POST( 'data' );
1144
+ } else {
1145
+ $data = array();
1146
+ }
1147
+ }
1148
+
1149
+ wp_send_json_success( array(
1150
+ 'html' => fw()->backend->render_options( $options, $values, $data )
1151
+ ) );
1152
+ }
1153
+
1154
+ private static function _fix_POST_values(array $values) {
1155
+ $fixed = array();
1156
+
1157
+ foreach ($values as $key => $value ) {
1158
+ if ('true' === $value || 'false' === $value) {
1159
+ /**
1160
+ * Fix booleans
1161
+ *
1162
+ * In POST, booleans are transformed to strings: 'true' and 'false'
1163
+ * Transform them back to booleans
1164
+ */
1165
+ $fixed[ $key ] = ( $value === 'true' );
1166
+ } elseif (is_numeric($value)) {
1167
+ /**
1168
+ * Fix integers
1169
+ *
1170
+ * In POST, integers are transformed to strings: '0', '1', '2', ...
1171
+ * Transform them back to integers
1172
+ */
1173
+ $fixed[ $key ] = (float) $value;
1174
+ } elseif (is_array($value)) {
1175
+ $fixed[ $key ] = self::_fix_POST_values($value);
1176
+ } else {
1177
+ $fixed[ $key ] = $value;
1178
+ }
1179
+ }
1180
+
1181
+ return $fixed;
1182
+ }
1183
+
1184
+ /**
1185
+ * Get options values from html generated with 'fw_backend_options_render' ajax action
1186
+ *
1187
+ * POST vars:
1188
+ * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1189
+ * - fw_options... // Use a jQuery "ajax form submit" to emulate real form submit
1190
+ *
1191
+ * Tip: Inside form html, add: <input type="hidden" name="options" value="[...json...]">
1192
+ */
1193
+ public function _action_ajax_options_get_values() {
1194
+ // options
1195
+ {
1196
+ if ( ! isset( $_POST['options'] ) ) {
1197
+ wp_send_json_error( array(
1198
+ 'message' => 'No options'
1199
+ ) );
1200
+ }
1201
+
1202
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1203
+
1204
+ if ( ! $options ) {
1205
+ wp_send_json_error( array(
1206
+ 'message' => 'Wrong options'
1207
+ ) );
1208
+ }
1209
+ }
1210
+
1211
+ // name_prefix
1212
+ {
1213
+ if ( isset( $_POST['name_prefix'] ) ) {
1214
+ $name_prefix = FW_Request::POST( 'name_prefix' );
1215
+ } else {
1216
+ $name_prefix = FW_Option_Type::get_default_name_prefix();
1217
+ }
1218
+ }
1219
+
1220
+ wp_send_json_success( array(
1221
+ 'values' => fw_get_options_values_from_input(
1222
+ $options,
1223
+ FW_Request::POST( fw_html_attr_name_to_array_multi_key( $name_prefix ), array() )
1224
+ )
1225
+ ) );
1226
+ }
1227
+
1228
+ public function _settings_form_render( $data ) {
1229
+ {
1230
+ $this->enqueue_options_static( array() );
1231
+
1232
+ wp_enqueue_script( 'fw-form-helpers' );
1233
+ }
1234
+
1235
+ $options = fw()->theme->get_settings_options();
1236
+
1237
+ if ( empty( $options ) ) {
1238
+ return $data;
1239
+ }
1240
+
1241
+ if ( $values = FW_Request::POST( FW_Option_Type::get_default_name_prefix() ) ) {
1242
+ // This is form submit, extract correct values from $_POST values
1243
+ $values = fw_get_options_values_from_input( $options, $values );
1244
+ } else {
1245
+ // Extract previously saved correct values
1246
+ $values = fw_get_db_settings_option();
1247
+ }
1248
+
1249
+ $ajax_submit = fw()->theme->get_config( 'settings_form_ajax_submit' );
1250
+ $side_tabs = fw()->theme->get_config( 'settings_form_side_tabs' );
1251
+
1252
+ $data['attr']['class'] = 'fw-settings-form';
1253
+
1254
+ if ( $side_tabs ) {
1255
+ $data['attr']['class'] .= ' fw-backend-side-tabs';
1256
+ }
1257
+
1258
+ $data['submit']['html'] = '<!-- -->'; // is generated in view
1259
+
1260
+ do_action( 'fw_settings_form_render', array(
1261
+ 'ajax_submit' => $ajax_submit,
1262
+ 'side_tabs' => $side_tabs,
1263
+ ) );
1264
+
1265
+ fw_render_view( fw_get_framework_directory( '/views/backend-settings-form.php' ), array(
1266
+ 'options' => $options,
1267
+ 'values' => $values,
1268
+ 'reset_input_name' => '_fw_reset_options',
1269
+ 'ajax_submit' => $ajax_submit,
1270
+ 'side_tabs' => $side_tabs,
1271
+ ), false );
1272
+
1273
+ return $data;
1274
+ }
1275
+
1276
+ public function _settings_form_validate( $errors ) {
1277
+ if ( ! current_user_can( 'manage_options' ) ) {
1278
+ $errors['_no_permission'] = __( 'You have no permissions to change settings options', 'fw' );
1279
+ }
1280
+
1281
+ return $errors;
1282
+ }
1283
+
1284
+ public function _settings_form_save( $data ) {
1285
+ $flash_id = 'fw_settings_form_save';
1286
+ $old_values = (array) fw_get_db_settings_option();
1287
+
1288
+ if ( ! empty( $_POST['_fw_reset_options'] ) ) { // The "Reset" button was pressed
1289
+ fw_set_db_settings_option(
1290
+ null,
1291
+ /**
1292
+ * Some values that don't relate to design, like API credentials, are useful to not be wiped out.
1293
+ *
1294
+ * Usage:
1295
+ *
1296
+ * add_filter('fw_settings_form_reset:values', '_filter_add_persisted_option', 10, 2);
1297
+ * function _filter_add_persisted_option ($current_persisted, $old_values) {
1298
+ * $value_to_persist = fw_akg('my/multi/key', $old_values);
1299
+ * fw_aks('my/multi/key', $value_to_persist, $current_persisted);
1300
+ *
1301
+ * return $current_persisted;
1302
+ * }
1303
+ */
1304
+ apply_filters('fw_settings_form_reset:values', array(), $old_values)
1305
+ );
1306
+
1307
+ FW_Flash_Messages::add( $flash_id, __( 'The options were successfully reset', 'fw' ), 'success' );
1308
+
1309
+ do_action( 'fw_settings_form_reset', $old_values );
1310
+ } else { // The "Save" button was pressed
1311
+ fw_set_db_settings_option(
1312
+ null,
1313
+ fw_get_options_values_from_input(
1314
+ fw()->theme->get_settings_options()
1315
+ )
1316
+ );
1317
+
1318
+ FW_Flash_Messages::add( $flash_id, __( 'The options were successfully saved', 'fw' ), 'success' );
1319
+
1320
+ do_action( 'fw_settings_form_saved', $old_values );
1321
+ }
1322
+
1323
+ $redirect_url = fw_current_url();
1324
+
1325
+ $data['redirect'] = $redirect_url;
1326
+
1327
+ return $data;
1328
+ }
1329
+
1330
+ /**
1331
+ * Render options array and return the generated HTML
1332
+ *
1333
+ * @param array $options
1334
+ * @param array $values Correct values returned by fw_get_options_values_from_input()
1335
+ * @param array $options_data {id_prefix => ..., name_prefix => ...}
1336
+ * @param string $design
1337
+ *
1338
+ * @return string HTML
1339
+ */
1340
+ public function render_options( $options, $values = array(), $options_data = array(), $design = null ) {
1341
+ if (empty($design)) {
1342
+ $design = $this->default_render_design;
1343
+ }
1344
+
1345
+ if (
1346
+ !doing_action('admin_enqueue_scripts')
1347
+ &&
1348
+ !did_action('admin_enqueue_scripts')
1349
+ ) {
1350
+ /**
1351
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1352
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
1353
+ * So as a result some handles will not be equeued because of not registered dependecies.
1354
+ */
1355
+ } else {
1356
+ /**
1357
+ * register scripts and styles
1358
+ * in case if this method is called before enqueue_scripts action
1359
+ * and option types has some of these in their dependencies
1360
+ */
1361
+ $this->register_static();
1362
+
1363
+ wp_enqueue_media();
1364
+ wp_enqueue_style( 'fw-backend-options' );
1365
+ wp_enqueue_script( 'fw-backend-options' );
1366
+ }
1367
+
1368
+ $collected = array();
1369
+
1370
+ fw_collect_options( $collected, $options, array(
1371
+ 'limit_option_types' => false,
1372
+ 'limit_container_types' => false,
1373
+ 'limit_level' => 1,
1374
+ 'info_wrapper' => true,
1375
+ ) );
1376
+
1377
+ if ( empty( $collected ) ) {
1378
+ return false;
1379
+ }
1380
+
1381
+ $html = '';
1382
+
1383
+ $option = reset( $collected );
1384
+
1385
+ $collected_type = array(
1386
+ 'group' => $option['group'],
1387
+ 'type' => $option['option']['type'],
1388
+ );
1389
+ $collected_type_options = array(
1390
+ $option['id'] => &$option['option']
1391
+ );
1392
+
1393
+ while ( $collected_type_options ) {
1394
+ $option = next( $collected );
1395
+
1396
+ if ( $option ) {
1397
+ if (
1398
+ $option['group'] === $collected_type['group']
1399
+ &&
1400
+ $option['option']['type'] === $collected_type['type']
1401
+ ) {
1402
+ $collected_type_options[ $option['id'] ] = &$option['option'];
1403
+ continue;
1404
+ }
1405
+ }
1406
+
1407
+ switch ( $collected_type['group'] ) {
1408
+ case 'container':
1409
+ if ($design === 'taxonomy') {
1410
+ $html .= fw_render_view(
1411
+ fw_get_framework_directory('/views/backend-container-design-'. $design .'.php'),
1412
+ array(
1413
+ 'type' => $collected_type['type'],
1414
+ 'html' => $this->container_type($collected_type['type'])->render(
1415
+ $collected_type_options, $values, $options_data
1416
+ ),
1417
+ )
1418
+ );
1419
+ } else {
1420
+ $html .= $this->container_type($collected_type['type'])->render(
1421
+ $collected_type_options, $values, $options_data
1422
+ );
1423
+ }
1424
+ break;
1425
+ case 'option':
1426
+ foreach ( $collected_type_options as $id => &$_option ) {
1427
+ $data = $options_data; // do not change directly to not affect next loops
1428
+
1429
+ $data['value'] = isset( $values[ $id ] ) ? $values[ $id ] : null;
1430
+
1431
+ $html .= $this->render_option(
1432
+ $id,
1433
+ $_option,
1434
+ $data,
1435
+ $design
1436
+ );
1437
+ }
1438
+ unset($_option);
1439
+ break;
1440
+ default:
1441
+ $html .= '<p><em>' . __( 'Unknown collected group', 'fw' ) . ': ' . $collected_type['group'] . '</em></p>';
1442
+ }
1443
+
1444
+ unset( $collected_type, $collected_type_options );
1445
+
1446
+ if ( $option ) {
1447
+ $collected_type = array(
1448
+ 'group' => $option['group'],
1449
+ 'type' => $option['option']['type'],
1450
+ );
1451
+ $collected_type_options = array(
1452
+ $option['id'] => &$option['option']
1453
+ );
1454
+ } else {
1455
+ $collected_type_options = array();
1456
+ }
1457
+ }
1458
+
1459
+ return $html;
1460
+ }
1461
+
1462
+ /**
1463
+ * Enqueue options static
1464
+ *
1465
+ * Useful when you have dynamic options html on the page (for e.g. options modal)
1466
+ * and in order to initialize that html properly, the option types scripts styles must be enqueued on the page
1467
+ *
1468
+ * @param array $options
1469
+ */
1470
+ public function enqueue_options_static( $options ) {
1471
+ if (
1472
+ !doing_action('admin_enqueue_scripts')
1473
+ &&
1474
+ !did_action('admin_enqueue_scripts')
1475
+ ) {
1476
+ /**
1477
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1478
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
1479
+ * So as a result some handles will not be equeued because of not registered dependecies.
1480
+ */
1481
+ return;
1482
+ } else {
1483
+ /**
1484
+ * register scripts and styles
1485
+ * in case if this method is called before enqueue_scripts action
1486
+ * and option types has some of these in their dependencies
1487
+ */
1488
+ $this->register_static();
1489
+
1490
+ wp_enqueue_media();
1491
+ wp_enqueue_style( 'fw-backend-options' );
1492
+ wp_enqueue_script( 'fw-backend-options' );
1493
+ }
1494
+
1495
+ $collected = array();
1496
+
1497
+ fw_collect_options( $collected, $options, array(
1498
+ 'limit_option_types' => false,
1499
+ 'limit_container_types' => false,
1500
+ 'limit_level' => 0,
1501
+ 'info_wrapper' => true,
1502
+ ) );
1503
+
1504
+ foreach ( $collected as &$option ) {
1505
+ if ($option['group'] === 'option') {
1506
+ fw()->backend->option_type($option['option']['type'])->enqueue_static($option['id'], $option['option']);
1507
+ } elseif ($option['group'] === 'container') {
1508
+ fw()->backend->container_type($option['option']['type'])->enqueue_static($option['id'], $option['option']);
1509
+ }
1510
+ }
1511
+ }
1512
+
1513
+ /**
1514
+ * Render option enclosed in backend design
1515
+ *
1516
+ * @param string $id
1517
+ * @param array $option
1518
+ * @param array $data
1519
+ * @param string $design default or taxonomy
1520
+ *
1521
+ * @return string
1522
+ */
1523
+ public function render_option( $id, $option, $data = array(), $design = null ) {
1524
+ if (empty($design)) {
1525
+ $design = $this->default_render_design;
1526
+ }
1527
+
1528
+ if (
1529
+ !doing_action('admin_enqueue_scripts')
1530
+ &&
1531
+ !did_action('admin_enqueue_scripts')
1532
+ ) {
1533
+ /**
1534
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1535
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
1536
+ * So as a result some handles will not be equeued because of not registered dependecies.
1537
+ */
1538
+ } else {
1539
+ $this->register_static();
1540
+ }
1541
+
1542
+
1543
+ if ( ! in_array( $design, $this->available_render_designs ) ) {
1544
+ trigger_error( 'Invalid render design specified: ' . $design, E_USER_WARNING );
1545
+ $design = 'post';
1546
+ }
1547
+
1548
+ if ( ! isset( $data['id_prefix'] ) ) {
1549
+ $data['id_prefix'] = FW_Option_Type::get_default_id_prefix();
1550
+ }
1551
+
1552
+ if (
1553
+ isset($option['option_handler']) &&
1554
+ $option['option_handler'] instanceof FW_Option_Handler
1555
+ ) {
1556
+
1557
+ /*
1558
+ * if the option has a custom option_handler
1559
+ * then the handler provides the option's value
1560
+ */
1561
+ $data['value'] = $option['option_handler']->get_option_value($id, $option, $data);
1562
+ }
1563
+
1564
+ $data = apply_filters(
1565
+ 'fw:backend:option-render:data',
1566
+ $data
1567
+ );
1568
+
1569
+ return fw_render_view(fw_get_framework_directory('/views/backend-option-design-'. $design .'.php'), array(
1570
+ 'id' => $id,
1571
+ 'option' => $option,
1572
+ 'data' => $data,
1573
+ ) );
1574
+ }
1575
+
1576
+ /**
1577
+ * Render a meta box
1578
+ *
1579
+ * @param string $id
1580
+ * @param string $title
1581
+ * @param string $content HTML
1582
+ * @param array $other Optional elements
1583
+ *
1584
+ * @return string Generated meta box html
1585
+ */
1586
+ public function render_box( $id, $title, $content, $other = array() ) {
1587
+ if ( ! function_exists( 'add_meta_box' ) ) {
1588
+ trigger_error( 'Try call this method later (\'admin_init\' action), add_meta_box() function does not exists yet.',
1589
+ E_USER_WARNING );
1590
+
1591
+ return '';
1592
+ }
1593
+
1594
+ $other = array_merge( array(
1595
+ 'html_before_title' => false,
1596
+ 'html_after_title' => false,
1597
+ 'attr' => array(),
1598
+ ), $other );
1599
+
1600
+ {
1601
+ $placeholders = array(
1602
+ 'id' => '{{meta_box_id}}',
1603
+ 'title' => '{{meta_box_title}}',
1604
+ 'content' => '{{meta_box_content}}',
1605
+ );
1606
+
1607
+ // other placeholders
1608
+ {
1609
+ $placeholders['html_before_title'] = '{{meta_box_html_before_title}}';
1610
+ $placeholders['html_after_title'] = '{{meta_box_html_after_title}}';
1611
+ $placeholders['attr'] = '{{meta_box_attr}}';
1612
+ $placeholders['attr_class'] = '{{meta_box_attr_class}}';
1613
+ }
1614
+ }
1615
+
1616
+ $cache_key = 'fw_meta_box_template';
1617
+
1618
+ try {
1619
+ $meta_box_template = FW_Cache::get( $cache_key );
1620
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
1621
+ $temp_screen_id = 'fw-temp-meta-box-screen-id-' . fw_unique_increment();
1622
+ $context = 'normal';
1623
+
1624
+ add_meta_box(
1625
+ $placeholders['id'],
1626
+ $placeholders['title'],
1627
+ $this->print_meta_box_content_callback,
1628
+ $temp_screen_id,
1629
+ $context,
1630
+ 'default',
1631
+ $placeholders['content']
1632
+ );
1633
+
1634
+ ob_start();
1635
+
1636
+ do_meta_boxes( $temp_screen_id, $context, null );
1637
+
1638
+ $meta_box_template = ob_get_clean();
1639
+
1640
+ remove_meta_box( $id, $temp_screen_id, $context );
1641
+
1642
+ // remove wrapper div, leave only meta box div
1643
+ {
1644
+ // <div ...>
1645
+ {
1646
+ $meta_box_template = str_replace(
1647
+ '<div id="' . $context . '-sortables" class="meta-box-sortables">',
1648
+ '',
1649
+ $meta_box_template
1650
+ );
1651
+ }
1652
+
1653
+ // </div>
1654
+ {
1655
+ $meta_box_template = explode( '</div>', $meta_box_template );
1656
+ array_pop( $meta_box_template );
1657
+ $meta_box_template = implode( '</div>', $meta_box_template );
1658
+ }
1659
+ }
1660
+
1661
+ // add 'fw-postbox' class and some attr related placeholders
1662
+ $meta_box_template = str_replace(
1663
+ 'class="postbox',
1664
+ $placeholders['attr'] . ' class="postbox fw-postbox' . $placeholders['attr_class'],
1665
+ $meta_box_template
1666
+ );
1667
+
1668
+ // add html_before|after_title placeholders
1669
+ {
1670
+ $meta_box_template = str_replace(
1671
+ '<span>' . $placeholders['title'] . '</span>',
1672
+
1673
+ /**
1674
+ * used <small> not <span> because there is a lot of css and js
1675
+ * that thinks inside <h2 class="hndle"> there is only one <span>
1676
+ * so do not brake their logic
1677
+ */
1678
+ '<small class="fw-html-before-title">' . $placeholders['html_before_title'] . '</small>' .
1679
+ '<span>' . $placeholders['title'] . '</span>' .
1680
+ '<small class="fw-html-after-title">' . $placeholders['html_after_title'] . '</small>',
1681
+
1682
+ $meta_box_template
1683
+ );
1684
+ }
1685
+
1686
+ FW_Cache::set( $cache_key, $meta_box_template );
1687
+ }
1688
+
1689
+ // prepare attributes
1690
+ {
1691
+ $attr_class = '';
1692
+ if ( isset( $other['attr']['class'] ) ) {
1693
+ $attr_class = ' ' . $other['attr']['class'];
1694
+
1695
+ unset( $other['attr']['class'] );
1696
+ }
1697
+
1698
+ unset( $other['attr']['id'] );
1699
+ }
1700
+
1701
+ // replace placeholders with data/content
1702
+ return str_replace(
1703
+ array(
1704
+ $placeholders['id'],
1705
+ $placeholders['title'],
1706
+ $placeholders['content'],
1707
+ $placeholders['html_before_title'],
1708
+ $placeholders['html_after_title'],
1709
+ $placeholders['attr'],
1710
+ $placeholders['attr_class'],
1711
+ ),
1712
+ array(
1713
+ esc_attr( $id ),
1714
+ $title,
1715
+ $content,
1716
+ $other['html_before_title'],
1717
+ $other['html_after_title'],
1718
+ fw_attr_to_html( $other['attr'] ),
1719
+ esc_attr( $attr_class )
1720
+ ),
1721
+ $meta_box_template
1722
+ );
1723
+ }
1724
+
1725
+ /**
1726
+ * @param FW_Access_Key $access_key
1727
+ * @param string|FW_Option_Type $option_type_class
1728
+ *
1729
+ * @internal
1730
+ */
1731
+ public function _register_option_type( FW_Access_Key $access_key, $option_type_class ) {
1732
+ if ( $access_key->get_key() !== 'fw_option_type' ) {
1733
+ trigger_error( 'Call denied', E_USER_ERROR );
1734
+ }
1735
+
1736
+ $this->register_option_type( $option_type_class );
1737
+ }
1738
+
1739
+ /**
1740
+ * @param FW_Access_Key $access_key
1741
+ * @param string|FW_Container_Type $container_type_class
1742
+ *
1743
+ * @internal
1744
+ */
1745
+ public function _register_container_type( FW_Access_Key $access_key, $container_type_class ) {
1746
+ if ( $access_key->get_key() !== 'fw_container_type' ) {
1747
+ trigger_error( 'Call denied', E_USER_ERROR );
1748
+ }
1749
+
1750
+ $this->register_container_type( $container_type_class );
1751
+ }
1752
+
1753
+ /**
1754
+ * @param string $option_type
1755
+ *
1756
+ * @return FW_Option_Type|FW_Option_Type_Undefined
1757
+ */
1758
+ public function option_type( $option_type ) {
1759
+ if ( is_array( $this->option_types_pending_registration ) ) {
1760
+ // This method is called first time
1761
+
1762
+ do_action('fw_option_types_init');
1763
+
1764
+ // Register pending option types
1765
+ {
1766
+ $pending_option_types = $this->option_types_pending_registration;
1767
+
1768
+ // clear this property, so register_option_type() will not add option types to pending anymore
1769
+ $this->option_types_pending_registration = false;
1770
+
1771
+ foreach ( $pending_option_types as $option_type_class ) {
1772
+ $this->register_option_type( $option_type_class );
1773
+ }
1774
+
1775
+ unset( $pending_option_types );
1776
+ }
1777
+ }
1778
+
1779
+ if ( isset( $this->option_types[ $option_type ] ) ) {
1780
+ return $this->option_types[ $option_type ];
1781
+ } else {
1782
+ if ( is_admin() ) {
1783
+ FW_Flash_Messages::add(
1784
+ 'fw-get-option-type-undefined-' . $option_type,
1785
+ sprintf( __( 'Undefined option type: %s', 'fw' ), $option_type ),
1786
+ 'warning'
1787
+ );
1788
+ }
1789
+
1790
+ if (!$this->undefined_option_type) {
1791
+ require_once fw_get_framework_directory('/includes/option-types/class-fw-option-type-undefined.php');
1792
+
1793
+ $this->undefined_option_type = new FW_Option_Type_Undefined();
1794
+ }
1795
+
1796
+ return $this->undefined_option_type;
1797
+ }
1798
+ }
1799
+
1800
+ /**
1801
+ * @param string $container_type
1802
+ *
1803
+ * @return FW_Container_Type|FW_Container_Type_Undefined
1804
+ */
1805
+ public function container_type( $container_type ) {
1806
+ if ( is_array( $this->container_types_pending_registration ) ) {
1807
+ // This method is called first time
1808
+
1809
+ do_action('fw_container_types_init');
1810
+
1811
+ // Register pending container types
1812
+ {
1813
+ $pending_container_types = $this->container_types_pending_registration;
1814
+
1815
+ // clear this property, so register_container_type() will not add container types to pending anymore
1816
+ $this->container_types_pending_registration = false;
1817
+
1818
+ foreach ( $pending_container_types as $container_type_class ) {
1819
+ $this->register_container_type( $container_type_class );
1820
+ }
1821
+
1822
+ unset( $pending_container_types );
1823
+ }
1824
+ }
1825
+
1826
+ if ( isset( $this->container_types[ $container_type ] ) ) {
1827
+ return $this->container_types[ $container_type ];
1828
+ } else {
1829
+ if ( is_admin() ) {
1830
+ FW_Flash_Messages::add(
1831
+ 'fw-get-container-type-undefined-' . $container_type,
1832
+ sprintf( __( 'Undefined container type: %s', 'fw' ), $container_type ),
1833
+ 'warning'
1834
+ );
1835
+ }
1836
+
1837
+ if (!$this->undefined_container_type) {
1838
+ require_once fw_get_framework_directory('/includes/container-types/class-fw-container-type-undefined.php');
1839
+
1840
+ $this->undefined_container_type = new FW_Container_Type_Undefined();
1841
+ }
1842
+
1843
+ return $this->undefined_container_type;
1844
+ }
1845
+ }
1846
+
1847
+ /**
1848
+ * @param WP_Customize_Manager $wp_customize
1849
+ * @internal
1850
+ */
1851
+ public function _action_customize_register($wp_customize) {
1852
+ if (is_admin()) {
1853
+ add_action('admin_enqueue_scripts', array($this, '_action_enqueue_customizer_static'));
1854
+ }
1855
+
1856
+ $this->customizer_register_options(
1857
+ $wp_customize,
1858
+ fw()->theme->get_customizer_options()
1859
+ );
1860
+ }
1861
+
1862
+ /**
1863
+ * @internal
1864
+ */
1865
+ public function _action_enqueue_customizer_static()
1866
+ {
1867
+ {
1868
+ $options_for_enqueue = array();
1869
+ $customizer_options = fw()->theme->get_customizer_options();
1870
+
1871
+ /**
1872
+ * In customizer options is allowed to have container with unspecified (or not existing) 'type'
1873
+ * fw()->backend->enqueue_options_static() tries to enqueue both options and container static
1874
+ * not existing container types will throw notices.
1875
+ * To prevent that, extract and send it only options (without containers)
1876
+ */
1877
+ fw_collect_options($options_for_enqueue, $customizer_options);
1878
+
1879
+ fw()->backend->enqueue_options_static($options_for_enqueue);
1880
+
1881
+ unset($options_for_enqueue, $customizer_options);
1882
+ }
1883
+
1884
+ wp_enqueue_script(
1885
+ 'fw-backend-customizer',
1886
+ fw_get_framework_directory_uri( '/static/js/backend-customizer.js' ),
1887
+ array( 'jquery', 'fw-events', 'backbone' ),
1888
+ fw()->manifest->get_version(),
1889
+ true
1890
+ );
1891
+ wp_localize_script(
1892
+ 'fw-backend-customizer',
1893
+ '_fw_backend_customizer_localized',
1894
+ array(
1895
+ 'change_timeout' => apply_filters('fw_customizer_option_change_timeout', 333),
1896
+ )
1897
+ );
1898
+
1899
+ do_action('fw_admin_enqueue_scripts:customizer');
1900
+ }
1901
+
1902
+ /**
1903
+ * @param WP_Customize_Manager $wp_customize
1904
+ * @param array $options
1905
+ * @param array $parent_data {'type':'...','id':'...'}
1906
+ */
1907
+ private function customizer_register_options($wp_customize, $options, $parent_data = array()) {
1908
+ $collected = array();
1909
+
1910
+ fw_collect_options( $collected, $options, array(
1911
+ 'limit_option_types' => false,
1912
+ 'limit_container_types' => false,
1913
+ 'limit_level' => 1,
1914
+ 'info_wrapper' => true,
1915
+ ) );
1916
+
1917
+ if ( empty( $collected ) ) {
1918
+ return;
1919
+ }
1920
+
1921
+ foreach ($collected as &$opt) {
1922
+ switch ($opt['group']) {
1923
+ case 'container':
1924
+ // Check if has container options
1925
+ {
1926
+ $_collected = array();
1927
+
1928
+ fw_collect_options( $_collected, $opt['option']['options'], array(
1929
+ 'limit_option_types' => array(),
1930
+ 'limit_container_types' => false,
1931
+ 'limit_level' => 1,
1932
+ 'limit' => 1,
1933
+ 'info_wrapper' => false,
1934
+ ) );
1935
+
1936
+ $has_containers = !empty($_collected);
1937
+
1938
+ unset($_collected);
1939
+ }
1940
+
1941
+ $children_data = array(
1942
+ 'group' => 'container',
1943
+ 'id' => $opt['id']
1944
+ );
1945
+
1946
+ $args = array(
1947
+ 'title' => empty($opt['option']['title'])
1948
+ ? fw_id_to_title($opt['id'])
1949
+ : $opt['option']['title'],
1950
+ 'description' => empty($opt['option']['desc'])
1951
+ ? ''
1952
+ : $opt['option']['desc'],
1953
+ );
1954
+
1955
+ if (isset($opt['option']['wp-customizer-args']) && is_array($opt['option']['wp-customizer-args'])) {
1956
+ $args = array_merge($opt['option']['wp-customizer-args'], $args);
1957
+ }
1958
+
1959
+ if ($has_containers) {
1960
+ if ($parent_data) {
1961
+ trigger_error($opt['id'] .' panel can\'t have a parent ('. $parent_data['id'] .')', E_USER_WARNING);
1962
+ break;
1963
+ }
1964
+
1965
+ $wp_customize->add_panel($opt['id'], $args);
1966
+
1967
+ $children_data['customizer_type'] = 'panel';
1968
+ } else {
1969
+ if ($parent_data) {
1970
+ if ($parent_data['customizer_type'] === 'panel') {
1971
+ $args['panel'] = $parent_data['id'];
1972
+ } else {
1973
+ trigger_error($opt['id'] .' section can have only panel parent ('. $parent_data['id'] .')', E_USER_WARNING);
1974
+ break;
1975
+ }
1976
+ }
1977
+
1978
+ $wp_customize->add_section($opt['id'], $args);
1979
+
1980
+ $children_data['customizer_type'] = 'section';
1981
+ }
1982
+
1983
+ $this->customizer_register_options(
1984
+ $wp_customize,
1985
+ $opt['option']['options'],
1986
+ $children_data
1987
+ );
1988
+
1989
+ unset($children_data);
1990
+ break;
1991
+ case 'option':
1992
+ $setting_id = FW_Option_Type::get_default_name_prefix() .'['. $opt['id'] .']';
1993
+
1994
+ {
1995
+ $args = array(
1996
+ 'label' => empty($opt['option']['label'])
1997
+ ? fw_id_to_title($opt['id'])
1998
+ : $opt['option']['label'],
1999
+ 'description' => empty($opt['option']['desc'])
2000
+ ? ''
2001
+ : $opt['option']['desc'],
2002
+ 'settings' => $setting_id,
2003
+ );
2004
+
2005
+ if (isset($opt['option']['wp-customizer-args']) && is_array($opt['option']['wp-customizer-args'])) {
2006
+ $args = array_merge($opt['option']['wp-customizer-args'], $args);
2007
+ }
2008
+
2009
+ if ($parent_data) {
2010
+ if ($parent_data['customizer_type'] === 'section') {
2011
+ $args['section'] = $parent_data['id'];
2012
+ } else {
2013
+ trigger_error('Invalid control parent: '. $parent_data['customizer_type'], E_USER_WARNING);
2014
+ break;
2015
+ }
2016
+ } else { // the option is not placed in a section, create a section automatically
2017
+ $args['section'] = 'fw_option_auto_section_'. $opt['id'];
2018
+ $wp_customize->add_section($args['section'], array(
2019
+ 'title' => empty($opt['option']['label'])
2020
+ ? fw_id_to_title($opt['id'])
2021
+ : $opt['option']['label'],
2022
+ ));
2023
+ }
2024
+ }
2025
+
2026
+ if (!class_exists('_FW_Customizer_Setting_Option')) {
2027
+ require_once fw_get_framework_directory('/includes/customizer/class--fw-customizer-setting-option.php');
2028
+ }
2029
+
2030
+ if (!class_exists('_FW_Customizer_Control_Option_Wrapper')) {
2031
+ require_once fw_get_framework_directory('/includes/customizer/class--fw-customizer-control-option-wrapper.php');
2032
+ }
2033
+
2034
+ $wp_customize->add_setting(
2035
+ new _FW_Customizer_Setting_Option(
2036
+ $wp_customize,
2037
+ $setting_id,
2038
+ array(
2039
+ 'default' => fw()->backend->option_type($opt['option']['type'])->get_value_from_input($opt['option'], null),
2040
+ 'fw_option' => $opt['option'],
2041
+ )
2042
+ )
2043
+ );
2044
+
2045
+ $wp_customize->add_control(
2046
+ new _FW_Customizer_Control_Option_Wrapper(
2047
+ $wp_customize,
2048
+ $opt['id'],
2049
+ $args
2050
+ )
2051
+ );
2052
+ break;
2053
+ default:
2054
+ trigger_error('Unknown group: '. $opt['group'], E_USER_WARNING);
2055
+ }
2056
+ }
2057
+ }
2058
+
2059
+ /**
2060
+ * For e.g. an option-type was rendered using 'customizer' design,
2061
+ * but inside it uses render_options() but it doesn't know the current render design
2062
+ * and the options will be rendered with 'default' design.
2063
+ * This method allows to specify the default design that will be used if not specified on render_options()
2064
+ * @param null|string $design
2065
+ * @internal
2066
+ */
2067
+ public function _set_default_render_design($design = null)
2068
+ {
2069
+ if (empty($design) || !in_array($design, $this->available_render_designs)) {
2070
+ $this->default_render_design = 'default';
2071
+ } else {
2072
+ $this->default_render_design = $design;
2073
+ }
2074
+ }
2075
+ }
framework/core/components/extensions.php CHANGED
@@ -1,655 +1,661 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Extensions component
5
- */
6
- final class _FW_Component_Extensions
7
- {
8
- /**
9
- * All existing extensions
10
- * @var FW_Extension[] { 'extension_name' => instance }
11
- */
12
- private static $all_extensions = array();
13
-
14
- /**
15
- * All existing extensions names arranged in hierarchical tree like they are in directories
16
- * @var array
17
- */
18
- private static $all_extensions_tree = array();
19
-
20
- /**
21
- * Active extensions
22
- *
23
- * On every extension activation, it will be pushed at the end of this array.
24
- * The extensions order is important when including files.
25
- * If extension A requires extension B, extension B is activated before extension A,
26
- * and all files of the extension B (hooks.php, static.php, etc.) must be included before extension A
27
- * For e.g. extension A may have in static.php:
28
- * wp_enqueue_script( 'ext-A-script', 'script.js', array( 'ext-B-script' ) );
29
- * so 'ext-B-script' must be registered before 'ext-A-script'
30
- *
31
- * @var FW_Extension[] { 'extension_name' => instance }
32
- */
33
- private static $active_extensions = array();
34
-
35
- /**
36
- * Active extensions names arranged in hierarchical tree like they are in directories
37
- * @var array
38
- */
39
- private static $active_extensions_tree = array();
40
-
41
- /**
42
- * @var array { 'extension_name' => array('required_by', 'required_by') }
43
- */
44
- private static $extensions_required_by_extensions = array();
45
-
46
- /**
47
- * @var array { 'extension_name' => &array() }
48
- */
49
- private static $extension_to_all_tree = array();
50
-
51
- /**
52
- * @var array { 'extension_name' => &array() }
53
- */
54
- private static $extension_to_active_tree = array();
55
-
56
- /**
57
- * @var FW_Access_Key
58
- */
59
- private static $access_key;
60
-
61
- /**
62
- * @var null|_FW_Extensions_Manager
63
- */
64
- public $manager;
65
-
66
- /**
67
- * Option name that stores the active extensions array
68
- * @internal
69
- */
70
- public function _get_active_extensions_db_option_name()
71
- {
72
- return 'fw_active_extensions';
73
- }
74
-
75
- /**
76
- * @param null|string $extension_name Check if an extension is set as active in database
77
- * @internal
78
- * @return array|bool
79
- */
80
- public function _get_db_active_extensions($extension_name = null)
81
- {
82
- $extensions = get_option($this->_get_active_extensions_db_option_name(), array());
83
-
84
- if ($extension_name) {
85
- return isset($extensions[$extension_name]);
86
- } else {
87
- return $extensions;
88
- }
89
- }
90
-
91
- public function __construct()
92
- {
93
- require dirname(__FILE__) .'/extensions/class-fw-extension-default.php';
94
-
95
- if (
96
- /**
97
- * Do not load in frontend because it has no functionality that can be useful in frontend
98
- */
99
- is_admin()
100
- ||
101
- /**
102
- * While in cron request (on auto-update), is_admin() is false
103
- * but we need the actions that moves extensions to a tmp dir then back, on plugin update
104
- * todo: maybe move those actions here in this class?
105
- */
106
- defined( 'DOING_CRON' )
107
- ) {
108
- require dirname(__FILE__) .'/extensions/manager/class--fw-extensions-manager.php';
109
-
110
- $this->manager = new _FW_Extensions_Manager();
111
- }
112
- }
113
-
114
- /**
115
- * Load extension from directory
116
- *
117
- * @param array $data
118
- */
119
- private static function load_extensions($data)
120
- {
121
- if (false) {
122
- $data = array(
123
- 'rel_path' => '/extension',
124
- 'path' => '/path/to/extension',
125
- 'uri' => 'https://uri.to/extension',
126
- 'customizations_locations' => array(
127
- '/path/to/parent/theme/customizations/extensions/ext/rel/path' => 'https://uri.to/customization/path',
128
- '/path/to/child/theme/customizations/extensions/ext/rel/path' => 'https://uri.to/customization/path',
129
- ),
130
-
131
- 'all_extensions_tree' => array(),
132
- 'all_extensions' => array(),
133
- 'current_depth' => 1,
134
- 'parent' => '&$parent_extension_instance',
135
- );
136
- }
137
-
138
- /**
139
- * Do not check all keys
140
- * if one not set, then sure others are not set (this is a private method)
141
- */
142
- if (!isset($data['all_extensions_tree'])) {
143
- $data['all_extensions_tree'] = &self::$all_extensions_tree;
144
- $data['all_extensions'] = &self::$all_extensions;
145
- $data['current_depth'] = 1;
146
- $data['rel_path'] = '';
147
- $data['parent'] = null;
148
- }
149
-
150
- $dirs = glob($data['path'] .'/*', GLOB_ONLYDIR);
151
-
152
- if (empty($dirs)) {
153
- return;
154
- }
155
-
156
- if ($data['current_depth'] > 1) {
157
- $customizations_locations = array();
158
-
159
- foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
160
- $customizations_locations[ $customization_path .'/extensions' ] = $customization_uri .'/extensions';
161
- }
162
-
163
- $data['customizations_locations'] = $customizations_locations;
164
- }
165
-
166
- foreach ($dirs as $extension_dir) {
167
- $extension_name = basename($extension_dir);
168
-
169
- {
170
- $customizations_locations = array();
171
-
172
- foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
173
- $customizations_locations[ $customization_path .'/'. $extension_name ] = $customization_uri .'/'. $extension_name;
174
- }
175
- }
176
-
177
- if (isset($data['all_extensions'][$extension_name])) {
178
- if ($data['all_extensions'][$extension_name]->get_parent() !== $data['parent']) {
179
- // extension with the same name exists in another tree
180
- trigger_error(
181
- 'Extension "'. $extension_name .'" is already defined '.
182
- 'in "'. $data['all_extensions'][$extension_name]->get_declared_path() .'" '.
183
- 'found again in "'. $extension_dir .'"',
184
- E_USER_ERROR
185
- );
186
- }
187
-
188
- // this is a directory with customizations for an extension
189
-
190
- self::load_extensions(array(
191
- 'rel_path' => $data['rel_path'] .'/'. $extension_name .'/extensions',
192
- 'path' => $data['path'] .'/'. $extension_name .'/extensions',
193
- 'uri' => $data['uri'] .'/'. $extension_name .'/extensions',
194
- 'customizations_locations' => $customizations_locations,
195
-
196
- 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
197
- 'all_extensions' => &$data['all_extensions'],
198
- 'current_depth' => $data['current_depth'] + 1,
199
- 'parent' => &$data['all_extensions'][$extension_name],
200
- ));
201
- } else {
202
- $class_file_name = 'class-fw-extension-'. $extension_name .'.php';
203
-
204
- if (file_exists($extension_dir .'/manifest.php')) {
205
- $data['all_extensions_tree'][$extension_name] = array();
206
-
207
- self::$extension_to_all_tree[$extension_name] = &$data['all_extensions_tree'][$extension_name];
208
-
209
- if (fw_include_file_isolated($extension_dir .'/'. $class_file_name)) {
210
- $class_name = 'FW_Extension_'. fw_dirname_to_classname($extension_name);
211
- } else {
212
- $parent_class_name = get_class($data['parent']);
213
- // check if parent extension has been defined custom Default class for its child extensions
214
- if (class_exists($parent_class_name .'_Default')) {
215
- $class_name = $parent_class_name .'_Default';
216
- } else {
217
- $class_name = 'FW_Extension_Default';
218
- }
219
- }
220
-
221
- if (!is_subclass_of($class_name, 'FW_Extension')) {
222
- trigger_error('Extension "'. $extension_name .'" must extend FW_Extension class', E_USER_ERROR);
223
- }
224
-
225
- $data['all_extensions'][$extension_name] = new $class_name(array(
226
- 'rel_path' => $data['rel_path'] .'/'. $extension_name,
227
- 'path' => $data['path'] .'/'. $extension_name,
228
- 'uri' => $data['uri'] .'/'. $extension_name,
229
- 'parent' => $data['parent'],
230
- 'depth' => $data['current_depth'],
231
- 'customizations_locations' => $customizations_locations,
232
- ));
233
- } else {
234
- /**
235
- * The manifest file does not exist, do not load this extension.
236
- * Maybe it's a directory with configurations for a not existing extension.
237
- */
238
- continue;
239
- }
240
-
241
- self::load_extensions(array(
242
- 'rel_path' => $data['all_extensions'][$extension_name]->get_rel_path() .'/extensions',
243
- 'path' => $data['all_extensions'][$extension_name]->get_path() .'/extensions',
244
- 'uri' => $data['all_extensions'][$extension_name]->get_uri() .'/extensions',
245
- 'customizations_locations' => $customizations_locations,
246
-
247
- 'parent' => &$data['all_extensions'][$extension_name],
248
- 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
249
- 'all_extensions' => &$data['all_extensions'],
250
- 'current_depth' => $data['current_depth'] + 1,
251
- ));
252
- }
253
- }
254
- }
255
-
256
- /**
257
- * Include file from all extension's locations: framework, parent, child
258
- * @param string|FW_Extension $extension
259
- * @param string $file_rel_path
260
- * @param bool $themeFirst
261
- * false - [framework, parent, child]
262
- * true - [child, parent, framework]
263
- * @param bool $onlyFirstFound
264
- */
265
- private static function include_extension_file_all_locations($extension, $file_rel_path, $themeFirst = false, $onlyFirstFound = false)
266
- {
267
- if (is_string($extension)) {
268
- $extension = fw()->extensions->get($extension);
269
- }
270
-
271
- $paths = $extension->get_customizations_locations();
272
- $paths[$extension->get_path()] = $extension->get_uri();
273
-
274
- if (!$themeFirst) {
275
- $paths = array_reverse($paths);
276
- }
277
-
278
- foreach ($paths as $path => $uri) {
279
- if (fw_include_file_isolated($path . $file_rel_path)) {
280
- if ($onlyFirstFound) {
281
- return;
282
- }
283
- }
284
- }
285
- }
286
-
287
- /**
288
- * Include all files from directory, from all extension's locations: framework, child, parent
289
- * @param string|FW_Extension $extension
290
- * @param string $dir_rel_path
291
- * @param bool $themeFirst
292
- * false - [framework, parent, child]
293
- * true - [child, parent, framework]
294
- */
295
- private static function include_extension_directory_all_locations($extension, $dir_rel_path, $themeFirst = false)
296
- {
297
- if (is_string($extension)) {
298
- $extension = fw()->extensions->get($extension);
299
- }
300
-
301
- $paths = $extension->get_customizations_locations();
302
- $paths[$extension->get_path()] = $extension->get_uri();
303
-
304
- if (!$themeFirst) {
305
- $paths = array_reverse($paths);
306
- }
307
-
308
- foreach ($paths as $path => $uri) {
309
- if ($files = glob($path . $dir_rel_path .'/*.php')) {
310
- foreach ($files as $dir_file_path) {
311
- fw_include_file_isolated($dir_file_path);
312
- }
313
- }
314
- }
315
- }
316
-
317
- public function get_locations()
318
- {
319
- $cache_key = 'fw_extensions_locations';
320
-
321
- try {
322
- return FW_Cache::get($cache_key);
323
- } catch (FW_Cache_Not_Found_Exception $e) {
324
- /**
325
- * { '/hello/world/extensions' => 'https://hello.com/world/extensions' }
326
- */
327
- $custom_locations = apply_filters('fw_extensions_locations', array());
328
-
329
- {
330
- $customizations_locations = array();
331
-
332
- if (is_child_theme()) {
333
- $customizations_locations[fw_get_stylesheet_customizations_directory('/extensions')]
334
- = fw_get_stylesheet_customizations_directory_uri('/extensions');
335
- }
336
-
337
- $customizations_locations[fw_get_template_customizations_directory('/extensions')]
338
- = fw_get_template_customizations_directory_uri('/extensions');
339
-
340
- $customizations_locations += $custom_locations;
341
- }
342
-
343
- $locations = array();
344
-
345
- $locations[ fw_get_framework_directory('/extensions') ] = array(
346
- 'path' => fw_get_framework_directory('/extensions'),
347
- 'uri' => fw_get_framework_directory_uri('/extensions'),
348
- 'customizations_locations' => $customizations_locations,
349
- 'is' => array(
350
- 'framework' => true,
351
- 'custom' => false,
352
- 'theme' => false,
353
- ),
354
- );
355
-
356
- foreach ($custom_locations as $path => $uri) {
357
- unset($customizations_locations[$path]);
358
- $locations[ $path ] = array(
359
- 'path' => $path,
360
- 'uri' => $uri,
361
- 'customizations_locations' => $customizations_locations,
362
- 'is' => array(
363
- 'framework' => false,
364
- 'custom' => true,
365
- 'theme' => false,
366
- ),
367
- );
368
- }
369
-
370
- array_pop($customizations_locations);
371
- $locations[ fw_get_template_customizations_directory('/extensions') ] = array(
372
- 'path' => fw_get_template_customizations_directory('/extensions'),
373
- 'uri' => fw_get_template_customizations_directory_uri('/extensions'),
374
- 'customizations_locations' => $customizations_locations,
375
- 'is' => array(
376
- 'framework' => false,
377
- 'custom' => false,
378
- 'theme' => true,
379
- ),
380
- );
381
-
382
- if (is_child_theme()) {
383
- array_pop($customizations_locations);
384
- $locations[ fw_get_stylesheet_customizations_directory('/extensions') ] = array(
385
- 'path' => fw_get_stylesheet_customizations_directory('/extensions'),
386
- 'uri' => fw_get_stylesheet_customizations_directory_uri('/extensions'),
387
- 'customizations_locations' => $customizations_locations,
388
- 'is' => array(
389
- 'framework' => false,
390
- 'custom' => false,
391
- 'theme' => true,
392
- ),
393
- );
394
- }
395
-
396
- FW_Cache::set($cache_key, $locations);
397
-
398
- return $locations;
399
- }
400
- }
401
-
402
- private function load_all_extensions()
403
- {
404
- foreach ($this->get_locations() as $location) {
405
- self::load_extensions(array(
406
- 'path' => $location['path'],
407
- 'uri' => $location['uri'],
408
- 'customizations_locations' => $location['customizations_locations'],
409
- ));
410
- }
411
- }
412
-
413
- /**
414
- * Activate extensions from given tree point
415
- *
416
- * @param null|string $parent_extension_name
417
- */
418
- private function activate_extensions($parent_extension_name = null)
419
- {
420
- if ($parent_extension_name === null) {
421
- $all_tree =& self::$all_extensions_tree;
422
- } else {
423
- $all_tree =& self::$extension_to_all_tree[$parent_extension_name];
424
- }
425
-
426
- foreach ($all_tree as $extension_name => &$sub_extensions) {
427
- if (fw()->extensions->get($extension_name)) {
428
- // extension already active
429
- continue;
430
- }
431
-
432
- $extension =& self::$all_extensions[$extension_name];
433
-
434
- if ($extension->manifest->check_requirements()) {
435
- if (!$this->_get_db_active_extensions($extension_name)) {
436
- // extension is not set as active
437
- } elseif (
438
- $extension->get_parent()
439
- &&
440
- !$extension->get_parent()->_child_extension_is_valid($extension)
441
- ) {
442
- // extension does not pass parent extension rules
443
- if (is_admin()) {
444
- // show warning only in admin side
445
- FW_Flash_Messages::add(
446
- 'fw-invalid-extension',
447
- sprintf(__('Extension %s is invalid.', 'fw'), $extension->get_name()),
448
- 'warning'
449
- );
450
- }
451
- } else {
452
- // all requirements met, activate extension
453
- $this->activate_extension($extension_name);
454
- }
455
- } else {
456
- // requirements not met, tell required extensions that this extension is waiting for them
457
-
458
- foreach ($extension->manifest->get_required_extensions() as $required_extension_name => $requirements) {
459
- if (!isset(self::$extensions_required_by_extensions[$required_extension_name])) {
460
- self::$extensions_required_by_extensions[$required_extension_name] = array();
461
- }
462
-
463
- self::$extensions_required_by_extensions[$required_extension_name][] = $extension_name;
464
- }
465
- }
466
- }
467
- unset($sub_extensions);
468
- }
469
-
470
- /**
471
- * @param string $extension_name
472
- * @return bool
473
- */
474
- private function activate_extension($extension_name)
475
- {
476
- if (fw()->extensions->get($extension_name)) {
477
- // already active
478
- return false;
479
- }
480
-
481
- if (!self::$all_extensions[$extension_name]->manifest->requirements_met()) {
482
- trigger_error('Wrong '. __METHOD__ .' call', E_USER_WARNING);
483
- return false;
484
- }
485
-
486
- // add to active extensions so inside includes/ and extension it will be accessible from fw()->extensions->get(...)
487
- self::$active_extensions[$extension_name] =& self::$all_extensions[$extension_name];
488
-
489
- $parent = self::$all_extensions[$extension_name]->get_parent();
490
-
491
- if ($parent) {
492
- self::$extension_to_active_tree[ $parent->get_name() ][$extension_name] = array();
493
- self::$extension_to_active_tree[$extension_name] =& self::$extension_to_active_tree[ $parent->get_name() ][$extension_name];
494
- } else {
495
- self::$active_extensions_tree[$extension_name] = array();
496
- self::$extension_to_active_tree[$extension_name] =& self::$active_extensions_tree[$extension_name];
497
- }
498
-
499
- self::include_extension_directory_all_locations($extension_name, '/includes');
500
- self::include_extension_file_all_locations($extension_name, '/helpers.php');
501
- self::include_extension_file_all_locations($extension_name, '/hooks.php');
502
-
503
- if (self::$all_extensions[$extension_name]->_call_init(self::$access_key) !== false) {
504
- $this->activate_extensions($extension_name);
505
- }
506
-
507
- // check if other extensions are waiting for this extension and try to activate them
508
- if (isset(self::$extensions_required_by_extensions[$extension_name])) {
509
- foreach (self::$extensions_required_by_extensions[$extension_name] as $waiting_extension_name) {
510
- if (self::$all_extensions[$waiting_extension_name]->manifest->check_requirements()) {
511
- $waiting_extension = self::$all_extensions[$waiting_extension_name];
512
-
513
- if (!$this->_get_db_active_extensions($waiting_extension_name)) {
514
- // extension is set as active
515
- } elseif (
516
- $waiting_extension->get_parent()
517
- &&
518
- !$waiting_extension->get_parent()->_child_extension_is_valid($waiting_extension)
519
- ) {
520
- // extension does not pass parent extension rules
521
- if (is_admin()) {
522
- // show warning only in admin side
523
- FW_Flash_Messages::add(
524
- 'fw-invalid-extension',
525
- sprintf(__('Extension %s is invalid.', 'fw'), $waiting_extension_name),
526
- 'warning'
527
- );
528
- }
529
- } else {
530
- $this->activate_extension($waiting_extension_name);
531
- }
532
- }
533
- }
534
-
535
- unset(self::$extensions_required_by_extensions[$extension_name]);
536
- }
537
-
538
- return true;
539
- }
540
-
541
- private function add_actions()
542
- {
543
- add_action('init', array($this, '_action_init'));
544
- add_action('wp_enqueue_scripts', array($this, '_action_enqueue_scripts'));
545
- add_action('admin_enqueue_scripts', array($this, '_action_enqueue_scripts'));
546
- }
547
-
548
- /**
549
- * Give extensions possibility to access their active_tree
550
- * @internal
551
- *
552
- * @param FW_Access_Key $access_key
553
- * @param $extension_name
554
- *
555
- * @return array
556
- */
557
- public function _get_extension_tree(FW_Access_Key $access_key, $extension_name)
558
- {
559
- if ($access_key->get_key() !== 'extension') {
560
- trigger_error('Call denied', E_USER_ERROR);
561
- }
562
-
563
- return self::$extension_to_active_tree[$extension_name];
564
- }
565
-
566
- /**
567
- * @internal
568
- */
569
- public function _init()
570
- {
571
- self::$access_key = new FW_Access_Key('fw_extensions');
572
-
573
- $this->load_all_extensions();
574
- $this->add_actions();
575
- }
576
-
577
- /**
578
- * @internal
579
- */
580
- public function _after_components_init()
581
- {
582
- $this->activate_extensions();
583
-
584
- /**
585
- * Extensions are activated
586
- * Now $this->get_children() inside extensions is available
587
- */
588
- do_action('fw_extensions_init');
589
- }
590
-
591
- public function _action_init()
592
- {
593
- foreach (self::$active_extensions as &$extension) {
594
- /** register posts and taxonomies */
595
- self::include_extension_file_all_locations($extension, '/posts.php');
596
- }
597
- }
598
-
599
- public function _action_enqueue_scripts()
600
- {
601
- foreach (self::$active_extensions as &$extension) {
602
- /** js and css */
603
- self::include_extension_file_all_locations($extension, '/static.php', true, true);
604
- }
605
- }
606
-
607
- /**
608
- * @param string $extension_name returned by FW_Extension::get_name()
609
- * @return FW_Extension|null
610
- */
611
- public function get($extension_name)
612
- {
613
- if (isset(self::$active_extensions[$extension_name])) {
614
- return self::$active_extensions[$extension_name];
615
- } else {
616
- return null;
617
- }
618
- }
619
-
620
- /**
621
- * Get all active extensions
622
- * @return FW_Extension[]
623
- */
624
- public function get_all()
625
- {
626
- return self::$active_extensions;
627
- }
628
-
629
- /**
630
- * Get extensions tree (how they are arranged in directories)
631
- * @return array
632
- */
633
- public function get_tree()
634
- {
635
- return self::$active_extensions_tree;
636
- }
637
-
638
- /**
639
- * @return false
640
- * @deprecated Use $extension->locate_path()
641
- */
642
- public function locate_path()
643
- {
644
- return false;
645
- }
646
-
647
- /**
648
- * @return false
649
- * @deprecated Use $extension->locate_URI()
650
- */
651
- public function locate_path_URI()
652
- {
653
- return false;
654
- }
655
- }
 
 
 
 
 
 
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Extensions component
5
+ */
6
+ final class _FW_Component_Extensions
7
+ {
8
+ /**
9
+ * All existing extensions
10
+ * @var FW_Extension[] { 'extension_name' => instance }
11
+ */
12
+ private static $all_extensions = array();
13
+
14
+ /**
15
+ * All existing extensions names arranged in hierarchical tree like they are in directories
16
+ * @var array
17
+ */
18
+ private static $all_extensions_tree = array();
19
+
20
+ /**
21
+ * Active extensions
22
+ *
23
+ * On every extension activation, it will be pushed at the end of this array.
24
+ * The extensions order is important when including files.
25
+ * If extension A requires extension B, extension B is activated before extension A,
26
+ * and all files of the extension B (hooks.php, static.php, etc.) must be included before extension A
27
+ * For e.g. extension A may have in static.php:
28
+ * wp_enqueue_script( 'ext-A-script', 'script.js', array( 'ext-B-script' ) );
29
+ * so 'ext-B-script' must be registered before 'ext-A-script'
30
+ *
31
+ * @var FW_Extension[] { 'extension_name' => instance }
32
+ */
33
+ private static $active_extensions = array();
34
+
35
+ /**
36
+ * Active extensions names arranged in hierarchical tree like they are in directories
37
+ * @var array
38
+ */
39
+ private static $active_extensions_tree = array();
40
+
41
+ /**
42
+ * @var array { 'extension_name' => array('required_by', 'required_by') }
43
+ */
44
+ private static $extensions_required_by_extensions = array();
45
+
46
+ /**
47
+ * @var array { 'extension_name' => &array() }
48
+ */
49
+ private static $extension_to_all_tree = array();
50
+
51
+ /**
52
+ * @var array { 'extension_name' => &array() }
53
+ */
54
+ private static $extension_to_active_tree = array();
55
+
56
+ /**
57
+ * @var FW_Access_Key
58
+ */
59
+ private static $access_key;
60
+
61
+ /**
62
+ * @var null|_FW_Extensions_Manager
63
+ */
64
+ public $manager;
65
+
66
+ /**
67
+ * Option name that stores the active extensions array
68
+ * @internal
69
+ */
70
+ public function _get_active_extensions_db_option_name()
71
+ {
72
+ return 'fw_active_extensions';
73
+ }
74
+
75
+ /**
76
+ * @param null|string $extension_name Check if an extension is set as active in database
77
+ * @internal
78
+ * @return array|bool
79
+ */
80
+ public function _get_db_active_extensions($extension_name = null)
81
+ {
82
+ $extensions = get_option($this->_get_active_extensions_db_option_name(), array());
83
+
84
+ if ($extension_name) {
85
+ return isset($extensions[$extension_name]);
86
+ } else {
87
+ return $extensions;
88
+ }
89
+ }
90
+
91
+ public function __construct()
92
+ {
93
+ require dirname(__FILE__) .'/extensions/class-fw-extension-default.php';
94
+
95
+ if (
96
+ /**
97
+ * Do not load in frontend because it has no functionality that can be useful in frontend
98
+ */
99
+ is_admin()
100
+ ||
101
+ /**
102
+ * While in cron request (on auto-update), is_admin() is false
103
+ * but we need the actions that moves extensions to a tmp dir then back, on plugin update
104
+ * todo: maybe move those actions here in this class?
105
+ */
106
+ defined( 'DOING_CRON' )
107
+ ) {
108
+ require dirname(__FILE__) .'/extensions/manager/class--fw-extensions-manager.php';
109
+
110
+ $this->manager = new _FW_Extensions_Manager();
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Load extension from directory
116
+ *
117
+ * @param array $data
118
+ */
119
+ private static function load_extensions($data)
120
+ {
121
+ if (false) {
122
+ $data = array(
123
+ 'rel_path' => '/extension',
124
+ 'path' => '/path/to/extension',
125
+ 'uri' => 'https://uri.to/extension',
126
+ 'customizations_locations' => array(
127
+ '/path/to/parent/theme/customizations/extensions/ext/rel/path' => 'https://uri.to/customization/path',
128
+ '/path/to/child/theme/customizations/extensions/ext/rel/path' => 'https://uri.to/customization/path',
129
+ ),
130
+
131
+ 'all_extensions_tree' => array(),
132
+ 'all_extensions' => array(),
133
+ 'current_depth' => 1,
134
+ 'parent' => '&$parent_extension_instance',
135
+ );
136
+ }
137
+
138
+ /**
139
+ * Do not check all keys
140
+ * if one not set, then sure others are not set (this is a private method)
141
+ */
142
+ if (!isset($data['all_extensions_tree'])) {
143
+ $data['all_extensions_tree'] = &self::$all_extensions_tree;
144
+ $data['all_extensions'] = &self::$all_extensions;
145
+ $data['current_depth'] = 1;
146
+ $data['rel_path'] = '';
147
+ $data['parent'] = null;
148
+ }
149
+
150
+ $dirs = glob($data['path'] .'/*', GLOB_ONLYDIR);
151
+
152
+ if (empty($dirs)) {
153
+ return;
154
+ }
155
+
156
+ if ($data['current_depth'] > 1) {
157
+ $customizations_locations = array();
158
+
159
+ foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
160
+ $customizations_locations[ $customization_path .'/extensions' ] = $customization_uri .'/extensions';
161
+ }
162
+
163
+ $data['customizations_locations'] = $customizations_locations;
164
+ }
165
+
166
+ foreach ($dirs as $extension_dir) {
167
+ $extension_name = basename($extension_dir);
168
+
169
+ {
170
+ $customizations_locations = array();
171
+
172
+ foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
173
+ $customizations_locations[ $customization_path .'/'. $extension_name ] = $customization_uri .'/'. $extension_name;
174
+ }
175
+ }
176
+
177
+ if (isset($data['all_extensions'][$extension_name])) {
178
+ if ($data['all_extensions'][$extension_name]->get_parent() !== $data['parent']) {
179
+ // extension with the same name exists in another tree
180
+ trigger_error(
181
+ 'Extension "'. $extension_name .'" is already defined '.
182
+ 'in "'. $data['all_extensions'][$extension_name]->get_declared_path() .'" '.
183
+ 'found again in "'. $extension_dir .'"',
184
+ E_USER_ERROR
185
+ );
186
+ }
187
+
188
+ // this is a directory with customizations for an extension
189
+
190
+ self::load_extensions(array(
191
+ 'rel_path' => $data['rel_path'] .'/'. $extension_name .'/extensions',
192
+ 'path' => $data['path'] .'/'. $extension_name .'/extensions',
193
+ 'uri' => $data['uri'] .'/'. $extension_name .'/extensions',
194
+ 'customizations_locations' => $customizations_locations,
195
+
196
+ 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
197
+ 'all_extensions' => &$data['all_extensions'],
198
+ 'current_depth' => $data['current_depth'] + 1,
199
+ 'parent' => &$data['all_extensions'][$extension_name],
200
+ ));
201
+ } else {
202
+ $class_file_name = 'class-fw-extension-'. $extension_name .'.php';
203
+
204
+ if (file_exists($extension_dir .'/manifest.php')) {
205
+ $data['all_extensions_tree'][$extension_name] = array();
206
+
207
+ self::$extension_to_all_tree[$extension_name] = &$data['all_extensions_tree'][$extension_name];
208
+
209
+ if (fw_include_file_isolated($extension_dir .'/'. $class_file_name)) {
210
+ $class_name = 'FW_Extension_'. fw_dirname_to_classname($extension_name);
211
+ } else {
212
+ $parent_class_name = get_class($data['parent']);
213
+ // check if parent extension has been defined custom Default class for its child extensions
214
+ if (class_exists($parent_class_name .'_Default')) {
215
+ $class_name = $parent_class_name .'_Default';
216
+ } else {
217
+ $class_name = 'FW_Extension_Default';
218
+ }
219
+ }
220
+
221
+ if (!is_subclass_of($class_name, 'FW_Extension')) {
222
+ trigger_error('Extension "'. $extension_name .'" must extend FW_Extension class', E_USER_ERROR);
223
+ }
224
+
225
+ $data['all_extensions'][$extension_name] = new $class_name(array(
226
+ 'rel_path' => $data['rel_path'] .'/'. $extension_name,
227
+ 'path' => $data['path'] .'/'. $extension_name,
228
+ 'uri' => $data['uri'] .'/'. $extension_name,
229
+ 'parent' => $data['parent'],
230
+ 'depth' => $data['current_depth'],
231
+ 'customizations_locations' => $customizations_locations,
232
+ ));
233
+ } else {
234
+ /**
235
+ * The manifest file does not exist, do not load this extension.
236
+ * Maybe it's a directory with configurations for a not existing extension.
237
+ */
238
+ continue;
239
+ }
240
+
241
+ self::load_extensions(array(
242
+ 'rel_path' => $data['all_extensions'][$extension_name]->get_rel_path() .'/extensions',
243
+ 'path' => $data['all_extensions'][$extension_name]->get_path() .'/extensions',
244
+ 'uri' => $data['all_extensions'][$extension_name]->get_uri() .'/extensions',
245
+ 'customizations_locations' => $customizations_locations,
246
+
247
+ 'parent' => &$data['all_extensions'][$extension_name],
248
+ 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
249
+ 'all_extensions' => &$data['all_extensions'],
250
+ 'current_depth' => $data['current_depth'] + 1,
251
+ ));
252
+ }
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Include file from all extension's locations: framework, parent, child
258
+ * @param string|FW_Extension $extension
259
+ * @param string $file_rel_path
260
+ * @param bool $themeFirst
261
+ * false - [framework, parent, child]
262
+ * true - [child, parent, framework]
263
+ * @param bool $onlyFirstFound
264
+ */
265
+ private static function include_extension_file_all_locations($extension, $file_rel_path, $themeFirst = false, $onlyFirstFound = false)
266
+ {
267
+ if (is_string($extension)) {
268
+ $extension = fw()->extensions->get($extension);
269
+ }
270
+
271
+ $paths = $extension->get_customizations_locations();
272
+ $paths[$extension->get_path()] = $extension->get_uri();
273
+
274
+ if (!$themeFirst) {
275
+ $paths = array_reverse($paths);
276
+ }
277
+
278
+ foreach ($paths as $path => $uri) {
279
+ if (fw_include_file_isolated($path . $file_rel_path)) {
280
+ if ($onlyFirstFound) {
281
+ return;
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Include all files from directory, from all extension's locations: framework, child, parent
289
+ * @param string|FW_Extension $extension
290
+ * @param string $dir_rel_path
291
+ * @param bool $themeFirst
292
+ * false - [framework, parent, child]
293
+ * true - [child, parent, framework]
294
+ */
295
+ private static function include_extension_directory_all_locations($extension, $dir_rel_path, $themeFirst = false)
296
+ {
297
+ if (is_string($extension)) {
298
+ $extension = fw()->extensions->get($extension);
299
+ }
300
+
301
+ $paths = $extension->get_customizations_locations();
302
+ $paths[$extension->get_path()] = $extension->get_uri();
303
+
304
+ if (!$themeFirst) {
305
+ $paths = array_reverse($paths);
306
+ }
307
+
308
+ foreach ($paths as $path => $uri) {
309
+ if ($files = glob($path . $dir_rel_path .'/*.php')) {
310
+ foreach ($files as $dir_file_path) {
311
+ fw_include_file_isolated($dir_file_path);
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ public function get_locations()
318
+ {
319
+ $cache_key = 'fw_extensions_locations';
320
+
321
+ try {
322
+ return FW_Cache::get($cache_key);
323
+ } catch (FW_Cache_Not_Found_Exception $e) {
324
+ /**
325
+ * { '/hello/world/extensions' => 'https://hello.com/world/extensions' }
326
+ */
327
+ $custom_locations = apply_filters('fw_extensions_locations', array());
328
+
329
+ {
330
+ $customizations_locations = array();
331
+
332
+ if (is_child_theme()) {
333
+ $customizations_locations[fw_get_stylesheet_customizations_directory('/extensions')]
334
+ = fw_get_stylesheet_customizations_directory_uri('/extensions');
335
+ }
336
+
337
+ $customizations_locations[fw_get_template_customizations_directory('/extensions')]
338
+ = fw_get_template_customizations_directory_uri('/extensions');
339
+
340
+ $customizations_locations += $custom_locations;
341
+ }
342
+
343
+ $locations = array();
344
+
345
+ $locations[ fw_get_framework_directory('/extensions') ] = array(
346
+ 'path' => fw_get_framework_directory('/extensions'),
347
+ 'uri' => fw_get_framework_directory_uri('/extensions'),
348
+ 'customizations_locations' => $customizations_locations,
349
+ 'is' => array(
350
+ 'framework' => true,
351
+ 'custom' => false,
352
+ 'theme' => false,
353
+ ),
354
+ );
355
+
356
+ foreach ($custom_locations as $path => $uri) {
357
+ unset($customizations_locations[$path]);
358
+ $locations[ $path ] = array(
359
+ 'path' => $path,
360
+ 'uri' => $uri,
361
+ 'customizations_locations' => $customizations_locations,
362
+ 'is' => array(
363
+ 'framework' => false,
364
+ 'custom' => true,
365
+ 'theme' => false,
366
+ ),
367
+ );
368
+ }
369
+
370
+ array_pop($customizations_locations);
371
+ $locations[ fw_get_template_customizations_directory('/extensions') ] = array(
372
+ 'path' => fw_get_template_customizations_directory('/extensions'),
373
+ 'uri' => fw_get_template_customizations_directory_uri('/extensions'),
374
+ 'customizations_locations' => $customizations_locations,
375
+ 'is' => array(
376
+ 'framework' => false,
377
+ 'custom' => false,
378
+ 'theme' => true,
379
+ ),
380
+ );
381
+
382
+ if (is_child_theme()) {
383
+ array_pop($customizations_locations);
384
+ $locations[ fw_get_stylesheet_customizations_directory('/extensions') ] = array(
385
+ 'path' => fw_get_stylesheet_customizations_directory('/extensions'),
386
+ 'uri' => fw_get_stylesheet_customizations_directory_uri('/extensions'),
387
+ 'customizations_locations' => $customizations_locations,
388
+ 'is' => array(
389
+ 'framework' => false,
390
+ 'custom' => false,
391
+ 'theme' => true,
392
+ ),
393
+ );
394
+ }
395
+
396
+ FW_Cache::set($cache_key, $locations);
397
+
398
+ return $locations;
399
+ }
400
+ }
401
+
402
+ private function load_all_extensions()
403
+ {
404
+ foreach ($this->get_locations() as $location) {
405
+ self::load_extensions(array(
406
+ 'path' => $location['path'],
407
+ 'uri' => $location['uri'],
408
+ 'customizations_locations' => $location['customizations_locations'],
409
+ ));
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Activate extensions from given tree point
415
+ *
416
+ * @param null|string $parent_extension_name
417
+ */
418
+ private function activate_extensions($parent_extension_name = null)
419
+ {
420
+ if ($parent_extension_name === null) {
421
+ $all_tree =& self::$all_extensions_tree;
422
+ } else {
423
+ $all_tree =& self::$extension_to_all_tree[$parent_extension_name];
424
+ }
425
+
426
+ foreach ($all_tree as $extension_name => &$sub_extensions) {
427
+ if (fw()->extensions->get($extension_name)) {
428
+ // extension already active
429
+ continue;
430
+ }
431
+
432
+ $extension =& self::$all_extensions[$extension_name];
433
+
434
+ if ($extension->manifest->check_requirements()) {
435
+ if (!$this->_get_db_active_extensions($extension_name)) {
436
+ // extension is not set as active
437
+ } elseif (
438
+ $extension->get_parent()
439
+ &&
440
+ !$extension->get_parent()->_child_extension_is_valid($extension)
441
+ ) {
442
+ // extension does not pass parent extension rules
443
+ if (is_admin()) {
444
+ // show warning only in admin side
445
+ FW_Flash_Messages::add(
446
+ 'fw-invalid-extension',
447
+ sprintf(__('Extension %s is invalid.', 'fw'), $extension->get_name()),
448
+ 'warning'
449
+ );
450
+ }
451
+ } else {
452
+ // all requirements met, activate extension
453
+ $this->activate_extension($extension_name);
454
+ }
455
+ } else {
456
+ // requirements not met, tell required extensions that this extension is waiting for them
457
+
458
+ foreach ($extension->manifest->get_required_extensions() as $required_extension_name => $requirements) {
459
+ if (!isset(self::$extensions_required_by_extensions[$required_extension_name])) {
460
+ self::$extensions_required_by_extensions[$required_extension_name] = array();
461
+ }
462
+
463
+ self::$extensions_required_by_extensions[$required_extension_name][] = $extension_name;
464
+ }
465
+ }
466
+ }
467
+ unset($sub_extensions);
468
+ }
469
+
470
+ /**
471
+ * @param string $extension_name
472
+ * @return bool
473
+ */
474
+ private function activate_extension($extension_name)
475
+ {
476
+ if (fw()->extensions->get($extension_name)) {
477
+ // already active
478
+ return false;
479
+ }
480
+
481
+ if (!self::$all_extensions[$extension_name]->manifest->requirements_met()) {
482
+ trigger_error('Wrong '. __METHOD__ .' call', E_USER_WARNING);
483
+ return false;
484
+ }
485
+
486
+ // add to active extensions so inside includes/ and extension it will be accessible from fw()->extensions->get(...)
487
+ self::$active_extensions[$extension_name] =& self::$all_extensions[$extension_name];
488
+
489
+ $parent = self::$all_extensions[$extension_name]->get_parent();
490
+
491
+ if ($parent) {
492
+ self::$extension_to_active_tree[ $parent->get_name() ][$extension_name] = array();
493
+ self::$extension_to_active_tree[$extension_name] =& self::$extension_to_active_tree[ $parent->get_name() ][$extension_name];
494
+ } else {
495
+ self::$active_extensions_tree[$extension_name] = array();
496
+ self::$extension_to_active_tree[$extension_name] =& self::$active_extensions_tree[$extension_name];
497
+ }
498
+
499
+ self::include_extension_directory_all_locations($extension_name, '/includes');
500
+ self::include_extension_file_all_locations($extension_name, '/helpers.php');
501
+ self::include_extension_file_all_locations($extension_name, '/hooks.php');
502
+
503
+ if (self::$all_extensions[$extension_name]->_call_init(self::$access_key) !== false) {
504
+ $this->activate_extensions($extension_name);
505
+ }
506
+
507
+ // check if other extensions are waiting for this extension and try to activate them
508
+ if (isset(self::$extensions_required_by_extensions[$extension_name])) {
509
+ foreach (self::$extensions_required_by_extensions[$extension_name] as $waiting_extension_name) {
510
+ if (self::$all_extensions[$waiting_extension_name]->manifest->check_requirements()) {
511
+ $waiting_extension = self::$all_extensions[$waiting_extension_name];
512
+
513
+ if (!$this->_get_db_active_extensions($waiting_extension_name)) {
514
+ // extension is set as active
515
+ } elseif (
516
+ $waiting_extension->get_parent()
517
+ &&
518
+ !$waiting_extension->get_parent()->_child_extension_is_valid($waiting_extension)
519
+ ) {
520
+ // extension does not pass parent extension rules
521
+ if (is_admin()) {
522
+ // show warning only in admin side
523
+ FW_Flash_Messages::add(
524
+ 'fw-invalid-extension',
525
+ sprintf(__('Extension %s is invalid.', 'fw'), $waiting_extension_name),
526
+ 'warning'
527
+ );
528
+ }
529
+ } else {
530
+ $this->activate_extension($waiting_extension_name);
531
+ }
532
+ }
533
+ }
534
+
535
+ unset(self::$extensions_required_by_extensions[$extension_name]);
536
+ }
537
+
538
+ return true;
539
+ }
540
+
541
+ private function add_actions()
542
+ {
543
+ add_action('init', array($this, '_action_init'));
544
+ add_action('wp_enqueue_scripts', array($this, '_action_enqueue_scripts'));
545
+ add_action('admin_enqueue_scripts', array($this, '_action_enqueue_scripts'));
546
+ }
547
+
548
+ /**
549
+ * Give extensions possibility to access their active_tree
550
+ * @internal
551
+ *
552
+ * @param FW_Access_Key $access_key
553
+ * @param $extension_name
554
+ *
555
+ * @return array
556
+ */
557
+ public function _get_extension_tree(FW_Access_Key $access_key, $extension_name)
558
+ {
559
+ if ($access_key->get_key() !== 'extension') {
560
+ trigger_error('Call denied', E_USER_ERROR);
561
+ }
562
+
563
+ return self::$extension_to_active_tree[$extension_name];
564
+ }
565
+
566
+ /**
567
+ * @internal
568
+ */
569
+ public function _init()
570
+ {
571
+ self::$access_key = new FW_Access_Key('fw_extensions');
572
+
573
+ /**
574
+ * Extensions are about to activate.
575
+ * You can add subclasses to FW_Extension at this point.
576
+ */
577
+ do_action('fw_extensions_before_init');
578
+
579
+ $this->load_all_extensions();
580
+ $this->add_actions();
581
+ }
582
+
583
+ /**
584
+ * @internal
585
+ */
586
+ public function _after_components_init()
587
+ {
588
+ $this->activate_extensions();
589
+
590
+ /**
591
+ * Extensions are activated
592
+ * Now $this->get_children() inside extensions is available
593
+ */
594
+ do_action('fw_extensions_init');
595
+ }
596
+
597
+ public function _action_init()
598
+ {
599
+ foreach (self::$active_extensions as &$extension) {
600
+ /** register posts and taxonomies */
601
+ self::include_extension_file_all_locations($extension, '/posts.php');
602
+ }
603
+ }
604
+
605
+ public function _action_enqueue_scripts()
606
+ {
607
+ foreach (self::$active_extensions as &$extension) {
608
+ /** js and css */
609
+ self::include_extension_file_all_locations($extension, '/static.php', true, true);
610
+ }
611
+ }
612
+
613
+ /**
614
+ * @param string $extension_name returned by FW_Extension::get_name()
615
+ * @return FW_Extension|null
616
+ */
617
+ public function get($extension_name)
618
+ {
619
+ if (isset(self::$active_extensions[$extension_name])) {
620
+ return self::$active_extensions[$extension_name];
621
+ } else {
622
+ return null;
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Get all active extensions
628
+ * @return FW_Extension[]
629
+ */
630
+ public function get_all()
631
+ {
632
+ return self::$active_extensions;
633
+ }
634
+
635
+ /**
636
+ * Get extensions tree (how they are arranged in directories)
637
+ * @return array
638
+ */
639
+ public function get_tree()
640
+ {
641
+ return self::$active_extensions_tree;
642
+ }
643
+
644
+ /**
645
+ * @return false
646
+ * @deprecated Use $extension->locate_path()
647
+ */
648
+ public function locate_path()
649
+ {
650
+ return false;
651
+ }
652
+
653
+ /**
654
+ * @return false
655
+ * @deprecated Use $extension->locate_URI()
656
+ */
657
+ public function locate_path_URI()
658
+ {
659
+ return false;
660
+ }
661
+ }
framework/core/components/extensions/class-fw-extension-default.php CHANGED
@@ -1,11 +1,11 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Instances of this class will be created for extensions without class
5
- */
6
- class FW_Extension_Default extends FW_Extension
7
- {
8
- protected function _init()
9
- {
10
- }
11
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Instances of this class will be created for extensions without class
5
+ */
6
+ class FW_Extension_Default extends FW_Extension
7
+ {
8
+ protected function _init()
9
+ {
10
+ }
11
+ }
framework/core/components/extensions/manager/available-extensions.php CHANGED
@@ -1,273 +1,273 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- $thumbnails_uri = fw_get_framework_directory_uri( '/core/components/extensions/manager/static/img/thumbnails' );
6
- $github_account = 'ThemeFuse';
7
-
8
- $extensions = array(
9
- 'slider' => array(
10
- 'display' => true,
11
- 'parent' => 'media',
12
- 'name' => __( 'Sliders', 'fw' ),
13
- 'description' => __( 'Adds a sliders module to your website from where you\'ll be able to create different built in jQuery sliders for your homepage and rest of the pages.', 'fw' ),
14
- 'thumbnail' => $thumbnails_uri . '/sliders.jpg',
15
- 'download' => array(
16
- 'github' => array(
17
- 'user_repo' => $github_account . '/Unyson-Sliders-Extension',
18
- ),
19
- ),
20
- ),
21
- 'media' => array(
22
- 'display' => false,
23
- 'parent' => null,
24
- 'name' => __( 'Media', 'fw' ),
25
- 'description' => '',
26
- 'thumbnail' => 'about:blank',
27
- 'download' => array(
28
- 'github' => array(
29
- 'user_repo' => $github_account . '/Unyson-Empty-Extension',
30
- ),
31
- ),
32
- ),
33
- 'population-method' => array(
34
- 'display' => false,
35
- 'parent' => 'media',
36
- 'name' => __( 'Population method', 'fw' ),
37
- 'description' => '',
38
- 'thumbnail' => 'about:blank',
39
- 'download' => array(
40
- 'github' => array(
41
- 'user_repo' => $github_account . '/Unyson-PopulationMethods-Extension',
42
- ),
43
- ),
44
- ),
45
- 'styling' => array(
46
- 'display' => true,
47
- 'parent' => null,
48
- 'name' => __( 'Styling', 'fw' ),
49
- 'description' => __( 'This extension lets you control the website visual style. Starting from predefined styles to changing specific fonts and colors across the website.', 'fw' ),
50
- 'thumbnail' => $thumbnails_uri . '/styling.jpg',
51
- 'download' => array(
52
- 'github' => array(
53
- 'user_repo' => $github_account . '/Unyson-Styling-Extension',
54
- ),
55
- ),
56
- ),
57
- 'megamenu' => array(
58
- 'display' => true,
59
- 'parent' => null,
60
- 'name' => __( 'Mega Menu', 'fw' ),
61
- 'description' => __( 'The Mega Menu extension adds a user-friendly drop down menu that will let you easily create highly customized menu configurations.', 'fw' ),
62
- 'thumbnail' => $thumbnails_uri . '/mega-menu.jpg',
63
- 'download' => array(
64
- 'github' => array(
65
- 'user_repo' => $github_account . '/Unyson-MegaMenu-Extension',
66
- ),
67
- ),
68
- ),
69
- 'portfolio' => array(
70
- 'display' => true,
71
- 'parent' => null,
72
- 'name' => __( 'Portfolio', 'fw' ),
73
- 'description' => __( 'This extension will add a fully fledged portfolio module that will let you display your projects using the built in portfolio pages.', 'fw' ),
74
- 'thumbnail' => $thumbnails_uri . '/portfolio.jpg',
75
- 'download' => array(
76
- 'github' => array(
77
- 'user_repo' => $github_account . '/Unyson-Portfolio-Extension',
78
- ),
79
- ),
80
- ),
81
- 'page-builder' => array(
82
- 'display' => true,
83
- 'parent' => 'shortcodes',
84
- 'name' => __( 'Page Builder', 'fw' ),
85
- 'description' => __( "Let's you easily build countless pages with the help of the drag and drop visual page builder that comes with a lot of already created shortcodes.", 'fw' ),
86
- 'thumbnail' => $thumbnails_uri . '/page-builder.jpg',
87
- 'download' => array(
88
- 'github' => array(
89
- 'user_repo' => $github_account . '/Unyson-PageBuilder-Extension',
90
- ),
91
- ),
92
- ),
93
- 'shortcodes' => array(
94
- 'display' => false,
95
- 'parent' => null,
96
- 'name' => __( 'Shortcodes', 'fw' ),
97
- 'description' => '',
98
- 'thumbnail' => 'about:blank',
99
- 'download' => array(
100
- 'github' => array(
101
- 'user_repo' => $github_account . '/Unyson-Shortcodes-Extension',
102
- ),
103
- ),
104
- ),
105
- 'breadcrumbs' => array(
106
- 'display' => true,
107
- 'parent' => null,
108
- 'name' => __( 'Breadcrumbs', 'fw' ),
109
- 'description' => __( 'Creates a simplified navigation menu for the pages that can be placed anywhere in the theme. This will make navigating the website much easier.', 'fw' ),
110
- 'thumbnail' => $thumbnails_uri . '/breadcrumbs.jpg',
111
- 'download' => array(
112
- 'github' => array(
113
- 'user_repo' => $github_account . '/Unyson-Breadcrumbs-Extension',
114
- ),
115
- ),
116
- ),
117
- 'seo' => array(
118
- 'display' => true,
119
- 'parent' => null,
120
- 'name' => __( 'SEO', 'fw' ),
121
- 'description' => __( 'This extension will enable you to have a fully optimized WordPress website by adding optimized meta titles, keywords and descriptions.', 'fw' ),
122
- 'thumbnail' => $thumbnails_uri . '/seo.jpg',
123
- 'download' => array(
124
- 'github' => array(
125
- 'user_repo' => $github_account . '/Unyson-SEO-Extension',
126
- ),
127
- ),
128
- ),
129
- 'sidebars' => array(
130
- 'display' => true,
131
- 'parent' => null,
132
- 'name' => __( 'Sidebars', 'fw' ),
133
- 'description' => __( 'Brings a new layer of customization freedom to your website by letting you add more than one sidebar to a page, or different sidebars on different pages.', 'fw' ),
134
- 'thumbnail' => $thumbnails_uri . '/sidebars.jpg',
135
- 'download' => array(
136
- 'github' => array(
137
- 'user_repo' => $github_account . '/Unyson-Sidebars-Extension',
138
- ),
139
- ),
140
- ),
141
- 'feedback' => array(
142
- 'display' => true,
143
- 'parent' => null,
144
- 'name' => __( 'Feedback', 'fw' ),
145
- 'description' => __( 'Adds the possibility to leave feedback (comments, reviews and rating) about your products, articles, etc. This replaces the default comments system.', 'fw' ),
146
- 'thumbnail' => $thumbnails_uri . '/feedback.jpg',
147
- 'download' => array(
148
- 'github' => array(
149
- 'user_repo' => $github_account . '/Unyson-Feedback-Extension',
150
- ),
151
- ),
152
- ),
153
- 'backup' => array(
154
- 'display' => true,
155
- 'parent' => null,
156
- 'name' => __( 'Backup', 'fw' ),
157
- 'description' => __( 'This extension lets you set up daily, weekly or monthly backup schedule. You can choose between a full backup or a data base only backup.', 'fw' ),
158
- 'thumbnail' => $thumbnails_uri . '/backup.jpg',
159
- 'download' => array(
160
- 'github' => array(
161
- 'user_repo' => $github_account . '/Unyson-Backup-Extension',
162
- ),
163
- ),
164
- ),
165
- 'backups' => array(
166
- 'display' => true,
167
- 'parent' => null,
168
- 'name' => __( 'Backup & Demo Content', 'fw' ),
169
- 'description' => __( 'This extension lets you create an automated backup schedule, import demo content or even create a demo content archive for migration purposes.', 'fw' ),
170
- 'thumbnail' => $thumbnails_uri . '/backups.jpg',
171
- 'download' => array(
172
- 'github' => array(
173
- 'user_repo' => $github_account . '/Unyson-Backups-Extension',
174
- ),
175
- ),
176
- ),
177
- 'events' => array(
178
- 'display' => true,
179
- 'parent' => null,
180
- 'name' => __( 'Events', 'fw' ),
181
- 'description' => __( 'This extension adds a fully fledged Events module to your theme. It comes with built in pages that contain a calendar where events can be added.', 'fw' ),
182
- 'thumbnail' => $thumbnails_uri . '/events.jpg',
183
- 'download' => array(
184
- 'github' => array(
185
- 'user_repo' => $github_account . '/Unyson-Events-Extension',
186
- ),
187
- ),
188
- ),
189
- 'analytics' => array(
190
- 'display' => true,
191
- 'parent' => null,
192
- 'name' => __( 'Analytics', 'fw' ),
193
- 'description' => __( 'Enables the possibility to add the Google Analytics tracking code that will let you get all the analytics about visitors, page views and more.', 'fw' ),
194
- 'thumbnail' => $thumbnails_uri . '/analytics.jpg',
195
- 'download' => array(
196
- 'github' => array(
197
- 'user_repo' => $github_account . '/Unyson-Analytics-Extension',
198
- ),
199
- ),
200
- ),
201
- 'builder' => array(
202
- 'display' => false,
203
- 'parent' => null,
204
- 'name' => __( 'Builder', 'fw' ),
205
- 'description' => '',
206
- 'thumbnail' => 'about:blank',
207
- 'download' => array(
208
- 'github' => array(
209
- 'user_repo' => $github_account . '/Unyson-Builder-Extension',
210
- ),
211
- ),
212
- ),
213
- 'learning' => array(
214
- 'display' => true,
215
- 'parent' => null,
216
- 'name' => __( 'Learning', 'fw' ),
217
- 'description' => __( 'This extension adds a Learning module to your theme. Using this extension you can add courses, lessons and tests for your users to take.', 'fw' ),
218
- 'thumbnail' => $thumbnails_uri . '/learning.jpg',
219
- 'download' => array(
220
- 'github' => array(
221
- 'user_repo' => $github_account . '/Unyson-Learning-Extension',
222
- ),
223
- ),
224
- ),
225
- 'forms' => array(
226
- 'display' => false,
227
- 'parent' => null,
228
- 'name' => __( 'Forms', 'fw' ),
229
- 'description' => __( 'This extension adds the possibility to create a contact form. Use the drag & drop form builder to create any contact form you\'ll ever want or need.', 'fw' ),
230
- 'thumbnail' => $thumbnails_uri . '/forms.jpg',
231
- 'download' => array(
232
- 'github' => array(
233
- 'user_repo' => $github_account . '/Unyson-Forms-Extension',
234
- ),
235
- ),
236
- ),
237
- 'mailer' => array(
238
- 'display' => false,
239
- 'parent' => null,
240
- 'name' => __( 'Mailer', 'fw' ),
241
- 'description' => __( 'This extension will let you set some global email options and it is used by other extensions (like Forms) to send emails.', 'fw' ),
242
- 'thumbnail' => $thumbnails_uri . '/mailer.jpg',
243
- 'download' => array(
244
- 'github' => array(
245
- 'user_repo' => $github_account . '/Unyson-Mailer-Extension',
246
- ),
247
- ),
248
- ),
249
- 'social' => array(
250
- 'display' => true,
251
- 'parent' => null,
252
- 'name' => __( 'Social', 'fw' ),
253
- 'description' => __( 'Use this extension to configure all your social related APIs. Other extensions will use the Social extension to connect to your social accounts.', 'fw' ),
254
- 'thumbnail' => $thumbnails_uri . '/social.jpg',
255
- 'download' => array(
256
- 'github' => array(
257
- 'user_repo' => $github_account . '/Unyson-Social-Extension',
258
- ),
259
- ),
260
- ),
261
- 'translation' => array(
262
- 'display' => true,
263
- 'parent' => null,
264
- 'name' => __( 'Translations', 'fw' ),
265
- 'description' => __( 'This extension lets you translate your website in any language or even add multiple languages for your users to change at their will from the front-end.', 'fw' ),
266
- 'thumbnail' => $thumbnails_uri . '/translation.jpg',
267
- 'download' => array(
268
- 'github' => array(
269
- 'user_repo' => $github_account . '/Unyson-Translation-Extension',
270
- ),
271
- ),
272
- ),
273
- );
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ $thumbnails_uri = fw_get_framework_directory_uri( '/core/components/extensions/manager/static/img/thumbnails' );
6
+ $github_account = 'ThemeFuse';
7
+
8
+ $extensions = array(
9
+ 'slider' => array(
10
+ 'display' => true,
11
+ 'parent' => 'media',
12
+ 'name' => __( 'Sliders', 'fw' ),
13
+ 'description' => __( 'Adds a sliders module to your website from where you\'ll be able to create different built in jQuery sliders for your homepage and rest of the pages.', 'fw' ),
14
+ 'thumbnail' => $thumbnails_uri . '/sliders.jpg',
15
+ 'download' => array(
16
+ 'github' => array(
17
+ 'user_repo' => $github_account . '/Unyson-Sliders-Extension',
18
+ ),
19
+ ),
20
+ ),
21
+ 'media' => array(
22
+ 'display' => false,
23
+ 'parent' => null,
24
+ 'name' => __( 'Media', 'fw' ),
25
+ 'description' => '',
26
+ 'thumbnail' => 'about:blank',
27
+ 'download' => array(
28
+ 'github' => array(
29
+ 'user_repo' => $github_account . '/Unyson-Empty-Extension',
30
+ ),
31
+ ),
32
+ ),
33
+ 'population-method' => array(
34
+ 'display' => false,
35
+ 'parent' => 'media',
36
+ 'name' => __( 'Population method', 'fw' ),
37
+ 'description' => '',
38
+ 'thumbnail' => 'about:blank',
39
+ 'download' => array(
40
+ 'github' => array(
41
+ 'user_repo' => $github_account . '/Unyson-PopulationMethods-Extension',
42
+ ),
43
+ ),
44
+ ),
45
+ 'styling' => array(
46
+ 'display' => true,
47
+ 'parent' => null,
48
+ 'name' => __( 'Styling', 'fw' ),
49
+ 'description' => __( 'This extension lets you control the website visual style. Starting from predefined styles to changing specific fonts and colors across the website.', 'fw' ),
50
+ 'thumbnail' => $thumbnails_uri . '/styling.jpg',
51
+ 'download' => array(
52
+ 'github' => array(
53
+ 'user_repo' => $github_account . '/Unyson-Styling-Extension',
54
+ ),
55
+ ),
56
+ ),
57
+ 'megamenu' => array(
58
+ 'display' => true,
59
+ 'parent' => null,
60
+ 'name' => __( 'Mega Menu', 'fw' ),
61
+ 'description' => __( 'The Mega Menu extension adds a user-friendly drop down menu that will let you easily create highly customized menu configurations.', 'fw' ),
62
+ 'thumbnail' => $thumbnails_uri . '/mega-menu.jpg',
63
+ 'download' => array(
64
+ 'github' => array(
65
+ 'user_repo' => $github_account . '/Unyson-MegaMenu-Extension',
66
+ ),
67
+ ),
68
+ ),
69
+ 'portfolio' => array(
70
+ 'display' => true,
71
+ 'parent' => null,
72
+ 'name' => __( 'Portfolio', 'fw' ),
73
+ 'description' => __( 'This extension will add a fully fledged portfolio module that will let you display your projects using the built in portfolio pages.', 'fw' ),
74
+ 'thumbnail' => $thumbnails_uri . '/portfolio.jpg',
75
+ 'download' => array(
76
+ 'github' => array(
77
+ 'user_repo' => $github_account . '/Unyson-Portfolio-Extension',
78
+ ),
79
+ ),
80
+ ),
81
+ 'page-builder' => array(
82
+ 'display' => true,
83
+ 'parent' => 'shortcodes',
84
+ 'name' => __( 'Page Builder', 'fw' ),
85
+ 'description' => __( "Let's you easily build countless pages with the help of the drag and drop visual page builder that comes with a lot of already created shortcodes.", 'fw' ),
86
+ 'thumbnail' => $thumbnails_uri . '/page-builder.jpg',
87
+ 'download' => array(
88
+ 'github' => array(
89
+ 'user_repo' => $github_account . '/Unyson-PageBuilder-Extension',
90
+ ),
91
+ ),
92
+ ),
93
+ 'shortcodes' => array(
94
+ 'display' => false,
95
+ 'parent' => null,
96
+ 'name' => __( 'Shortcodes', 'fw' ),
97
+ 'description' => '',
98
+ 'thumbnail' => 'about:blank',
99
+ 'download' => array(
100
+ 'github' => array(
101
+ 'user_repo' => $github_account . '/Unyson-Shortcodes-Extension',
102
+ ),
103
+ ),
104
+ ),
105
+ 'breadcrumbs' => array(
106
+ 'display' => true,
107
+ 'parent' => null,
108
+ 'name' => __( 'Breadcrumbs', 'fw' ),
109
+ 'description' => __( 'Creates a simplified navigation menu for the pages that can be placed anywhere in the theme. This will make navigating the website much easier.', 'fw' ),
110
+ 'thumbnail' => $thumbnails_uri . '/breadcrumbs.jpg',
111
+ 'download' => array(
112
+ 'github' => array(
113
+ 'user_repo' => $github_account . '/Unyson-Breadcrumbs-Extension',
114
+ ),
115
+ ),
116
+ ),
117
+ 'seo' => array(
118
+ 'display' => true,
119
+ 'parent' => null,
120
+ 'name' => __( 'SEO', 'fw' ),
121
+ 'description' => __( 'This extension will enable you to have a fully optimized WordPress website by adding optimized meta titles, keywords and descriptions.', 'fw' ),
122
+ 'thumbnail' => $thumbnails_uri . '/seo.jpg',
123
+ 'download' => array(
124
+ 'github' => array(
125
+ 'user_repo' => $github_account . '/Unyson-SEO-Extension',
126
+ ),
127
+ ),
128
+ ),
129
+ 'sidebars' => array(
130
+ 'display' => true,
131
+ 'parent' => null,
132
+ 'name' => __( 'Sidebars', 'fw' ),
133
+ 'description' => __( 'Brings a new layer of customization freedom to your website by letting you add more than one sidebar to a page, or different sidebars on different pages.', 'fw' ),
134
+ 'thumbnail' => $thumbnails_uri . '/sidebars.jpg',
135
+ 'download' => array(
136
+ 'github' => array(
137
+ 'user_repo' => $github_account . '/Unyson-Sidebars-Extension',
138
+ ),
139
+ ),
140
+ ),
141
+ 'feedback' => array(
142
+ 'display' => true,
143
+ 'parent' => null,
144
+ 'name' => __( 'Feedback', 'fw' ),
145
+ 'description' => __( 'Adds the possibility to leave feedback (comments, reviews and rating) about your products, articles, etc. This replaces the default comments system.', 'fw' ),
146
+ 'thumbnail' => $thumbnails_uri . '/feedback.jpg',
147
+ 'download' => array(
148
+ 'github' => array(
149
+ 'user_repo' => $github_account . '/Unyson-Feedback-Extension',
150
+ ),
151
+ ),
152
+ ),
153
+ 'backup' => array(
154
+ 'display' => true,
155
+ 'parent' => null,
156
+ 'name' => __( 'Backup', 'fw' ),
157
+ 'description' => __( 'This extension lets you set up daily, weekly or monthly backup schedule. You can choose between a full backup or a data base only backup.', 'fw' ),
158
+ 'thumbnail' => $thumbnails_uri . '/backup.jpg',
159
+ 'download' => array(
160
+ 'github' => array(
161
+ 'user_repo' => $github_account . '/Unyson-Backup-Extension',
162
+ ),
163
+ ),
164
+ ),
165
+ 'backups' => array(
166
+ 'display' => true,
167
+ 'parent' => null,
168
+ 'name' => __( 'Backup & Demo Content', 'fw' ),
169
+ 'description' => __( 'This extension lets you create an automated backup schedule, import demo content or even create a demo content archive for migration purposes.', 'fw' ),
170
+ 'thumbnail' => $thumbnails_uri . '/backups.jpg',
171
+ 'download' => array(
172
+ 'github' => array(
173
+ 'user_repo' => $github_account . '/Unyson-Backups-Extension',
174
+ ),
175
+ ),
176
+ ),
177
+ 'events' => array(
178
+ 'display' => true,
179
+ 'parent' => null,
180
+ 'name' => __( 'Events', 'fw' ),
181
+ 'description' => __( 'This extension adds a fully fledged Events module to your theme. It comes with built in pages that contain a calendar where events can be added.', 'fw' ),
182
+ 'thumbnail' => $thumbnails_uri . '/events.jpg',
183
+ 'download' => array(
184
+ 'github' => array(
185
+ 'user_repo' => $github_account . '/Unyson-Events-Extension',
186
+ ),
187
+ ),
188
+ ),
189
+ 'analytics' => array(
190
+ 'display' => true,
191
+ 'parent' => null,
192
+ 'name' => __( 'Analytics', 'fw' ),
193
+ 'description' => __( 'Enables the possibility to add the Google Analytics tracking code that will let you get all the analytics about visitors, page views and more.', 'fw' ),
194
+ 'thumbnail' => $thumbnails_uri . '/analytics.jpg',
195
+ 'download' => array(
196
+ 'github' => array(
197
+ 'user_repo' => $github_account . '/Unyson-Analytics-Extension',
198
+ ),
199
+ ),
200
+ ),
201
+ 'builder' => array(
202
+ 'display' => false,
203
+ 'parent' => null,
204
+ 'name' => __( 'Builder', 'fw' ),
205
+ 'description' => '',
206
+ 'thumbnail' => 'about:blank',
207
+ 'download' => array(
208
+ 'github' => array(
209
+ 'user_repo' => $github_account . '/Unyson-Builder-Extension',
210
+ ),
211
+ ),
212
+ ),
213
+ 'learning' => array(
214
+ 'display' => true,
215
+ 'parent' => null,
216
+ 'name' => __( 'Learning', 'fw' ),
217
+ 'description' => __( 'This extension adds a Learning module to your theme. Using this extension you can add courses, lessons and tests for your users to take.', 'fw' ),
218
+ 'thumbnail' => $thumbnails_uri . '/learning.jpg',
219
+ 'download' => array(
220
+ 'github' => array(
221
+ 'user_repo' => $github_account . '/Unyson-Learning-Extension',
222
+ ),
223
+ ),
224
+ ),
225
+ 'forms' => array(
226
+ 'display' => false,
227
+ 'parent' => null,
228
+ 'name' => __( 'Forms', 'fw' ),
229
+ 'description' => __( 'This extension adds the possibility to create a contact form. Use the drag & drop form builder to create any contact form you\'ll ever want or need.', 'fw' ),
230
+ 'thumbnail' => $thumbnails_uri . '/forms.jpg',
231
+ 'download' => array(
232
+ 'github' => array(
233
+ 'user_repo' => $github_account . '/Unyson-Forms-Extension',
234
+ ),
235
+ ),
236
+ ),
237
+ 'mailer' => array(
238
+ 'display' => false,
239
+ 'parent' => null,
240
+ 'name' => __( 'Mailer', 'fw' ),
241
+ 'description' => __( 'This extension will let you set some global email options and it is used by other extensions (like Forms) to send emails.', 'fw' ),
242
+ 'thumbnail' => $thumbnails_uri . '/mailer.jpg',
243
+ 'download' => array(
244
+ 'github' => array(
245
+ 'user_repo' => $github_account . '/Unyson-Mailer-Extension',
246
+ ),
247
+ ),
248
+ ),
249
+ 'social' => array(
250
+ 'display' => true,
251
+ 'parent' => null,
252
+ 'name' => __( 'Social', 'fw' ),
253
+ 'description' => __( 'Use this extension to configure all your social related APIs. Other extensions will use the Social extension to connect to your social accounts.', 'fw' ),
254
+ 'thumbnail' => $thumbnails_uri . '/social.jpg',
255
+ 'download' => array(
256
+ 'github' => array(
257
+ 'user_repo' => $github_account . '/Unyson-Social-Extension',
258
+ ),
259
+ ),
260
+ ),
261
+ 'translation' => array(
262
+ 'display' => true,
263
+ 'parent' => null,
264
+ 'name' => __( 'Translations', 'fw' ),
265
+ 'description' => __( 'This extension lets you translate your website in any language or even add multiple languages for your users to change at their will from the front-end.', 'fw' ),
266
+ 'thumbnail' => $thumbnails_uri . '/translation.jpg',
267
+ 'download' => array(
268
+ 'github' => array(
269
+ 'user_repo' => $github_account . '/Unyson-Translation-Extension',
270
+ ),
271
+ ),
272
+ ),
273
+ );
framework/core/components/extensions/manager/class--fw-extensions-manager.php CHANGED
@@ -1,3201 +1,3219 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Install/Activate/Deactivate/Remove Extensions
5
- * @internal
6
- */
7
- final class _FW_Extensions_Manager
8
- {
9
- /**
10
- * @var FW_Form
11
- */
12
- private $extension_settings_form;
13
-
14
- /**
15
- * @var Parsedown
16
- */
17
- private $markdown_parser;
18
-
19
- private $manifest_default_values = array(
20
- 'display' => false,
21
- 'standalone' => false,
22
- );
23
-
24
- private $download_timeout = 300;
25
-
26
- private $default_thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
27
-
28
- public function __construct()
29
- {
30
- // In any case/permission, make sure to not miss the plugin update actions to prevent extensions delete
31
- {
32
- add_action('fw_plugin_pre_update', array($this, '_action_plugin_pre_update'));
33
- add_action('fw_plugin_post_update', array($this, '_action_plugin_post_update'));
34
- }
35
-
36
- if (!is_admin()) {
37
- return;
38
- }
39
-
40
- if (!$this->can_activate() && !$this->can_install()) {
41
- return;
42
- }
43
-
44
- /** Actions */
45
- {
46
- add_action('fw_init', array($this, '_action_fw_init'));
47
- add_action('admin_menu', array($this, '_action_admin_menu'));
48
- add_action('network_admin_menu', array($this, '_action_admin_menu'));
49
- add_action('admin_footer', array($this, '_action_admin_footer'));
50
- add_action('admin_enqueue_scripts', array($this, '_action_enqueue_scripts'));
51
- add_action('fw_after_plugin_activate', array($this, '_action_after_plugin_activate'), 100);
52
- add_action('after_switch_theme', array($this, '_action_theme_switch'));
53
- add_action('admin_notices', array($this, '_action_admin_notices'));
54
-
55
- if ($this->can_install()) {
56
- add_action('wp_ajax_fw_extensions_check_direct_fs_access', array($this, '_action_ajax_check_direct_fs_access'));
57
- add_action('wp_ajax_fw_extensions_install', array($this, '_action_ajax_install'));
58
- add_action('wp_ajax_fw_extensions_uninstall', array($this, '_action_ajax_uninstall'));
59
- }
60
- }
61
-
62
- /** Filters */
63
- {
64
- add_filter('fw_plugin_action_list', array($this, '_filter_plugin_action_list'));
65
- }
66
- }
67
-
68
- /**
69
- * If current user can:
70
- * - activate extension
71
- * - disable extensions
72
- * - save extension settings options
73
- * @return bool
74
- */
75
- public function can_activate()
76
- {
77
- static $can_activate = null;
78
-
79
- if ($can_activate === null) {
80
- $can_activate = current_user_can('manage_options');
81
-
82
- if ($can_activate) {
83
- // also you can use this method to get the capability
84
- $can_activate = 'manage_options';
85
- }
86
-
87
- if (!$can_activate) {
88
- // make sure if can install, then also can activate. (can install) > (can activate)
89
- $can_activate = $this->can_install();
90
- }
91
- }
92
-
93
- return $can_activate;
94
- }
95
-
96
- /**
97
- * If current user can:
98
- * - install extensions
99
- * - delete extensions
100
- * @return bool
101
- */
102
- public function can_install()
103
- {
104
- static $can_install = null;
105
-
106
- if ($can_install === null) {
107
- $capability = 'install_plugins';
108
-
109
- if (is_multisite()) {
110
- // only network admin can change files that affects the entire network
111
- $can_install = current_user_can_for_blog(get_current_blog_id(), $capability);
112
- } else {
113
- $can_install = current_user_can($capability);
114
- }
115
-
116
- if ($can_install) {
117
- // also you can use this method to get the capability
118
- $can_install = $capability;
119
- }
120
- }
121
-
122
- return $can_install;
123
- }
124
-
125
- public function get_page_slug()
126
- {
127
- return 'fw-extensions';
128
- }
129
-
130
- private function get_cache_key($sub_key)
131
- {
132
- return 'fw_extensions_manager/'. $sub_key;
133
- }
134
-
135
- private function get_uri($append = '')
136
- {
137
- return fw_get_framework_directory_uri('/core/components/extensions/manager'. $append);
138
- }
139
-
140
- private function get_markdown_parser()
141
- {
142
- if (!$this->markdown_parser) {
143
- if (!class_exists('Parsedown')) {
144
- require_once dirname(__FILE__) .'/includes/parsedown/Parsedown.php';
145
- }
146
-
147
- $this->markdown_parser = new Parsedown();
148
- }
149
-
150
- return $this->markdown_parser;
151
- }
152
-
153
- private function get_nonce($form) {
154
- switch ($form) {
155
- case 'install':
156
- return array(
157
- 'name' => '_nonce_fw_extensions_install',
158
- 'action' => 'install',
159
- );
160
- case 'delete':
161
- return array(
162
- 'name' => '_nonce_fw_extensions_delete',
163
- 'action' => 'delete',
164
- );
165
- case 'activate':
166
- return array(
167
- 'name' => '_nonce_fw_extensions_activate',
168
- 'action' => 'activate',
169
- );
170
- case 'deactivate':
171
- return array(
172
- 'name' => '_nonce_fw_extensions_deactivate',
173
- 'action' => 'deactivate',
174
- );
175
- default:
176
- return array(
177
- 'name' => '_nonce_fw_extensions',
178
- 'action' => 'default',
179
- );
180
- }
181
- }
182
-
183
- /**
184
- * Extensions available for download
185
- * @return array {name => data}
186
- */
187
- private function get_available_extensions()
188
- {
189
- try {
190
- $cache_key = $this->get_cache_key( 'available_extensions' );
191
-
192
- return FW_Cache::get($cache_key);
193
- } catch (FW_Cache_Not_Found_Exception $e) {
194
- $vars = fw_get_variables_from_file( dirname( __FILE__ ) . '/available-extensions.php', array(
195
- 'extensions' => array()
196
- ) );
197
-
198
- {
199
- $installed_extensions = $this->get_installed_extensions();
200
- $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
201
-
202
- if (isset($installed_extensions['backup'])) {
203
- // make sure only Backup or Backups can be installed
204
- unset($vars['extensions']['backups']);
205
- }
206
-
207
- foreach (
208
- array('backup', 'styling', 'translation', 'learning')
209
- as $obsolete_extension
210
- ) {
211
- if (
212
- !isset($supported_extensions[$obsolete_extension])
213
- &&
214
- !isset($installed_extensions[$obsolete_extension])
215
- ) {
216
- unset($vars['extensions'][$obsolete_extension]);
217
- }
218
- }
219
- }
220
-
221
- FW_Cache::set($cache_key, $vars['extensions']);
222
-
223
- return $vars['extensions'];
224
- }
225
- }
226
-
227
- /**
228
- * @internal
229
- */
230
- public function _action_ajax_check_direct_fs_access()
231
- {
232
- if (!$this->can_install()) {
233
- // if can't install, no need to know if has access or not
234
- wp_send_json_error();
235
- }
236
-
237
- if (FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
238
- wp_send_json_success();
239
- } else {
240
- wp_send_json_error();
241
- }
242
- }
243
-
244
- /**
245
- * @internal
246
- */
247
- public function _action_ajax_install()
248
- {
249
- if (!$this->can_install()) {
250
- // if can't install, no need to know if has access or not
251
- wp_send_json_error();
252
- }
253
-
254
- if (!FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
255
- wp_send_json_error();
256
- }
257
-
258
- $extension = (string)FW_Request::POST('extension');
259
-
260
- $install_result = $this->install_extensions(array(
261
- $extension => array()
262
- ), array(
263
- 'cancel_on_error' => true
264
- ));
265
-
266
- if ($install_result === true) {
267
- wp_send_json_success();
268
- } else {
269
- wp_send_json_error($install_result);
270
- }
271
- }
272
-
273
- /**
274
- * @internal
275
- */
276
- public function _action_ajax_uninstall()
277
- {
278
- if (!$this->can_install()) {
279
- // if can't install, no need to know if has access or not
280
- wp_send_json_error();
281
- }
282
-
283
- if (!FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
284
- wp_send_json_error();
285
- }
286
-
287
- $extension = (string)FW_Request::POST('extension');
288
-
289
- $install_result = $this->uninstall_extensions(array(
290
- $extension => array()
291
- ), array(
292
- 'cancel_on_error' => true
293
- ));
294
-
295
- if ($install_result === true) {
296
- wp_send_json_success();
297
- } else {
298
- wp_send_json_error($install_result);
299
- }
300
- }
301
-
302
- /**
303
- * @internal
304
- */
305
- public function _action_after_plugin_activate()
306
- {
307
- $this->activate_theme_extensions();
308
- $this->activate_extensions(
309
- array_fill_keys(
310
- array_keys(fw()->theme->manifest->get('supported_extensions', array())),
311
- array()
312
- )
313
- );
314
-
315
- if ($this->can_install()) {
316
- if ($this->get_supported_extensions_for_install()) {
317
- $link = $this->get_link();
318
-
319
- wp_redirect($link . '&sub-page=install&supported');
320
- exit;
321
- }
322
- }
323
- }
324
-
325
- /**
326
- * Copy all extensions to a temp backup directory
327
- * @internal
328
- */
329
- public function _action_plugin_pre_update()
330
- {
331
- /** @var WP_Filesystem_Base $wp_filesystem */
332
- global $wp_filesystem;
333
-
334
- if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
335
- return;
336
- }
337
-
338
- // a directory outside the plugin
339
- $tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
340
- fw_fix_path(WP_CONTENT_DIR) .'/tmp/fw-plugin-update-extensions-backup'
341
- );
342
- $extensions_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
343
- fw_get_framework_directory('/extensions')
344
- );
345
-
346
- $error = false;
347
-
348
- do {
349
- if ($wp_filesystem->exists($tmp_dir)) {
350
- if (!$wp_filesystem->delete($tmp_dir, true, 'd')) {
351
- $error = __('Cannot remove the old extensions backup dir', 'fw');
352
- break;
353
- }
354
- }
355
-
356
- if (!FW_WP_Filesystem::mkdir_recursive($tmp_dir)) {
357
- $error = __('Cannot create the extensions backup dir', 'fw');
358
- break;
359
- }
360
-
361
- if (true !== copy_dir($extensions_dir, $tmp_dir)) {
362
- $error = __('Cannot backup the extensions', 'fw');
363
- break;
364
- }
365
- } while(false);
366
-
367
- if ($error) {
368
- trigger_error($error, E_USER_WARNING);
369
-
370
- $wp_filesystem->delete($tmp_dir, true, 'd');
371
- }
372
- }
373
-
374
- /**
375
- * Copy all extensions from the temp backup directory to the framework extensions directory (recover)
376
- * @internal
377
- */
378
- public function _action_plugin_post_update()
379
- {
380
- /** @var WP_Filesystem_Base $wp_filesystem */
381
- global $wp_filesystem;
382
-
383
- if ( !$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) ) {
384
- return;
385
- }
386
-
387
- // a directory outside the plugin
388
- $tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
389
- fw_fix_path( WP_CONTENT_DIR ) .'/tmp/fw-plugin-update-extensions-backup'
390
- );
391
- $extensions_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
392
- fw_get_framework_directory( '/extensions' )
393
- );
394
-
395
- if (!$wp_filesystem->exists($tmp_dir) || !$wp_filesystem->exists($extensions_dir)) {
396
- return;
397
- }
398
-
399
- $error = false;
400
-
401
- do {
402
- if ($wp_filesystem->exists($extensions_dir)) {
403
- /**
404
- * Make sure to remove framework initial extensions
405
- * The user do not need them because he already used the framework and has in backup the extensions he uses
406
- */
407
- if (!$wp_filesystem->delete( $extensions_dir, true, 'd' )) {
408
- $error = __( 'Cannot clear the extensions directory', 'fw' );
409
- break;
410
- }
411
-
412
- if ( ! FW_WP_Filesystem::mkdir_recursive( $extensions_dir ) ) {
413
- $error = __( 'Cannot recreate the extensions directory', 'fw' );
414
- break;
415
- }
416
- }
417
-
418
- if (true !== copy_dir($tmp_dir, $extensions_dir)) {
419
- $error = __('Cannot recover the extensions', 'fw');
420
- break;
421
- }
422
- } while(false);
423
-
424
- if ($error) {
425
- trigger_error($error, E_USER_WARNING);
426
- } else {
427
- // extensions successfully recovered, the backup is not needed anymore
428
- $wp_filesystem->delete($tmp_dir, true, 'd');
429
- }
430
- }
431
-
432
- /**
433
- * Scan all directories for extensions
434
- *
435
- * @param bool $reset_cache
436
- * @return array
437
- */
438
- private function get_installed_extensions($reset_cache = false)
439
- {
440
- $cache_key = $this->get_cache_key('installed_extensions');
441
-
442
- if ($reset_cache) {
443
- FW_Cache::del($cache_key);
444
- }
445
-
446
- try {
447
- return FW_Cache::get($cache_key);
448
- } catch (FW_Cache_Not_Found_Exception $e) {
449
- $extensions = array();
450
-
451
- foreach (fw()->extensions->get_locations() as $location) {
452
- // leave only used keys
453
- $location = array(
454
- 'path' => $location['path'],
455
- 'is' => $location['is'],
456
- );
457
-
458
- $this->read_extensions($location, $extensions);
459
- }
460
-
461
- FW_Cache::set($cache_key, $extensions);
462
-
463
- return $extensions;
464
- }
465
- }
466
-
467
- /**
468
- * used by $this->get_installed_extensions()
469
- * @param string $location
470
- * @param array $list
471
- * @param null|string $parent_extension_name
472
- */
473
- private function read_extensions($location, &$list, $parent_extension_name = null)
474
- {
475
- $paths = glob($location['path'] .'/*', GLOB_ONLYDIR | GLOB_NOSORT);
476
-
477
- if (empty($paths)) {
478
- return;
479
- }
480
-
481
- foreach ($paths as $extension_path) {
482
- $extension_name = basename($extension_path);
483
-
484
- if (isset($list[$extension_name])) {
485
- // extension already found
486
- } elseif (file_exists($extension_path .'/manifest.php')) {
487
- $vars = fw_get_variables_from_file($extension_path .'/manifest.php', array(
488
- 'manifest' => array(),
489
- ));
490
-
491
- $list[$extension_name] = array(
492
- 'path' => $extension_path,
493
- 'manifest' => $vars['manifest'],
494
- 'children' => array(),
495
- 'active' => (bool)fw()->extensions->get($extension_name),
496
- 'parent' => $parent_extension_name,
497
- 'is' => $location['is'],
498
- );
499
-
500
- if ($parent_extension_name) {
501
- $list[ $parent_extension_name ]['children'][$extension_name] = array();
502
- }
503
- } else {
504
- // it's a directory with customizations for an extension
505
- continue;
506
- }
507
-
508
- $sub_extension_location = $location;
509
- $sub_extension_location['path'] .= '/'. $extension_name .'/extensions';
510
-
511
- $this->read_extensions(
512
- $sub_extension_location,
513
- $list,
514
- $extension_name
515
- );
516
- }
517
- }
518
-
519
- private function get_tmp_dir($append = '')
520
- {
521
- return apply_filters('fw_tmp_dir', fw_fix_path(WP_CONTENT_DIR) .'/tmp') . $append;
522
- }
523
-
524
- /**
525
- * @internal
526
- */
527
- public function _action_fw_init()
528
- {
529
- $this->extension_settings_form = new FW_Form('fw_extension_settings', array(
530
- 'render' => array($this, '_extension_settings_form_render'),
531
- 'validate' => array($this, '_extension_settings_form_validate'),
532
- 'save' => array($this, '_extension_settings_form_save'),
533
- ));
534
-
535
- if (is_admin() && $this->can_activate()) {
536
- $db_wp_option_name = 'fw_extensions_activation';
537
-
538
- if ($db_wp_option_value = get_option($db_wp_option_name, array())) {
539
- $db_wp_option_value = array_merge(array(
540
- 'activated' => array(),
541
- 'deactivated' => array(),
542
- ), $db_wp_option_value);
543
-
544
- /**
545
- * Fire the 'fw_extensions_after_activation' action
546
- */
547
- if ($db_wp_option_value['activated']) {
548
- $succeeded_extensions = $failed_extensions = array();
549
-
550
- foreach ($db_wp_option_value['activated'] as $extension_name => $not_used_var) {
551
- if (fw_ext($extension_name)) {
552
- $succeeded_extensions[$extension_name] = array();
553
- } else {
554
- $failed_extensions[$extension_name] = array();
555
- }
556
- }
557
-
558
- if (!empty($succeeded_extensions)) {
559
- do_action('fw_extensions_after_activation', $succeeded_extensions);
560
- }
561
- if (!empty($failed_extensions)) {
562
- do_action('fw_extensions_activation_failed', $failed_extensions);
563
- }
564
- }
565
-
566
- /**
567
- * Fire the 'fw_extensions_after_deactivation' action
568
- */
569
- if ($db_wp_option_value['deactivated']) {
570
- $succeeded_extensions = $failed_extensions = array();
571
-
572
- foreach ($db_wp_option_value['deactivated'] as $extension_name => $not_used_var) {
573
- if (!fw_ext($extension_name)) {
574
- $succeeded_extensions[$extension_name] = array();
575
- } else {
576
- $failed_extensions[$extension_name] = array();
577
- }
578
- }
579
-
580
- if (!empty($succeeded_extensions)) {
581
- do_action('fw_extensions_after_deactivation', $succeeded_extensions);
582
- }
583
- if (!empty($failed_extensions)) {
584
- do_action('fw_extensions_deactivation_failed', $failed_extensions);
585
- }
586
- }
587
-
588
- delete_option($db_wp_option_name);
589
- }
590
- }
591
- }
592
-
593
- /**
594
- * Activate extensions with $manifest['display'] = false; $manifest['standalone'] = true;
595
- * - First level extensions
596
- * - Child extensions of the active extensions
597
- */
598
- private function activate_hidden_standalone_extensions()
599
- {
600
- if (!is_admin()) {
601
- return;
602
- }
603
-
604
- if (!$this->can_activate()) {
605
- return;
606
- }
607
-
608
- $activate_extensions = array();
609
-
610
- foreach (
611
- // all disabled extensions
612
- array_diff_key($this->get_installed_extensions(), fw()->extensions->get_all())
613
- as $ext_name => $ext_data
614
- ) {
615
- if ($ext_data['parent'] && !fw_ext($ext_data['parent'])) {
616
- // child extensions of an inactive extension
617
- continue;
618
- }
619
-
620
- if (false !== fw_akg(
621
- 'display',
622
- $ext_data['manifest'],
623
- $this->manifest_default_values['display']
624
- )) {
625
- // is visible
626
- continue;
627
- }
628
-
629
- if (true !== fw_akg(
630
- 'standalone',
631
- $ext_data['manifest'],
632
- $this->manifest_default_values['standalone']
633
- )) {
634
- // not standalone
635
- continue;
636
- }
637
-
638
- $collected = $this->get_extensions_for_activation($ext_name);
639
-
640
- if (is_wp_error($collected)) {
641
- if (defined('WP_DEBUG') && WP_DEBUG) {
642
- if ($this->is_extensions_page()) {
643
- // display this warning only on Unyson extensions page
644
- FW_Flash_Messages::add('fw_ext_auto_activate_hidden_standalone',
645
- sprintf(__('Cannot activate hidden standalone extension %s', 'fw'),
646
- fw_akg('name', $ext_data['manifest'], fw_id_to_title($ext_name))
647
- ),
648
- 'error'
649
- );
650
- }
651
- }
652
- return;
653
- }
654
-
655
- $activate_extensions = array_merge($activate_extensions, $collected);
656
- }
657
-
658
- if (empty($activate_extensions)) {
659
- return;
660
- }
661
-
662
- $option_name = fw()->extensions->_get_active_extensions_db_option_name();
663
-
664
- $db_active_extensions = array_merge(get_option($option_name, array()), $activate_extensions);
665
-
666
- update_option($option_name, $db_active_extensions);
667
- }
668
-
669
- /**
670
- * @internal
671
- */
672
- public function _action_admin_menu()
673
- {
674
- $capability = $this->can_activate();
675
-
676
- if (!$capability) {
677
- return;
678
- }
679
-
680
- $data = array(
681
- 'title' => fw()->manifest->get_name(),
682
- 'capability' => $capability,
683
- 'slug' => $this->get_page_slug(),
684
- 'content_callback' => array($this, '_display_page'),
685
- );
686
-
687
- /**
688
- * Collect $hookname that contains $data['slug'] before the action
689
- * and skip them in verification after action
690
- */
691
- {
692
- global $_registered_pages;
693
-
694
- $found_hooknames = array();
695
-
696
- if (!empty($_registered_pages)) {
697
- foreach ( $_registered_pages as $hookname => $b ) {
698
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
699
- $found_hooknames[$hookname] = true;
700
- }
701
- }
702
- }
703
- }
704
-
705
- /**
706
- * Use this action if you what to add the extensions page in a custom place in menu
707
- * Usage example http://pastebin.com/2iWVRPAU
708
- */
709
- do_action('fw_backend_add_custom_extensions_menu', $data);
710
-
711
- /**
712
- * Check if menu was added in the action above
713
- */
714
- {
715
- $menu_exists = false;
716
-
717
- if (!empty($_registered_pages)) {
718
- foreach ( $_registered_pages as $hookname => $b ) {
719
- if (isset($found_hooknames[$hookname])) {
720
- continue;
721
- }
722
-
723
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
724
- $menu_exists = true;
725
- break;
726
- }
727
- }
728
- }
729
- }
730
-
731
- if ($menu_exists) {
732
- // do nothing
733
- } else {
734
- add_menu_page(
735
- $data['title'],
736
- $data['title'],
737
- $data['capability'],
738
- $data['slug'],
739
- $data['content_callback'],
740
- 'none',
741
- 3
742
- );
743
- }
744
- }
745
-
746
- /**
747
- * If output already started, we cannot set the redirect header, do redirect from js
748
- */
749
- private function js_redirect()
750
- {
751
- echo
752
- '<script type="text/javascript">'.
753
- 'window.location.replace("'. esc_js($this->get_link()) .'");'.
754
- '</script>';
755
- }
756
-
757
- /**
758
- * @internal
759
- */
760
- public function _display_page()
761
- {
762
- $page = FW_Request::GET('sub-page');
763
-
764
- switch ($page) {
765
- case 'install':
766
- $this->display_install_page();
767
- break;
768
- case 'delete':
769
- $this->display_delete_page();
770
- break;
771
- case 'extension':
772
- $this->display_extension_page();
773
- break;
774
- case 'activate':
775
- $this->display_activate_page();
776
- break;
777
- case 'deactivate':
778
- $this->display_deactivate_page();
779
- break;
780
- default:
781
- $this->display_list_page();
782
- }
783
- }
784
-
785
- private function display_list_page()
786
- {
787
- // note: static is enqueued in 'admin_enqueue_scripts' action
788
-
789
- /** Prepare extensions list for view */
790
- {
791
- $lists = array(
792
- 'active' => array(),
793
- 'disabled' => array(),
794
- 'installed' => array(),
795
- 'available' => array(),
796
- 'supported' => array(),
797
- );
798
-
799
- foreach ($this->get_installed_extensions() as $ext_name => $ext_data) {
800
- $lists[ $ext_data['active'] ? 'active' : 'disabled' ][$ext_name] = $ext_data;
801
- }
802
-
803
- $lists['installed'] = $lists['active'] + $lists['disabled'];
804
-
805
- unset($ext_data); // prevent change by reference
806
-
807
- foreach ($this->get_available_extensions() as $ext_name => $ext_data) {
808
- $lists['available'][$ext_name] = array(
809
- 'name' => $ext_data['name'],
810
- 'description' => $ext_data['description'],
811
- 'thumbnail' => isset($ext_data['thumbnail'])
812
- ? $ext_data['thumbnail']
813
- : (isset($lists['installed'][$ext_name])
814
- ? fw_akg('thumbnail', $lists['installed'][$ext_name]['manifest'], $this->default_thumbnail)
815
- : $this->default_thumbnail),
816
- 'display' => isset($ext_data['display'])
817
- ? $ext_data['display']
818
- : $this->manifest_default_values['display'],
819
- );
820
- }
821
-
822
- foreach (fw()->theme->manifest->get('supported_extensions', array()) as $required_ext_name => $required_ext_data) {
823
- if (isset($lists['installed'][ $required_ext_name ])) {
824
- $lists['supported'][ $required_ext_name ] = array(
825
- 'name' => fw_akg( 'name', $lists['installed'][ $required_ext_name ]['manifest'], fw_id_to_title( $required_ext_name ) ),
826
- 'description' => fw_akg( 'description', $lists['installed'][ $required_ext_name ]['manifest'], '' ),
827
- );
828
- } elseif (isset($lists['available'][$required_ext_name])) {
829
- $lists['supported'][ $required_ext_name ] = array(
830
- 'name' => $lists['available'][ $required_ext_name ]['name'],
831
- 'description' => $lists['available'][ $required_ext_name ]['description'],
832
- );
833
- } else {
834
- $lists['supported'][ $required_ext_name ] = array(
835
- 'name' => fw_id_to_title( $required_ext_name ),
836
- 'description' => '',
837
- );
838
- }
839
- }
840
- }
841
-
842
- echo '<div class="wrap">';
843
-
844
- echo '<h2>'. sprintf(__('%s Extensions', 'fw'), fw()->manifest->get_name()) .'</h2><br/>';
845
-
846
- echo '<div id="fw-extensions-list-wrapper">';
847
-
848
- fw_render_view(dirname(__FILE__) .'/views/extensions-page.php', array(
849
- 'lists' => &$lists,
850
- 'link' => $this->get_link(),
851
- 'display_default_value' => $this->manifest_default_values['display'],
852
- 'default_thumbnail' => $this->default_thumbnail,
853
- 'nonces' => array(
854
- 'delete' => $this->get_nonce('delete'),
855
- 'install' => $this->get_nonce('install'),
856
- 'activate' => $this->get_nonce('activate'),
857
- 'deactivate' => $this->get_nonce('deactivate'),
858
- ),
859
- 'can_install' => $this->can_install(),
860
- ), false);
861
-
862
- echo '</div>';
863
-
864
- echo '</div>';
865
- }
866
-
867
- private function display_install_page()
868
- {
869
- $flash_id = 'fw_extensions_install';
870
-
871
- if (!$this->can_install()) {
872
- FW_Flash_Messages::add(
873
- $flash_id,
874
- __('You are not allowed to install extensions.', 'fw'),
875
- 'error'
876
- );
877
- $this->js_redirect();
878
- return;
879
- }
880
-
881
- if (array_key_exists('supported', $_GET)) {
882
- $supported = true;
883
- $extensions = array_fill_keys(
884
- array_keys($this->get_supported_extensions_for_install()),
885
- array()
886
- );
887
-
888
- if (empty($extensions)) {
889
- FW_Flash_Messages::add(
890
- $flash_id,
891
- __('All supported extensions are already installed.', 'fw'),
892
- 'info'
893
- );
894
- $this->js_redirect();
895
- return;
896
- }
897
- } else {
898
- $supported = false;
899
-
900
- $extensions = array_fill_keys(
901
- array_map( 'trim', explode( ',', FW_Request::GET( 'extension', '' ) )),
902
- array()
903
- );
904
-
905
- // activate already installed extensions
906
- $this->activate_extensions($extensions);
907
- }
908
-
909
- {
910
- if (!class_exists('_FW_Extensions_Install_Upgrader_Skin')) {
911
- fw_include_file_isolated(
912
- dirname(__FILE__) .'/includes/class--fw-extensions-install-upgrader-skin.php'
913
- );
914
- }
915
-
916
- $skin = new _FW_Extensions_Install_Upgrader_Skin(array(
917
- 'title' => $supported
918
- ? _n('Install Compatible Extension', 'Install Compatible Extensions', count($extensions), 'fw')
919
- : _n('Install Extension', 'Install Extensions', count($extensions), 'fw'),
920
- ));
921
- }
922
-
923
- $skin->header();
924
-
925
- do {
926
- $nonce = $this->get_nonce('install');
927
-
928
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
929
- if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
930
- $skin->error(__('Invalid nonce.', 'fw'));
931
- }
932
-
933
- if (!FW_WP_Filesystem::request_access(
934
- fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
935
- )) {
936
- break;
937
- }
938
-
939
- $install_result = $this->install_extensions($extensions, array('verbose' => $skin));
940
-
941
- if (is_wp_error($install_result)) {
942
- $skin->error($install_result);
943
- } elseif (is_array($install_result)) {
944
- $error = array();
945
-
946
- foreach ($install_result as $extension_name => $extension_result) {
947
- if (is_wp_error($extension_result)) {
948
- $error[] = $extension_result->get_error_message();
949
- }
950
- }
951
-
952
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
953
-
954
- $skin->error($error);
955
- } elseif ($install_result === true) {
956
- $skin->set_result(true);
957
- }
958
-
959
- /** @var WP_Filesystem_Base $wp_filesystem */
960
- global $wp_filesystem;
961
-
962
- $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
963
-
964
- if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
965
- if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
966
- $skin->error(
967
- sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
968
- );
969
- }
970
- }
971
-
972
- $skin->after(array(
973
- 'extensions_page_link' => $this->get_link()
974
- ));
975
- } else {
976
- echo '<form method="post">';
977
-
978
- wp_nonce_field($nonce['action'], $nonce['name']);
979
-
980
- $extension_titles = array();
981
- foreach ($extensions as $extension_name => $not_used_var) {
982
- $extension_titles[$extension_name] = $this->get_extension_title($extension_name);
983
- }
984
-
985
- fw_render_view(dirname(__FILE__) .'/views/install-form.php', array(
986
- 'extension_titles' => $extension_titles,
987
- 'list_page_link' => $this->get_link(),
988
- 'supported' => $supported
989
- ), false);
990
-
991
- echo '</form>';
992
- }
993
- } while(false);
994
-
995
- $skin->footer();
996
- }
997
-
998
- /**
999
- * Download (and activate) extensions
1000
- * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1001
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1002
- * @param array $opts
1003
- * @return WP_Error|bool|array
1004
- * true: when all extensions succeeded
1005
- * array: when some/all failed
1006
- */
1007
- public function install_extensions(array $extensions, $opts = array())
1008
- {
1009
- {
1010
- $opts = array_merge(array(
1011
- /**
1012
- * @type bool
1013
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1014
- * true: return first WP_Error or true on success
1015
- */
1016
- 'cancel_on_error' => false,
1017
- /**
1018
- * @type bool Activate installed extensions
1019
- */
1020
- 'activate' => true,
1021
- /**
1022
- * @type bool|WP_Upgrader_Skin
1023
- */
1024
- 'verbose' => false,
1025
- ), $opts);
1026
-
1027
- $cancel_on_error = $opts['cancel_on_error']; // fixme: remove successfully installed extensions before error?
1028
- $activate = $opts['activate'];
1029
- $verbose = $opts['verbose'];
1030
-
1031
- unset($opts);
1032
- }
1033
-
1034
- if (!$this->can_install()) {
1035
- return new WP_Error(
1036
- 'access_denied',
1037
- __('You have no permissions to install extensions', 'fw')
1038
- );
1039
- }
1040
-
1041
- if (empty($extensions)) {
1042
- return new WP_Error(
1043
- 'no_extensions',
1044
- __('No extensions provided', 'fw')
1045
- );
1046
- }
1047
-
1048
- global $wp_filesystem;
1049
-
1050
- if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
1051
- return new WP_Error(
1052
- 'fs_not_initialized',
1053
- __('WP Filesystem is not initialized', 'fw')
1054
- );
1055
- }
1056
-
1057
- if (function_exists('ini_get')) {
1058
- $timeout = intval(ini_get('max_execution_time'));
1059
- } else {
1060
- $timeout = false;
1061
- }
1062
-
1063
- $available_extensions = $this->get_available_extensions();
1064
- $installed_extensions = $this->get_installed_extensions();
1065
-
1066
- $result = $downloaded_extensions = array();
1067
- $has_errors = false;
1068
-
1069
- while (!empty($extensions)) {
1070
- $not_used_var = reset($extensions);
1071
- $extension_name = key($extensions);
1072
- unset($extensions[$extension_name]);
1073
-
1074
- $extensions_before_install = array_keys($installed_extensions);
1075
-
1076
- if (isset($installed_extensions[$extension_name])) {
1077
- $result[$extension_name] = new WP_Error(
1078
- 'extension_installed',
1079
- sprintf(__('Extension "%s" is already installed.', 'fw'), $this->get_extension_title($extension_name))
1080
- );
1081
- $has_errors = true;
1082
-
1083
- if ($cancel_on_error) {
1084
- break;
1085
- } else {
1086
- continue;
1087
- }
1088
- }
1089
-
1090
- if (!isset($available_extensions[ $extension_name ])) {
1091
- $result[$extension_name] = new WP_Error(
1092
- 'extension_not_available',
1093
- sprintf(
1094
- __('Extension "%s" is not available for install.', 'fw'),
1095
- $this->get_extension_title($extension_name)
1096
- )
1097
- );
1098
- $has_errors = true;
1099
-
1100
- if ($cancel_on_error) {
1101
- break;
1102
- } else {
1103
- continue;
1104
- }
1105
- }
1106
-
1107
- /**
1108
- * Find parent extensions
1109
- * they will be installed if does not exist
1110
- */
1111
- {
1112
- $parents = array($extension_name);
1113
-
1114
- $current_parent = $extension_name;
1115
- while (!empty($available_extensions[$current_parent]['parent'])) {
1116
- $current_parent = $available_extensions[$current_parent]['parent'];
1117
-
1118
- if (!isset($available_extensions[$current_parent])) {
1119
- $result[$extension_name] = new WP_Error(
1120
- 'parent_extension_not_available',
1121
- sprintf(
1122
- __('Parent extension "%s" not available.', 'fw'),
1123
- $this->get_extension_title($current_parent)
1124
- )
1125
- );
1126
- $has_errors = true;
1127
-
1128
- if ($cancel_on_error) {
1129
- break 2;
1130
- } else {
1131
- continue 2;
1132
- }
1133
- }
1134
-
1135
- $parents[] = $current_parent;
1136
- }
1137
-
1138
- $parents = array_reverse($parents);
1139
- }
1140
-
1141
- /**
1142
- * Install parent extensions and the extension
1143
- */
1144
- {
1145
- $current_extension_path = fw_get_framework_directory();
1146
-
1147
- foreach ($parents as $parent_extension_name) {
1148
- $current_extension_path .= '/extensions/'. $parent_extension_name;
1149
-
1150
- if (isset($installed_extensions[$parent_extension_name])) {
1151
- // skip already installed extensions
1152
- continue;
1153
- }
1154
-
1155
- if ($verbose) {
1156
- $verbose_message = sprintf(__('Downloading the "%s" extension...', 'fw'),
1157
- $this->get_extension_title($parent_extension_name)
1158
- );
1159
-
1160
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1161
- $verbose->feedback($verbose_message);
1162
- } else {
1163
- echo fw_html_tag('p', array(), $verbose_message);
1164
- }
1165
- }
1166
-
1167
- // increase timeout
1168
- if ($timeout !== false && function_exists('set_time_limit')) {
1169
- $timeout += 30;
1170
- set_time_limit($timeout);
1171
- }
1172
-
1173
- $wp_fw_downloaded_dir = $this->download(
1174
- $parent_extension_name,
1175
- $available_extensions[$parent_extension_name]
1176
- );
1177
-
1178
- if (is_wp_error($wp_fw_downloaded_dir)) {
1179
- if ($verbose) {
1180
- $verbose_message = $wp_fw_downloaded_dir->get_error_message();
1181
-
1182
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1183
- $verbose->error($verbose_message);
1184
- } else {
1185
- echo fw_html_tag('p', array(), $verbose_message);
1186
- }
1187
- }
1188
-
1189
- $result[$extension_name] = $wp_fw_downloaded_dir;
1190
- $has_errors = true;
1191
-
1192
- if ($cancel_on_error) {
1193
- break 2;
1194
- } else {
1195
- continue 2;
1196
- }
1197
- }
1198
-
1199
- if ($verbose) {
1200
- $verbose_message = sprintf(__('Installing the "%s" extension...', 'fw'),
1201
- $this->get_extension_title($parent_extension_name)
1202
- );
1203
-
1204
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1205
- $verbose->feedback($verbose_message);
1206
- } else {
1207
- echo fw_html_tag('p', array(), $verbose_message);
1208
- }
1209
- }
1210
-
1211
- $merge_result = $this->merge_extension(
1212
- $wp_fw_downloaded_dir,
1213
- FW_WP_Filesystem::real_path_to_filesystem_path($current_extension_path)
1214
- );
1215
-
1216
- if (is_wp_error($merge_result)) {
1217
- if ($verbose) {
1218
- $verbose_message = $merge_result->get_error_message();
1219
-
1220
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1221
- $verbose->error($verbose_message);
1222
- } else {
1223
- echo fw_html_tag('p', array(), $verbose_message);
1224
- }
1225
- }
1226
-
1227
- $result[$extension_name] = $merge_result;
1228
- $has_errors = true;
1229
-
1230
- if ($cancel_on_error) {
1231
- break 2;
1232
- } else {
1233
- continue 2;
1234
- }
1235
- }
1236
-
1237
- if ($verbose) {
1238
- $verbose_message = sprintf(__('The %s extension has been successfully installed.', 'fw'),
1239
- $this->get_extension_title($parent_extension_name)
1240
- );
1241
-
1242
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1243
- $verbose->feedback($verbose_message);
1244
- } else {
1245
- echo fw_html_tag('p', array(), $verbose_message);
1246
- }
1247
- }
1248
-
1249
- $downloaded_extensions[$parent_extension_name] = array();
1250
-
1251
- /**
1252
- * Read again all extensions
1253
- * The downloaded extension may contain more sub extensions
1254
- */
1255
- {
1256
- unset($installed_extensions);
1257
- $installed_extensions = $this->get_installed_extensions(true);
1258
- }
1259
- }
1260
- }
1261
-
1262
- $result[$extension_name] = true;
1263
-
1264
- /**
1265
- * Collect required extensions of the newly installed extensions
1266
- */
1267
- foreach (
1268
- // new extensions
1269
- array_diff(
1270
- array_keys($installed_extensions),
1271
- $extensions_before_install
1272
- )
1273
- as $new_extension_name
1274
- ) {
1275
- foreach (
1276
- array_keys(
1277
- fw_akg(
1278
- 'requirements/extensions',
1279
- $installed_extensions[$new_extension_name]['manifest'],
1280
- array()
1281
- )
1282
- )
1283
- as $required_extension_name
1284
- ) {
1285
- if (isset($installed_extensions[$required_extension_name])) {
1286
- // already installed
1287
- continue;
1288
- }
1289
-
1290
- $extensions[$required_extension_name] = array();
1291
- }
1292
- }
1293
- }
1294
-
1295
- if ($activate) {
1296
- $activate_extensions = array();
1297
-
1298
- foreach ($result as $extension_name => $extension_result) {
1299
- if (!is_wp_error($extension_result)) {
1300
- $activate_extensions[$extension_name] = array();
1301
- }
1302
- }
1303
-
1304
- if (!empty($activate_extensions)) {
1305
- if ($verbose) {
1306
- $verbose_message = _n(
1307
- 'Activating extension...',
1308
- 'Activating extensions...',
1309
- count($activate_extensions),
1310
- 'fw'
1311
- );
1312
-
1313
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1314
- $verbose->feedback($verbose_message);
1315
- } else {
1316
- echo fw_html_tag('p', array(), $verbose_message);
1317
- }
1318
- }
1319
-
1320
- $activation_result = $this->activate_extensions($activate_extensions);
1321
-
1322
- if ($verbose) {
1323
- if (is_wp_error($activation_result)) {
1324
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1325
- $verbose->error($activation_result->get_error_message());
1326
- } else {
1327
- echo fw_html_tag('p', array(), $activation_result->get_error_message());
1328
- }
1329
- } elseif (is_array($activation_result)) {
1330
- $verbose_message = array();
1331
-
1332
- foreach ($activation_result as $extension_name => $extension_result) {
1333
- if (is_wp_error($extension_result)) {
1334
- $verbose_message[] = $extension_result->get_error_message();
1335
- }
1336
- }
1337
-
1338
- $verbose_message = '<ul><li>' . implode('</li><li>', $verbose_message) . '</li></ul>';
1339
-
1340
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1341
- $verbose->error($verbose_message);
1342
- } else {
1343
- echo fw_html_tag('p', array(), $verbose_message);
1344
- }
1345
- } elseif ($activation_result === true) {
1346
- $verbose_message = _n(
1347
- 'Extension has been successfully activated.',
1348
- 'Extensions has been successfully activated.',
1349
- count($activate_extensions),
1350
- 'fw'
1351
- );
1352
-
1353
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1354
- $verbose->feedback($verbose_message);
1355
- } else {
1356
- echo fw_html_tag('p', array(), $verbose_message);
1357
- }
1358
- }
1359
- }
1360
- }
1361
- }
1362
-
1363
- do_action('fw_extensions_install', $result);
1364
-
1365
- if (
1366
- $cancel_on_error
1367
- &&
1368
- $has_errors
1369
- ) {
1370
- if (
1371
- ($last_result = end($result))
1372
- &&
1373
- is_wp_error($last_result)
1374
- ) {
1375
- return $last_result;
1376
- } else {
1377
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
1378
- return new WP_Error(
1379
- 'installation_failed',
1380
- _n('Cannot install extension', 'Cannot install extensions', count($extensions), 'fw')
1381
- );
1382
- }
1383
- }
1384
-
1385
- if ($has_errors) {
1386
- return $result;
1387
- } else {
1388
- return true;
1389
- }
1390
- }
1391
-
1392
- private function display_delete_page()
1393
- {
1394
- $flash_id = 'fw_extensions_delete';
1395
-
1396
- if (!$this->can_install()) {
1397
- FW_Flash_Messages::add(
1398
- $flash_id,
1399
- __('You are not allowed to delete extensions.', 'fw'),
1400
- 'error'
1401
- );
1402
- $this->js_redirect();
1403
- return;
1404
- }
1405
-
1406
- $extensions = array_fill_keys(array_map('trim', explode(',', FW_Request::GET('extension', ''))), array());
1407
-
1408
- {
1409
- if (!class_exists('_FW_Extensions_Delete_Upgrader_Skin')) {
1410
- fw_include_file_isolated(
1411
- dirname(__FILE__) .'/includes/class--fw-extensions-delete-upgrader-skin.php'
1412
- );
1413
- }
1414
-
1415
- $skin = new _FW_Extensions_Delete_Upgrader_Skin(array(
1416
- 'title' => _n('Delete Extension', 'Delete Extensions', count($extensions), 'fw'),
1417
- ));
1418
- }
1419
-
1420
- $skin->header();
1421
-
1422
- do {
1423
- $nonce = $this->get_nonce('delete');
1424
-
1425
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1426
- if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1427
- $skin->error(__('Invalid nonce.', 'fw'));
1428
- }
1429
-
1430
- if (!FW_WP_Filesystem::request_access(
1431
- fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
1432
- )) {
1433
- break;
1434
- }
1435
-
1436
- $uninstall_result = $this->uninstall_extensions($extensions, array('verbose' => $skin));
1437
-
1438
- if (is_wp_error($uninstall_result)) {
1439
- $skin->error($uninstall_result);
1440
- } elseif (is_array($uninstall_result)) {
1441
- $error = array();
1442
-
1443
- foreach ($uninstall_result as $extension_name => $extension_result) {
1444
- if (is_wp_error($extension_result)) {
1445
- $error[] = $extension_result->get_error_message();
1446
- }
1447
- }
1448
-
1449
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1450
-
1451
- $skin->error($error);
1452
- } elseif ($uninstall_result === true) {
1453
- $skin->set_result(true);
1454
- }
1455
-
1456
- $skin->after(array(
1457
- 'extensions_page_link' => $this->get_link()
1458
- ));
1459
- } else {
1460
- echo '<form method="post">';
1461
-
1462
- wp_nonce_field($nonce['action'], $nonce['name']);
1463
-
1464
- fw_render_view(dirname(__FILE__) .'/views/delete-form.php', array(
1465
- 'extension_names' => array_keys($extensions),
1466
- 'installed_extensions' => $this->get_installed_extensions(),
1467
- 'list_page_link' => $this->get_link(),
1468
- ), false);
1469
-
1470
- echo '</form>';
1471
- }
1472
- } while(false);
1473
-
1474
- $skin->footer();
1475
- }
1476
-
1477
- /**
1478
- * Remove extensions
1479
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1480
- * @param array $opts
1481
- * @return WP_Error|bool|array
1482
- * true: when all extensions succeeded
1483
- * array: when some/all failed
1484
- */
1485
- public function uninstall_extensions(array $extensions, $opts = array())
1486
- {
1487
- {
1488
- $opts = array_merge(array(
1489
- /**
1490
- * @type bool
1491
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1492
- * true: return first WP_Error or true on success
1493
- */
1494
- 'cancel_on_error' => false,
1495
- /**
1496
- * @type bool|WP_Upgrader_Skin
1497
- */
1498
- 'verbose' => false,
1499
- ), $opts);
1500
-
1501
- $cancel_on_error = $opts['cancel_on_error']; // fixme: install back successfully removed extensions before error?
1502
- $verbose = $opts['verbose'];
1503
-
1504
- unset($opts);
1505
- }
1506
-
1507
- if (!$this->can_install()) {
1508
- return new WP_Error(
1509
- 'access_denied',
1510
- __('You have no permissions to uninstall extensions', 'fw')
1511
- );
1512
- }
1513
-
1514
- if (empty($extensions)) {
1515
- return new WP_Error(
1516
- 'no_extensions',
1517
- __('No extensions provided', 'fw')
1518
- );
1519
- }
1520
-
1521
- /** @var WP_Filesystem_Base $wp_filesystem */
1522
- global $wp_filesystem;
1523
-
1524
- if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
1525
- return new WP_Error(
1526
- 'fs_not_initialized',
1527
- __('WP Filesystem is not initialized', 'fw')
1528
- );
1529
- }
1530
-
1531
- $installed_extensions = $this->get_installed_extensions();
1532
- $extensions_before_uninstall = array_fill_keys(array_keys($installed_extensions), array());
1533
-
1534
- $result = $uninstalled_extensions = array();
1535
- $has_errors = false;
1536
-
1537
- while (!empty($extensions)) {
1538
- $not_used_var = reset($extensions);
1539
- $extension_name = key($extensions);
1540
- unset($extensions[$extension_name]);
1541
-
1542
- $extension_title = $this->get_extension_title($extension_name);
1543
-
1544
- if (!isset($installed_extensions[ $extension_name ])) {
1545
- // already deleted
1546
- $result[$extension_name] = true;
1547
- continue;
1548
- }
1549
-
1550
- if (
1551
- !isset($installed_extensions[ $extension_name ]['path'])
1552
- ||
1553
- empty($installed_extensions[ $extension_name ]['path'])
1554
- ) {
1555
- /**
1556
- * This happens sometimes, but I don't know why
1557
- * If the script will continue, it will delete the root folder
1558
- */
1559
- fw_print(
1560
- 'Please report this to https://github.com/ThemeFuse/Unyson/issues',
1561
- $extension_name,
1562
- $installed_extensions
1563
- );
1564
- die;
1565
- }
1566
-
1567
- $wp_fs_extension_path = FW_WP_Filesystem::real_path_to_filesystem_path(
1568
- $installed_extensions[ $extension_name ]['path']
1569
- );
1570
-
1571
- if (!$wp_filesystem->exists($wp_fs_extension_path)) {
1572
- // already deleted, maybe because it was a sub-extension of an deleted extension
1573
- $result[$extension_name] = true;
1574
- continue;
1575
- }
1576
-
1577
- if ($verbose) {
1578
- $verbose_message = sprintf(__('Deleting the "%s" extension...', 'fw'), $extension_title);
1579
-
1580
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1581
- $verbose->feedback($verbose_message);
1582
- } else {
1583
- echo fw_html_tag('p', array(), $verbose_message);
1584
- }
1585
- }
1586
-
1587
- if (!$wp_filesystem->delete($wp_fs_extension_path, true, 'd')) {
1588
- $result[$extension_name] = new WP_Error(
1589
- 'cannot_delete_directory',
1590
- sprintf(__('Cannot delete the "%s" extension.', 'fw'), $extension_title)
1591
- );
1592
- $has_errors = true;
1593
-
1594
- if ($cancel_on_error) {
1595
- break;
1596
- } else {
1597
- continue;
1598
- }
1599
- } else {
1600
- if ($verbose) {
1601
- $verbose_message = sprintf(
1602
- __('The %s extension has been successfully deleted.', 'fw'),
1603
- $extension_title
1604
- );
1605
-
1606
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1607
- $verbose->feedback($verbose_message);
1608
- } else {
1609
- echo fw_html_tag('p', array(), $verbose_message);
1610
- }
1611
- }
1612
-
1613
- $result[$extension_name] = true;
1614
- }
1615
-
1616
- /**
1617
- * Read again all extensions
1618
- * The delete extension may contain more sub extensions
1619
- */
1620
- {
1621
- unset($installed_extensions);
1622
- $installed_extensions = $this->get_installed_extensions(true);
1623
- }
1624
-
1625
- /**
1626
- * Add for deletion not used extensions
1627
- * For e.g. standalone=false extension that were required by the deleted extension
1628
- * and now are not required by any other extension
1629
- */
1630
- {
1631
- $not_used_extensions = array_fill_keys(
1632
- array_keys(
1633
- array_diff_key(
1634
- $installed_extensions,
1635
- $this->get_used_extensions($extensions, array_keys($installed_extensions))
1636
- )
1637
- ),
1638
- array()
1639
- );
1640
-
1641
- $extensions = array_merge($extensions, $not_used_extensions);
1642
- }
1643
- }
1644
-
1645
- do_action('fw_extensions_uninstall', $result);
1646
-
1647
- if (
1648
- $cancel_on_error
1649
- &&
1650
- $has_errors
1651
- ) {
1652
- if (
1653
- ($last_result = end($result))
1654
- &&
1655
- is_wp_error($last_result)
1656
- ) {
1657
- return $last_result;
1658
- } else {
1659
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
1660
- return new WP_Error(
1661
- 'uninstall_failed',
1662
- _n('Cannot uninstall extension', 'Cannot uninstall extensions', count($extensions), 'fw')
1663
- );
1664
- }
1665
- }
1666
-
1667
- // remove from active list the deleted extensions
1668
- {
1669
- update_option(
1670
- fw()->extensions->_get_active_extensions_db_option_name(),
1671
- array_diff_key(
1672
- fw()->extensions->_get_db_active_extensions(),
1673
- array_diff_key(
1674
- $extensions_before_uninstall,
1675
- $installed_extensions
1676
- )
1677
- )
1678
- );
1679
- }
1680
-
1681
- if ($has_errors) {
1682
- return $result;
1683
- } else {
1684
- return true;
1685
- }
1686
- }
1687
-
1688
- private function display_extension_page()
1689
- {
1690
- // note: static is enqueued in 'admin_enqueue_scripts' action
1691
-
1692
- $extension_name = trim(FW_Request::GET('extension', ''));
1693
-
1694
- $installed_extensions = $this->get_installed_extensions();
1695
-
1696
- $flash_id = 'fw_extension_page';
1697
-
1698
- {
1699
- $error = '';
1700
-
1701
- do {
1702
- if (empty($extension_name)) {
1703
- $error = __('Extension not specified.', 'fw');
1704
- break;
1705
- }
1706
-
1707
- if (!isset($installed_extensions[$extension_name])) {
1708
- $error = sprintf(__('Extension "%s" is not installed.', 'fw'), $this->get_extension_title($extension_name));
1709
- break;
1710
- }
1711
- } while(false);
1712
-
1713
- if ($error) {
1714
- FW_Flash_Messages::add($flash_id, $error, 'error');
1715
- $this->js_redirect();
1716
- return;
1717
- }
1718
- }
1719
-
1720
- {
1721
- $tab = fw_akg('tab', $_GET, 'settings');
1722
-
1723
- if (!in_array($tab, array('settings', 'docs'))) {
1724
- $tab = 'settings';
1725
- }
1726
- }
1727
-
1728
- $extension_title = $this->get_extension_title($extension_name);
1729
- $link = $this->get_link();
1730
-
1731
- echo '<div class="wrap" id="fw-extension-page">';
1732
-
1733
- fw_render_view(dirname(__FILE__) .'/views/extension-page-header.php', array(
1734
- 'extension_name' => $extension_name,
1735
- 'extension_data' => $installed_extensions[$extension_name],
1736
- 'link_delete' => $link .'&sub-page=delete',
1737
- 'link_extension' => $link .'&sub-page=extension',
1738
- 'extension_title' => $extension_title,
1739
- 'tab' => $tab,
1740
- 'is_supported' =>
1741
- fw()->theme->manifest->get('supported_extensions/'. $extension_name, false) !== false
1742
- ||
1743
- $installed_extensions[$extension_name]['is']['theme']
1744
- ), false);
1745
-
1746
- unset($installed_extensions);
1747
-
1748
- echo '<div id="fw-extension-tab-content">';
1749
- {
1750
- $method_data = array();
1751
-
1752
- switch ($tab) {
1753
- case 'settings':
1754
- $error = $this->display_extension_settings_page($extension_name, $method_data);
1755
- break;
1756
- case 'docs':
1757
- $error = $this->display_extension_docs_page($extension_name, $method_data);
1758
- break;
1759
- }
1760
- }
1761
- echo '</div>';
1762
-
1763
- echo '</div>';
1764
-
1765
- if ($error) {
1766
- FW_Flash_Messages::add($flash_id, $error, 'error');
1767
- $this->js_redirect();
1768
- return;
1769
- }
1770
- }
1771
-
1772
- private function display_extension_settings_page($extension_name, $data)
1773
- {
1774
- if (!fw()->extensions->get($extension_name)) {
1775
- return sprintf(
1776
- __('Extension "%s" does not exist or is not active.', 'fw'),
1777
- fw_htmlspecialchars($extension_name)
1778
- );
1779
- }
1780
-
1781
- $extension = fw()->extensions->get($extension_name);
1782
-
1783
- if (!$extension->get_settings_options()) {
1784
- return sprintf(
1785
- __('%s extension does not have settings.', 'fw'),
1786
- $extension->manifest->get_name()
1787
- );
1788
- }
1789
-
1790
- echo '<div id="fw-extension-settings">';
1791
-
1792
- echo $this->extension_settings_form->render(array(
1793
- 'extension' => $extension,
1794
- ));
1795
-
1796
- echo '</div>';
1797
- }
1798
-
1799
- private function display_extension_docs_page($extension_name, $data)
1800
- {
1801
- $installed_extensions = $this->get_installed_extensions();
1802
- $docs_path = $installed_extensions[$extension_name]['path'] .'/readme.md.php';
1803
- unset($installed_extensions);
1804
-
1805
- if (!file_exists($docs_path)) {
1806
- return __('Extension has no Install Instructions', 'fw');
1807
- }
1808
-
1809
- echo fw()->backend->render_box(
1810
- 'fw-extension-docs',
1811
- '',
1812
- fw()->backend->render_options(array(
1813
- 'docs' => array(
1814
- 'label' => false,
1815
- 'type' => 'html-full',
1816
- 'html' => $this->get_markdown_parser()->text(
1817
- fw_render_view($docs_path, array())
1818
- ),
1819
- ),
1820
- ))
1821
- );
1822
- }
1823
-
1824
- private function display_activate_page()
1825
- {
1826
- $error = '';
1827
-
1828
- do {
1829
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
1830
- $error = __('Invalid request method.', 'fw');
1831
- break;
1832
- }
1833
-
1834
- $nonce = $this->get_nonce('activate');
1835
-
1836
- if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
1837
- $error = __('Invalid nonce.', 'fw');
1838
- break;
1839
- }
1840
-
1841
- if (!isset($_GET['extension'])) {
1842
- $error = __('No extension specified.', 'fw');
1843
- break;
1844
- }
1845
-
1846
- $activation_result = $this->activate_extensions(
1847
- array_fill_keys(explode(',', $_GET['extension']), array())
1848
- );
1849
-
1850
- if (is_wp_error($activation_result)) {
1851
- $error = $activation_result->get_error_message();
1852
- } elseif (is_array($activation_result)) {
1853
- $error = array();
1854
-
1855
- foreach ($activation_result as $extension_name => $extension_result) {
1856
- if (is_wp_error($extension_result)) {
1857
- $error[] = $extension_result->get_error_message();
1858
- }
1859
- }
1860
-
1861
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1862
- }
1863
- } while(false);
1864
-
1865
- if ($error) {
1866
- FW_Flash_Messages::add(
1867
- 'fw_extensions_activate_page',
1868
- $error,
1869
- 'error'
1870
- );
1871
- $this->js_redirect();
1872
- return;
1873
- }
1874
-
1875
- $this->js_redirect();
1876
- }
1877
-
1878
- /**
1879
- * Add extensions to active extensions list in database
1880
- * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1881
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1882
- * @param bool $cancel_on_error
1883
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1884
- * true: return first WP_Error or true on success
1885
- * @return WP_Error|bool|array
1886
- * true: when all extensions succeeded
1887
- * array: when some/all failed
1888
- */
1889
- public function activate_extensions(array $extensions, $cancel_on_error = false)
1890
- {
1891
- if (!$this->can_activate()) {
1892
- return new WP_Error(
1893
- 'access_denied',
1894
- __('You have no permissions to activate extensions', 'fw')
1895
- );
1896
- }
1897
-
1898
- if (empty($extensions)) {
1899
- return new WP_Error(
1900
- 'no_extensions',
1901
- __('No extensions provided', 'fw')
1902
- );
1903
- }
1904
-
1905
- $installed_extensions = $this->get_installed_extensions();
1906
-
1907
- $result = $extensions_for_activation = array();
1908
- $has_errors = false;
1909
-
1910
- foreach ($extensions as $extension_name => $not_used_var) {
1911
- if (!isset($installed_extensions[$extension_name])) {
1912
- $result[$extension_name] = new WP_Error(
1913
- 'extension_not_installed',
1914
- sprintf(__('Extension "%s" does not exist.', 'fw'), $this->get_extension_title($extension_name))
1915
- );
1916
- $has_errors = true;
1917
-
1918
- if ($cancel_on_error) {
1919
- break;
1920
- } else {
1921
- continue;
1922
- }
1923
- }
1924
-
1925
- $collected = $this->get_extensions_for_activation($extension_name);
1926
-
1927
- if (is_wp_error($collected)) {
1928
- $result[$extension_name] = $collected;
1929
- $has_errors = true;
1930
-
1931
- if ($cancel_on_error) {
1932
- break;
1933
- } else {
1934
- continue;
1935
- }
1936
- }
1937
-
1938
- $extensions_for_activation = array_merge($extensions_for_activation, $collected);
1939
-
1940
- $result[$extension_name] = true;
1941
- }
1942
-
1943
- if (
1944
- $cancel_on_error
1945
- &&
1946
- $has_errors
1947
- ) {
1948
- if (
1949
- ($last_result = end($result))
1950
- &&
1951
- is_wp_error($last_result)
1952
- ) {
1953
- return $last_result;
1954
- } else {
1955
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
1956
- return new WP_Error(
1957
- 'activation_failed',
1958
- _n('Cannot activate extension', 'Cannot activate extensions', count($extensions), 'fw')
1959
- );
1960
- }
1961
- }
1962
-
1963
- update_option(
1964
- fw()->extensions->_get_active_extensions_db_option_name(),
1965
- array_merge(fw()->extensions->_get_db_active_extensions(), $extensions_for_activation)
1966
- );
1967
-
1968
- // remove already active extensions
1969
- foreach ($extensions_for_activation as $extension_name => $not_used_var) {
1970
- if (fw_ext($extension_name)) {
1971
- unset($extensions_for_activation[$extension_name]);
1972
- }
1973
- }
1974
-
1975
- /**
1976
- * Prepare db wp option used to fire the 'fw_extensions_after_activation' action on next refresh
1977
- */
1978
- {
1979
- $db_wp_option_name = 'fw_extensions_activation';
1980
- $db_wp_option_value = get_option($db_wp_option_name, array(
1981
- 'activated' => array(),
1982
- 'deactivated' => array(),
1983
- ));
1984
-
1985
- /**
1986
- * Keep adding to the existing value instead of resetting it on each method call
1987
- * in case the method will be called multiple times
1988
- */
1989
- $db_wp_option_value['activated'] = array_merge($db_wp_option_value['activated'], $extensions_for_activation);
1990
-
1991
- /**
1992
- * Remove activated extensions from deactivated
1993
- */
1994
- $db_wp_option_value['deactivated'] = array_diff_key($db_wp_option_value['deactivated'], $db_wp_option_value['activated']);
1995
-
1996
- update_option($db_wp_option_name, $db_wp_option_value, false);
1997
- }
1998
-
1999
- do_action('fw_extensions_before_activation', $extensions_for_activation);
2000
-
2001
- if ($has_errors) {
2002
- return $result;
2003
- } else {
2004
- return true;
2005
- }
2006
- }
2007
-
2008
- private function collect_sub_extensions($ext_name, &$installed_extensions)
2009
- {
2010
- $result = array();
2011
-
2012
- foreach ($installed_extensions[$ext_name]['children'] as $child_ext_name => $child_ext_data) {
2013
- $result[$child_ext_name] = array();
2014
-
2015
- $result += $this->collect_sub_extensions($child_ext_name, $installed_extensions);
2016
- }
2017
-
2018
- return $result;
2019
- }
2020
-
2021
- private function collect_required_extensions($ext_name, &$installed_extensions, &$collected)
2022
- {
2023
- if (!isset($installed_extensions[$ext_name])) {
2024
- return;
2025
- }
2026
-
2027
- foreach (fw_akg('requirements/extensions', $installed_extensions[$ext_name]['manifest'], array()) as $req_ext_name => $req_ext_data) {
2028
- if (isset($collected[$req_ext_name])) {
2029
- // prevent requirements recursion
2030
- continue;
2031
- }
2032
-
2033
- $collected[$req_ext_name] = array();
2034
-
2035
- $this->collect_required_extensions($req_ext_name, $installed_extensions, $collected);
2036
- }
2037
- }
2038
-
2039
- private function display_deactivate_page()
2040
- {
2041
- $installed_extensions = $this->get_installed_extensions();
2042
-
2043
- $error = '';
2044
-
2045
- do {
2046
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
2047
- $error = __('Invalid request method.', 'fw');
2048
- break;
2049
- }
2050
-
2051
- $nonce = $this->get_nonce('deactivate');
2052
-
2053
- if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
2054
- $error = __('Invalid nonce.', 'fw');
2055
- break;
2056
- }
2057
-
2058
- if (!isset($_GET['extension'])) {
2059
- $error = __('No extension specified.', 'fw');
2060
- break;
2061
- }
2062
-
2063
- $deactivation_result = $this->deactivate_extensions(
2064
- array_fill_keys(explode(',', $_GET['extension']), array())
2065
- );
2066
-
2067
- if (is_wp_error($deactivation_result)) {
2068
- $error = $deactivation_result->get_error_message();
2069
- } elseif (is_array($deactivation_result)) {
2070
- $error = array();
2071
-
2072
- foreach ($deactivation_result as $extension_name => $extension_result) {
2073
- if (is_wp_error($extension_result)) {
2074
- $error[] = $extension_result->get_error_message();
2075
- }
2076
- }
2077
-
2078
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
2079
- }
2080
- } while(false);
2081
-
2082
- if ($error) {
2083
- FW_Flash_Messages::add(
2084
- 'fw_extensions_activate_page',
2085
- $error,
2086
- 'error'
2087
- );
2088
- }
2089
-
2090
- $this->js_redirect();
2091
- }
2092
-
2093
- /**
2094
- * Remove extensions from active extensions list in database
2095
- * After refresh they will be inactive
2096
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
2097
- * @param bool $cancel_on_error
2098
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
2099
- * true: return first WP_Error or true on success
2100
- * @return WP_Error|bool|array
2101
- * true: when all extensions succeeded
2102
- * array: when some/all failed
2103
- */
2104
- public function deactivate_extensions(array $extensions, $cancel_on_error = false)
2105
- {
2106
- if (!$this->can_activate()) {
2107
- return new WP_Error(
2108
- 'access_denied',
2109
- __('You have no permissions to deactivate extensions', 'fw')
2110
- );
2111
- }
2112
-
2113
- if (empty($extensions)) {
2114
- return new WP_Error(
2115
- 'no_extensions',
2116
- __('No extensions provided', 'fw')
2117
- );
2118
- }
2119
-
2120
- $installed_extensions = $this->get_installed_extensions();
2121
-
2122
- $result = $extensions_for_deactivation = array();
2123
- $has_errors = false;
2124
-
2125
- foreach ($extensions as $extension_name => $not_used_var) {
2126
- if (!isset($installed_extensions[$extension_name])) {
2127
- // anyway remove from the active list
2128
- $extensions_for_deactivation[$extension_name] = array();
2129
-
2130
- $result[$extension_name] = new WP_Error(
2131
- 'extension_not_installed',
2132
- sprintf(__( 'Extension "%s" does not exist.' , 'fw' ), $this->get_extension_title($extension_name))
2133
- );
2134
- $has_errors = true;
2135
-
2136
- if ($cancel_on_error) {
2137
- break;
2138
- } else {
2139
- continue;
2140
- }
2141
- }
2142
-
2143
- $current_deactivating_extensions = array(
2144
- $extension_name => array()
2145
- );
2146
-
2147
- // add sub-extensions for deactivation
2148
- foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2149
- $current_deactivating_extensions[ $sub_extension_name ] = array();
2150
- }
2151
-
2152
- // add extensions that requires deactivated extensions
2153
- $this->collect_extensions_that_requires($current_deactivating_extensions, $current_deactivating_extensions);
2154
-
2155
- $extensions_for_deactivation = array_merge(
2156
- $extensions_for_deactivation,
2157
- $current_deactivating_extensions
2158
- );
2159
-
2160
- unset($current_deactivating_extensions);
2161
-
2162
- $result[$extension_name] = true;
2163
- }
2164
-
2165
- if (
2166
- $cancel_on_error
2167
- &&
2168
- $has_errors
2169
- ) {
2170
- if (
2171
- ($last_result = end($result))
2172
- &&
2173
- is_wp_error($last_result)
2174
- ) {
2175
- return $last_result;
2176
- } else {
2177
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
2178
- return new WP_Error(
2179
- 'deactivation_failed',
2180
- _n('Cannot deactivate extension', 'Cannot activate extensions', count($extensions), 'fw')
2181
- );
2182
- }
2183
- }
2184
-
2185
- // add not used extensions for deactivation
2186
- $extensions_for_deactivation = array_merge($extensions_for_deactivation,
2187
- array_fill_keys(
2188
- array_keys(
2189
- array_diff_key(
2190
- $installed_extensions,
2191
- $this->get_used_extensions($extensions_for_deactivation, array_keys(fw()->extensions->get_all()))
2192
- )
2193
- ),
2194
- array()
2195
- )
2196
- );
2197
-
2198
- update_option(
2199
- fw()->extensions->_get_active_extensions_db_option_name(),
2200
- array_diff_key(
2201
- fw()->extensions->_get_db_active_extensions(),
2202
- $extensions_for_deactivation
2203
- )
2204
- );
2205
-
2206
- // remove already inactive extensions
2207
- foreach ($extensions_for_deactivation as $extension_name => $not_used_var) {
2208
- if (!fw_ext($extension_name)) {
2209
- unset($extensions_for_deactivation[$extension_name]);
2210
- }
2211
- }
2212
-
2213
- /**
2214
- * Prepare db wp option used to fire the 'fw_extensions_after_deactivation' action on next refresh
2215
- */
2216
- {
2217
- $db_wp_option_name = 'fw_extensions_activation';
2218
- $db_wp_option_value = get_option($db_wp_option_name, array(
2219
- 'activated' => array(),
2220
- 'deactivated' => array(),
2221
- ));
2222
-
2223
- /**
2224
- * Keep adding to the existing value instead of resetting it on each method call
2225
- * in case the method will be called multiple times
2226
- */
2227
- $db_wp_option_value['deactivated'] = array_merge($db_wp_option_value['deactivated'], $extensions_for_deactivation);
2228
-
2229
- /**
2230
- * Remove deactivated extensions from activated
2231
- */
2232
- $db_wp_option_value['activated'] = array_diff_key($db_wp_option_value['activated'], $db_wp_option_value['deactivated']);
2233
-
2234
- update_option($db_wp_option_name, $db_wp_option_value, false);
2235
- }
2236
-
2237
- do_action('fw_extensions_before_deactivation', $extensions_for_deactivation);
2238
-
2239
- if ($has_errors) {
2240
- return $result;
2241
- } else {
2242
- return true;
2243
- }
2244
- }
2245
-
2246
- /**
2247
- * @param array $data
2248
- * @return array
2249
- * @internal
2250
- */
2251
- public function _extension_settings_form_render($data)
2252
- {
2253
- /**
2254
- * @var FW_Extension $extension
2255
- */
2256
- $extension = $data['data']['extension'];
2257
-
2258
- do_action('fw_extension_settings_form_render:'. $extension->get_name());
2259
-
2260
- echo fw_html_tag('input', array(
2261
- 'type' => 'hidden',
2262
- 'name' => 'fw_extension_name',
2263
- 'value' => $extension->get_name(),
2264
- ), true);
2265
-
2266
- echo fw()->backend->render_options(
2267
- $extension->get_settings_options(),
2268
- fw_get_db_ext_settings_option($extension->get_name())
2269
- );
2270
-
2271
- $data['submit']['html'] = '';
2272
-
2273
- echo '<p>';
2274
- echo fw_html_tag('input', array(
2275
- 'type' => 'submit',
2276
- 'class' => 'button-primary',
2277
- 'value' => __('Save', 'fw'),
2278
- ));
2279
- echo '&nbsp;&nbsp;&nbsp;&nbsp;';
2280
- echo fw_html_tag('a', array(
2281
- 'href' => $this->get_link(),
2282
- ), __('Cancel', 'fw'));
2283
- echo '</p>';
2284
-
2285
- return $data;
2286
- }
2287
-
2288
- /**
2289
- * @param array $errors
2290
- * @return array
2291
- * @internal
2292
- */
2293
- public function _extension_settings_form_validate($errors)
2294
- {
2295
- do {
2296
- if (!current_user_can($this->can_activate())) {
2297
- $errors[] = __('You are not allowed to save extensions settings.', 'fw');
2298
- break;
2299
- }
2300
-
2301
- $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2302
-
2303
- if (!$extension) {
2304
- $errors[] = __('Invalid extension.', 'fw');
2305
- break;
2306
- }
2307
-
2308
- if (!$extension->get_settings_options()) {
2309
- $errors[] = __('Extension does not have settings options.', 'fw');
2310
- break;
2311
- }
2312
- } while(false);
2313
-
2314
- return $errors;
2315
- }
2316
-
2317
- /**
2318
- * @param array $data
2319
- * @return array
2320
- * @internal
2321
- */
2322
- public function _extension_settings_form_save($data)
2323
- {
2324
- $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2325
-
2326
- $options_before_save = (array)fw_get_db_ext_settings_option($extension->get_name());
2327
-
2328
- fw_set_db_ext_settings_option(
2329
- $extension->get_name(),
2330
- null,
2331
- array_merge(
2332
- $options_before_save,
2333
- fw_get_options_values_from_input(
2334
- $extension->get_settings_options()
2335
- )
2336
- )
2337
- );
2338
-
2339
- FW_Flash_Messages::add(
2340
- 'fw_extension_settings_saved',
2341
- __('Extensions settings successfully saved.', 'fw'),
2342
- 'success'
2343
- );
2344
-
2345
- $data['redirect'] = fw_current_url();
2346
-
2347
- do_action('fw_extension_settings_form_saved:'. $extension->get_name(), $options_before_save);
2348
-
2349
- return $data;
2350
- }
2351
-
2352
- /**
2353
- * Download an extension
2354
- *
2355
- * global $wp_filesystem; must me initialized
2356
- *
2357
- * @param string $extension_name
2358
- * @param array $data Extension data from the "available extensions" array
2359
- * @return string|WP_Error WP Filesystem path to the downloaded directory
2360
- */
2361
- private function download($extension_name, $data)
2362
- {
2363
- $wp_error_id = 'fw_extension_download';
2364
-
2365
- if (empty($data['download'])) {
2366
- return new WP_Error(
2367
- $wp_error_id,
2368
- sprintf(__('Extension "%s" has no download sources.', 'fw'), $this->get_extension_title($extension_name))
2369
- );
2370
- }
2371
-
2372
- /** @var WP_Filesystem_Base $wp_filesystem */
2373
- global $wp_filesystem;
2374
-
2375
- // create temporary directory
2376
- {
2377
- $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
2378
-
2379
- if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
2380
- // just in case it already exists, clear everything, it may contain old files
2381
- if (!$wp_filesystem->rmdir($wp_fs_tmp_dir, true)) {
2382
- return new WP_Error(
2383
- $wp_error_id,
2384
- sprintf(__('Cannot remove temporary directory: %s', 'fw'), $wp_fs_tmp_dir)
2385
- );
2386
- }
2387
- }
2388
-
2389
- if (!FW_WP_Filesystem::mkdir_recursive($wp_fs_tmp_dir)) {
2390
- return new WP_Error(
2391
- $wp_error_id,
2392
- sprintf(__('Cannot create temporary directory: %s', 'fw'), $wp_fs_tmp_dir)
2393
- );
2394
- }
2395
- }
2396
-
2397
- foreach ($data['download'] as $source => $source_data) {
2398
- switch ($source) {
2399
- case 'github':
2400
- if (empty($source_data['user_repo'])) {
2401
- return new WP_Error(
2402
- $wp_error_id,
2403
- sprintf(__('"%s" extension github source "user_repo" parameter is required', 'fw'), $this->get_extension_title($extension_name))
2404
- );
2405
- }
2406
-
2407
- {
2408
- $transient_name = 'fw_ext_mngr_gh_dl';
2409
- $transient_ttl = HOUR_IN_SECONDS;
2410
-
2411
- $cache = get_site_transient($transient_name);
2412
-
2413
- if ($cache === false) {
2414
- $cache = array();
2415
- }
2416
- }
2417
-
2418
- if (isset($cache[ $source_data['user_repo'] ])) {
2419
- $download_link = $cache[ $source_data['user_repo'] ]['zipball_url'];
2420
- } else {
2421
- $http = new WP_Http();
2422
-
2423
- $response = $http->get(
2424
- apply_filters('fw_github_api_url', 'https://api.github.com')
2425
- . '/repos/'. $source_data['user_repo'] .'/releases/latest'
2426
- );
2427
-
2428
- unset($http);
2429
-
2430
- $response_code = intval(wp_remote_retrieve_response_code($response));
2431
-
2432
- if ($response_code !== 200) {
2433
- if ($response_code === 403) {
2434
- $json_response = json_decode($response['body'], true);
2435
-
2436
- if ($json_response) {
2437
- return new WP_Error(
2438
- $wp_error_id,
2439
- __('Github error:', 'fw') .' '. $json_response['message']
2440
- );
2441
- }
2442
- } elseif ($response_code) {
2443
- return new WP_Error(
2444
- $wp_error_id,
2445
- sprintf(
2446
- __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
2447
- $source_data['user_repo'], $response_code
2448
- )
2449
- );
2450
- } elseif (is_wp_error($response)) {
2451
- return new WP_Error(
2452
- $wp_error_id,
2453
- sprintf(
2454
- __( 'Failed to access Github repository "%s" releases. (%s)', 'fw' ),
2455
- $source_data['user_repo'], $response->get_error_message()
2456
- )
2457
- );
2458
- } else {
2459
- return new WP_Error(
2460
- $wp_error_id,
2461
- sprintf(
2462
- __( 'Failed to access Github repository "%s" releases.', 'fw' ),
2463
- $source_data['user_repo']
2464
- )
2465
- );
2466
- }
2467
- }
2468
-
2469
- $release = json_decode($response['body'], true);
2470
-
2471
- unset($response);
2472
-
2473
- if (empty($release)) {
2474
- return new WP_Error(
2475
- $wp_error_id,
2476
- sprintf(
2477
- __('"%s" extension github repository "%s" has no releases.', 'fw'),
2478
- $this->get_extension_title($extension_name), $source_data['user_repo']
2479
- )
2480
- );
2481
- }
2482
-
2483
- {
2484
- $cache[ $source_data['user_repo'] ] = array(
2485
- 'zipball_url' => 'https://github.com/'. $source_data['user_repo'] .'/archive/'. $release['tag_name'] .'.zip',
2486
- 'tag_name' => $release['tag_name']
2487
- );
2488
-
2489
- set_site_transient($transient_name, $cache, $transient_ttl);
2490
- }
2491
-
2492
- $download_link = $cache[ $source_data['user_repo'] ]['zipball_url'];
2493
-
2494
- unset($release);
2495
- }
2496
-
2497
- {
2498
- $http = new WP_Http();
2499
-
2500
- $response = $http->request($download_link, array(
2501
- 'timeout' => $this->download_timeout,
2502
- ));
2503
-
2504
- unset($http);
2505
-
2506
- if (($response_code = intval(wp_remote_retrieve_response_code($response))) !== 200) {
2507
- if ($response_code) {
2508
- return new WP_Error(
2509
- $wp_error_id,
2510
- sprintf( __( 'Cannot download the "%s" extension zip. (Response code: %d)', 'fw' ),
2511
- $this->get_extension_title( $extension_name ), $response_code
2512
- )
2513
- );
2514
- } elseif (is_wp_error($response)) {
2515
- return new WP_Error(
2516
- $wp_error_id,
2517
- sprintf( __( 'Cannot download the "%s" extension zip. %s', 'fw' ),
2518
- $this->get_extension_title( $extension_name ),
2519
- $response->get_error_message()
2520
- )
2521
- );
2522
- } else {
2523
- return new WP_Error(
2524
- $wp_error_id,
2525
- sprintf( __( 'Cannot download the "%s" extension zip.', 'fw' ),
2526
- $this->get_extension_title( $extension_name )
2527
- )
2528
- );
2529
- }
2530
- }
2531
-
2532
- $zip_path = $wp_fs_tmp_dir .'/temp.zip';
2533
-
2534
- // save zip to file
2535
- if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
2536
- return new WP_Error(
2537
- $wp_error_id,
2538
- sprintf(__('Cannot save the "%s" extension zip.', 'fw'), $this->get_extension_title($extension_name))
2539
- );
2540
- }
2541
-
2542
- unset($response);
2543
-
2544
- $unzip_result = unzip_file(
2545
- FW_WP_Filesystem::filesystem_path_to_real_path($zip_path),
2546
- $wp_fs_tmp_dir
2547
- );
2548
-
2549
- if (is_wp_error($unzip_result)) {
2550
- return $unzip_result;
2551
- }
2552
-
2553
- // remove zip file
2554
- if (!$wp_filesystem->delete($zip_path, false, 'f')) {
2555
- return new WP_Error(
2556
- $wp_error_id,
2557
- sprintf(__('Cannot remove the "%s" extension downloaded zip.', 'fw'), $this->get_extension_title($extension_name))
2558
- );
2559
- }
2560
-
2561
- $unzipped_dir_files = $wp_filesystem->dirlist($wp_fs_tmp_dir);
2562
-
2563
- if (!$unzipped_dir_files) {
2564
- return new WP_Error(
2565
- $wp_error_id,
2566
- __('Cannot access the unzipped directory files.', 'fw')
2567
- );
2568
- }
2569
-
2570
- /**
2571
- * get first found directory
2572
- * (if everything worked well, there should be only one directory)
2573
- */
2574
- foreach ($unzipped_dir_files as $file) {
2575
- if ($file['type'] == 'd') {
2576
- return $wp_fs_tmp_dir .'/'. $file['name'];
2577
- }
2578
- }
2579
-
2580
- return new WP_Error(
2581
- $wp_error_id,
2582
- sprintf(__('The unzipped "%s" extension directory not found.', 'fw'), $this->get_extension_title($extension_name))
2583
- );
2584
- }
2585
- break;
2586
- default:
2587
- return new WP_Error(
2588
- $wp_error_id,
2589
- sprintf(__('Unknown "%s" extension download source "%s"', 'fw'), $this->get_extension_title($extension_name), $source)
2590
- );
2591
- }
2592
- }
2593
- }
2594
-
2595
- /**
2596
- * Merge the downloaded extension directory with the existing directory
2597
- *
2598
- * @param string $source_wp_fs_dir Downloaded extension directory
2599
- * @param string $destination_wp_fs_dir
2600
- *
2601
- * @return null|WP_Error
2602
- */
2603
- private function merge_extension($source_wp_fs_dir, $destination_wp_fs_dir)
2604
- {
2605
- /** @var WP_Filesystem_Base $wp_filesystem */
2606
- global $wp_filesystem;
2607
-
2608
- $wp_error_id = 'fw_extensions_merge';
2609
-
2610
- $source_files = $wp_filesystem->dirlist($source_wp_fs_dir);
2611
-
2612
- if ($source_files === false) {
2613
- return new WP_Error(
2614
- $wp_error_id,
2615
- sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir)
2616
- );
2617
- }
2618
-
2619
- if (empty($source_files)) {
2620
- // directory is empty, nothing to move
2621
- return;
2622
- }
2623
-
2624
- /**
2625
- * Prepare destination directory
2626
- * Remove everything except the extensions/ directory
2627
- */
2628
- if ($wp_filesystem->exists($destination_wp_fs_dir)) {
2629
- $destination_files = $wp_filesystem->dirlist($destination_wp_fs_dir);
2630
-
2631
- if ($destination_files === false) {
2632
- return new WP_Error(
2633
- $wp_error_id,
2634
- sprintf(__('Cannot read directory "%s".', 'fw'), $destination_wp_fs_dir)
2635
- );
2636
- }
2637
-
2638
- if (!empty($destination_files)) {
2639
- // the directory contains some files, delete everything
2640
- foreach ($destination_files as $file) {
2641
- if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2642
- // do not touch the extensions/ directory
2643
- continue;
2644
- }
2645
-
2646
- if (!$wp_filesystem->delete($destination_wp_fs_dir .'/'. $file['name'], true, $file['type'])) {
2647
- return new WP_Error(
2648
- $wp_error_id,
2649
- sprintf(__('Cannot delete "%s".', 'fw'), $destination_wp_fs_dir .'/'. $file['name'])
2650
- );
2651
- }
2652
- }
2653
-
2654
- unset($destination_files);
2655
- }
2656
- } else {
2657
- if (!FW_WP_Filesystem::mkdir_recursive($destination_wp_fs_dir)) {
2658
- return new WP_Error(
2659
- $wp_error_id,
2660
- sprintf(__('Cannot create the "%s" directory.', 'fw'), $destination_wp_fs_dir)
2661
- );
2662
- }
2663
- }
2664
-
2665
- $has_sub_extensions = false;
2666
-
2667
- foreach ($source_files as $file) {
2668
- if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2669
- // do not touch the extensions/ directory
2670
- $has_sub_extensions = true;
2671
- continue;
2672
- }
2673
-
2674
- if (!$wp_filesystem->move($source_wp_fs_dir .'/'. $file['name'], $destination_wp_fs_dir .'/'. $file['name'])) {
2675
- return new WP_Error(
2676
- $wp_error_id,
2677
- sprintf(
2678
- __('Cannot move "%s" to "%s".', 'fw'),
2679
- $source_wp_fs_dir .'/'. $file['name'],
2680
- $destination_wp_fs_dir .'/'. $file['name']
2681
- )
2682
- );
2683
- }
2684
- }
2685
-
2686
- unset($source_files);
2687
-
2688
- if (!$has_sub_extensions) {
2689
- return;
2690
- }
2691
-
2692
- $sub_extensions = $wp_filesystem->dirlist($source_wp_fs_dir .'/extensions');
2693
-
2694
- if ($sub_extensions === false) {
2695
- return new WP_Error(
2696
- $wp_error_id,
2697
- sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir .'/extensions')
2698
- );
2699
- }
2700
-
2701
- if (empty($sub_extensions)) {
2702
- // directory is empty, nothing to remove
2703
- return;
2704
- }
2705
-
2706
- foreach ($sub_extensions as $file) {
2707
- if ($file['type'] !== 'd') {
2708
- // wrong, only directories must exist in the extensions/ directory
2709
- continue;
2710
- }
2711
-
2712
- $merge_result = $this->merge_extension(
2713
- $source_wp_fs_dir .'/extensions/'. $file['name'],
2714
- $destination_wp_fs_dir .'/extensions/'. $file['name']
2715
- );
2716
-
2717
- if (is_wp_error($merge_result)) {
2718
- return $merge_result;
2719
- }
2720
- }
2721
- }
2722
-
2723
- private function get_supported_extensions_for_install()
2724
- {
2725
- $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2726
-
2727
- if (empty($supported_extensions)) {
2728
- return array();
2729
- }
2730
-
2731
- // remove not available extensions
2732
- $supported_extensions = array_intersect_key($supported_extensions, $this->get_available_extensions());
2733
-
2734
- if (empty($supported_extensions)) {
2735
- return array();
2736
- }
2737
-
2738
- // remove already installed extensions
2739
- $supported_extensions = array_diff_key($supported_extensions, $this->get_installed_extensions());
2740
-
2741
- if (empty($supported_extensions)) {
2742
- return array();
2743
- }
2744
-
2745
- return $supported_extensions;
2746
- }
2747
-
2748
- /**
2749
- * @param $actions
2750
- * @return array
2751
- * @internal
2752
- */
2753
- public function _filter_plugin_action_list($actions)
2754
- {
2755
- return array_merge(
2756
- array(
2757
- 'fw-extensions' => fw_html_tag('a', array(
2758
- 'href' => $this->get_link(),
2759
- ), fw()->manifest->get_name()),
2760
- ),
2761
- $actions
2762
- );
2763
- }
2764
-
2765
- /**
2766
- * @return string Extensions page link
2767
- */
2768
- private function get_link()
2769
- {
2770
- static $cache_link = null;
2771
-
2772
- if ($cache_link === null) {
2773
- $cache_link = menu_page_url( $this->get_page_slug(), false );
2774
-
2775
- // https://core.trac.wordpress.org/ticket/28226
2776
- if (is_multisite() && is_network_admin()) {
2777
- $cache_link = self_admin_url(
2778
- // extract relative link
2779
- preg_replace('/^'. preg_quote(admin_url(), '/') .'/', '', $cache_link)
2780
- );
2781
- }
2782
- }
2783
-
2784
- return $cache_link;
2785
- }
2786
-
2787
- /**
2788
- * @param array $skip_extensions {'ext' => mixed}
2789
- * @param array $check_for_deps ['ext', 'ext', ...] Extensions to check if has in dependencies the used extensions
2790
- *
2791
- * @return array
2792
- */
2793
- private function get_used_extensions($skip_extensions, $check_for_deps)
2794
- {
2795
- $used_extensions = array();
2796
-
2797
- $installed_extensions = $this->get_installed_extensions();
2798
-
2799
- foreach ($installed_extensions as $inst_ext_name => &$inst_ext_data) {
2800
- if (isset($skip_extensions[ $inst_ext_name ])) {
2801
- continue;
2802
- }
2803
-
2804
- if (isset($used_extensions[$inst_ext_name])) {
2805
- // already marked as used
2806
- continue;
2807
- }
2808
-
2809
- do {
2810
- foreach ($check_for_deps as $deps_ext) {
2811
- if (isset($skip_extensions[$deps_ext])) {
2812
- continue;
2813
- }
2814
-
2815
- if (false !== fw_akg(
2816
- 'requirements/extensions/'. $inst_ext_name,
2817
- $installed_extensions[$deps_ext]['manifest'],
2818
- false
2819
- )) {
2820
- // is required by an active extension
2821
- break 2;
2822
- }
2823
- }
2824
-
2825
- if ( true === fw_akg(
2826
- 'standalone',
2827
- $inst_ext_data['manifest'],
2828
- $this->manifest_default_values['standalone']
2829
- ) ) {
2830
- // can exist alone
2831
- break;
2832
- }
2833
-
2834
- // not used
2835
- continue 2;
2836
- } while(false);
2837
-
2838
- $used_extensions[$inst_ext_name] = array();
2839
-
2840
- // Set all sub-extensions as used
2841
- foreach ($this->collect_sub_extensions($inst_ext_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2842
- if (isset($skip_extensions[$sub_extension_name])) {
2843
- continue;
2844
- }
2845
-
2846
- $used_extensions[ $sub_extension_name ] = array();
2847
- }
2848
-
2849
- // Set all parents as used
2850
- {
2851
- $current_parent = $inst_ext_name;
2852
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
2853
- $used_extensions[$current_parent] = array();
2854
- }
2855
- }
2856
- }
2857
- unset($inst_ext_data);
2858
-
2859
- // remove all skipped extensions and sub-extension from used extensions
2860
- foreach (array_keys($skip_extensions) as $skip_extension_name) {
2861
- unset($used_extensions[$skip_extension_name]);
2862
-
2863
- if (isset($installed_extensions[$skip_extension_name])) {
2864
- foreach ($this->collect_sub_extensions($skip_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2865
- unset($used_extensions[$sub_extension_name]);
2866
- }
2867
- }
2868
- }
2869
-
2870
- return $used_extensions;
2871
- }
2872
-
2873
- /**
2874
- * @internal
2875
- */
2876
- public function _action_admin_footer()
2877
- {
2878
- $this->activate_hidden_standalone_extensions();
2879
- }
2880
-
2881
- public function get_extension_title($extension_name)
2882
- {
2883
- $installed_extensions = $this->get_installed_extensions();
2884
-
2885
- if (isset($installed_extensions[$extension_name])) {
2886
- return fw_akg('name', $installed_extensions[$extension_name]['manifest'], fw_id_to_title($extension_name));
2887
- }
2888
-
2889
- unset($installed_extensions);
2890
-
2891
- $available_extensions = $this->get_available_extensions();
2892
-
2893
- if (isset($available_extensions[$extension_name])) {
2894
- return $available_extensions[$extension_name]['name'];
2895
- }
2896
-
2897
- return fw_id_to_title($extension_name);
2898
- }
2899
-
2900
- public function is_extensions_page()
2901
- {
2902
- $current_screen = get_current_screen();
2903
-
2904
- if (empty($current_screen)) {
2905
- return false;
2906
- }
2907
-
2908
- return (
2909
- property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2910
- &&
2911
- property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2912
- &&
2913
- !isset($_GET['sub-page'])
2914
- );
2915
- }
2916
-
2917
- public function is_extension_page()
2918
- {
2919
- $current_screen = get_current_screen();
2920
-
2921
- if (empty($current_screen)) {
2922
- return false;
2923
- }
2924
-
2925
- return (
2926
- property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2927
- &&
2928
- property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2929
- &&
2930
- isset($_GET['sub-page']) && $_GET['sub-page'] === 'extension'
2931
- );
2932
- }
2933
-
2934
- /**
2935
- * @internal
2936
- */
2937
- public function _action_enqueue_scripts()
2938
- {
2939
- wp_enqueue_style(
2940
- 'fw-extensions-menu-icon',
2941
- $this->get_uri('/static/unyson-font-icon/style.css'),
2942
- array(),
2943
- fw()->manifest->get_version()
2944
- );
2945
-
2946
- /**
2947
- * Enqueue only on Extensions List page
2948
- */
2949
- if ($this->is_extensions_page()) {
2950
- wp_enqueue_style(
2951
- 'fw-extensions-page',
2952
- $this->get_uri('/static/extensions-page.css'),
2953
- array(
2954
- 'fw',
2955
- 'fw-unycon', 'fw-font-awesome', // in case some extension has font-icon thumbnail
2956
- ),
2957
- fw()->manifest->get_version()
2958
- );
2959
- wp_enqueue_script(
2960
- 'fw-extensions-page',
2961
- $this->get_uri('/static/extensions-page.js'),
2962
- array('fw'),
2963
- fw()->manifest->get_version(),
2964
- true
2965
- );
2966
- wp_localize_script('fw-extensions-page', '_fw_extensions_script_data', array(
2967
- 'link' => $this->get_link(),
2968
- ));
2969
-
2970
- /**
2971
- * this is needed for fw.soleModal design
2972
- * it is displayed when extension ajax install returns an error
2973
- */
2974
- wp_enqueue_media();
2975
- }
2976
-
2977
- if ($this->is_extension_page()) {
2978
- wp_enqueue_style(
2979
- 'fw-extension-page',
2980
- $this->get_uri('/static/extension-page.css'),
2981
- array('fw'),
2982
- fw()->manifest->get_version()
2983
- );
2984
- wp_enqueue_script(
2985
- 'fw-extension-page',
2986
- $this->get_uri('/static/extension-page.js'),
2987
- array('fw'),
2988
- fw()->manifest->get_version(),
2989
- true
2990
- );
2991
-
2992
- /**
2993
- * Enqueue extension settings options static
2994
- */
2995
- if (
2996
- isset($_GET['extension'])
2997
- &&
2998
- is_string($extension_name = $_GET['extension'])
2999
- &&
3000
- fw()->extensions->get($extension_name)
3001
- &&
3002
- ($extension_settings_options = fw()->extensions->get($extension_name)->get_settings_options())
3003
- ) {
3004
- fw()->backend->enqueue_options_static($extension_settings_options);
3005
- }
3006
- }
3007
- }
3008
-
3009
- private function activate_theme_extensions()
3010
- {
3011
- $db_active_extensions = fw()->extensions->_get_db_active_extensions();
3012
-
3013
- foreach ($this->get_installed_extensions() as $extension_name => $extension) {
3014
- if ($extension['is']['theme']) {
3015
- $db_active_extensions[ $extension_name ] = array();
3016
- }
3017
- }
3018
-
3019
- update_option(
3020
- fw()->extensions->_get_active_extensions_db_option_name(),
3021
- $db_active_extensions
3022
- );
3023
- }
3024
-
3025
- /**
3026
- * @internal
3027
- */
3028
- public function _action_theme_switch()
3029
- {
3030
- $this->activate_theme_extensions();
3031
- $this->activate_extensions(
3032
- array_fill_keys(
3033
- array_keys(fw()->theme->manifest->get('supported_extensions', array())),
3034
- array()
3035
- )
3036
- );
3037
- }
3038
-
3039
- /**
3040
- * @param array $collected The found extensions {'extension_name' => array()}
3041
- * @param array $extensions {'extension_name' => array()}
3042
- * @param bool $check_all Check all extensions or only active extensions
3043
- */
3044
- private function collect_extensions_that_requires(&$collected, $extensions, $check_all = false)
3045
- {
3046
- if (empty($extensions)) {
3047
- return;
3048
- }
3049
-
3050
- $found_extensions = array();
3051
-
3052
- foreach ($this->get_installed_extensions() as $extension_name => $extension_data) {
3053
- if (isset($collected[$extension_name])) {
3054
- continue;
3055
- }
3056
-
3057
- if (!$check_all) {
3058
- if (!fw_ext($extension_name)) {
3059
- continue;
3060
- }
3061
- }
3062
-
3063
- if (
3064
- array_intersect_key(
3065
- $extensions,
3066
- fw_akg(
3067
- 'requirements/extensions',
3068
- $extension_data['manifest'],
3069
- array()
3070
- )
3071
- )
3072
- ) {
3073
- $found_extensions[$extension_name] = $collected[$extension_name] = array();
3074
- }
3075
- }
3076
-
3077
- $this->collect_extensions_that_requires($collected, $found_extensions, $check_all);
3078
- }
3079
-
3080
- /**
3081
- * Get extension settings page link
3082
- * @param string $extension_name
3083
- * @return string
3084
- */
3085
- public function get_extension_link($extension_name)
3086
- {
3087
- return $this->get_link() .'&sub-page=extension&extension='. $extension_name;
3088
- }
3089
-
3090
- /**
3091
- * @param string $extension_name
3092
- * @return array|WP_Error Extensions to merge with db active extensions list
3093
- */
3094
- private function get_extensions_for_activation($extension_name)
3095
- {
3096
- $installed_extensions = $this->get_installed_extensions();
3097
-
3098
- $wp_error_id = 'fw_ext_activation';
3099
-
3100
- if (!isset($installed_extensions[$extension_name])) {
3101
- return new WP_Error($wp_error_id,
3102
- sprintf(
3103
- __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3104
- $this->get_extension_title($extension_name),
3105
- fw_html_tag('a', array(
3106
- 'href' => $this->get_link() .'&sub-page=install&extension='. $extension_name
3107
- ), __('Install', 'fw'))
3108
- )
3109
- );
3110
- }
3111
-
3112
- {
3113
- $extension_parents = array($extension_name);
3114
-
3115
- $current_parent = $extension_name;
3116
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
3117
- $extension_parents[] = $current_parent;
3118
- }
3119
-
3120
- $extension_parents = array_reverse($extension_parents);
3121
- }
3122
-
3123
- $extensions = array();
3124
-
3125
- foreach ($extension_parents as $parent_extension_name) {
3126
- $extensions[ $parent_extension_name ] = array();
3127
- }
3128
-
3129
- // search sub-extensions
3130
- foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3131
- $extensions[ $sub_extension_name ] = array();
3132
- }
3133
-
3134
- // search required extensions
3135
- {
3136
- $pending_required_search = $extensions;
3137
-
3138
- while ($pending_required_search) {
3139
- foreach (array_keys($pending_required_search) as $pend_req_extension_name) {
3140
- unset($pending_required_search[$pend_req_extension_name]);
3141
-
3142
- unset($required_extensions); // reset reference
3143
- $required_extensions = array();
3144
- $this->collect_required_extensions($pend_req_extension_name, $installed_extensions, $required_extensions);
3145
-
3146
- foreach ($required_extensions as $required_extension_name => $required_extension_data) {
3147
- if (!isset($installed_extensions[$required_extension_name])) {
3148
- return new WP_Error($wp_error_id,
3149
- sprintf(
3150
- __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3151
- $this->get_extension_title($required_extension_name),
3152
- fw_html_tag('a', array(
3153
- 'href' => $this->get_link() .'&sub-page=install&extension='. $required_extension_name
3154
- ), __('Install', 'fw'))
3155
- )
3156
- );
3157
- }
3158
-
3159
- $extensions[$required_extension_name] = array();
3160
-
3161
- // search sub-extensions
3162
- foreach ($this->collect_sub_extensions($required_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3163
- if (isset($extensions[$sub_extension_name])) {
3164
- continue;
3165
- }
3166
-
3167
- $extensions[$sub_extension_name] = array();
3168
-
3169
- $pending_required_search[$sub_extension_name] = array();
3170
- }
3171
- }
3172
- }
3173
- }
3174
- }
3175
-
3176
- return $extensions;
3177
- }
3178
-
3179
- public function _action_admin_notices() {
3180
- /**
3181
- * In v2.4.12 was done a terrible mistake https://github.com/ThemeFuse/Unyson-Extensions-Approval/issues/160
3182
- * Show a warning with link to install theme supported extensions
3183
- */
3184
- if (
3185
- !isset($_GET['supported']) // already on 'Install Supported Extensions' page
3186
- &&
3187
- $this->can_install()
3188
- &&
3189
- (($installed_extensions = $this->get_installed_extensions()) || true)
3190
- &&
3191
- !isset($installed_extensions['page-builder'])
3192
- &&
3193
- $this->get_supported_extensions_for_install()
3194
- ) {
3195
- echo '<div class="error"> <p>'
3196
- , fw_html_tag('a', array('href' => $this->get_link() .'&sub-page=install&supported'),
3197
- __('Install theme compatible extensions', 'fw'))
3198
- , '</p></div>';
3199
- }
3200
- }
3201
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Install/Activate/Deactivate/Remove Extensions
5
+ * @internal
6
+ */
7
+ final class _FW_Extensions_Manager
8
+ {
9
+ /**
10
+ * @var FW_Form
11
+ */
12
+ private $extension_settings_form;
13
+
14
+ /**
15
+ * @var Parsedown
16
+ */
17
+ private $markdown_parser;
18
+
19
+ private $manifest_default_values = array(
20
+ 'display' => false,
21
+ 'standalone' => false,
22
+ );
23
+
24
+ private $download_timeout = 300;
25
+
26
+ private $default_thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
27
+
28
+ public function __construct()
29
+ {
30
+ // In any case/permission, make sure to not miss the plugin update actions to prevent extensions delete
31
+ {
32
+ add_action('fw_plugin_pre_update', array($this, '_action_plugin_pre_update'));
33
+ add_action('fw_plugin_post_update', array($this, '_action_plugin_post_update'));
34
+ }
35
+
36
+ if (!is_admin()) {
37
+ return;
38
+ }
39
+
40
+ if (!$this->can_activate() && !$this->can_install()) {
41
+ return;
42
+ }
43
+
44
+ /** Actions */
45
+ {
46
+ add_action('fw_init', array($this, '_action_fw_init'));
47
+ add_action('admin_menu', array($this, '_action_admin_menu'));
48
+ add_action('network_admin_menu', array($this, '_action_admin_menu'));
49
+ add_action('admin_footer', array($this, '_action_admin_footer'));
50
+ add_action('admin_enqueue_scripts', array($this, '_action_enqueue_scripts'));
51
+ add_action('fw_after_plugin_activate', array($this, '_action_after_plugin_activate'), 100);
52
+ add_action('after_switch_theme', array($this, '_action_theme_switch'));
53
+ add_action('admin_notices', array($this, '_action_admin_notices'));
54
+
55
+ if ($this->can_install()) {
56
+ add_action('wp_ajax_fw_extensions_check_direct_fs_access', array($this, '_action_ajax_check_direct_fs_access'));
57
+ add_action('wp_ajax_fw_extensions_install', array($this, '_action_ajax_install'));
58
+ add_action('wp_ajax_fw_extensions_uninstall', array($this, '_action_ajax_uninstall'));
59
+ }
60
+ }
61
+
62
+ /** Filters */
63
+ {
64
+ add_filter('fw_plugin_action_list', array($this, '_filter_plugin_action_list'));
65
+ }
66
+ }
67
+
68
+ /**
69
+ * If current user can:
70
+ * - activate extension
71
+ * - disable extensions
72
+ * - save extension settings options
73
+ * @return bool
74
+ */
75
+ public function can_activate()
76
+ {
77
+ static $can_activate = null;
78
+
79
+ if ($can_activate === null) {
80
+ $can_activate = current_user_can('manage_options');
81
+
82
+ if ($can_activate) {
83
+ // also you can use this method to get the capability
84
+ $can_activate = 'manage_options';
85
+ }
86
+
87
+ if (!$can_activate) {
88
+ // make sure if can install, then also can activate. (can install) > (can activate)
89
+ $can_activate = $this->can_install();
90
+ }
91
+ }
92
+
93
+ return $can_activate;
94
+ }
95
+
96
+ /**
97
+ * If current user can:
98
+ * - install extensions
99
+ * - delete extensions
100
+ * @return bool
101
+ */
102
+ public function can_install()
103
+ {
104
+ static $can_install = null;
105
+
106
+ if ($can_install === null) {
107
+ $capability = 'install_plugins';
108
+
109
+ if (is_multisite()) {
110
+ // only network admin can change files that affects the entire network
111
+ $can_install = current_user_can_for_blog(get_current_blog_id(), $capability);
112
+ } else {
113
+ $can_install = current_user_can($capability);
114
+ }
115
+
116
+ if ($can_install) {
117
+ // also you can use this method to get the capability
118
+ $can_install = $capability;
119
+ }
120
+ }
121
+
122
+ return $can_install;
123
+ }
124
+
125
+ public function get_page_slug()
126
+ {
127
+ return 'fw-extensions';
128
+ }
129
+
130
+ private function get_cache_key($sub_key)
131
+ {
132
+ return 'fw_extensions_manager/'. $sub_key;
133
+ }
134
+
135
+ private function get_uri($append = '')
136
+ {
137
+ return fw_get_framework_directory_uri('/core/components/extensions/manager'. $append);
138
+ }
139
+
140
+ private function get_markdown_parser()
141
+ {
142
+ if (!$this->markdown_parser) {
143
+ if (!class_exists('Parsedown')) {
144
+ require_once dirname(__FILE__) .'/includes/parsedown/Parsedown.php';
145
+ }
146
+
147
+ $this->markdown_parser = new Parsedown();
148
+ }
149
+
150
+ return $this->markdown_parser;
151
+ }
152
+
153
+ private function get_nonce($form) {
154
+ switch ($form) {
155
+ case 'install':
156
+ return array(
157
+ 'name' => '_nonce_fw_extensions_install',
158
+ 'action' => 'install',
159
+ );
160
+ case 'delete':
161
+ return array(
162
+ 'name' => '_nonce_fw_extensions_delete',
163
+ 'action' => 'delete',
164
+ );
165
+ case 'activate':
166
+ return array(
167
+ 'name' => '_nonce_fw_extensions_activate',
168
+ 'action' => 'activate',
169
+ );
170
+ case 'deactivate':
171
+ return array(
172
+ 'name' => '_nonce_fw_extensions_deactivate',
173
+ 'action' => 'deactivate',
174
+ );
175
+ default:
176
+ return array(
177
+ 'name' => '_nonce_fw_extensions',
178
+ 'action' => 'default',
179
+ );
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Extensions available for download
185
+ * @return array {name => data}
186
+ */
187
+ private function get_available_extensions()
188
+ {
189
+ try {
190
+ $cache_key = $this->get_cache_key( 'available_extensions' );
191
+
192
+ return FW_Cache::get($cache_key);
193
+ } catch (FW_Cache_Not_Found_Exception $e) {
194
+ $vars = fw_get_variables_from_file( dirname( __FILE__ ) . '/available-extensions.php', array(
195
+ 'extensions' => array()
196
+ ) );
197
+
198
+ {
199
+ $installed_extensions = $this->get_installed_extensions();
200
+ $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
201
+
202
+ if (isset($installed_extensions['backup'])) {
203
+ // make sure only Backup or Backups can be installed
204
+ unset($vars['extensions']['backups']);
205
+ }
206
+
207
+ foreach (
208
+ array('backup', 'styling', 'translation', 'learning')
209
+ as $obsolete_extension
210
+ ) {
211
+ if (
212
+ !isset($supported_extensions[$obsolete_extension])
213
+ &&
214
+ !isset($installed_extensions[$obsolete_extension])
215
+ ) {
216
+ unset($vars['extensions'][$obsolete_extension]);
217
+ }
218
+ }
219
+ }
220
+
221
+ FW_Cache::set($cache_key, $vars['extensions']);
222
+
223
+ return $vars['extensions'];
224
+ }
225
+ }
226
+
227
+ /**
228
+ * @internal
229
+ */
230
+ public function _action_ajax_check_direct_fs_access()
231
+ {
232
+ if (!$this->can_install()) {
233
+ // if can't install, no need to know if has access or not
234
+ wp_send_json_error();
235
+ }
236
+
237
+ if (FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
238
+ wp_send_json_success();
239
+ } else {
240
+ wp_send_json_error();
241
+ }
242
+ }
243
+
244
+ /**
245
+ * @internal
246
+ */
247
+ public function _action_ajax_install()
248
+ {
249
+ if (!$this->can_install()) {
250
+ // if can't install, no need to know if has access or not
251
+ wp_send_json_error();
252
+ }
253
+
254
+ if (!FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
255
+ wp_send_json_error();
256
+ }
257
+
258
+ $extension = (string)FW_Request::POST('extension');
259
+
260
+ $install_result = $this->install_extensions(array(
261
+ $extension => array()
262
+ ), array(
263
+ 'cancel_on_error' => true
264
+ ));
265
+
266
+ if ($install_result === true) {
267
+ wp_send_json_success();
268
+ } else {
269
+ wp_send_json_error($install_result);
270
+ }
271
+ }
272
+
273
+ /**
274
+ * @internal
275
+ */
276
+ public function _action_ajax_uninstall()
277
+ {
278
+ if (!$this->can_install()) {
279
+ // if can't install, no need to know if has access or not
280
+ wp_send_json_error();
281
+ }
282
+
283
+ if (!FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
284
+ wp_send_json_error();
285
+ }
286
+
287
+ $extension = (string)FW_Request::POST('extension');
288
+
289
+ $install_result = $this->uninstall_extensions(array(
290
+ $extension => array()
291
+ ), array(
292
+ 'cancel_on_error' => true
293
+ ));
294
+
295
+ if ($install_result === true) {
296
+ wp_send_json_success();
297
+ } else {
298
+ wp_send_json_error($install_result);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * @internal
304
+ */
305
+ public function _action_after_plugin_activate()
306
+ {
307
+ $this->activate_theme_extensions();
308
+ $this->activate_extensions(
309
+ array_fill_keys(
310
+ array_keys(fw()->theme->manifest->get('supported_extensions', array())),
311
+ array()
312
+ )
313
+ );
314
+
315
+ if ($this->can_install()) {
316
+ if ($this->get_supported_extensions_for_install()) {
317
+ $link = $this->get_link();
318
+
319
+ wp_redirect($link . '&sub-page=install&supported');
320
+ exit;
321
+ }
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Copy all extensions to a temp backup directory
327
+ * @internal
328
+ */
329
+ public function _action_plugin_pre_update()
330
+ {
331
+ /** @var WP_Filesystem_Base $wp_filesystem */
332
+ global $wp_filesystem;
333
+
334
+ if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
335
+ return;
336
+ }
337
+
338
+ // a directory outside the plugin
339
+ $tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
340
+ fw_fix_path(WP_CONTENT_DIR) .'/tmp/fw-plugin-update-extensions-backup'
341
+ );
342
+ $extensions_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
343
+ fw_get_framework_directory('/extensions')
344
+ );
345
+
346
+ $error = false;
347
+
348
+ do {
349
+ if ($wp_filesystem->exists($tmp_dir)) {
350
+ if (!$wp_filesystem->delete($tmp_dir, true, 'd')) {
351
+ $error = __('Cannot remove the old extensions backup dir', 'fw');
352
+ break;
353
+ }
354
+ }
355
+
356
+ if (!FW_WP_Filesystem::mkdir_recursive($tmp_dir)) {
357
+ $error = __('Cannot create the extensions backup dir', 'fw');
358
+ break;
359
+ }
360
+
361
+ if (true !== copy_dir($extensions_dir, $tmp_dir)) {
362
+ $error = __('Cannot backup the extensions', 'fw');
363
+ break;
364
+ }
365
+ } while(false);
366
+
367
+ if ($error) {
368
+ trigger_error($error, E_USER_WARNING);
369
+
370
+ $wp_filesystem->delete($tmp_dir, true, 'd');
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Copy all extensions from the temp backup directory to the framework extensions directory (recover)
376
+ * @internal
377
+ */
378
+ public function _action_plugin_post_update()
379
+ {
380
+ /** @var WP_Filesystem_Base $wp_filesystem */
381
+ global $wp_filesystem;
382
+
383
+ if ( !$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) ) {
384
+ return;
385
+ }
386
+
387
+ // a directory outside the plugin
388
+ $tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
389
+ fw_fix_path( WP_CONTENT_DIR ) .'/tmp/fw-plugin-update-extensions-backup'
390
+ );
391
+ $extensions_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
392
+ fw_get_framework_directory( '/extensions' )
393
+ );
394
+
395
+ if (!$wp_filesystem->exists($tmp_dir) || !$wp_filesystem->exists($extensions_dir)) {
396
+ return;
397
+ }
398
+
399
+ $error = false;
400
+
401
+ do {
402
+ if ($wp_filesystem->exists($extensions_dir)) {
403
+ /**
404
+ * Make sure to remove framework initial extensions
405
+ * The user do not need them because he already used the framework and has in backup the extensions he uses
406
+ */
407
+ if (!$wp_filesystem->delete( $extensions_dir, true, 'd' )) {
408
+ $error = __( 'Cannot clear the extensions directory', 'fw' );
409
+ break;
410
+ }
411
+
412
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $extensions_dir ) ) {
413
+ $error = __( 'Cannot recreate the extensions directory', 'fw' );
414
+ break;
415
+ }
416
+ }
417
+
418
+ if (true !== copy_dir($tmp_dir, $extensions_dir)) {
419
+ $error = __('Cannot recover the extensions', 'fw');
420
+ break;
421
+ }
422
+ } while(false);
423
+
424
+ if ($error) {
425
+ trigger_error($error, E_USER_WARNING);
426
+ } else {
427
+ // extensions successfully recovered, the backup is not needed anymore
428
+ $wp_filesystem->delete($tmp_dir, true, 'd');
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Scan all directories for extensions
434
+ *
435
+ * @param bool $reset_cache
436
+ * @return array
437
+ */
438
+ private function get_installed_extensions($reset_cache = false)
439
+ {
440
+ $cache_key = $this->get_cache_key('installed_extensions');
441
+
442
+ if ($reset_cache) {
443
+ FW_Cache::del($cache_key);
444
+ }
445
+
446
+ try {
447
+ return FW_Cache::get($cache_key);
448
+ } catch (FW_Cache_Not_Found_Exception $e) {
449
+ $extensions = array();
450
+
451
+ foreach (fw()->extensions->get_locations() as $location) {
452
+ // leave only used keys
453
+ $location = array(
454
+ 'path' => $location['path'],
455
+ 'is' => $location['is'],
456
+ );
457
+
458
+ $this->read_extensions($location, $extensions);
459
+ }
460
+
461
+ FW_Cache::set($cache_key, $extensions);
462
+
463
+ return $extensions;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * used by $this->get_installed_extensions()
469
+ * @param string $location
470
+ * @param array $list
471
+ * @param null|string $parent_extension_name
472
+ */
473
+ private function read_extensions($location, &$list, $parent_extension_name = null)
474
+ {
475
+ $paths = glob($location['path'] .'/*', GLOB_ONLYDIR | GLOB_NOSORT);
476
+
477
+ if (empty($paths)) {
478
+ return;
479
+ }
480
+
481
+ foreach ($paths as $extension_path) {
482
+ $extension_name = basename($extension_path);
483
+
484
+ if (isset($list[$extension_name])) {
485
+ // extension already found
486
+ } elseif (file_exists($extension_path .'/manifest.php')) {
487
+ $vars = fw_get_variables_from_file($extension_path .'/manifest.php', array(
488
+ 'manifest' => array(),
489
+ ));
490
+
491
+ $list[$extension_name] = array(
492
+ 'path' => $extension_path,
493
+ 'manifest' => $vars['manifest'],
494
+ 'children' => array(),
495
+ 'active' => (bool)fw()->extensions->get($extension_name),
496
+ 'parent' => $parent_extension_name,
497
+ 'is' => $location['is'],
498
+ );
499
+
500
+ if ($parent_extension_name) {
501
+ $list[ $parent_extension_name ]['children'][$extension_name] = array();
502
+ }
503
+ } else {
504
+ // it's a directory with customizations for an extension
505
+ continue;
506
+ }
507
+
508
+ $sub_extension_location = $location;
509
+ $sub_extension_location['path'] .= '/'. $extension_name .'/extensions';
510
+
511
+ $this->read_extensions(
512
+ $sub_extension_location,
513
+ $list,
514
+ $extension_name
515
+ );
516
+ }
517
+ }
518
+
519
+ private function get_tmp_dir($append = '')
520
+ {
521
+ return apply_filters('fw_tmp_dir', fw_fix_path(WP_CONTENT_DIR) .'/tmp') . $append;
522
+ }
523
+
524
+ /**
525
+ * @internal
526
+ */
527
+ public function _action_fw_init()
528
+ {
529
+ $this->extension_settings_form = new FW_Form('fw_extension_settings', array(
530
+ 'render' => array($this, '_extension_settings_form_render'),
531
+ 'validate' => array($this, '_extension_settings_form_validate'),
532
+ 'save' => array($this, '_extension_settings_form_save'),
533
+ ));
534
+
535
+ if (is_admin() && $this->can_activate()) {
536
+ $db_wp_option_name = 'fw_extensions_activation';
537
+
538
+ if ($db_wp_option_value = get_option($db_wp_option_name, array())) {
539
+ $db_wp_option_value = array_merge(array(
540
+ 'activated' => array(),
541
+ 'deactivated' => array(),
542
+ ), $db_wp_option_value);
543
+
544
+ /**
545
+ * Fire the 'fw_extensions_after_activation' action
546
+ */
547
+ if ($db_wp_option_value['activated']) {
548
+ $succeeded_extensions = $failed_extensions = array();
549
+
550
+ foreach ($db_wp_option_value['activated'] as $extension_name => $not_used_var) {
551
+ if (fw_ext($extension_name)) {
552
+ $succeeded_extensions[$extension_name] = array();
553
+ } else {
554
+ $failed_extensions[$extension_name] = array();
555
+ }
556
+ }
557
+
558
+ if (!empty($succeeded_extensions)) {
559
+ do_action('fw_extensions_after_activation', $succeeded_extensions);
560
+ }
561
+ if (!empty($failed_extensions)) {
562
+ do_action('fw_extensions_activation_failed', $failed_extensions);
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Fire the 'fw_extensions_after_deactivation' action
568
+ */
569
+ if ($db_wp_option_value['deactivated']) {
570
+ $succeeded_extensions = $failed_extensions = array();
571
+
572
+ foreach ($db_wp_option_value['deactivated'] as $extension_name => $not_used_var) {
573
+ if (!fw_ext($extension_name)) {
574
+ $succeeded_extensions[$extension_name] = array();
575
+ } else {
576
+ $failed_extensions[$extension_name] = array();
577
+ }
578
+ }
579
+
580
+ if (!empty($succeeded_extensions)) {
581
+ do_action('fw_extensions_after_deactivation', $succeeded_extensions);
582
+ }
583
+ if (!empty($failed_extensions)) {
584
+ do_action('fw_extensions_deactivation_failed', $failed_extensions);
585
+ }
586
+ }
587
+
588
+ delete_option($db_wp_option_name);
589
+ }
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Activate extensions with $manifest['display'] = false; $manifest['standalone'] = true;
595
+ * - First level extensions
596
+ * - Child extensions of the active extensions
597
+ */
598
+ private function activate_hidden_standalone_extensions()
599
+ {
600
+ if (!is_admin()) {
601
+ return;
602
+ }
603
+
604
+ if (!$this->can_activate()) {
605
+ return;
606
+ }
607
+
608
+ $activate_extensions = array();
609
+
610
+ foreach (
611
+ // all disabled extensions
612
+ array_diff_key($this->get_installed_extensions(), fw()->extensions->get_all())
613
+ as $ext_name => $ext_data
614
+ ) {
615
+ if ($ext_data['parent'] && !fw_ext($ext_data['parent'])) {
616
+ // child extensions of an inactive extension
617
+ continue;
618
+ }
619
+
620
+ if (false !== fw_akg(
621
+ 'display',
622
+ $ext_data['manifest'],
623
+ $this->manifest_default_values['display']
624
+ )) {
625
+ // is visible
626
+ continue;
627
+ }
628
+
629
+ if (true !== fw_akg(
630
+ 'standalone',
631
+ $ext_data['manifest'],
632
+ $this->manifest_default_values['standalone']
633
+ )) {
634
+ // not standalone
635
+ continue;
636
+ }
637
+
638
+ $collected = $this->get_extensions_for_activation($ext_name);
639
+
640
+ if (is_wp_error($collected)) {
641
+ if (defined('WP_DEBUG') && WP_DEBUG) {
642
+ if ($this->is_extensions_page()) {
643
+ // display this warning only on Unyson extensions page
644
+ FW_Flash_Messages::add('fw_ext_auto_activate_hidden_standalone',
645
+ sprintf(__('Cannot activate hidden standalone extension %s', 'fw'),
646
+ fw_akg('name', $ext_data['manifest'], fw_id_to_title($ext_name))
647
+ ),
648
+ 'error'
649
+ );
650
+ }
651
+ }
652
+ return;
653
+ }
654
+
655
+ $activate_extensions = array_merge($activate_extensions, $collected);
656
+ }
657
+
658
+ if (empty($activate_extensions)) {
659
+ return;
660
+ }
661
+
662
+ $option_name = fw()->extensions->_get_active_extensions_db_option_name();
663
+
664
+ $db_active_extensions = array_merge(get_option($option_name, array()), $activate_extensions);
665
+
666
+ update_option($option_name, $db_active_extensions);
667
+ }
668
+
669
+ /**
670
+ * @internal
671
+ */
672
+ public function _action_admin_menu()
673
+ {
674
+ $capability = $this->can_activate();
675
+
676
+ if (!$capability) {
677
+ return;
678
+ }
679
+
680
+ $data = array(
681
+ 'title' => fw()->manifest->get_name(),
682
+ 'capability' => $capability,
683
+ 'slug' => $this->get_page_slug(),
684
+ 'content_callback' => array($this, '_display_page'),
685
+ );
686
+
687
+ /**
688
+ * Collect $hookname that contains $data['slug'] before the action
689
+ * and skip them in verification after action
690
+ */
691
+ {
692
+ global $_registered_pages;
693
+
694
+ $found_hooknames = array();
695
+
696
+ if (!empty($_registered_pages)) {
697
+ foreach ( $_registered_pages as $hookname => $b ) {
698
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
699
+ $found_hooknames[$hookname] = true;
700
+ }
701
+ }
702
+ }
703
+ }
704
+
705
+ /**
706
+ * Use this action if you what to add the extensions page in a custom place in menu
707
+ * Usage example http://pastebin.com/2iWVRPAU
708
+ */
709
+ do_action('fw_backend_add_custom_extensions_menu', $data);
710
+
711
+ /**
712
+ * Check if menu was added in the action above
713
+ */
714
+ {
715
+ $menu_exists = false;
716
+
717
+ if (!empty($_registered_pages)) {
718
+ foreach ( $_registered_pages as $hookname => $b ) {
719
+ if (isset($found_hooknames[$hookname])) {
720
+ continue;
721
+ }
722
+
723
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
724
+ $menu_exists = true;
725
+ break;
726
+ }
727
+ }
728
+ }
729
+ }
730
+
731
+ if ($menu_exists) {
732
+ // do nothing
733
+ } else {
734
+ add_menu_page(
735
+ $data['title'],
736
+ $data['title'],
737
+ $data['capability'],
738
+ $data['slug'],
739
+ $data['content_callback'],
740
+ 'none',
741
+ 3
742
+ );
743
+ }
744
+ }
745
+
746
+ /**
747
+ * If output already started, we cannot set the redirect header, do redirect from js
748
+ */
749
+ private function js_redirect()
750
+ {
751
+ echo
752
+ '<script type="text/javascript">'.
753
+ 'window.location.replace("'. esc_js($this->get_link()) .'");'.
754
+ '</script>';
755
+ }
756
+
757
+ /**
758
+ * @internal
759
+ */
760
+ public function _display_page()
761
+ {
762
+ $page = FW_Request::GET('sub-page');
763
+
764
+ switch ($page) {
765
+ case 'install':
766
+ $this->display_install_page();
767
+ break;
768
+ case 'delete':
769
+ $this->display_delete_page();
770
+ break;
771
+ case 'extension':
772
+ $this->display_extension_page();
773
+ break;
774
+ case 'activate':
775
+ $this->display_activate_page();
776
+ break;
777
+ case 'deactivate':
778
+ $this->display_deactivate_page();
779
+ break;
780
+ default:
781
+ $this->display_list_page();
782
+ }
783
+ }
784
+
785
+ private function display_list_page()
786
+ {
787
+ // note: static is enqueued in 'admin_enqueue_scripts' action
788
+
789
+ /** Prepare extensions list for view */
790
+ {
791
+ $lists = array(
792
+ 'active' => array(),
793
+ 'disabled' => array(),
794
+ 'installed' => array(),
795
+ 'available' => array(),
796
+ 'supported' => array(),
797
+ );
798
+
799
+ foreach ($this->get_installed_extensions() as $ext_name => $ext_data) {
800
+ $lists[ $ext_data['active'] ? 'active' : 'disabled' ][$ext_name] = $ext_data;
801
+ }
802
+
803
+ $lists['installed'] = $lists['active'] + $lists['disabled'];
804
+
805
+ unset($ext_data); // prevent change by reference
806
+
807
+ foreach ($this->get_available_extensions() as $ext_name => $ext_data) {
808
+ $lists['available'][$ext_name] = array(
809
+ 'name' => $ext_data['name'],
810
+ 'description' => $ext_data['description'],
811
+ 'thumbnail' => isset($ext_data['thumbnail'])
812
+ ? $ext_data['thumbnail']
813
+ : (isset($lists['installed'][$ext_name])
814
+ ? fw_akg('thumbnail', $lists['installed'][$ext_name]['manifest'], $this->default_thumbnail)
815
+ : $this->default_thumbnail),
816
+ 'display' => isset($ext_data['display'])
817
+ ? $ext_data['display']
818
+ : $this->manifest_default_values['display'],
819
+ );
820
+ }
821
+
822
+ foreach (fw()->theme->manifest->get('supported_extensions', array()) as $required_ext_name => $required_ext_data) {
823
+ if (isset($lists['installed'][ $required_ext_name ])) {
824
+ $lists['supported'][ $required_ext_name ] = array(
825
+ 'name' => fw_akg( 'name', $lists['installed'][ $required_ext_name ]['manifest'], fw_id_to_title( $required_ext_name ) ),
826
+ 'description' => fw_akg( 'description', $lists['installed'][ $required_ext_name ]['manifest'], '' ),
827
+ );
828
+ } elseif (isset($lists['available'][$required_ext_name])) {
829
+ $lists['supported'][ $required_ext_name ] = array(
830
+ 'name' => $lists['available'][ $required_ext_name ]['name'],
831
+ 'description' => $lists['available'][ $required_ext_name ]['description'],
832
+ );
833
+ } else {
834
+ $lists['supported'][ $required_ext_name ] = array(
835
+ 'name' => fw_id_to_title( $required_ext_name ),
836
+ 'description' => '',
837
+ );
838
+ }
839
+ }
840
+ }
841
+
842
+ echo '<div class="wrap">';
843
+
844
+ echo '<h2>'. sprintf(__('%s Extensions', 'fw'), fw()->manifest->get_name()) .'</h2><br/>';
845
+
846
+ echo '<div id="fw-extensions-list-wrapper">';
847
+
848
+ fw_render_view(dirname(__FILE__) .'/views/extensions-page.php', array(
849
+ 'lists' => &$lists,
850
+ 'link' => $this->get_link(),
851
+ 'display_default_value' => $this->manifest_default_values['display'],
852
+ 'default_thumbnail' => $this->default_thumbnail,
853
+ 'nonces' => array(
854
+ 'delete' => $this->get_nonce('delete'),
855
+ 'install' => $this->get_nonce('install'),
856
+ 'activate' => $this->get_nonce('activate'),
857
+ 'deactivate' => $this->get_nonce('deactivate'),
858
+ ),
859
+ 'can_install' => $this->can_install(),
860
+ ), false);
861
+
862
+ echo '</div>';
863
+
864
+ echo '</div>';
865
+ }
866
+
867
+ private function display_install_page()
868
+ {
869
+ $flash_id = 'fw_extensions_install';
870
+
871
+ if (!$this->can_install()) {
872
+ FW_Flash_Messages::add(
873
+ $flash_id,
874
+ __('You are not allowed to install extensions.', 'fw'),
875
+ 'error'
876
+ );
877
+ $this->js_redirect();
878
+ return;
879
+ }
880
+
881
+ if (array_key_exists('supported', $_GET)) {
882
+ $supported = true;
883
+ $extensions = array_fill_keys(
884
+ array_keys($this->get_supported_extensions_for_install()),
885
+ array()
886
+ );
887
+
888
+ if (empty($extensions)) {
889
+ FW_Flash_Messages::add(
890
+ $flash_id,
891
+ __('All supported extensions are already installed.', 'fw'),
892
+ 'info'
893
+ );
894
+ $this->js_redirect();
895
+ return;
896
+ }
897
+ } else {
898
+ $supported = false;
899
+
900
+ $extensions = array_fill_keys(
901
+ array_map( 'trim', explode( ',', FW_Request::GET( 'extension', '' ) )),
902
+ array()
903
+ );
904
+
905
+ // activate already installed extensions
906
+ $this->activate_extensions($extensions);
907
+ }
908
+
909
+ {
910
+ if (!class_exists('_FW_Extensions_Install_Upgrader_Skin')) {
911
+ fw_include_file_isolated(
912
+ dirname(__FILE__) .'/includes/class--fw-extensions-install-upgrader-skin.php'
913
+ );
914
+ }
915
+
916
+ $skin = new _FW_Extensions_Install_Upgrader_Skin(array(
917
+ 'title' => $supported
918
+ ? _n('Install Compatible Extension', 'Install Compatible Extensions', count($extensions), 'fw')
919
+ : _n('Install Extension', 'Install Extensions', count($extensions), 'fw'),
920
+ ));
921
+ }
922
+
923
+ $skin->header();
924
+
925
+ do {
926
+ $nonce = $this->get_nonce('install');
927
+
928
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
929
+ if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
930
+ $skin->error(__('Invalid nonce.', 'fw'));
931
+ }
932
+
933
+ if (!FW_WP_Filesystem::request_access(
934
+ fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
935
+ )) {
936
+ break;
937
+ }
938
+
939
+ $install_result = $this->install_extensions($extensions, array('verbose' => $skin));
940
+
941
+ if (is_wp_error($install_result)) {
942
+ $skin->error($install_result);
943
+ } elseif (is_array($install_result)) {
944
+ $error = array();
945
+
946
+ foreach ($install_result as $extension_name => $extension_result) {
947
+ if (is_wp_error($extension_result)) {
948
+ $error[] = $extension_result->get_error_message();
949
+ }
950
+ }
951
+
952
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
953
+
954
+ $skin->error($error);
955
+ } elseif ($install_result === true) {
956
+ $skin->set_result(true);
957
+ }
958
+
959
+ /** @var WP_Filesystem_Base $wp_filesystem */
960
+ global $wp_filesystem;
961
+
962
+ $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
963
+
964
+ if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
965
+ if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
966
+ $skin->error(
967
+ sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
968
+ );
969
+ }
970
+ }
971
+
972
+ $skin->after(array(
973
+ 'extensions_page_link' => $this->get_link()
974
+ ));
975
+ } else {
976
+ echo '<form method="post">';
977
+
978
+ wp_nonce_field($nonce['action'], $nonce['name']);
979
+
980
+ $extension_titles = array();
981
+ foreach ($extensions as $extension_name => $not_used_var) {
982
+ $extension_titles[$extension_name] = $this->get_extension_title($extension_name);
983
+ }
984
+
985
+ fw_render_view(dirname(__FILE__) .'/views/install-form.php', array(
986
+ 'extension_titles' => $extension_titles,
987
+ 'list_page_link' => $this->get_link(),
988
+ 'supported' => $supported
989
+ ), false);
990
+
991
+ echo '</form>';
992
+ }
993
+ } while(false);
994
+
995
+ $skin->footer();
996
+ }
997
+
998
+ /**
999
+ * Download (and activate) extensions
1000
+ * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1001
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1002
+ * @param array $opts
1003
+ * @return WP_Error|bool|array
1004
+ * true: when all extensions succeeded
1005
+ * array: when some/all failed
1006
+ */
1007
+ public function install_extensions(array $extensions, $opts = array())
1008
+ {
1009
+ {
1010
+ $opts = array_merge(array(
1011
+ /**
1012
+ * @type bool
1013
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1014
+ * true: return first WP_Error or true on success
1015
+ */
1016
+ 'cancel_on_error' => false,
1017
+ /**
1018
+ * @type bool Activate installed extensions
1019
+ */
1020
+ 'activate' => true,
1021
+ /**
1022
+ * @type bool|WP_Upgrader_Skin
1023
+ */
1024
+ 'verbose' => false,
1025
+ ), $opts);
1026
+
1027
+ $cancel_on_error = $opts['cancel_on_error']; // fixme: remove successfully installed extensions before error?
1028
+ $activate = $opts['activate'];
1029
+ $verbose = $opts['verbose'];
1030
+
1031
+ unset($opts);
1032
+ }
1033
+
1034
+ if (!$this->can_install()) {
1035
+ return new WP_Error(
1036
+ 'access_denied',
1037
+ __('You have no permissions to install extensions', 'fw')
1038
+ );
1039
+ }
1040
+
1041
+ if (empty($extensions)) {
1042
+ return new WP_Error(
1043
+ 'no_extensions',
1044
+ __('No extensions provided', 'fw')
1045
+ );
1046
+ }
1047
+
1048
+ global $wp_filesystem;
1049
+
1050
+ if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
1051
+ return new WP_Error(
1052
+ 'fs_not_initialized',
1053
+ __('WP Filesystem is not initialized', 'fw')
1054
+ );
1055
+ }
1056
+
1057
+ if (function_exists('ini_get')) {
1058
+ $timeout = intval(ini_get('max_execution_time'));
1059
+ } else {
1060
+ $timeout = false;
1061
+ }
1062
+
1063
+ $available_extensions = $this->get_available_extensions();
1064
+ $installed_extensions = $this->get_installed_extensions();
1065
+
1066
+ $result = $downloaded_extensions = array();
1067
+ $has_errors = false;
1068
+
1069
+ while (!empty($extensions)) {
1070
+ $not_used_var = reset($extensions);
1071
+ $extension_name = key($extensions);
1072
+ unset($extensions[$extension_name]);
1073
+
1074
+ $extensions_before_install = array_keys($installed_extensions);
1075
+
1076
+ if (isset($installed_extensions[$extension_name])) {
1077
+ $result[$extension_name] = new WP_Error(
1078
+ 'extension_installed',
1079
+ sprintf(__('Extension "%s" is already installed.', 'fw'), $this->get_extension_title($extension_name))
1080
+ );
1081
+ $has_errors = true;
1082
+
1083
+ if ($cancel_on_error) {
1084
+ break;
1085
+ } else {
1086
+ continue;
1087
+ }
1088
+ }
1089
+
1090
+ if (!isset($available_extensions[ $extension_name ])) {
1091
+ $result[$extension_name] = new WP_Error(
1092
+ 'extension_not_available',
1093
+ sprintf(
1094
+ __('Extension "%s" is not available for install.', 'fw'),
1095
+ $this->get_extension_title($extension_name)
1096
+ )
1097
+ );
1098
+ $has_errors = true;
1099
+
1100
+ if ($cancel_on_error) {
1101
+ break;
1102
+ } else {
1103
+ continue;
1104
+ }
1105
+ }
1106
+
1107
+ /**
1108
+ * Find parent extensions
1109
+ * they will be installed if does not exist
1110
+ */
1111
+ {
1112
+ $parents = array($extension_name);
1113
+
1114
+ $current_parent = $extension_name;
1115
+ while (!empty($available_extensions[$current_parent]['parent'])) {
1116
+ $current_parent = $available_extensions[$current_parent]['parent'];
1117
+
1118
+ if (!isset($available_extensions[$current_parent])) {
1119
+ $result[$extension_name] = new WP_Error(
1120
+ 'parent_extension_not_available',
1121
+ sprintf(
1122
+ __('Parent extension "%s" not available.', 'fw'),
1123
+ $this->get_extension_title($current_parent)
1124
+ )
1125
+ );
1126
+ $has_errors = true;
1127
+
1128
+ if ($cancel_on_error) {
1129
+ break 2;
1130
+ } else {
1131
+ continue 2;
1132
+ }
1133
+ }
1134
+
1135
+ $parents[] = $current_parent;
1136
+ }
1137
+
1138
+ $parents = array_reverse($parents);
1139
+ }
1140
+
1141
+ /**
1142
+ * Install parent extensions and the extension
1143
+ */
1144
+ {
1145
+ $current_extension_path = fw_get_framework_directory();
1146
+
1147
+ foreach ($parents as $parent_extension_name) {
1148
+ $current_extension_path .= '/extensions/'. $parent_extension_name;
1149
+
1150
+ if (isset($installed_extensions[$parent_extension_name])) {
1151
+ // skip already installed extensions
1152
+ continue;
1153
+ }
1154
+
1155
+ if ($verbose) {
1156
+ $verbose_message = sprintf(__('Downloading the "%s" extension...', 'fw'),
1157
+ $this->get_extension_title($parent_extension_name)
1158
+ );
1159
+
1160
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1161
+ $verbose->feedback($verbose_message);
1162
+ } else {
1163
+ echo fw_html_tag('p', array(), $verbose_message);
1164
+ }
1165
+ }
1166
+
1167
+ // increase timeout
1168
+ if ($timeout !== false && function_exists('set_time_limit')) {
1169
+ $timeout += 30;
1170
+ set_time_limit($timeout);
1171
+ }
1172
+
1173
+ $wp_fw_downloaded_dir = $this->download(
1174
+ $parent_extension_name,
1175
+ $available_extensions[$parent_extension_name]
1176
+ );
1177
+
1178
+ if (is_wp_error($wp_fw_downloaded_dir)) {
1179
+ if ($verbose) {
1180
+ $verbose_message = $wp_fw_downloaded_dir->get_error_message();
1181
+
1182
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1183
+ $verbose->error($verbose_message);
1184
+ } else {
1185
+ echo fw_html_tag('p', array(), $verbose_message);
1186
+ }
1187
+ }
1188
+
1189
+ $result[$extension_name] = $wp_fw_downloaded_dir;
1190
+ $has_errors = true;
1191
+
1192
+ if ($cancel_on_error) {
1193
+ break 2;
1194
+ } else {
1195
+ continue 2;
1196
+ }
1197
+ }
1198
+
1199
+ if ($verbose) {
1200
+ $verbose_message = sprintf(__('Installing the "%s" extension...', 'fw'),
1201
+ $this->get_extension_title($parent_extension_name)
1202
+ );
1203
+
1204
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1205
+ $verbose->feedback($verbose_message);
1206
+ } else {
1207
+ echo fw_html_tag('p', array(), $verbose_message);
1208
+ }
1209
+ }
1210
+
1211
+ $merge_result = $this->merge_extension(
1212
+ $wp_fw_downloaded_dir,
1213
+ FW_WP_Filesystem::real_path_to_filesystem_path($current_extension_path)
1214
+ );
1215
+
1216
+ if (is_wp_error($merge_result)) {
1217
+ if ($verbose) {
1218
+ $verbose_message = $merge_result->get_error_message();
1219
+
1220
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1221
+ $verbose->error($verbose_message);
1222
+ } else {
1223
+ echo fw_html_tag('p', array(), $verbose_message);
1224
+ }
1225
+ }
1226
+
1227
+ $result[$extension_name] = $merge_result;
1228
+ $has_errors = true;
1229
+
1230
+ if ($cancel_on_error) {
1231
+ break 2;
1232
+ } else {
1233
+ continue 2;
1234
+ }
1235
+ }
1236
+
1237
+ if ($verbose) {
1238
+ $verbose_message = sprintf(__('The %s extension has been successfully installed.', 'fw'),
1239
+ $this->get_extension_title($parent_extension_name)
1240
+ );
1241
+
1242
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1243
+ $verbose->feedback($verbose_message);
1244
+ } else {
1245
+ echo fw_html_tag('p', array(), $verbose_message);
1246
+ }
1247
+ }
1248
+
1249
+ $downloaded_extensions[$parent_extension_name] = array();
1250
+
1251
+ /**
1252
+ * Read again all extensions
1253
+ * The downloaded extension may contain more sub extensions
1254
+ */
1255
+ {
1256
+ unset($installed_extensions);
1257
+ $installed_extensions = $this->get_installed_extensions(true);
1258
+ }
1259
+ }
1260
+ }
1261
+
1262
+ $result[$extension_name] = true;
1263
+
1264
+ /**
1265
+ * Collect required extensions of the newly installed extensions
1266
+ */
1267
+ foreach (
1268
+ // new extensions
1269
+ array_diff(
1270
+ array_keys($installed_extensions),
1271
+ $extensions_before_install
1272
+ )
1273
+ as $new_extension_name
1274
+ ) {
1275
+ foreach (
1276
+ array_keys(
1277
+ fw_akg(
1278
+ 'requirements/extensions',
1279
+ $installed_extensions[$new_extension_name]['manifest'],
1280
+ array()
1281
+ )
1282
+ )
1283
+ as $required_extension_name
1284
+ ) {
1285
+ if (isset($installed_extensions[$required_extension_name])) {
1286
+ // already installed
1287
+ continue;
1288
+ }
1289
+
1290
+ $extensions[$required_extension_name] = array();
1291
+ }
1292
+ }
1293
+ }
1294
+
1295
+ if ($activate) {
1296
+ $activate_extensions = array();
1297
+
1298
+ foreach ($result as $extension_name => $extension_result) {
1299
+ if (!is_wp_error($extension_result)) {
1300
+ $activate_extensions[$extension_name] = array();
1301
+ }
1302
+ }
1303
+
1304
+ if (!empty($activate_extensions)) {
1305
+ if ($verbose) {
1306
+ $verbose_message = _n(
1307
+ 'Activating extension...',
1308
+ 'Activating extensions...',
1309
+ count($activate_extensions),
1310
+ 'fw'
1311
+ );
1312
+
1313
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1314
+ $verbose->feedback($verbose_message);
1315
+ } else {
1316
+ echo fw_html_tag('p', array(), $verbose_message);
1317
+ }
1318
+ }
1319
+
1320
+ $activation_result = $this->activate_extensions($activate_extensions);
1321
+
1322
+ if ($verbose) {
1323
+ if (is_wp_error($activation_result)) {
1324
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1325
+ $verbose->error($activation_result->get_error_message());
1326
+ } else {
1327
+ echo fw_html_tag('p', array(), $activation_result->get_error_message());
1328
+ }
1329
+ } elseif (is_array($activation_result)) {
1330
+ $verbose_message = array();
1331
+
1332
+ foreach ($activation_result as $extension_name => $extension_result) {
1333
+ if (is_wp_error($extension_result)) {
1334
+ $verbose_message[] = $extension_result->get_error_message();
1335
+ }
1336
+ }
1337
+
1338
+ $verbose_message = '<ul><li>' . implode('</li><li>', $verbose_message) . '</li></ul>';
1339
+
1340
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1341
+ $verbose->error($verbose_message);
1342
+ } else {
1343
+ echo fw_html_tag('p', array(), $verbose_message);
1344
+ }
1345
+ } elseif ($activation_result === true) {
1346
+ $verbose_message = _n(
1347
+ 'Extension has been successfully activated.',
1348
+ 'Extensions has been successfully activated.',
1349
+ count($activate_extensions),
1350
+ 'fw'
1351
+ );
1352
+
1353
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1354
+ $verbose->feedback($verbose_message);
1355
+ } else {
1356
+ echo fw_html_tag('p', array(), $verbose_message);
1357
+ }
1358
+ }
1359
+ }
1360
+ }
1361
+ }
1362
+
1363
+ do_action('fw_extensions_install', $result);
1364
+
1365
+ if (
1366
+ $cancel_on_error
1367
+ &&
1368
+ $has_errors
1369
+ ) {
1370
+ if (
1371
+ ($last_result = end($result))
1372
+ &&
1373
+ is_wp_error($last_result)
1374
+ ) {
1375
+ return $last_result;
1376
+ } else {
1377
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1378
+ return new WP_Error(
1379
+ 'installation_failed',
1380
+ _n('Cannot install extension', 'Cannot install extensions', count($extensions), 'fw')
1381
+ );
1382
+ }
1383
+ }
1384
+
1385
+ if ($has_errors) {
1386
+ return $result;
1387
+ } else {
1388
+ return true;
1389
+ }
1390
+ }
1391
+
1392
+ private function display_delete_page()
1393
+ {
1394
+ $flash_id = 'fw_extensions_delete';
1395
+
1396
+ if (!$this->can_install()) {
1397
+ FW_Flash_Messages::add(
1398
+ $flash_id,
1399
+ __('You are not allowed to delete extensions.', 'fw'),
1400
+ 'error'
1401
+ );
1402
+ $this->js_redirect();
1403
+ return;
1404
+ }
1405
+
1406
+ $extensions = array_fill_keys(array_map('trim', explode(',', FW_Request::GET('extension', ''))), array());
1407
+
1408
+ {
1409
+ if (!class_exists('_FW_Extensions_Delete_Upgrader_Skin')) {
1410
+ fw_include_file_isolated(
1411
+ dirname(__FILE__) .'/includes/class--fw-extensions-delete-upgrader-skin.php'
1412
+ );
1413
+ }
1414
+
1415
+ $skin = new _FW_Extensions_Delete_Upgrader_Skin(array(
1416
+ 'title' => _n('Delete Extension', 'Delete Extensions', count($extensions), 'fw'),
1417
+ ));
1418
+ }
1419
+
1420
+ $skin->header();
1421
+
1422
+ do {
1423
+ $nonce = $this->get_nonce('delete');
1424
+
1425
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1426
+ if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1427
+ $skin->error(__('Invalid nonce.', 'fw'));
1428
+ }
1429
+
1430
+ if (!FW_WP_Filesystem::request_access(
1431
+ fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
1432
+ )) {
1433
+ break;
1434
+ }
1435
+
1436
+ $uninstall_result = $this->uninstall_extensions($extensions, array('verbose' => $skin));
1437
+
1438
+ if (is_wp_error($uninstall_result)) {
1439
+ $skin->error($uninstall_result);
1440
+ } elseif (is_array($uninstall_result)) {
1441
+ $error = array();
1442
+
1443
+ foreach ($uninstall_result as $extension_name => $extension_result) {
1444
+ if (is_wp_error($extension_result)) {
1445
+ $error[] = $extension_result->get_error_message();
1446
+ }
1447
+ }
1448
+
1449
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1450
+
1451
+ $skin->error($error);
1452
+ } elseif ($uninstall_result === true) {
1453
+ $skin->set_result(true);
1454
+ }
1455
+
1456
+ $skin->after(array(
1457
+ 'extensions_page_link' => $this->get_link()
1458
+ ));
1459
+ } else {
1460
+ echo '<form method="post">';
1461
+
1462
+ wp_nonce_field($nonce['action'], $nonce['name']);
1463
+
1464
+ fw_render_view(dirname(__FILE__) .'/views/delete-form.php', array(
1465
+ 'extension_names' => array_keys($extensions),
1466
+ 'installed_extensions' => $this->get_installed_extensions(),
1467
+ 'list_page_link' => $this->get_link(),
1468
+ ), false);
1469
+
1470
+ echo '</form>';
1471
+ }
1472
+ } while(false);
1473
+
1474
+ $skin->footer();
1475
+ }
1476
+
1477
+ /**
1478
+ * Remove extensions
1479
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1480
+ * @param array $opts
1481
+ * @return WP_Error|bool|array
1482
+ * true: when all extensions succeeded
1483
+ * array: when some/all failed
1484
+ */
1485
+ public function uninstall_extensions(array $extensions, $opts = array())
1486
+ {
1487
+ {
1488
+ $opts = array_merge(array(
1489
+ /**
1490
+ * @type bool
1491
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1492
+ * true: return first WP_Error or true on success
1493
+ */
1494
+ 'cancel_on_error' => false,
1495
+ /**
1496
+ * @type bool|WP_Upgrader_Skin
1497
+ */
1498
+ 'verbose' => false,
1499
+ ), $opts);
1500
+
1501
+ $cancel_on_error = $opts['cancel_on_error']; // fixme: install back successfully removed extensions before error?
1502
+ $verbose = $opts['verbose'];
1503
+
1504
+ unset($opts);
1505
+ }
1506
+
1507
+ if (!$this->can_install()) {
1508
+ return new WP_Error(
1509
+ 'access_denied',
1510
+ __('You have no permissions to uninstall extensions', 'fw')
1511
+ );
1512
+ }
1513
+
1514
+ if (empty($extensions)) {
1515
+ return new WP_Error(
1516
+ 'no_extensions',
1517
+ __('No extensions provided', 'fw')
1518
+ );
1519
+ }
1520
+
1521
+ /** @var WP_Filesystem_Base $wp_filesystem */
1522
+ global $wp_filesystem;
1523
+
1524
+ if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
1525
+ return new WP_Error(
1526
+ 'fs_not_initialized',
1527
+ __('WP Filesystem is not initialized', 'fw')
1528
+ );
1529
+ }
1530
+
1531
+ $installed_extensions = $this->get_installed_extensions();
1532
+ $extensions_before_uninstall = array_fill_keys(array_keys($installed_extensions), array());
1533
+
1534
+ $result = $uninstalled_extensions = array();
1535
+ $has_errors = false;
1536
+
1537
+ while (!empty($extensions)) {
1538
+ $not_used_var = reset($extensions);
1539
+ $extension_name = key($extensions);
1540
+ unset($extensions[$extension_name]);
1541
+
1542
+ $extension_title = $this->get_extension_title($extension_name);
1543
+
1544
+ if (!isset($installed_extensions[ $extension_name ])) {
1545
+ // already deleted
1546
+ $result[$extension_name] = true;
1547
+ continue;
1548
+ }
1549
+
1550
+ if (
1551
+ !isset($installed_extensions[ $extension_name ]['path'])
1552
+ ||
1553
+ empty($installed_extensions[ $extension_name ]['path'])
1554
+ ) {
1555
+ /**
1556
+ * This happens sometimes, but I don't know why
1557
+ * If the script will continue, it will delete the root folder
1558
+ */
1559
+ fw_print(
1560
+ 'Please report this to https://github.com/ThemeFuse/Unyson/issues',
1561
+ $extension_name,
1562
+ $installed_extensions
1563
+ );
1564
+ die;
1565
+ }
1566
+
1567
+ $wp_fs_extension_path = FW_WP_Filesystem::real_path_to_filesystem_path(
1568
+ $installed_extensions[ $extension_name ]['path']
1569
+ );
1570
+
1571
+ if (!$wp_filesystem->exists($wp_fs_extension_path)) {
1572
+ // already deleted, maybe because it was a sub-extension of an deleted extension
1573
+ $result[$extension_name] = true;
1574
+ continue;
1575
+ }
1576
+
1577
+ if ($verbose) {
1578
+ $verbose_message = sprintf(__('Deleting the "%s" extension...', 'fw'), $extension_title);
1579
+
1580
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1581
+ $verbose->feedback($verbose_message);
1582
+ } else {
1583
+ echo fw_html_tag('p', array(), $verbose_message);
1584
+ }
1585
+ }
1586
+
1587
+ if (!$wp_filesystem->delete($wp_fs_extension_path, true, 'd')) {
1588
+ $result[$extension_name] = new WP_Error(
1589
+ 'cannot_delete_directory',
1590
+ sprintf(__('Cannot delete the "%s" extension.', 'fw'), $extension_title)
1591
+ );
1592
+ $has_errors = true;
1593
+
1594
+ if ($cancel_on_error) {
1595
+ break;
1596
+ } else {
1597
+ continue;
1598
+ }
1599
+ } else {
1600
+ if ($verbose) {
1601
+ $verbose_message = sprintf(
1602
+ __('The %s extension has been successfully deleted.', 'fw'),
1603
+ $extension_title
1604
+ );
1605
+
1606
+ if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1607
+ $verbose->feedback($verbose_message);
1608
+ } else {
1609
+ echo fw_html_tag('p', array(), $verbose_message);
1610
+ }
1611
+ }
1612
+
1613
+ $result[$extension_name] = true;
1614
+ }
1615
+
1616
+ /**
1617
+ * Read again all extensions
1618
+ * The delete extension may contain more sub extensions
1619
+ */
1620
+ {
1621
+ unset($installed_extensions);
1622
+ $installed_extensions = $this->get_installed_extensions(true);
1623
+ }
1624
+
1625
+ /**
1626
+ * Add for deletion not used extensions
1627
+ * For e.g. standalone=false extension that were required by the deleted extension
1628
+ * and now are not required by any other extension
1629
+ */
1630
+ {
1631
+ $not_used_extensions = array_fill_keys(
1632
+ array_keys(
1633
+ array_diff_key(
1634
+ $installed_extensions,
1635
+ $this->get_used_extensions($extensions, array_keys($installed_extensions))
1636
+ )
1637
+ ),
1638
+ array()
1639
+ );
1640
+
1641
+ $extensions = array_merge($extensions, $not_used_extensions);
1642
+ }
1643
+ }
1644
+
1645
+ do_action('fw_extensions_uninstall', $result);
1646
+
1647
+ if (
1648
+ $cancel_on_error
1649
+ &&
1650
+ $has_errors
1651
+ ) {
1652
+ if (
1653
+ ($last_result = end($result))
1654
+ &&
1655
+ is_wp_error($last_result)
1656
+ ) {
1657
+ return $last_result;
1658
+ } else {
1659
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1660
+ return new WP_Error(
1661
+ 'uninstall_failed',
1662
+ _n('Cannot uninstall extension', 'Cannot uninstall extensions', count($extensions), 'fw')
1663
+ );
1664
+ }
1665
+ }
1666
+
1667
+ // remove from active list the deleted extensions
1668
+ {
1669
+ update_option(
1670
+ fw()->extensions->_get_active_extensions_db_option_name(),
1671
+ array_diff_key(
1672
+ fw()->extensions->_get_db_active_extensions(),
1673
+ array_diff_key(
1674
+ $extensions_before_uninstall,
1675
+ $installed_extensions
1676
+ )
1677
+ )
1678
+ );
1679
+ }
1680
+
1681
+ if ($has_errors) {
1682
+ return $result;
1683
+ } else {
1684
+ return true;
1685
+ }
1686
+ }
1687
+
1688
+ private function display_extension_page()
1689
+ {
1690
+ // note: static is enqueued in 'admin_enqueue_scripts' action
1691
+
1692
+ $extension_name = trim(FW_Request::GET('extension', ''));
1693
+
1694
+ $installed_extensions = $this->get_installed_extensions();
1695
+
1696
+ $flash_id = 'fw_extension_page';
1697
+
1698
+ {
1699
+ $error = '';
1700
+
1701
+ do {
1702
+ if (empty($extension_name)) {
1703
+ $error = __('Extension not specified.', 'fw');
1704
+ break;
1705
+ }
1706
+
1707
+ if (!isset($installed_extensions[$extension_name])) {
1708
+ $error = sprintf(__('Extension "%s" is not installed.', 'fw'), $this->get_extension_title($extension_name));
1709
+ break;
1710
+ }
1711
+ } while(false);
1712
+
1713
+ if ($error) {
1714
+ FW_Flash_Messages::add($flash_id, $error, 'error');
1715
+ $this->js_redirect();
1716
+ return;
1717
+ }
1718
+ }
1719
+
1720
+ {
1721
+ $tab = fw_akg('tab', $_GET, 'settings');
1722
+
1723
+ if (!in_array($tab, array('settings', 'docs'))) {
1724
+ $tab = 'settings';
1725
+ }
1726
+ }
1727
+
1728
+ $extension_title = $this->get_extension_title($extension_name);
1729
+ $link = $this->get_link();
1730
+
1731
+ echo '<div class="wrap" id="fw-extension-page">';
1732
+
1733
+ fw_render_view(dirname(__FILE__) .'/views/extension-page-header.php', array(
1734
+ 'extension_name' => $extension_name,
1735
+ 'extension_data' => $installed_extensions[$extension_name],
1736
+ 'link_delete' => $link .'&sub-page=delete',
1737
+ 'link_extension' => $link .'&sub-page=extension',
1738
+ 'extension_title' => $extension_title,
1739
+ 'tab' => $tab,
1740
+ 'is_supported' =>
1741
+ fw()->theme->manifest->get('supported_extensions/'. $extension_name, false) !== false
1742
+ ||
1743
+ $installed_extensions[$extension_name]['is']['theme']
1744
+ ), false);
1745
+
1746
+ unset($installed_extensions);
1747
+
1748
+ echo '<div id="fw-extension-tab-content">';
1749
+ {
1750
+ $method_data = array();
1751
+
1752
+ switch ($tab) {
1753
+ case 'settings':
1754
+ $error = $this->display_extension_settings_page($extension_name, $method_data);
1755
+ break;
1756
+ case 'docs':
1757
+ $error = $this->display_extension_docs_page($extension_name, $method_data);
1758
+ break;
1759
+ }
1760
+ }
1761
+ echo '</div>';
1762
+
1763
+ echo '</div>';
1764
+
1765
+ if ($error) {
1766
+ FW_Flash_Messages::add($flash_id, $error, 'error');
1767
+ $this->js_redirect();
1768
+ return;
1769
+ }
1770
+ }
1771
+
1772
+ private function display_extension_settings_page($extension_name, $data)
1773
+ {
1774
+ if (!fw()->extensions->get($extension_name)) {
1775
+ return sprintf(
1776
+ __('Extension "%s" does not exist or is not active.', 'fw'),
1777
+ fw_htmlspecialchars($extension_name)
1778
+ );
1779
+ }
1780
+
1781
+ $extension = fw()->extensions->get($extension_name);
1782
+
1783
+ if (!$extension->get_settings_options()) {
1784
+ return sprintf(
1785
+ __('%s extension does not have settings.', 'fw'),
1786
+ $extension->manifest->get_name()
1787
+ );
1788
+ }
1789
+
1790
+ echo '<div id="fw-extension-settings">';
1791
+
1792
+ echo $this->extension_settings_form->render(array(
1793
+ 'extension' => $extension,
1794
+ ));
1795
+
1796
+ echo '</div>';
1797
+ }
1798
+
1799
+ private function display_extension_docs_page($extension_name, $data)
1800
+ {
1801
+ $installed_extensions = $this->get_installed_extensions();
1802
+ $docs_path = $installed_extensions[$extension_name]['path'] .'/readme.md.php';
1803
+ unset($installed_extensions);
1804
+
1805
+ if (!file_exists($docs_path)) {
1806
+ return __('Extension has no Install Instructions', 'fw');
1807
+ }
1808
+
1809
+ echo fw()->backend->render_box(
1810
+ 'fw-extension-docs',
1811
+ '',
1812
+ fw()->backend->render_options(array(
1813
+ 'docs' => array(
1814
+ 'label' => false,
1815
+ 'type' => 'html-full',
1816
+ 'html' => $this->get_markdown_parser()->text(
1817
+ fw_render_view($docs_path, array())
1818
+ ),
1819
+ ),
1820
+ ))
1821
+ );
1822
+ }
1823
+
1824
+ private function display_activate_page()
1825
+ {
1826
+ $error = '';
1827
+
1828
+ do {
1829
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
1830
+ $error = __('Invalid request method.', 'fw');
1831
+ break;
1832
+ }
1833
+
1834
+ $nonce = $this->get_nonce('activate');
1835
+
1836
+ if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
1837
+ $error = __('Invalid nonce.', 'fw');
1838
+ break;
1839
+ }
1840
+
1841
+ if (!isset($_GET['extension'])) {
1842
+ $error = __('No extension specified.', 'fw');
1843
+ break;
1844
+ }
1845
+
1846
+ $activation_result = $this->activate_extensions(
1847
+ array_fill_keys(explode(',', $_GET['extension']), array())
1848
+ );
1849
+
1850
+ if (is_wp_error($activation_result)) {
1851
+ $error = $activation_result->get_error_message();
1852
+ } elseif (is_array($activation_result)) {
1853
+ $error = array();
1854
+
1855
+ foreach ($activation_result as $extension_name => $extension_result) {
1856
+ if (is_wp_error($extension_result)) {
1857
+ $error[] = $extension_result->get_error_message();
1858
+ }
1859
+ }
1860
+
1861
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1862
+ }
1863
+ } while(false);
1864
+
1865
+ if ($error) {
1866
+ FW_Flash_Messages::add(
1867
+ 'fw_extensions_activate_page',
1868
+ $error,
1869
+ 'error'
1870
+ );
1871
+ $this->js_redirect();
1872
+ return;
1873
+ }
1874
+
1875
+ $this->js_redirect();
1876
+ }
1877
+
1878
+ /**
1879
+ * Add extensions to active extensions list in database
1880
+ * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1881
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1882
+ * @param bool $cancel_on_error
1883
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1884
+ * true: return first WP_Error or true on success
1885
+ * @return WP_Error|bool|array
1886
+ * true: when all extensions succeeded
1887
+ * array: when some/all failed
1888
+ */
1889
+ public function activate_extensions(array $extensions, $cancel_on_error = false)
1890
+ {
1891
+ if (!$this->can_activate()) {
1892
+ return new WP_Error(
1893
+ 'access_denied',
1894
+ __('You have no permissions to activate extensions', 'fw')
1895
+ );
1896
+ }
1897
+
1898
+ if (empty($extensions)) {
1899
+ return new WP_Error(
1900
+ 'no_extensions',
1901
+ __('No extensions provided', 'fw')
1902
+ );
1903
+ }
1904
+
1905
+ $installed_extensions = $this->get_installed_extensions();
1906
+
1907
+ $result = $extensions_for_activation = array();
1908
+ $has_errors = false;
1909
+
1910
+ foreach ($extensions as $extension_name => $not_used_var) {
1911
+ if (!isset($installed_extensions[$extension_name])) {
1912
+ $result[$extension_name] = new WP_Error(
1913
+ 'extension_not_installed',
1914
+ sprintf(__('Extension "%s" does not exist.', 'fw'), $this->get_extension_title($extension_name))
1915
+ );
1916
+ $has_errors = true;
1917
+
1918
+ if ($cancel_on_error) {
1919
+ break;
1920
+ } else {
1921
+ continue;
1922
+ }
1923
+ }
1924
+
1925
+ $collected = $this->get_extensions_for_activation($extension_name);
1926
+
1927
+ if (is_wp_error($collected)) {
1928
+ $result[$extension_name] = $collected;
1929
+ $has_errors = true;
1930
+
1931
+ if ($cancel_on_error) {
1932
+ break;
1933
+ } else {
1934
+ continue;
1935
+ }
1936
+ }
1937
+
1938
+ $extensions_for_activation = array_merge($extensions_for_activation, $collected);
1939
+
1940
+ $result[$extension_name] = true;
1941
+ }
1942
+
1943
+ if (
1944
+ $cancel_on_error
1945
+ &&
1946
+ $has_errors
1947
+ ) {
1948
+ if (
1949
+ ($last_result = end($result))
1950
+ &&
1951
+ is_wp_error($last_result)
1952
+ ) {
1953
+ return $last_result;
1954
+ } else {
1955
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1956
+ return new WP_Error(
1957
+ 'activation_failed',
1958
+ _n('Cannot activate extension', 'Cannot activate extensions', count($extensions), 'fw')
1959
+ );
1960
+ }
1961
+ }
1962
+
1963
+ update_option(
1964
+ fw()->extensions->_get_active_extensions_db_option_name(),
1965
+ array_merge(fw()->extensions->_get_db_active_extensions(), $extensions_for_activation)
1966
+ );
1967
+
1968
+ // remove already active extensions
1969
+ foreach ($extensions_for_activation as $extension_name => $not_used_var) {
1970
+ if (fw_ext($extension_name)) {
1971
+ unset($extensions_for_activation[$extension_name]);
1972
+ }
1973
+ }
1974
+
1975
+ /**
1976
+ * Prepare db wp option used to fire the 'fw_extensions_after_activation' action on next refresh
1977
+ */
1978
+ {
1979
+ $db_wp_option_name = 'fw_extensions_activation';
1980
+ $db_wp_option_value = get_option($db_wp_option_name, array(
1981
+ 'activated' => array(),
1982
+ 'deactivated' => array(),
1983
+ ));
1984
+
1985
+ /**
1986
+ * Keep adding to the existing value instead of resetting it on each method call
1987
+ * in case the method will be called multiple times
1988
+ */
1989
+ $db_wp_option_value['activated'] = array_merge($db_wp_option_value['activated'], $extensions_for_activation);
1990
+
1991
+ /**
1992
+ * Remove activated extensions from deactivated
1993
+ */
1994
+ $db_wp_option_value['deactivated'] = array_diff_key($db_wp_option_value['deactivated'], $db_wp_option_value['activated']);
1995
+
1996
+ update_option($db_wp_option_name, $db_wp_option_value, false);
1997
+ }
1998
+
1999
+ do_action('fw_extensions_before_activation', $extensions_for_activation);
2000
+
2001
+ if ($has_errors) {
2002
+ return $result;
2003
+ } else {
2004
+ return true;
2005
+ }
2006
+ }
2007
+
2008
+ private function collect_sub_extensions($ext_name, &$installed_extensions)
2009
+ {
2010
+ $result = array();
2011
+
2012
+ foreach ($installed_extensions[$ext_name]['children'] as $child_ext_name => $child_ext_data) {
2013
+ $result[$child_ext_name] = array();
2014
+
2015
+ $result += $this->collect_sub_extensions($child_ext_name, $installed_extensions);
2016
+ }
2017
+
2018
+ return $result;
2019
+ }
2020
+
2021
+ private function collect_required_extensions($ext_name, &$installed_extensions, &$collected)
2022
+ {
2023
+ if (!isset($installed_extensions[$ext_name])) {
2024
+ return;
2025
+ }
2026
+
2027
+ foreach (fw_akg('requirements/extensions', $installed_extensions[$ext_name]['manifest'], array()) as $req_ext_name => $req_ext_data) {
2028
+ if (isset($collected[$req_ext_name])) {
2029
+ // prevent requirements recursion
2030
+ continue;
2031
+ }
2032
+
2033
+ $collected[$req_ext_name] = array();
2034
+
2035
+ $this->collect_required_extensions($req_ext_name, $installed_extensions, $collected);
2036
+ }
2037
+ }
2038
+
2039
+ private function display_deactivate_page()
2040
+ {
2041
+ $installed_extensions = $this->get_installed_extensions();
2042
+
2043
+ $error = '';
2044
+
2045
+ do {
2046
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
2047
+ $error = __('Invalid request method.', 'fw');
2048
+ break;
2049
+ }
2050
+
2051
+ $nonce = $this->get_nonce('deactivate');
2052
+
2053
+ if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
2054
+ $error = __('Invalid nonce.', 'fw');
2055
+ break;
2056
+ }
2057
+
2058
+ if (!isset($_GET['extension'])) {
2059
+ $error = __('No extension specified.', 'fw');
2060
+ break;
2061
+ }
2062
+
2063
+ $deactivation_result = $this->deactivate_extensions(
2064
+ array_fill_keys(explode(',', $_GET['extension']), array())
2065
+ );
2066
+
2067
+ if (is_wp_error($deactivation_result)) {
2068
+ $error = $deactivation_result->get_error_message();
2069
+ } elseif (is_array($deactivation_result)) {
2070
+ $error = array();
2071
+
2072
+ foreach ($deactivation_result as $extension_name => $extension_result) {
2073
+ if (is_wp_error($extension_result)) {
2074
+ $error[] = $extension_result->get_error_message();
2075
+ }
2076
+ }
2077
+
2078
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
2079
+ }
2080
+ } while(false);
2081
+
2082
+ if ($error) {
2083
+ FW_Flash_Messages::add(
2084
+ 'fw_extensions_activate_page',
2085
+ $error,
2086
+ 'error'
2087
+ );
2088
+ }
2089
+
2090
+ $this->js_redirect();
2091
+ }
2092
+
2093
+ /**
2094
+ * Remove extensions from active extensions list in database
2095
+ * After refresh they will be inactive
2096
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
2097
+ * @param bool $cancel_on_error
2098
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
2099
+ * true: return first WP_Error or true on success
2100
+ * @return WP_Error|bool|array
2101
+ * true: when all extensions succeeded
2102
+ * array: when some/all failed
2103
+ */
2104
+ public function deactivate_extensions(array $extensions, $cancel_on_error = false)
2105
+ {
2106
+ if (!$this->can_activate()) {
2107
+ return new WP_Error(
2108
+ 'access_denied',
2109
+ __('You have no permissions to deactivate extensions', 'fw')
2110
+ );
2111
+ }
2112
+
2113
+ if (empty($extensions)) {
2114
+ return new WP_Error(
2115
+ 'no_extensions',
2116
+ __('No extensions provided', 'fw')
2117
+ );
2118
+ }
2119
+
2120
+ $installed_extensions = $this->get_installed_extensions();
2121
+
2122
+ $result = $extensions_for_deactivation = array();
2123
+ $has_errors = false;
2124
+
2125
+ foreach ($extensions as $extension_name => $not_used_var) {
2126
+ if (!isset($installed_extensions[$extension_name])) {
2127
+ // anyway remove from the active list
2128
+ $extensions_for_deactivation[$extension_name] = array();
2129
+
2130
+ $result[$extension_name] = new WP_Error(
2131
+ 'extension_not_installed',
2132
+ sprintf(__( 'Extension "%s" does not exist.' , 'fw' ), $this->get_extension_title($extension_name))
2133
+ );
2134
+ $has_errors = true;
2135
+
2136
+ if ($cancel_on_error) {
2137
+ break;
2138
+ } else {
2139
+ continue;
2140
+ }
2141
+ }
2142
+
2143
+ $current_deactivating_extensions = array(
2144
+ $extension_name => array()
2145
+ );
2146
+
2147
+ // add sub-extensions for deactivation
2148
+ foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2149
+ $current_deactivating_extensions[ $sub_extension_name ] = array();
2150
+ }
2151
+
2152
+ // add extensions that requires deactivated extensions
2153
+ $this->collect_extensions_that_requires($current_deactivating_extensions, $current_deactivating_extensions);
2154
+
2155
+ $extensions_for_deactivation = array_merge(
2156
+ $extensions_for_deactivation,
2157
+ $current_deactivating_extensions
2158
+ );
2159
+
2160
+ unset($current_deactivating_extensions);
2161
+
2162
+ $result[$extension_name] = true;
2163
+ }
2164
+
2165
+ if (
2166
+ $cancel_on_error
2167
+ &&
2168
+ $has_errors
2169
+ ) {
2170
+ if (
2171
+ ($last_result = end($result))
2172
+ &&
2173
+ is_wp_error($last_result)
2174
+ ) {
2175
+ return $last_result;
2176
+ } else {
2177
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
2178
+ return new WP_Error(
2179
+ 'deactivation_failed',
2180
+ _n('Cannot deactivate extension', 'Cannot activate extensions', count($extensions), 'fw')
2181
+ );
2182
+ }
2183
+ }
2184
+
2185
+ // add not used extensions for deactivation
2186
+ $extensions_for_deactivation = array_merge($extensions_for_deactivation,
2187
+ array_fill_keys(
2188
+ array_keys(
2189
+ array_diff_key(
2190
+ $installed_extensions,
2191
+ $this->get_used_extensions($extensions_for_deactivation, array_keys(fw()->extensions->get_all()))
2192
+ )
2193
+ ),
2194
+ array()
2195
+ )
2196
+ );
2197
+
2198
+ update_option(
2199
+ fw()->extensions->_get_active_extensions_db_option_name(),
2200
+ array_diff_key(
2201
+ fw()->extensions->_get_db_active_extensions(),
2202
+ $extensions_for_deactivation
2203
+ )
2204
+ );
2205
+
2206
+ // remove already inactive extensions
2207
+ foreach ($extensions_for_deactivation as $extension_name => $not_used_var) {
2208
+ if (!fw_ext($extension_name)) {
2209
+ unset($extensions_for_deactivation[$extension_name]);
2210
+ }
2211
+ }
2212
+
2213
+ /**
2214
+ * Prepare db wp option used to fire the 'fw_extensions_after_deactivation' action on next refresh
2215
+ */
2216
+ {
2217
+ $db_wp_option_name = 'fw_extensions_activation';
2218
+ $db_wp_option_value = get_option($db_wp_option_name, array(
2219
+ 'activated' => array(),
2220
+ 'deactivated' => array(),
2221
+ ));
2222
+
2223
+ /**
2224
+ * Keep adding to the existing value instead of resetting it on each method call
2225
+ * in case the method will be called multiple times
2226
+ */
2227
+ $db_wp_option_value['deactivated'] = array_merge($db_wp_option_value['deactivated'], $extensions_for_deactivation);
2228
+
2229
+ /**
2230
+ * Remove deactivated extensions from activated
2231
+ */
2232
+ $db_wp_option_value['activated'] = array_diff_key($db_wp_option_value['activated'], $db_wp_option_value['deactivated']);
2233
+
2234
+ update_option($db_wp_option_name, $db_wp_option_value, false);
2235
+ }
2236
+
2237
+ do_action('fw_extensions_before_deactivation', $extensions_for_deactivation);
2238
+
2239
+ if ($has_errors) {
2240
+ return $result;
2241
+ } else {
2242
+ return true;
2243
+ }
2244
+ }
2245
+
2246
+ /**
2247
+ * @param array $data
2248
+ * @return array
2249
+ * @internal
2250
+ */
2251
+ public function _extension_settings_form_render($data)
2252
+ {
2253
+ /**
2254
+ * @var FW_Extension $extension
2255
+ */
2256
+ $extension = $data['data']['extension'];
2257
+
2258
+ do_action('fw_extension_settings_form_render:'. $extension->get_name());
2259
+
2260
+ echo fw_html_tag('input', array(
2261
+ 'type' => 'hidden',
2262
+ 'name' => 'fw_extension_name',
2263
+ 'value' => $extension->get_name(),
2264
+ ), true);
2265
+
2266
+ echo fw()->backend->render_options(
2267
+ $extension->get_settings_options(),
2268
+ fw_get_db_ext_settings_option($extension->get_name())
2269
+ );
2270
+
2271
+ $data['submit']['html'] = '';
2272
+
2273
+ echo '<p>';
2274
+ echo fw_html_tag('input', array(
2275
+ 'type' => 'submit',
2276
+ 'class' => 'button-primary',
2277
+ 'value' => __('Save', 'fw'),
2278
+ ));
2279
+ echo '&nbsp;&nbsp;&nbsp;&nbsp;';
2280
+ echo fw_html_tag('a', array(
2281
+ 'href' => $this->get_link(),
2282
+ ), __('Cancel', 'fw'));
2283
+ echo '</p>';
2284
+
2285
+ return $data;
2286
+ }
2287
+
2288
+ /**
2289
+ * @param array $errors
2290
+ * @return array
2291
+ * @internal
2292
+ */
2293
+ public function _extension_settings_form_validate($errors)
2294
+ {
2295
+ do {
2296
+ if (!current_user_can($this->can_activate())) {
2297
+ $errors[] = __('You are not allowed to save extensions settings.', 'fw');
2298
+ break;
2299
+ }
2300
+
2301
+ $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2302
+
2303
+ if (!$extension) {
2304
+ $errors[] = __('Invalid extension.', 'fw');
2305
+ break;
2306
+ }
2307
+
2308
+ if (!$extension->get_settings_options()) {
2309
+ $errors[] = __('Extension does not have settings options.', 'fw');
2310
+ break;
2311
+ }
2312
+ } while(false);
2313
+
2314
+ return $errors;
2315
+ }
2316
+
2317
+ /**
2318
+ * @param array $data
2319
+ * @return array
2320
+ * @internal
2321
+ */
2322
+ public function _extension_settings_form_save($data)
2323
+ {
2324
+ $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2325
+
2326
+ $options_before_save = (array)fw_get_db_ext_settings_option($extension->get_name());
2327
+
2328
+ fw_set_db_ext_settings_option(
2329
+ $extension->get_name(),
2330
+ null,
2331
+ array_merge(
2332
+ $options_before_save,
2333
+ fw_get_options_values_from_input(
2334
+ $extension->get_settings_options()
2335
+ )
2336
+ )
2337
+ );
2338
+
2339
+ FW_Flash_Messages::add(
2340
+ 'fw_extension_settings_saved',
2341
+ __('Extensions settings successfully saved.', 'fw'),
2342
+ 'success'
2343
+ );
2344
+
2345
+ $data['redirect'] = fw_current_url();
2346
+
2347
+ do_action('fw_extension_settings_form_saved:'. $extension->get_name(), $options_before_save);
2348
+
2349
+ return $data;
2350
+ }
2351
+
2352
+ /**
2353
+ * Download an extension
2354
+ *
2355
+ * global $wp_filesystem; must me initialized
2356
+ *
2357
+ * @param string $extension_name
2358
+ * @param array $data Extension data from the "available extensions" array
2359
+ * @return string|WP_Error WP Filesystem path to the downloaded directory
2360
+ */
2361
+ private function download($extension_name, $data)
2362
+ {
2363
+ $wp_error_id = 'fw_extension_download';
2364
+
2365
+ if (empty($data['download'])) {
2366
+ return new WP_Error(
2367
+ $wp_error_id,
2368
+ sprintf(__('Extension "%s" has no download sources.', 'fw'), $this->get_extension_title($extension_name))
2369
+ );
2370
+ }
2371
+
2372
+ /** @var WP_Filesystem_Base $wp_filesystem */
2373
+ global $wp_filesystem;
2374
+
2375
+ // create temporary directory
2376
+ {
2377
+ $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
2378
+
2379
+ if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
2380
+ // just in case it already exists, clear everything, it may contain old files
2381
+ if (!$wp_filesystem->rmdir($wp_fs_tmp_dir, true)) {
2382
+ return new WP_Error(
2383
+ $wp_error_id,
2384
+ sprintf(__('Cannot remove temporary directory: %s', 'fw'), $wp_fs_tmp_dir)
2385
+ );
2386
+ }
2387
+ }
2388
+
2389
+ if (!FW_WP_Filesystem::mkdir_recursive($wp_fs_tmp_dir)) {
2390
+ return new WP_Error(
2391
+ $wp_error_id,
2392
+ sprintf(__('Cannot create temporary directory: %s', 'fw'), $wp_fs_tmp_dir)
2393
+ );
2394
+ }
2395
+ }
2396
+
2397
+ $theme_ext_requirements = fw()->theme->manifest->get('requirements/extensions');
2398
+
2399
+ foreach ($data['download'] as $source => $source_data) {
2400
+ switch ($source) {
2401
+ case 'github':
2402
+ if (empty($source_data['user_repo'])) {
2403
+ return new WP_Error(
2404
+ $wp_error_id,
2405
+ sprintf(__('"%s" extension github source "user_repo" parameter is required', 'fw'), $this->get_extension_title($extension_name))
2406
+ );
2407
+ }
2408
+
2409
+ {
2410
+ $transient_name = 'fw_ext_mngr_gh_dl';
2411
+ $transient_ttl = HOUR_IN_SECONDS;
2412
+
2413
+ $cache = get_site_transient($transient_name);
2414
+
2415
+ if ($cache === false) {
2416
+ $cache = array();
2417
+ }
2418
+ }
2419
+
2420
+ if (isset($cache[ $source_data['user_repo'] ])) {
2421
+ $download_link = $cache[ $source_data['user_repo'] ]['zipball_url'];
2422
+ } else {
2423
+ $http = new WP_Http();
2424
+
2425
+ if (
2426
+ isset($theme_ext_requirements[$extension_name])
2427
+ &&
2428
+ isset($theme_ext_requirements[$extension_name]['max_version'])
2429
+ ) {
2430
+ $tag = 'tags/v'. $theme_ext_requirements[$extension_name]['max_version'];
2431
+ } else {
2432
+ $tag = 'latest';
2433
+ }
2434
+
2435
+ $response = $http->get(
2436
+ apply_filters('fw_github_api_url', 'https://api.github.com')
2437
+ . '/repos/'. $source_data['user_repo'] .'/releases/'. $tag
2438
+ );
2439
+
2440
+ unset($http);
2441
+
2442
+ $response_code = intval(wp_remote_retrieve_response_code($response));
2443
+
2444
+ if ($response_code !== 200) {
2445
+ if ($response_code === 403) {
2446
+ if ($json_response = json_decode($response['body'], true)) {
2447
+ return new WP_Error(
2448
+ $wp_error_id,
2449
+ __('Github error:', 'fw') .' '. $json_response['message']
2450
+ );
2451
+ } else {
2452
+ return new WP_Error(
2453
+ $wp_error_id,
2454
+ sprintf(
2455
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
2456
+ $source_data['user_repo'], $response_code
2457
+ )
2458
+ );
2459
+ }
2460
+ } elseif ($response_code) {
2461
+ return new WP_Error(
2462
+ $wp_error_id,
2463
+ sprintf(
2464
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
2465
+ $source_data['user_repo'], $response_code
2466
+ )
2467
+ );
2468
+ } elseif (is_wp_error($response)) {
2469
+ return new WP_Error(
2470
+ $wp_error_id,
2471
+ sprintf(
2472
+ __( 'Failed to access Github repository "%s" releases. (%s)', 'fw' ),
2473
+ $source_data['user_repo'], $response->get_error_message()
2474
+ )
2475
+ );
2476
+ } else {
2477
+ return new WP_Error(
2478
+ $wp_error_id,
2479
+ sprintf(
2480
+ __( 'Failed to access Github repository "%s" releases.', 'fw' ),
2481
+ $source_data['user_repo']
2482
+ )
2483
+ );
2484
+ }
2485
+ }
2486
+
2487
+ $release = json_decode($response['body'], true);
2488
+
2489
+ unset($response);
2490
+
2491
+ if (empty($release)) {
2492
+ return new WP_Error(
2493
+ $wp_error_id,
2494
+ sprintf(
2495
+ __('"%s" extension github repository "%s" has no releases.', 'fw'),
2496
+ $this->get_extension_title($extension_name), $source_data['user_repo']
2497
+ )
2498
+ );
2499
+ }
2500
+
2501
+ {
2502
+ $cache[ $source_data['user_repo'] ] = array(
2503
+ 'zipball_url' => 'https://github.com/'. $source_data['user_repo'] .'/archive/'. $release['tag_name'] .'.zip',
2504
+ 'tag_name' => $release['tag_name']
2505
+ );
2506
+
2507
+ set_site_transient($transient_name, $cache, $transient_ttl);
2508
+ }
2509
+
2510
+ $download_link = $cache[ $source_data['user_repo'] ]['zipball_url'];
2511
+
2512
+ unset($release);
2513
+ }
2514
+
2515
+ {
2516
+ $http = new WP_Http();
2517
+
2518
+ $response = $http->request($download_link, array(
2519
+ 'timeout' => $this->download_timeout,
2520
+ ));
2521
+
2522
+ unset($http);
2523
+
2524
+ if (($response_code = intval(wp_remote_retrieve_response_code($response))) !== 200) {
2525
+ if ($response_code) {
2526
+ return new WP_Error(
2527
+ $wp_error_id,
2528
+ sprintf( __( 'Cannot download the "%s" extension zip. (Response code: %d)', 'fw' ),
2529
+ $this->get_extension_title( $extension_name ), $response_code
2530
+ )
2531
+ );
2532
+ } elseif (is_wp_error($response)) {
2533
+ return new WP_Error(
2534
+ $wp_error_id,
2535
+ sprintf( __( 'Cannot download the "%s" extension zip. %s', 'fw' ),
2536
+ $this->get_extension_title( $extension_name ),
2537
+ $response->get_error_message()
2538
+ )
2539
+ );
2540
+ } else {
2541
+ return new WP_Error(
2542
+ $wp_error_id,
2543
+ sprintf( __( 'Cannot download the "%s" extension zip.', 'fw' ),
2544
+ $this->get_extension_title( $extension_name )
2545
+ )
2546
+ );
2547
+ }
2548
+ }
2549
+
2550
+ $zip_path = $wp_fs_tmp_dir .'/temp.zip';
2551
+
2552
+ // save zip to file
2553
+ if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
2554
+ return new WP_Error(
2555
+ $wp_error_id,
2556
+ sprintf(__('Cannot save the "%s" extension zip.', 'fw'), $this->get_extension_title($extension_name))
2557
+ );
2558
+ }
2559
+
2560
+ unset($response);
2561
+
2562
+ $unzip_result = unzip_file(
2563
+ FW_WP_Filesystem::filesystem_path_to_real_path($zip_path),
2564
+ $wp_fs_tmp_dir
2565
+ );
2566
+
2567
+ if (is_wp_error($unzip_result)) {
2568
+ return $unzip_result;
2569
+ }
2570
+
2571
+ // remove zip file
2572
+ if (!$wp_filesystem->delete($zip_path, false, 'f')) {
2573
+ return new WP_Error(
2574
+ $wp_error_id,
2575
+ sprintf(__('Cannot remove the "%s" extension downloaded zip.', 'fw'), $this->get_extension_title($extension_name))
2576
+ );
2577
+ }
2578
+
2579
+ $unzipped_dir_files = $wp_filesystem->dirlist($wp_fs_tmp_dir);
2580
+
2581
+ if (!$unzipped_dir_files) {
2582
+ return new WP_Error(
2583
+ $wp_error_id,
2584
+ __('Cannot access the unzipped directory files.', 'fw')
2585
+ );
2586
+ }
2587
+
2588
+ /**
2589
+ * get first found directory
2590
+ * (if everything worked well, there should be only one directory)
2591
+ */
2592
+ foreach ($unzipped_dir_files as $file) {
2593
+ if ($file['type'] == 'd') {
2594
+ return $wp_fs_tmp_dir .'/'. $file['name'];
2595
+ }
2596
+ }
2597
+
2598
+ return new WP_Error(
2599
+ $wp_error_id,
2600
+ sprintf(__('The unzipped "%s" extension directory not found.', 'fw'), $this->get_extension_title($extension_name))
2601
+ );
2602
+ }
2603
+ break;
2604
+ default:
2605
+ return new WP_Error(
2606
+ $wp_error_id,
2607
+ sprintf(__('Unknown "%s" extension download source "%s"', 'fw'), $this->get_extension_title($extension_name), $source)
2608
+ );
2609
+ }
2610
+ }
2611
+ }
2612
+
2613
+ /**
2614
+ * Merge the downloaded extension directory with the existing directory
2615
+ *
2616
+ * @param string $source_wp_fs_dir Downloaded extension directory
2617
+ * @param string $destination_wp_fs_dir
2618
+ *
2619
+ * @return null|WP_Error
2620
+ */
2621
+ private function merge_extension($source_wp_fs_dir, $destination_wp_fs_dir)
2622
+ {
2623
+ /** @var WP_Filesystem_Base $wp_filesystem */
2624
+ global $wp_filesystem;
2625
+
2626
+ $wp_error_id = 'fw_extensions_merge';
2627
+
2628
+ $source_files = $wp_filesystem->dirlist($source_wp_fs_dir);
2629
+
2630
+ if ($source_files === false) {
2631
+ return new WP_Error(
2632
+ $wp_error_id,
2633
+ sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir)
2634
+ );
2635
+ }
2636
+
2637
+ if (empty($source_files)) {
2638
+ // directory is empty, nothing to move
2639
+ return;
2640
+ }
2641
+
2642
+ /**
2643
+ * Prepare destination directory
2644
+ * Remove everything except the extensions/ directory
2645
+ */
2646
+ if ($wp_filesystem->exists($destination_wp_fs_dir)) {
2647
+ $destination_files = $wp_filesystem->dirlist($destination_wp_fs_dir);
2648
+
2649
+ if ($destination_files === false) {
2650
+ return new WP_Error(
2651
+ $wp_error_id,
2652
+ sprintf(__('Cannot read directory "%s".', 'fw'), $destination_wp_fs_dir)
2653
+ );
2654
+ }
2655
+
2656
+ if (!empty($destination_files)) {
2657
+ // the directory contains some files, delete everything
2658
+ foreach ($destination_files as $file) {
2659
+ if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2660
+ // do not touch the extensions/ directory
2661
+ continue;
2662
+ }
2663
+
2664
+ if (!$wp_filesystem->delete($destination_wp_fs_dir .'/'. $file['name'], true, $file['type'])) {
2665
+ return new WP_Error(
2666
+ $wp_error_id,
2667
+ sprintf(__('Cannot delete "%s".', 'fw'), $destination_wp_fs_dir .'/'. $file['name'])
2668
+ );
2669
+ }
2670
+ }
2671
+
2672
+ unset($destination_files);
2673
+ }
2674
+ } else {
2675
+ if (!FW_WP_Filesystem::mkdir_recursive($destination_wp_fs_dir)) {
2676
+ return new WP_Error(
2677
+ $wp_error_id,
2678
+ sprintf(__('Cannot create the "%s" directory.', 'fw'), $destination_wp_fs_dir)
2679
+ );
2680
+ }
2681
+ }
2682
+
2683
+ $has_sub_extensions = false;
2684
+
2685
+ foreach ($source_files as $file) {
2686
+ if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2687
+ // do not touch the extensions/ directory
2688
+ $has_sub_extensions = true;
2689
+ continue;
2690
+ }
2691
+
2692
+ if (!$wp_filesystem->move($source_wp_fs_dir .'/'. $file['name'], $destination_wp_fs_dir .'/'. $file['name'])) {
2693
+ return new WP_Error(
2694
+ $wp_error_id,
2695
+ sprintf(
2696
+ __('Cannot move "%s" to "%s".', 'fw'),
2697
+ $source_wp_fs_dir .'/'. $file['name'],
2698
+ $destination_wp_fs_dir .'/'. $file['name']
2699
+ )
2700
+ );
2701
+ }
2702
+ }
2703
+
2704
+ unset($source_files);
2705
+
2706
+ if (!$has_sub_extensions) {
2707
+ return;
2708
+ }
2709
+
2710
+ $sub_extensions = $wp_filesystem->dirlist($source_wp_fs_dir .'/extensions');
2711
+
2712
+ if ($sub_extensions === false) {
2713
+ return new WP_Error(
2714
+ $wp_error_id,
2715
+ sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir .'/extensions')
2716
+ );
2717
+ }
2718
+
2719
+ if (empty($sub_extensions)) {
2720
+ // directory is empty, nothing to remove
2721
+ return;
2722
+ }
2723
+
2724
+ foreach ($sub_extensions as $file) {
2725
+ if ($file['type'] !== 'd') {
2726
+ // wrong, only directories must exist in the extensions/ directory
2727
+ continue;
2728
+ }
2729
+
2730
+ $merge_result = $this->merge_extension(
2731
+ $source_wp_fs_dir .'/extensions/'. $file['name'],
2732
+ $destination_wp_fs_dir .'/extensions/'. $file['name']
2733
+ );
2734
+
2735
+ if (is_wp_error($merge_result)) {
2736
+ return $merge_result;
2737
+ }
2738
+ }
2739
+ }
2740
+
2741
+ private function get_supported_extensions_for_install()
2742
+ {
2743
+ $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2744
+
2745
+ if (empty($supported_extensions)) {
2746
+ return array();
2747
+ }
2748
+
2749
+ // remove not available extensions
2750
+ $supported_extensions = array_intersect_key($supported_extensions, $this->get_available_extensions());
2751
+
2752
+ if (empty($supported_extensions)) {
2753
+ return array();
2754
+ }
2755
+
2756
+ // remove already installed extensions
2757
+ $supported_extensions = array_diff_key($supported_extensions, $this->get_installed_extensions());
2758
+
2759
+ if (empty($supported_extensions)) {
2760
+ return array();
2761
+ }
2762
+
2763
+ return $supported_extensions;
2764
+ }
2765
+
2766
+ /**
2767
+ * @param $actions
2768
+ * @return array
2769
+ * @internal
2770
+ */
2771
+ public function _filter_plugin_action_list($actions)
2772
+ {
2773
+ return array_merge(
2774
+ array(
2775
+ 'fw-extensions' => fw_html_tag('a', array(
2776
+ 'href' => $this->get_link(),
2777
+ ), fw()->manifest->get_name()),
2778
+ ),
2779
+ $actions
2780
+ );
2781
+ }
2782
+
2783
+ /**
2784
+ * @return string Extensions page link
2785
+ */
2786
+ private function get_link()
2787
+ {
2788
+ static $cache_link = null;
2789
+
2790
+ if ($cache_link === null) {
2791
+ $cache_link = menu_page_url( $this->get_page_slug(), false );
2792
+
2793
+ // https://core.trac.wordpress.org/ticket/28226
2794
+ if (is_multisite() && is_network_admin()) {
2795
+ $cache_link = self_admin_url(
2796
+ // extract relative link
2797
+ preg_replace('/^'. preg_quote(admin_url(), '/') .'/', '', $cache_link)
2798
+ );
2799
+ }
2800
+ }
2801
+
2802
+ return $cache_link;
2803
+ }
2804
+
2805
+ /**
2806
+ * @param array $skip_extensions {'ext' => mixed}
2807
+ * @param array $check_for_deps ['ext', 'ext', ...] Extensions to check if has in dependencies the used extensions
2808
+ *
2809
+ * @return array
2810
+ */
2811
+ private function get_used_extensions($skip_extensions, $check_for_deps)
2812
+ {
2813
+ $used_extensions = array();
2814
+
2815
+ $installed_extensions = $this->get_installed_extensions();
2816
+
2817
+ foreach ($installed_extensions as $inst_ext_name => &$inst_ext_data) {
2818
+ if (isset($skip_extensions[ $inst_ext_name ])) {
2819
+ continue;
2820
+ }
2821
+
2822
+ if (isset($used_extensions[$inst_ext_name])) {
2823
+ // already marked as used
2824
+ continue;
2825
+ }
2826
+
2827
+ do {
2828
+ foreach ($check_for_deps as $deps_ext) {
2829
+ if (isset($skip_extensions[$deps_ext])) {
2830
+ continue;
2831
+ }
2832
+
2833
+ if (false !== fw_akg(
2834
+ 'requirements/extensions/'. $inst_ext_name,
2835
+ $installed_extensions[$deps_ext]['manifest'],
2836
+ false
2837
+ )) {
2838
+ // is required by an active extension
2839
+ break 2;
2840
+ }
2841
+ }
2842
+
2843
+ if ( true === fw_akg(
2844
+ 'standalone',
2845
+ $inst_ext_data['manifest'],
2846
+ $this->manifest_default_values['standalone']
2847
+ ) ) {
2848
+ // can exist alone
2849
+ break;
2850
+ }
2851
+
2852
+ // not used
2853
+ continue 2;
2854
+ } while(false);
2855
+
2856
+ $used_extensions[$inst_ext_name] = array();
2857
+
2858
+ // Set all sub-extensions as used
2859
+ foreach ($this->collect_sub_extensions($inst_ext_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2860
+ if (isset($skip_extensions[$sub_extension_name])) {
2861
+ continue;
2862
+ }
2863
+
2864
+ $used_extensions[ $sub_extension_name ] = array();
2865
+ }
2866
+
2867
+ // Set all parents as used
2868
+ {
2869
+ $current_parent = $inst_ext_name;
2870
+ while ($current_parent = $installed_extensions[$current_parent]['parent']) {
2871
+ $used_extensions[$current_parent] = array();
2872
+ }
2873
+ }
2874
+ }
2875
+ unset($inst_ext_data);
2876
+
2877
+ // remove all skipped extensions and sub-extension from used extensions
2878
+ foreach (array_keys($skip_extensions) as $skip_extension_name) {
2879
+ unset($used_extensions[$skip_extension_name]);
2880
+
2881
+ if (isset($installed_extensions[$skip_extension_name])) {
2882
+ foreach ($this->collect_sub_extensions($skip_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2883
+ unset($used_extensions[$sub_extension_name]);
2884
+ }
2885
+ }
2886
+ }
2887
+
2888
+ return $used_extensions;
2889
+ }
2890
+
2891
+ /**
2892
+ * @internal
2893
+ */
2894
+ public function _action_admin_footer()
2895
+ {
2896
+ $this->activate_hidden_standalone_extensions();
2897
+ }
2898
+
2899
+ public function get_extension_title($extension_name)
2900
+ {
2901
+ $installed_extensions = $this->get_installed_extensions();
2902
+
2903
+ if (isset($installed_extensions[$extension_name])) {
2904
+ return fw_akg('name', $installed_extensions[$extension_name]['manifest'], fw_id_to_title($extension_name));
2905
+ }
2906
+
2907
+ unset($installed_extensions);
2908
+
2909
+ $available_extensions = $this->get_available_extensions();
2910
+
2911
+ if (isset($available_extensions[$extension_name])) {
2912
+ return $available_extensions[$extension_name]['name'];
2913
+ }
2914
+
2915
+ return fw_id_to_title($extension_name);
2916
+ }
2917
+
2918
+ public function is_extensions_page()
2919
+ {
2920
+ $current_screen = get_current_screen();
2921
+
2922
+ if (empty($current_screen)) {
2923
+ return false;
2924
+ }
2925
+
2926
+ return (
2927
+ property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2928
+ &&
2929
+ property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2930
+ &&
2931
+ !isset($_GET['sub-page'])
2932
+ );
2933
+ }
2934
+
2935
+ public function is_extension_page()
2936
+ {
2937
+ $current_screen = get_current_screen();
2938
+
2939
+ if (empty($current_screen)) {
2940
+ return false;
2941
+ }
2942
+
2943
+ return (
2944
+ property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2945
+ &&
2946
+ property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2947
+ &&
2948
+ isset($_GET['sub-page']) && $_GET['sub-page'] === 'extension'
2949
+ );
2950
+ }
2951
+
2952
+ /**
2953
+ * @internal
2954
+ */
2955
+ public function _action_enqueue_scripts()
2956
+ {
2957
+ wp_enqueue_style(
2958
+ 'fw-extensions-menu-icon',
2959
+ $this->get_uri('/static/unyson-font-icon/style.css'),
2960
+ array(),
2961
+ fw()->manifest->get_version()
2962
+ );
2963
+
2964
+ /**
2965
+ * Enqueue only on Extensions List page
2966
+ */
2967
+ if ($this->is_extensions_page()) {
2968
+ wp_enqueue_style(
2969
+ 'fw-extensions-page',
2970
+ $this->get_uri('/static/extensions-page.css'),
2971
+ array(
2972
+ 'fw',
2973
+ 'fw-unycon', 'fw-font-awesome', // in case some extension has font-icon thumbnail
2974
+ ),
2975
+ fw()->manifest->get_version()
2976
+ );
2977
+ wp_enqueue_script(
2978
+ 'fw-extensions-page',
2979
+ $this->get_uri('/static/extensions-page.js'),
2980
+ array('fw'),
2981
+ fw()->manifest->get_version(),
2982
+ true
2983
+ );
2984
+ wp_localize_script('fw-extensions-page', '_fw_extensions_script_data', array(
2985
+ 'link' => $this->get_link(),
2986
+ ));
2987
+
2988
+ /**
2989
+ * this is needed for fw.soleModal design
2990
+ * it is displayed when extension ajax install returns an error
2991
+ */
2992
+ wp_enqueue_media();
2993
+ }
2994
+
2995
+ if ($this->is_extension_page()) {
2996
+ wp_enqueue_style(
2997
+ 'fw-extension-page',
2998
+ $this->get_uri('/static/extension-page.css'),
2999
+ array('fw'),
3000
+ fw()->manifest->get_version()
3001
+ );
3002
+ wp_enqueue_script(
3003
+ 'fw-extension-page',
3004
+ $this->get_uri('/static/extension-page.js'),
3005
+ array('fw'),
3006
+ fw()->manifest->get_version(),
3007
+ true
3008
+ );
3009
+
3010
+ /**
3011
+ * Enqueue extension settings options static
3012
+ */
3013
+ if (
3014
+ isset($_GET['extension'])
3015
+ &&
3016
+ is_string($extension_name = $_GET['extension'])
3017
+ &&
3018
+ fw()->extensions->get($extension_name)
3019
+ &&
3020
+ ($extension_settings_options = fw()->extensions->get($extension_name)->get_settings_options())
3021
+ ) {
3022
+ fw()->backend->enqueue_options_static($extension_settings_options);
3023
+ }
3024
+ }
3025
+ }
3026
+
3027
+ private function activate_theme_extensions()
3028
+ {
3029
+ $db_active_extensions = fw()->extensions->_get_db_active_extensions();
3030
+
3031
+ foreach ($this->get_installed_extensions() as $extension_name => $extension) {
3032
+ if ($extension['is']['theme']) {
3033
+ $db_active_extensions[ $extension_name ] = array();
3034
+ }
3035
+ }
3036
+
3037
+ update_option(
3038
+ fw()->extensions->_get_active_extensions_db_option_name(),
3039
+ $db_active_extensions
3040
+ );
3041
+ }
3042
+
3043
+ /**
3044
+ * @internal
3045
+ */
3046
+ public function _action_theme_switch()
3047
+ {
3048
+ $this->activate_theme_extensions();
3049
+ $this->activate_extensions(
3050
+ array_fill_keys(
3051
+ array_keys(fw()->theme->manifest->get('supported_extensions', array())),
3052
+ array()
3053
+ )
3054
+ );
3055
+ }
3056
+
3057
+ /**
3058
+ * @param array $collected The found extensions {'extension_name' => array()}
3059
+ * @param array $extensions {'extension_name' => array()}
3060
+ * @param bool $check_all Check all extensions or only active extensions
3061
+ */
3062
+ private function collect_extensions_that_requires(&$collected, $extensions, $check_all = false)
3063
+ {
3064
+ if (empty($extensions)) {
3065
+ return;
3066
+ }
3067
+
3068
+ $found_extensions = array();
3069
+
3070
+ foreach ($this->get_installed_extensions() as $extension_name => $extension_data) {
3071
+ if (isset($collected[$extension_name])) {
3072
+ continue;
3073
+ }
3074
+
3075
+ if (!$check_all) {
3076
+ if (!fw_ext($extension_name)) {
3077
+ continue;
3078
+ }
3079
+ }
3080
+
3081
+ if (
3082
+ array_intersect_key(
3083
+ $extensions,
3084
+ fw_akg(
3085
+ 'requirements/extensions',
3086
+ $extension_data['manifest'],
3087
+ array()
3088
+ )
3089
+ )
3090
+ ) {
3091
+ $found_extensions[$extension_name] = $collected[$extension_name] = array();
3092
+ }
3093
+ }
3094
+
3095
+ $this->collect_extensions_that_requires($collected, $found_extensions, $check_all);
3096
+ }
3097
+
3098
+ /**
3099
+ * Get extension settings page link
3100
+ * @param string $extension_name
3101
+ * @return string
3102
+ */
3103
+ public function get_extension_link($extension_name)
3104
+ {
3105
+ return $this->get_link() .'&sub-page=extension&extension='. $extension_name;
3106
+ }
3107
+
3108
+ /**
3109
+ * @param string $extension_name
3110
+ * @return array|WP_Error Extensions to merge with db active extensions list
3111
+ */
3112
+ private function get_extensions_for_activation($extension_name)
3113
+ {
3114
+ $installed_extensions = $this->get_installed_extensions();
3115
+
3116
+ $wp_error_id = 'fw_ext_activation';
3117
+
3118
+ if (!isset($installed_extensions[$extension_name])) {
3119
+ return new WP_Error($wp_error_id,
3120
+ sprintf(
3121
+ __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3122
+ $this->get_extension_title($extension_name),
3123
+ fw_html_tag('a', array(
3124
+ 'href' => $this->get_link() .'&sub-page=install&extension='. $extension_name
3125
+ ), __('Install', 'fw'))
3126
+ )
3127
+ );
3128
+ }
3129
+
3130
+ {
3131
+ $extension_parents = array($extension_name);
3132
+
3133
+ $current_parent = $extension_name;
3134
+ while ($current_parent = $installed_extensions[$current_parent]['parent']) {
3135
+ $extension_parents[] = $current_parent;
3136
+ }
3137
+
3138
+ $extension_parents = array_reverse($extension_parents);
3139
+ }
3140
+
3141
+ $extensions = array();
3142
+
3143
+ foreach ($extension_parents as $parent_extension_name) {
3144
+ $extensions[ $parent_extension_name ] = array();
3145
+ }
3146
+
3147
+ // search sub-extensions
3148
+ foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3149
+ $extensions[ $sub_extension_name ] = array();
3150
+ }
3151
+
3152
+ // search required extensions
3153
+ {
3154
+ $pending_required_search = $extensions;
3155
+
3156
+ while ($pending_required_search) {
3157
+ foreach (array_keys($pending_required_search) as $pend_req_extension_name) {
3158
+ unset($pending_required_search[$pend_req_extension_name]);
3159
+
3160
+ unset($required_extensions); // reset reference
3161
+ $required_extensions = array();
3162
+ $this->collect_required_extensions($pend_req_extension_name, $installed_extensions, $required_extensions);
3163
+
3164
+ foreach ($required_extensions as $required_extension_name => $required_extension_data) {
3165
+ if (!isset($installed_extensions[$required_extension_name])) {
3166
+ return new WP_Error($wp_error_id,
3167
+ sprintf(
3168
+ __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3169
+ $this->get_extension_title($required_extension_name),
3170
+ fw_html_tag('a', array(
3171
+ 'href' => $this->get_link() .'&sub-page=install&extension='. $required_extension_name
3172
+ ), __('Install', 'fw'))
3173
+ )
3174
+ );
3175
+ }
3176
+
3177
+ $extensions[$required_extension_name] = array();
3178
+
3179
+ // search sub-extensions
3180
+ foreach ($this->collect_sub_extensions($required_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3181
+ if (isset($extensions[$sub_extension_name])) {
3182
+ continue;
3183
+ }
3184
+
3185
+ $extensions[$sub_extension_name] = array();
3186
+
3187
+ $pending_required_search[$sub_extension_name] = array();
3188
+ }
3189
+ }
3190
+ }
3191
+ }
3192
+ }
3193
+
3194
+ return $extensions;
3195
+ }
3196
+
3197
+ public function _action_admin_notices() {
3198
+ /**
3199
+ * In v2.4.12 was done a terrible mistake https://github.com/ThemeFuse/Unyson-Extensions-Approval/issues/160
3200
+ * Show a warning with link to install theme supported extensions
3201
+ */
3202
+ if (
3203
+ !isset($_GET['supported']) // already on 'Install Supported Extensions' page
3204
+ &&
3205
+ $this->can_install()
3206
+ &&
3207
+ (($installed_extensions = $this->get_installed_extensions()) || true)
3208
+ &&
3209
+ !isset($installed_extensions['page-builder'])
3210
+ &&
3211
+ $this->get_supported_extensions_for_install()
3212
+ ) {
3213
+ echo '<div class="error"> <p>'
3214
+ , fw_html_tag('a', array('href' => $this->get_link() .'&sub-page=install&supported'),
3215
+ __('Install theme compatible extensions', 'fw'))
3216
+ , '</p></div>';
3217
+ }
3218
+ }
3219
+ }
framework/core/components/extensions/manager/includes/class--fw-extensions-delete-upgrader-skin.php CHANGED
@@ -1,28 +1,28 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
-
5
- class _FW_Extensions_Delete_Upgrader_Skin extends WP_Upgrader_Skin
6
- {
7
- public function after($data = array())
8
- {
9
- $update_actions = array(
10
- 'extensions_page' => fw_html_tag(
11
- 'a',
12
- array(
13
- 'href' => fw_akg('extensions_page_link', $data, '#'),
14
- 'title' => __('Go to extensions page', 'fw'),
15
- 'target' => '_parent',
16
- ),
17
- __('Return to Extensions page', 'fw')
18
- )
19
- );
20
-
21
- $this->feedback(implode(' | ', (array)$update_actions));
22
-
23
- if ($this->result) {
24
- // used for popup ajax form submit result
25
- $this->feedback('<span success></span>');
26
- }
27
- }
28
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
+
5
+ class _FW_Extensions_Delete_Upgrader_Skin extends WP_Upgrader_Skin
6
+ {
7
+ public function after($data = array())
8
+ {
9
+ $update_actions = array(
10
+ 'extensions_page' => fw_html_tag(
11
+ 'a',
12
+ array(
13
+ 'href' => fw_akg('extensions_page_link', $data, '#'),
14
+ 'title' => __('Go to extensions page', 'fw'),
15
+ 'target' => '_parent',
16
+ ),
17
+ __('Return to Extensions page', 'fw')
18
+ )
19
+ );
20
+
21
+ $this->feedback(implode(' | ', (array)$update_actions));
22
+
23
+ if ($this->result) {
24
+ // used for popup ajax form submit result
25
+ $this->feedback('<span success></span>');
26
+ }
27
+ }
28
+ }
framework/core/components/extensions/manager/includes/class--fw-extensions-install-upgrader-skin.php CHANGED
@@ -1,28 +1,28 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
-
5
- class _FW_Extensions_Install_Upgrader_Skin extends WP_Upgrader_Skin
6
- {
7
- public function after($data = array())
8
- {
9
- $update_actions = array(
10
- 'extensions_page' => fw_html_tag(
11
- 'a',
12
- array(
13
- 'href' => fw_akg('extensions_page_link', $data, '#'),
14
- 'title' => __('Go to extensions page', 'fw'),
15
- 'target' => '_parent',
16
- ),
17
- __('Return to Extensions page', 'fw')
18
- )
19
- );
20
-
21
- $this->feedback(implode(' | ', (array)$update_actions));
22
-
23
- if ($this->result) {
24
- // used for popup ajax form submit result
25
- $this->feedback('<span success></span>');
26
- }
27
- }
28
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
+
5
+ class _FW_Extensions_Install_Upgrader_Skin extends WP_Upgrader_Skin
6
+ {
7
+ public function after($data = array())
8
+ {
9
+ $update_actions = array(
10
+ 'extensions_page' => fw_html_tag(
11
+ 'a',
12
+ array(
13
+ 'href' => fw_akg('extensions_page_link', $data, '#'),
14
+ 'title' => __('Go to extensions page', 'fw'),
15
+ 'target' => '_parent',
16
+ ),
17
+ __('Return to Extensions page', 'fw')
18
+ )
19
+ );
20
+
21
+ $this->feedback(implode(' | ', (array)$update_actions));
22
+
23
+ if ($this->result) {
24
+ // used for popup ajax form submit result
25
+ $this->feedback('<span success></span>');
26
+ }
27
+ }
28
+ }
framework/core/components/extensions/manager/includes/parsedown/LICENSE.txt CHANGED
@@ -1,20 +1,20 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2013 Emanuil Rusev, erusev.com
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of
6
- this software and associated documentation files (the "Software"), to deal in
7
- the Software without restriction, including without limitation the rights to
8
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
- the Software, and to permit persons to whom the Software is furnished to do so,
10
- subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Emanuil Rusev, erusev.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
framework/core/components/extensions/manager/includes/parsedown/Parsedown.php CHANGED
@@ -1,1528 +1,1528 @@
1
- <?php
2
-
3
- #
4
- #
5
- # Parsedown
6
- # http://parsedown.org
7
- #
8
- # (c) Emanuil Rusev
9
- # http://erusev.com
10
- #
11
- # For the full license information, view the LICENSE file that was distributed
12
- # with this source code.
13
- #
14
- #
15
-
16
- class Parsedown
17
- {
18
- # ~
19
-
20
- const version = '1.5.4';
21
-
22
- # ~
23
-
24
- function text($text)
25
- {
26
- # make sure no definitions are set
27
- $this->DefinitionData = array();
28
-
29
- # standardize line breaks
30
- $text = str_replace(array("\r\n", "\r"), "\n", $text);
31
-
32
- # remove surrounding line breaks
33
- $text = trim($text, "\n");
34
-
35
- # split text into lines
36
- $lines = explode("\n", $text);
37
-
38
- # iterate through lines to identify blocks
39
- $markup = $this->lines($lines);
40
-
41
- # trim line breaks
42
- $markup = trim($markup, "\n");
43
-
44
- return $markup;
45
- }
46
-
47
- #
48
- # Setters
49
- #
50
-
51
- function setBreaksEnabled($breaksEnabled)
52
- {
53
- $this->breaksEnabled = $breaksEnabled;
54
-
55
- return $this;
56
- }
57
-
58
- protected $breaksEnabled;
59
-
60
- function setMarkupEscaped($markupEscaped)
61
- {
62
- $this->markupEscaped = $markupEscaped;
63
-
64
- return $this;
65
- }
66
-
67
- protected $markupEscaped;
68
-
69
- function setUrlsLinked($urlsLinked)
70
- {
71
- $this->urlsLinked = $urlsLinked;
72
-
73
- return $this;
74
- }
75
-
76
- protected $urlsLinked = true;
77
-
78
- #
79
- # Lines
80
- #
81
-
82
- protected $BlockTypes = array(
83
- '#' => array('Header'),
84
- '*' => array('Rule', 'List'),
85
- '+' => array('List'),
86
- '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
87
- '0' => array('List'),
88
- '1' => array('List'),
89
- '2' => array('List'),
90
- '3' => array('List'),
91
- '4' => array('List'),
92
- '5' => array('List'),
93
- '6' => array('List'),
94
- '7' => array('List'),
95
- '8' => array('List'),
96
- '9' => array('List'),
97
- ':' => array('Table'),
98
- '<' => array('Comment', 'Markup'),
99
- '=' => array('SetextHeader'),
100
- '>' => array('Quote'),
101
- '[' => array('Reference'),
102
- '_' => array('Rule'),
103
- '`' => array('FencedCode'),
104
- '|' => array('Table'),
105
- '~' => array('FencedCode'),
106
- );
107
-
108
- # ~
109
-
110
- protected $unmarkedBlockTypes = array(
111
- 'Code',
112
- );
113
-
114
- #
115
- # Blocks
116
- #
117
-
118
- private function lines(array $lines)
119
- {
120
- $CurrentBlock = null;
121
-
122
- foreach ($lines as $line)
123
- {
124
- if (chop($line) === '')
125
- {
126
- if (isset($CurrentBlock))
127
- {
128
- $CurrentBlock['interrupted'] = true;
129
- }
130
-
131
- continue;
132
- }
133
-
134
- if (strpos($line, "\t") !== false)
135
- {
136
- $parts = explode("\t", $line);
137
-
138
- $line = $parts[0];
139
-
140
- unset($parts[0]);
141
-
142
- foreach ($parts as $part)
143
- {
144
- $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
145
-
146
- $line .= str_repeat(' ', $shortage);
147
- $line .= $part;
148
- }
149
- }
150
-
151
- $indent = 0;
152
-
153
- while (isset($line[$indent]) and $line[$indent] === ' ')
154
- {
155
- $indent ++;
156
- }
157
-
158
- $text = $indent > 0 ? substr($line, $indent) : $line;
159
-
160
- # ~
161
-
162
- $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
163
-
164
- # ~
165
-
166
- if (isset($CurrentBlock['continuable']))
167
- {
168
- $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
169
-
170
- if (isset($Block))
171
- {
172
- $CurrentBlock = $Block;
173
-
174
- continue;
175
- }
176
- else
177
- {
178
- if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
179
- {
180
- $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
181
- }
182
- }
183
- }
184
-
185
- # ~
186
-
187
- $marker = $text[0];
188
-
189
- # ~
190
-
191
- $blockTypes = $this->unmarkedBlockTypes;
192
-
193
- if (isset($this->BlockTypes[$marker]))
194
- {
195
- foreach ($this->BlockTypes[$marker] as $blockType)
196
- {
197
- $blockTypes []= $blockType;
198
- }
199
- }
200
-
201
- #
202
- # ~
203
-
204
- foreach ($blockTypes as $blockType)
205
- {
206
- $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
207
-
208
- if (isset($Block))
209
- {
210
- $Block['type'] = $blockType;
211
-
212
- if ( ! isset($Block['identified']))
213
- {
214
- $Blocks []= $CurrentBlock;
215
-
216
- $Block['identified'] = true;
217
- }
218
-
219
- if (method_exists($this, 'block'.$blockType.'Continue'))
220
- {
221
- $Block['continuable'] = true;
222
- }
223
-
224
- $CurrentBlock = $Block;
225
-
226
- continue 2;
227
- }
228
- }
229
-
230
- # ~
231
-
232
- if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
233
- {
234
- $CurrentBlock['element']['text'] .= "\n".$text;
235
- }
236
- else
237
- {
238
- $Blocks []= $CurrentBlock;
239
-
240
- $CurrentBlock = $this->paragraph($Line);
241
-
242
- $CurrentBlock['identified'] = true;
243
- }
244
- }
245
-
246
- # ~
247
-
248
- if (isset($CurrentBlock['continuable']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
249
- {
250
- $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
251
- }
252
-
253
- # ~
254
-
255
- $Blocks []= $CurrentBlock;
256
-
257
- unset($Blocks[0]);
258
-
259
- # ~
260
-
261
- $markup = '';
262
-
263
- foreach ($Blocks as $Block)
264
- {
265
- if (isset($Block['hidden']))
266
- {
267
- continue;
268
- }
269
-
270
- $markup .= "\n";
271
- $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
272
- }
273
-
274
- $markup .= "\n";
275
-
276
- # ~
277
-
278
- return $markup;
279
- }
280
-
281
- #
282
- # Code
283
-
284
- protected function blockCode($Line, $Block = null)
285
- {
286
- if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
287
- {
288
- return;
289
- }
290
-
291
- if ($Line['indent'] >= 4)
292
- {
293
- $text = substr($Line['body'], 4);
294
-
295
- $Block = array(
296
- 'element' => array(
297
- 'name' => 'pre',
298
- 'handler' => 'element',
299
- 'text' => array(
300
- 'name' => 'code',
301
- 'text' => $text,
302
- ),
303
- ),
304
- );
305
-
306
- return $Block;
307
- }
308
- }
309
-
310
- protected function blockCodeContinue($Line, $Block)
311
- {
312
- if ($Line['indent'] >= 4)
313
- {
314
- if (isset($Block['interrupted']))
315
- {
316
- $Block['element']['text']['text'] .= "\n";
317
-
318
- unset($Block['interrupted']);
319
- }
320
-
321
- $Block['element']['text']['text'] .= "\n";
322
-
323
- $text = substr($Line['body'], 4);
324
-
325
- $Block['element']['text']['text'] .= $text;
326
-
327
- return $Block;
328
- }
329
- }
330
-
331
- protected function blockCodeComplete($Block)
332
- {
333
- $text = $Block['element']['text']['text'];
334
-
335
- $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
336
-
337
- $Block['element']['text']['text'] = $text;
338
-
339
- return $Block;
340
- }
341
-
342
- #
343
- # Comment
344
-
345
- protected function blockComment($Line)
346
- {
347
- if ($this->markupEscaped)
348
- {
349
- return;
350
- }
351
-
352
- if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
353
- {
354
- $Block = array(
355
- 'markup' => $Line['body'],
356
- );
357
-
358
- if (preg_match('/-->$/', $Line['text']))
359
- {
360
- $Block['closed'] = true;
361
- }
362
-
363
- return $Block;
364
- }
365
- }
366
-
367
- protected function blockCommentContinue($Line, array $Block)
368
- {
369
- if (isset($Block['closed']))
370
- {
371
- return;
372
- }
373
-
374
- $Block['markup'] .= "\n" . $Line['body'];
375
-
376
- if (preg_match('/-->$/', $Line['text']))
377
- {
378
- $Block['closed'] = true;
379
- }
380
-
381
- return $Block;
382
- }
383
-
384
- #
385
- # Fenced Code
386
-
387
- protected function blockFencedCode($Line)
388
- {
389
- if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
390
- {
391
- $Element = array(
392
- 'name' => 'code',
393
- 'text' => '',
394
- );
395
-
396
- if (isset($matches[1]))
397
- {
398
- $class = 'language-'.$matches[1];
399
-
400
- $Element['attributes'] = array(
401
- 'class' => $class,
402
- );
403
- }
404
-
405
- $Block = array(
406
- 'char' => $Line['text'][0],
407
- 'element' => array(
408
- 'name' => 'pre',
409
- 'handler' => 'element',
410
- 'text' => $Element,
411
- ),
412
- );
413
-
414
- return $Block;
415
- }
416
- }
417
-
418
- protected function blockFencedCodeContinue($Line, $Block)
419
- {
420
- if (isset($Block['complete']))
421
- {
422
- return;
423
- }
424
-
425
- if (isset($Block['interrupted']))
426
- {
427
- $Block['element']['text']['text'] .= "\n";
428
-
429
- unset($Block['interrupted']);
430
- }
431
-
432
- if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
433
- {
434
- $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
435
-
436
- $Block['complete'] = true;
437
-
438
- return $Block;
439
- }
440
-
441
- $Block['element']['text']['text'] .= "\n".$Line['body'];;
442
-
443
- return $Block;
444
- }
445
-
446
- protected function blockFencedCodeComplete($Block)
447
- {
448
- $text = $Block['element']['text']['text'];
449
-
450
- $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
451
-
452
- $Block['element']['text']['text'] = $text;
453
-
454
- return $Block;
455
- }
456
-
457
- #
458
- # Header
459
-
460
- protected function blockHeader($Line)
461
- {
462
- if (isset($Line['text'][1]))
463
- {
464
- $level = 1;
465
-
466
- while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
467
- {
468
- $level ++;
469
- }
470
-
471
- if ($level > 6)
472
- {
473
- return;
474
- }
475
-
476
- $text = trim($Line['text'], '# ');
477
-
478
- $Block = array(
479
- 'element' => array(
480
- 'name' => 'h' . min(6, $level),
481
- 'text' => $text,
482
- 'handler' => 'line',
483
- ),
484
- );
485
-
486
- return $Block;
487
- }
488
- }
489
-
490
- #
491
- # List
492
-
493
- protected function blockList($Line)
494
- {
495
- list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
496
-
497
- if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
498
- {
499
- $Block = array(
500
- 'indent' => $Line['indent'],
501
- 'pattern' => $pattern,
502
- 'element' => array(
503
- 'name' => $name,
504
- 'handler' => 'elements',
505
- ),
506
- );
507
-
508
- $Block['li'] = array(
509
- 'name' => 'li',
510
- 'handler' => 'li',
511
- 'text' => array(
512
- $matches[2],
513
- ),
514
- );
515
-
516
- $Block['element']['text'] []= & $Block['li'];
517
-
518
- return $Block;
519
- }
520
- }
521
-
522
- protected function blockListContinue($Line, array $Block)
523
- {
524
- if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
525
- {
526
- if (isset($Block['interrupted']))
527
- {
528
- $Block['li']['text'] []= '';
529
-
530
- unset($Block['interrupted']);
531
- }
532
-
533
- unset($Block['li']);
534
-
535
- $text = isset($matches[1]) ? $matches[1] : '';
536
-
537
- $Block['li'] = array(
538
- 'name' => 'li',
539
- 'handler' => 'li',
540
- 'text' => array(
541
- $text,
542
- ),
543
- );
544
-
545
- $Block['element']['text'] []= & $Block['li'];
546
-
547
- return $Block;
548
- }
549
-
550
- if ($Line['text'][0] === '[' and $this->blockReference($Line))
551
- {
552
- return $Block;
553
- }
554
-
555
- if ( ! isset($Block['interrupted']))
556
- {
557
- $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
558
-
559
- $Block['li']['text'] []= $text;
560
-
561
- return $Block;
562
- }
563
-
564
- if ($Line['indent'] > 0)
565
- {
566
- $Block['li']['text'] []= '';
567
-
568
- $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
569
-
570
- $Block['li']['text'] []= $text;
571
-
572
- unset($Block['interrupted']);
573
-
574
- return $Block;
575
- }
576
- }
577
-
578
- #
579
- # Quote
580
-
581
- protected function blockQuote($Line)
582
- {
583
- if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
584
- {
585
- $Block = array(
586
- 'element' => array(
587
- 'name' => 'blockquote',
588
- 'handler' => 'lines',
589
- 'text' => (array) $matches[1],
590
- ),
591
- );
592
-
593
- return $Block;
594
- }
595
- }
596
-
597
- protected function blockQuoteContinue($Line, array $Block)
598
- {
599
- if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
600
- {
601
- if (isset($Block['interrupted']))
602
- {
603
- $Block['element']['text'] []= '';
604
-
605
- unset($Block['interrupted']);
606
- }
607
-
608
- $Block['element']['text'] []= $matches[1];
609
-
610
- return $Block;
611
- }
612
-
613
- if ( ! isset($Block['interrupted']))
614
- {
615
- $Block['element']['text'] []= $Line['text'];
616
-
617
- return $Block;
618
- }
619
- }
620
-
621
- #
622
- # Rule
623
-
624
- protected function blockRule($Line)
625
- {
626
- if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
627
- {
628
- $Block = array(
629
- 'element' => array(
630
- 'name' => 'hr'
631
- ),
632
- );
633
-
634
- return $Block;
635
- }
636
- }
637
-
638
- #
639
- # Setext
640
-
641
- protected function blockSetextHeader($Line, array $Block = null)
642
- {
643
- if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
644
- {
645
- return;
646
- }
647
-
648
- if (chop($Line['text'], $Line['text'][0]) === '')
649
- {
650
- $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
651
-
652
- return $Block;
653
- }
654
- }
655
-
656
- #
657
- # Markup
658
-
659
- protected function blockMarkup($Line)
660
- {
661
- if ($this->markupEscaped)
662
- {
663
- return;
664
- }
665
-
666
- if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
667
- {
668
- $element = strtolower($matches[1]);
669
-
670
- if (in_array($element, $this->textLevelElements))
671
- {
672
- return;
673
- }
674
-
675
- $Block = array(
676
- 'name' => $matches[1],
677
- 'depth' => 0,
678
- 'markup' => $Line['text'],
679
- );
680
-
681
- $length = strlen($matches[0]);
682
-
683
- $remainder = substr($Line['text'], $length);
684
-
685
- if (trim($remainder) === '')
686
- {
687
- if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
688
- {
689
- $Block['closed'] = true;
690
-
691
- $Block['void'] = true;
692
- }
693
- }
694
- else
695
- {
696
- if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
697
- {
698
- return;
699
- }
700
-
701
- if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
702
- {
703
- $Block['closed'] = true;
704
- }
705
- }
706
-
707
- return $Block;
708
- }
709
- }
710
-
711
- protected function blockMarkupContinue($Line, array $Block)
712
- {
713
- if (isset($Block['closed']))
714
- {
715
- return;
716
- }
717
-
718
- if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
719
- {
720
- $Block['depth'] ++;
721
- }
722
-
723
- if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
724
- {
725
- if ($Block['depth'] > 0)
726
- {
727
- $Block['depth'] --;
728
- }
729
- else
730
- {
731
- $Block['closed'] = true;
732
- }
733
- }
734
-
735
- if (isset($Block['interrupted']))
736
- {
737
- $Block['markup'] .= "\n";
738
-
739
- unset($Block['interrupted']);
740
- }
741
-
742
- $Block['markup'] .= "\n".$Line['body'];
743
-
744
- return $Block;
745
- }
746
-
747
- #
748
- # Reference
749
-
750
- protected function blockReference($Line)
751
- {
752
- if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
753
- {
754
- $id = strtolower($matches[1]);
755
-
756
- $Data = array(
757
- 'url' => $matches[2],
758
- 'title' => null,
759
- );
760
-
761
- if (isset($matches[3]))
762
- {
763
- $Data['title'] = $matches[3];
764
- }
765
-
766
- $this->DefinitionData['Reference'][$id] = $Data;
767
-
768
- $Block = array(
769
- 'hidden' => true,
770
- );
771
-
772
- return $Block;
773
- }
774
- }
775
-
776
- #
777
- # Table
778
-
779
- protected function blockTable($Line, array $Block = null)
780
- {
781
- if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
782
- {
783
- return;
784
- }
785
-
786
- if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
787
- {
788
- $alignments = array();
789
-
790
- $divider = $Line['text'];
791
-
792
- $divider = trim($divider);
793
- $divider = trim($divider, '|');
794
-
795
- $dividerCells = explode('|', $divider);
796
-
797
- foreach ($dividerCells as $dividerCell)
798
- {
799
- $dividerCell = trim($dividerCell);
800
-
801
- if ($dividerCell === '')
802
- {
803
- continue;
804
- }
805
-
806
- $alignment = null;
807
-
808
- if ($dividerCell[0] === ':')
809
- {
810
- $alignment = 'left';
811
- }
812
-
813
- if (substr($dividerCell, - 1) === ':')
814
- {
815
- $alignment = $alignment === 'left' ? 'center' : 'right';
816
- }
817
-
818
- $alignments []= $alignment;
819
- }
820
-
821
- # ~
822
-
823
- $HeaderElements = array();
824
-
825
- $header = $Block['element']['text'];
826
-
827
- $header = trim($header);
828
- $header = trim($header, '|');
829
-
830
- $headerCells = explode('|', $header);
831
-
832
- foreach ($headerCells as $index => $headerCell)
833
- {
834
- $headerCell = trim($headerCell);
835
-
836
- $HeaderElement = array(
837
- 'name' => 'th',
838
- 'text' => $headerCell,
839
- 'handler' => 'line',
840
- );
841
-
842
- if (isset($alignments[$index]))
843
- {
844
- $alignment = $alignments[$index];
845
-
846
- $HeaderElement['attributes'] = array(
847
- 'style' => 'text-align: '.$alignment.';',
848
- );
849
- }
850
-
851
- $HeaderElements []= $HeaderElement;
852
- }
853
-
854
- # ~
855
-
856
- $Block = array(
857
- 'alignments' => $alignments,
858
- 'identified' => true,
859
- 'element' => array(
860
- 'name' => 'table',
861
- 'handler' => 'elements',
862
- ),
863
- );
864
-
865
- $Block['element']['text'] []= array(
866
- 'name' => 'thead',
867
- 'handler' => 'elements',
868
- );
869
-
870
- $Block['element']['text'] []= array(
871
- 'name' => 'tbody',
872
- 'handler' => 'elements',
873
- 'text' => array(),
874
- );
875
-
876
- $Block['element']['text'][0]['text'] []= array(
877
- 'name' => 'tr',
878
- 'handler' => 'elements',
879
- 'text' => $HeaderElements,
880
- );
881
-
882
- return $Block;
883
- }
884
- }
885
-
886
- protected function blockTableContinue($Line, array $Block)
887
- {
888
- if (isset($Block['interrupted']))
889
- {
890
- return;
891
- }
892
-
893
- if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
894
- {
895
- $Elements = array();
896
-
897
- $row = $Line['text'];
898
-
899
- $row = trim($row);
900
- $row = trim($row, '|');
901
-
902
- preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
903
-
904
- foreach ($matches[0] as $index => $cell)
905
- {
906
- $cell = trim($cell);
907
-
908
- $Element = array(
909
- 'name' => 'td',
910
- 'handler' => 'line',
911
- 'text' => $cell,
912
- );
913
-
914
- if (isset($Block['alignments'][$index]))
915
- {
916
- $Element['attributes'] = array(
917
- 'style' => 'text-align: '.$Block['alignments'][$index].';',
918
- );
919
- }
920
-
921
- $Elements []= $Element;
922
- }
923
-
924
- $Element = array(
925
- 'name' => 'tr',
926
- 'handler' => 'elements',
927
- 'text' => $Elements,
928
- );
929
-
930
- $Block['element']['text'][1]['text'] []= $Element;
931
-
932
- return $Block;
933
- }
934
- }
935
-
936
- #
937
- # ~
938
- #
939
-
940
- protected function paragraph($Line)
941
- {
942
- $Block = array(
943
- 'element' => array(
944
- 'name' => 'p',
945
- 'text' => $Line['text'],
946
- 'handler' => 'line',
947
- ),
948
- );
949
-
950
- return $Block;
951
- }
952
-
953
- #
954
- # Inline Elements
955
- #
956
-
957
- protected $InlineTypes = array(
958
- '"' => array('SpecialCharacter'),
959
- '!' => array('Image'),
960
- '&' => array('SpecialCharacter'),
961
- '*' => array('Emphasis'),
962
- ':' => array('Url'),
963
- '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
964
- '>' => array('SpecialCharacter'),
965
- '[' => array('Link'),
966
- '_' => array('Emphasis'),
967
- '`' => array('Code'),
968
- '~' => array('Strikethrough'),
969
- '\\' => array('EscapeSequence'),
970
- );
971
-
972
- # ~
973
-
974
- protected $inlineMarkerList = '!"*_&[:<>`~\\';
975
-
976
- #
977
- # ~
978
- #
979
-
980
- public function line($text)
981
- {
982
- $markup = '';
983
-
984
- # $excerpt is based on the first occurrence of a marker
985
-
986
- while ($excerpt = strpbrk($text, $this->inlineMarkerList))
987
- {
988
- $marker = $excerpt[0];
989
-
990
- $markerPosition = strpos($text, $marker);
991
-
992
- $Excerpt = array('text' => $excerpt, 'context' => $text);
993
-
994
- foreach ($this->InlineTypes[$marker] as $inlineType)
995
- {
996
- $Inline = $this->{'inline'.$inlineType}($Excerpt);
997
-
998
- if ( ! isset($Inline))
999
- {
1000
- continue;
1001
- }
1002
-
1003
- # makes sure that the inline belongs to "our" marker
1004
-
1005
- if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1006
- {
1007
- continue;
1008
- }
1009
-
1010
- # sets a default inline position
1011
-
1012
- if ( ! isset($Inline['position']))
1013
- {
1014
- $Inline['position'] = $markerPosition;
1015
- }
1016
-
1017
- # the text that comes before the inline
1018
- $unmarkedText = substr($text, 0, $Inline['position']);
1019
-
1020
- # compile the unmarked text
1021
- $markup .= $this->unmarkedText($unmarkedText);
1022
-
1023
- # compile the inline
1024
- $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
1025
-
1026
- # remove the examined text
1027
- $text = substr($text, $Inline['position'] + $Inline['extent']);
1028
-
1029
- continue 2;
1030
- }
1031
-
1032
- # the marker does not belong to an inline
1033
-
1034
- $unmarkedText = substr($text, 0, $markerPosition + 1);
1035
-
1036
- $markup .= $this->unmarkedText($unmarkedText);
1037
-
1038
- $text = substr($text, $markerPosition + 1);
1039
- }
1040
-
1041
- $markup .= $this->unmarkedText($text);
1042
-
1043
- return $markup;
1044
- }
1045
-
1046
- #
1047
- # ~
1048
- #
1049
-
1050
- protected function inlineCode($Excerpt)
1051
- {
1052
- $marker = $Excerpt['text'][0];
1053
-
1054
- if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1055
- {
1056
- $text = $matches[2];
1057
- $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
1058
- $text = preg_replace("/[ ]*\n/", ' ', $text);
1059
-
1060
- return array(
1061
- 'extent' => strlen($matches[0]),
1062
- 'element' => array(
1063
- 'name' => 'code',
1064
- 'text' => $text,
1065
- ),
1066
- );
1067
- }
1068
- }
1069
-
1070
- protected function inlineEmailTag($Excerpt)
1071
- {
1072
- if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
1073
- {
1074
- $url = $matches[1];
1075
-
1076
- if ( ! isset($matches[2]))
1077
- {
1078
- $url = 'mailto:' . $url;
1079
- }
1080
-
1081
- return array(
1082
- 'extent' => strlen($matches[0]),
1083
- 'element' => array(
1084
- 'name' => 'a',
1085
- 'text' => $matches[1],
1086
- 'attributes' => array(
1087
- 'href' => $url,
1088
- ),
1089
- ),
1090
- );
1091
- }
1092
- }
1093
-
1094
- protected function inlineEmphasis($Excerpt)
1095
- {
1096
- if ( ! isset($Excerpt['text'][1]))
1097
- {
1098
- return;
1099
- }
1100
-
1101
- $marker = $Excerpt['text'][0];
1102
-
1103
- if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1104
- {
1105
- $emphasis = 'strong';
1106
- }
1107
- elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1108
- {
1109
- $emphasis = 'em';
1110
- }
1111
- else
1112
- {
1113
- return;
1114
- }
1115
-
1116
- return array(
1117
- 'extent' => strlen($matches[0]),
1118
- 'element' => array(
1119
- 'name' => $emphasis,
1120
- 'handler' => 'line',
1121
- 'text' => $matches[1],
1122
- ),
1123
- );
1124
- }
1125
-
1126
- protected function inlineEscapeSequence($Excerpt)
1127
- {
1128
- if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1129
- {
1130
- return array(
1131
- 'markup' => $Excerpt['text'][1],
1132
- 'extent' => 2,
1133
- );
1134
- }
1135
- }
1136
-
1137
- protected function inlineImage($Excerpt)
1138
- {
1139
- if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1140
- {
1141
- return;
1142
- }
1143
-
1144
- $Excerpt['text']= substr($Excerpt['text'], 1);
1145
-
1146
- $Link = $this->inlineLink($Excerpt);
1147
-
1148
- if ($Link === null)
1149
- {
1150
- return;
1151
- }
1152
-
1153
- $Inline = array(
1154
- 'extent' => $Link['extent'] + 1,
1155
- 'element' => array(
1156
- 'name' => 'img',
1157
- 'attributes' => array(
1158
- 'src' => $Link['element']['attributes']['href'],
1159
- 'alt' => $Link['element']['text'],
1160
- ),
1161
- ),
1162
- );
1163
-
1164
- $Inline['element']['attributes'] += $Link['element']['attributes'];
1165
-
1166
- unset($Inline['element']['attributes']['href']);
1167
-
1168
- return $Inline;
1169
- }
1170
-
1171
- protected function inlineLink($Excerpt)
1172
- {
1173
- $Element = array(
1174
- 'name' => 'a',
1175
- 'handler' => 'line',
1176
- 'text' => null,
1177
- 'attributes' => array(
1178
- 'href' => null,
1179
- 'title' => null,
1180
- ),
1181
- );
1182
-
1183
- $extent = 0;
1184
-
1185
- $remainder = $Excerpt['text'];
1186
-
1187
- if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
1188
- {
1189
- $Element['text'] = $matches[1];
1190
-
1191
- $extent += strlen($matches[0]);
1192
-
1193
- $remainder = substr($remainder, $extent);
1194
- }
1195
- else
1196
- {
1197
- return;
1198
- }
1199
-
1200
- if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
1201
- {
1202
- $Element['attributes']['href'] = $matches[1];
1203
-
1204
- if (isset($matches[2]))
1205
- {
1206
- $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1207
- }
1208
-
1209
- $extent += strlen($matches[0]);
1210
- }
1211
- else
1212
- {
1213
- if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1214
- {
1215
- $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
1216
- $definition = strtolower($definition);
1217
-
1218
- $extent += strlen($matches[0]);
1219
- }
1220
- else
1221
- {
1222
- $definition = strtolower($Element['text']);
1223
- }
1224
-
1225
- if ( ! isset($this->DefinitionData['Reference'][$definition]))
1226
- {
1227
- return;
1228
- }
1229
-
1230
- $Definition = $this->DefinitionData['Reference'][$definition];
1231
-
1232
- $Element['attributes']['href'] = $Definition['url'];
1233
- $Element['attributes']['title'] = $Definition['title'];
1234
- }
1235
-
1236
- $Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
1237
-
1238
- return array(
1239
- 'extent' => $extent,
1240
- 'element' => $Element,
1241
- );
1242
- }
1243
-
1244
- protected function inlineMarkup($Excerpt)
1245
- {
1246
- if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
1247
- {
1248
- return;
1249
- }
1250
-
1251
- if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
1252
- {
1253
- return array(
1254
- 'markup' => $matches[0],
1255
- 'extent' => strlen($matches[0]),
1256
- );
1257
- }
1258
-
1259
- if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
1260
- {
1261
- return array(
1262
- 'markup' => $matches[0],
1263
- 'extent' => strlen($matches[0]),
1264
- );
1265
- }
1266
-
1267
- if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
1268
- {
1269
- return array(
1270
- 'markup' => $matches[0],
1271
- 'extent' => strlen($matches[0]),
1272
- );
1273
- }
1274
- }
1275
-
1276
- protected function inlineSpecialCharacter($Excerpt)
1277
- {
1278
- if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
1279
- {
1280
- return array(
1281
- 'markup' => '&amp;',
1282
- 'extent' => 1,
1283
- );
1284
- }
1285
-
1286
- $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
1287
-
1288
- if (isset($SpecialCharacter[$Excerpt['text'][0]]))
1289
- {
1290
- return array(
1291
- 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
1292
- 'extent' => 1,
1293
- );
1294
- }
1295
- }
1296
-
1297
- protected function inlineStrikethrough($Excerpt)
1298
- {
1299
- if ( ! isset($Excerpt['text'][1]))
1300
- {
1301
- return;
1302
- }
1303
-
1304
- if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1305
- {
1306
- return array(
1307
- 'extent' => strlen($matches[0]),
1308
- 'element' => array(
1309
- 'name' => 'del',
1310
- 'text' => $matches[1],
1311
- 'handler' => 'line',
1312
- ),
1313
- );
1314
- }
1315
- }
1316
-
1317
- protected function inlineUrl($Excerpt)
1318
- {
1319
- if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1320
- {
1321
- return;
1322
- }
1323
-
1324
- if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
1325
- {
1326
- $Inline = array(
1327
- 'extent' => strlen($matches[0][0]),
1328
- 'position' => $matches[0][1],
1329
- 'element' => array(
1330
- 'name' => 'a',
1331
- 'text' => $matches[0][0],
1332
- 'attributes' => array(
1333
- 'href' => $matches[0][0],
1334
- ),
1335
- ),
1336
- );
1337
-
1338
- return $Inline;
1339
- }
1340
- }
1341
-
1342
- protected function inlineUrlTag($Excerpt)
1343
- {
1344
- if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
1345
- {
1346
- $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
1347
-
1348
- return array(
1349
- 'extent' => strlen($matches[0]),
1350
- 'element' => array(
1351
- 'name' => 'a',
1352
- 'text' => $url,
1353
- 'attributes' => array(
1354
- 'href' => $url,
1355
- ),
1356
- ),
1357
- );
1358
- }
1359
- }
1360
-
1361
- # ~
1362
-
1363
- protected function unmarkedText($text)
1364
- {
1365
- if ($this->breaksEnabled)
1366
- {
1367
- $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
1368
- }
1369
- else
1370
- {
1371
- $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
1372
- $text = str_replace(" \n", "\n", $text);
1373
- }
1374
-
1375
- return $text;
1376
- }
1377
-
1378
- #
1379
- # Handlers
1380
- #
1381
-
1382
- protected function element(array $Element)
1383
- {
1384
- $markup = '<'.$Element['name'];
1385
-
1386
- if (isset($Element['attributes']))
1387
- {
1388
- foreach ($Element['attributes'] as $name => $value)
1389
- {
1390
- if ($value === null)
1391
- {
1392
- continue;
1393
- }
1394
-
1395
- $markup .= ' '.$name.'="'.$value.'"';
1396
- }
1397
- }
1398
-
1399
- if (isset($Element['text']))
1400
- {
1401
- $markup .= '>';
1402
-
1403
- if (isset($Element['handler']))
1404
- {
1405
- $markup .= $this->{$Element['handler']}($Element['text']);
1406
- }
1407
- else
1408
- {
1409
- $markup .= $Element['text'];
1410
- }
1411
-
1412
- $markup .= '</'.$Element['name'].'>';
1413
- }
1414
- else
1415
- {
1416
- $markup .= ' />';
1417
- }
1418
-
1419
- return $markup;
1420
- }
1421
-
1422
- protected function elements(array $Elements)
1423
- {
1424
- $markup = '';
1425
-
1426
- foreach ($Elements as $Element)
1427
- {
1428
- $markup .= "\n" . $this->element($Element);
1429
- }
1430
-
1431
- $markup .= "\n";
1432
-
1433
- return $markup;
1434
- }
1435
-
1436
- # ~
1437
-
1438
- protected function li($lines)
1439
- {
1440
- $markup = $this->lines($lines);
1441
-
1442
- $trimmedMarkup = trim($markup);
1443
-
1444
- if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
1445
- {
1446
- $markup = $trimmedMarkup;
1447
- $markup = substr($markup, 3);
1448
-
1449
- $position = strpos($markup, "</p>");
1450
-
1451
- $markup = substr_replace($markup, '', $position, 4);
1452
- }
1453
-
1454
- return $markup;
1455
- }
1456
-
1457
- #
1458
- # Deprecated Methods
1459
- #
1460
-
1461
- function parse($text)
1462
- {
1463
- $markup = $this->text($text);
1464
-
1465
- return $markup;
1466
- }
1467
-
1468
- #
1469
- # Static Methods
1470
- #
1471
-
1472
- static function instance($name = 'default')
1473
- {
1474
- if (isset(self::$instances[$name]))
1475
- {
1476
- return self::$instances[$name];
1477
- }
1478
-
1479
- $instance = new static();
1480
-
1481
- self::$instances[$name] = $instance;
1482
-
1483
- return $instance;
1484
- }
1485
-
1486
- private static $instances = array();
1487
-
1488
- #
1489
- # Fields
1490
- #
1491
-
1492
- protected $DefinitionData;
1493
-
1494
- #
1495
- # Read-Only
1496
-
1497
- protected $specialCharacters = array(
1498
- '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
1499
- );
1500
-
1501
- protected $StrongRegex = array(
1502
- '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
1503
- '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
1504
- );
1505
-
1506
- protected $EmRegex = array(
1507
- '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1508
- '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1509
- );
1510
-
1511
- protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
1512
-
1513
- protected $voidElements = array(
1514
- 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1515
- );
1516
-
1517
- protected $textLevelElements = array(
1518
- 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1519
- 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1520
- 'i', 'rp', 'del', 'code', 'strike', 'marquee',
1521
- 'q', 'rt', 'ins', 'font', 'strong',
1522
- 's', 'tt', 'sub', 'mark',
1523
- 'u', 'xm', 'sup', 'nobr',
1524
- 'var', 'ruby',
1525
- 'wbr', 'span',
1526
- 'time',
1527
- );
1528
  }
1
+ <?php
2
+
3
+ #
4
+ #
5
+ # Parsedown
6
+ # http://parsedown.org
7
+ #
8
+ # (c) Emanuil Rusev
9
+ # http://erusev.com
10
+ #
11
+ # For the full license information, view the LICENSE file that was distributed
12
+ # with this source code.
13
+ #
14
+ #
15
+
16
+ class Parsedown
17
+ {
18
+ # ~
19
+
20
+ const version = '1.5.4';
21
+
22
+ # ~
23
+
24
+ function text($text)
25
+ {
26
+ # make sure no definitions are set
27
+ $this->DefinitionData = array();
28
+
29
+ # standardize line breaks
30
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
31
+
32
+ # remove surrounding line breaks
33
+ $text = trim($text, "\n");
34
+
35
+ # split text into lines
36
+ $lines = explode("\n", $text);
37
+
38
+ # iterate through lines to identify blocks
39
+ $markup = $this->lines($lines);
40
+
41
+ # trim line breaks
42
+ $markup = trim($markup, "\n");
43
+
44
+ return $markup;
45
+ }
46
+
47
+ #
48
+ # Setters
49
+ #
50
+
51
+ function setBreaksEnabled($breaksEnabled)
52
+ {
53
+ $this->breaksEnabled = $breaksEnabled;
54
+
55
+ return $this;
56
+ }
57
+
58
+ protected $breaksEnabled;
59
+
60
+ function setMarkupEscaped($markupEscaped)
61
+ {
62
+ $this->markupEscaped = $markupEscaped;
63
+
64
+ return $this;
65
+ }
66
+
67
+ protected $markupEscaped;
68
+
69
+ function setUrlsLinked($urlsLinked)
70
+ {
71
+ $this->urlsLinked = $urlsLinked;
72
+
73
+ return $this;
74
+ }
75
+
76
+ protected $urlsLinked = true;
77
+
78
+ #
79
+ # Lines
80
+ #
81
+
82
+ protected $BlockTypes = array(
83
+ '#' => array('Header'),
84
+ '*' => array('Rule', 'List'),
85
+ '+' => array('List'),
86
+ '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
87
+ '0' => array('List'),
88
+ '1' => array('List'),
89
+ '2' => array('List'),
90
+ '3' => array('List'),
91
+ '4' => array('List'),
92
+ '5' => array('List'),
93
+ '6' => array('List'),
94
+ '7' => array('List'),
95
+ '8' => array('List'),
96
+ '9' => array('List'),
97
+ ':' => array('Table'),
98
+ '<' => array('Comment', 'Markup'),
99
+ '=' => array('SetextHeader'),
100
+ '>' => array('Quote'),
101
+ '[' => array('Reference'),
102
+ '_' => array('Rule'),
103
+ '`' => array('FencedCode'),
104
+ '|' => array('Table'),
105
+ '~' => array('FencedCode'),
106
+ );
107
+
108
+ # ~
109
+
110
+ protected $unmarkedBlockTypes = array(
111
+ 'Code',
112
+ );
113
+
114
+ #
115
+ # Blocks
116
+ #
117
+
118
+ private function lines(array $lines)
119
+ {
120
+ $CurrentBlock = null;
121
+
122
+ foreach ($lines as $line)
123
+ {
124
+ if (chop($line) === '')
125
+ {
126
+ if (isset($CurrentBlock))
127
+ {
128
+ $CurrentBlock['interrupted'] = true;
129
+ }
130
+
131
+ continue;
132
+ }
133
+
134
+ if (strpos($line, "\t") !== false)
135
+ {
136
+ $parts = explode("\t", $line);
137
+
138
+ $line = $parts[0];
139
+
140
+ unset($parts[0]);
141
+
142
+ foreach ($parts as $part)
143
+ {
144
+ $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
145
+
146
+ $line .= str_repeat(' ', $shortage);
147
+ $line .= $part;
148
+ }
149
+ }
150
+
151
+ $indent = 0;
152
+
153
+ while (isset($line[$indent]) and $line[$indent] === ' ')
154
+ {
155
+ $indent ++;
156
+ }
157
+
158
+ $text = $indent > 0 ? substr($line, $indent) : $line;
159
+
160
+ # ~
161
+
162
+ $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
163
+
164
+ # ~
165
+
166
+ if (isset($CurrentBlock['continuable']))
167
+ {
168
+ $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
169
+
170
+ if (isset($Block))
171
+ {
172
+ $CurrentBlock = $Block;
173
+
174
+ continue;
175
+ }
176
+ else
177
+ {
178
+ if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
179
+ {
180
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
181
+ }
182
+ }
183
+ }
184
+
185
+ # ~
186
+
187
+ $marker = $text[0];
188
+
189
+ # ~
190
+
191
+ $blockTypes = $this->unmarkedBlockTypes;
192
+
193
+ if (isset($this->BlockTypes[$marker]))
194
+ {
195
+ foreach ($this->BlockTypes[$marker] as $blockType)
196
+ {
197
+ $blockTypes []= $blockType;
198
+ }
199
+ }
200
+
201
+ #
202
+ # ~
203
+
204
+ foreach ($blockTypes as $blockType)
205
+ {
206
+ $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
207
+
208
+ if (isset($Block))
209
+ {
210
+ $Block['type'] = $blockType;
211
+
212
+ if ( ! isset($Block['identified']))
213
+ {
214
+ $Blocks []= $CurrentBlock;
215
+
216
+ $Block['identified'] = true;
217
+ }
218
+
219
+ if (method_exists($this, 'block'.$blockType.'Continue'))
220
+ {
221
+ $Block['continuable'] = true;
222
+ }
223
+
224
+ $CurrentBlock = $Block;
225
+
226
+ continue 2;
227
+ }
228
+ }
229
+
230
+ # ~
231
+
232
+ if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
233
+ {
234
+ $CurrentBlock['element']['text'] .= "\n".$text;
235
+ }
236
+ else
237
+ {
238
+ $Blocks []= $CurrentBlock;
239
+
240
+ $CurrentBlock = $this->paragraph($Line);
241
+
242
+ $CurrentBlock['identified'] = true;
243
+ }
244
+ }
245
+
246
+ # ~
247
+
248
+ if (isset($CurrentBlock['continuable']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
249
+ {
250
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
251
+ }
252
+
253
+ # ~
254
+
255
+ $Blocks []= $CurrentBlock;
256
+
257
+ unset($Blocks[0]);
258
+
259
+ # ~
260
+
261
+ $markup = '';
262
+
263
+ foreach ($Blocks as $Block)
264
+ {
265
+ if (isset($Block['hidden']))
266
+ {
267
+ continue;
268
+ }
269
+
270
+ $markup .= "\n";
271
+ $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
272
+ }
273
+
274
+ $markup .= "\n";
275
+
276
+ # ~
277
+
278
+ return $markup;
279
+ }
280
+
281
+ #
282
+ # Code
283
+
284
+ protected function blockCode($Line, $Block = null)
285
+ {
286
+ if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
287
+ {
288
+ return;
289
+ }
290
+
291
+ if ($Line['indent'] >= 4)
292
+ {
293
+ $text = substr($Line['body'], 4);
294
+
295
+ $Block = array(
296
+ 'element' => array(
297
+ 'name' => 'pre',
298
+ 'handler' => 'element',
299
+ 'text' => array(
300
+ 'name' => 'code',
301
+ 'text' => $text,
302
+ ),
303
+ ),
304
+ );
305
+
306
+ return $Block;
307
+ }
308
+ }
309
+
310
+ protected function blockCodeContinue($Line, $Block)
311
+ {
312
+ if ($Line['indent'] >= 4)
313
+ {
314
+ if (isset($Block['interrupted']))
315
+ {
316
+ $Block['element']['text']['text'] .= "\n";
317
+
318
+ unset($Block['interrupted']);
319
+ }
320
+
321
+ $Block['element']['text']['text'] .= "\n";
322
+
323
+ $text = substr($Line['body'], 4);
324
+
325
+ $Block['element']['text']['text'] .= $text;
326
+
327
+ return $Block;
328
+ }
329
+ }
330
+
331
+ protected function blockCodeComplete($Block)
332
+ {
333
+ $text = $Block['element']['text']['text'];
334
+
335
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
336
+
337
+ $Block['element']['text']['text'] = $text;
338
+
339
+ return $Block;
340
+ }
341
+
342
+ #
343
+ # Comment
344
+
345
+ protected function blockComment($Line)
346
+ {
347
+ if ($this->markupEscaped)
348
+ {
349
+ return;
350
+ }
351
+
352
+ if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
353
+ {
354
+ $Block = array(
355
+ 'markup' => $Line['body'],
356
+ );
357
+
358
+ if (preg_match('/-->$/', $Line['text']))
359
+ {
360
+ $Block['closed'] = true;
361
+ }
362
+
363
+ return $Block;
364
+ }
365
+ }
366
+
367
+ protected function blockCommentContinue($Line, array $Block)
368
+ {
369
+ if (isset($Block['closed']))
370
+ {
371
+ return;
372
+ }
373
+
374
+ $Block['markup'] .= "\n" . $Line['body'];
375
+
376
+ if (preg_match('/-->$/', $Line['text']))
377
+ {
378
+ $Block['closed'] = true;
379
+ }
380
+
381
+ return $Block;
382
+ }
383
+
384
+ #
385
+ # Fenced Code
386
+
387
+ protected function blockFencedCode($Line)
388
+ {
389
+ if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
390
+ {
391
+ $Element = array(
392
+ 'name' => 'code',
393
+ 'text' => '',
394
+ );
395
+
396
+ if (isset($matches[1]))
397
+ {
398
+ $class = 'language-'.$matches[1];
399
+
400
+ $Element['attributes'] = array(
401
+ 'class' => $class,
402
+ );
403
+ }
404
+
405
+ $Block = array(
406
+ 'char' => $Line['text'][0],
407
+ 'element' => array(
408
+ 'name' => 'pre',
409
+ 'handler' => 'element',
410
+ 'text' => $Element,
411
+ ),
412
+ );
413
+
414
+ return $Block;
415
+ }
416
+ }
417
+
418
+ protected function blockFencedCodeContinue($Line, $Block)
419
+ {
420
+ if (isset($Block['complete']))
421
+ {
422
+ return;
423
+ }
424
+
425
+ if (isset($Block['interrupted']))
426
+ {
427
+ $Block['element']['text']['text'] .= "\n";
428
+
429
+ unset($Block['interrupted']);
430
+ }
431
+
432
+ if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
433
+ {
434
+ $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
435
+
436
+ $Block['complete'] = true;
437
+
438
+ return $Block;
439
+ }
440
+
441
+ $Block['element']['text']['text'] .= "\n".$Line['body'];;
442
+
443
+ return $Block;
444
+ }
445
+
446
+ protected function blockFencedCodeComplete($Block)
447
+ {
448
+ $text = $Block['element']['text']['text'];
449
+
450
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
451
+
452
+ $Block['element']['text']['text'] = $text;
453
+
454
+ return $Block;
455
+ }
456
+
457
+ #
458
+ # Header
459
+
460
+ protected function blockHeader($Line)
461
+ {
462
+ if (isset($Line['text'][1]))
463
+ {
464
+ $level = 1;
465
+
466
+ while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
467
+ {
468
+ $level ++;
469
+ }
470
+
471
+ if ($level > 6)
472
+ {
473
+ return;
474
+ }
475
+
476
+ $text = trim($Line['text'], '# ');
477
+
478
+ $Block = array(
479
+ 'element' => array(
480
+ 'name' => 'h' . min(6, $level),
481
+ 'text' => $text,
482
+ 'handler' => 'line',
483
+ ),
484
+ );
485
+
486
+ return $Block;
487
+ }
488
+ }
489
+
490
+ #
491
+ # List
492
+
493
+ protected function blockList($Line)
494
+ {
495
+ list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
496
+
497
+ if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
498
+ {
499
+ $Block = array(
500
+ 'indent' => $Line['indent'],
501
+ 'pattern' => $pattern,
502
+ 'element' => array(
503
+ 'name' => $name,
504
+ 'handler' => 'elements',
505
+ ),
506
+ );
507
+
508
+ $Block['li'] = array(
509
+ 'name' => 'li',
510
+ 'handler' => 'li',
511
+ 'text' => array(
512
+ $matches[2],
513
+ ),
514
+ );
515
+
516
+ $Block['element']['text'] []= & $Block['li'];
517
+
518
+ return $Block;
519
+ }
520
+ }
521
+
522
+ protected function blockListContinue($Line, array $Block)
523
+ {
524
+ if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
525
+ {
526
+ if (isset($Block['interrupted']))
527
+ {
528
+ $Block['li']['text'] []= '';
529
+
530
+ unset($Block['interrupted']);
531
+ }
532
+
533
+ unset($Block['li']);
534
+
535
+ $text = isset($matches[1]) ? $matches[1] : '';
536
+
537
+ $Block['li'] = array(
538
+ 'name' => 'li',
539
+ 'handler' => 'li',
540
+ 'text' => array(
541
+ $text,
542
+ ),
543
+ );
544
+
545
+ $Block['element']['text'] []= & $Block['li'];
546
+
547
+ return $Block;
548
+ }
549
+
550
+ if ($Line['text'][0] === '[' and $this->blockReference($Line))
551
+ {
552
+ return $Block;
553
+ }
554
+
555
+ if ( ! isset($Block['interrupted']))
556
+ {
557
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
558
+
559
+ $Block['li']['text'] []= $text;
560
+
561
+ return $Block;
562
+ }
563
+
564
+ if ($Line['indent'] > 0)
565
+ {
566
+ $Block['li']['text'] []= '';
567
+
568
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
569
+
570
+ $Block['li']['text'] []= $text;
571
+
572
+ unset($Block['interrupted']);
573
+
574
+ return $Block;
575
+ }
576
+ }
577
+
578
+ #
579
+ # Quote
580
+
581
+ protected function blockQuote($Line)
582
+ {
583
+ if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
584
+ {
585
+ $Block = array(
586
+ 'element' => array(
587
+ 'name' => 'blockquote',
588
+ 'handler' => 'lines',
589
+ 'text' => (array) $matches[1],
590
+ ),
591
+ );
592
+
593
+ return $Block;
594
+ }
595
+ }
596
+
597
+ protected function blockQuoteContinue($Line, array $Block)
598
+ {
599
+ if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
600
+ {
601
+ if (isset($Block['interrupted']))
602
+ {
603
+ $Block['element']['text'] []= '';
604
+
605
+ unset($Block['interrupted']);
606
+ }
607
+
608
+ $Block['element']['text'] []= $matches[1];
609
+
610
+ return $Block;
611
+ }
612
+
613
+ if ( ! isset($Block['interrupted']))
614
+ {
615
+ $Block['element']['text'] []= $Line['text'];
616
+
617
+ return $Block;
618
+ }
619
+ }
620
+
621
+ #
622
+ # Rule
623
+
624
+ protected function blockRule($Line)
625
+ {
626
+ if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
627
+ {
628
+ $Block = array(
629
+ 'element' => array(
630
+ 'name' => 'hr'
631
+ ),
632
+ );
633
+
634
+ return $Block;
635
+ }
636
+ }
637
+
638
+ #
639
+ # Setext
640
+
641
+ protected function blockSetextHeader($Line, array $Block = null)
642
+ {
643
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
644
+ {
645
+ return;
646
+ }
647
+
648
+ if (chop($Line['text'], $Line['text'][0]) === '')
649
+ {
650
+ $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
651
+
652
+ return $Block;
653
+ }
654
+ }
655
+
656
+ #
657
+ # Markup
658
+
659
+ protected function blockMarkup($Line)
660
+ {
661
+ if ($this->markupEscaped)
662
+ {
663
+ return;
664
+ }
665
+
666
+ if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
667
+ {
668
+ $element = strtolower($matches[1]);
669
+
670
+ if (in_array($element, $this->textLevelElements))
671
+ {
672
+ return;
673
+ }
674
+
675
+ $Block = array(
676
+ 'name' => $matches[1],
677
+ 'depth' => 0,
678
+ 'markup' => $Line['text'],
679
+ );
680
+
681
+ $length = strlen($matches[0]);
682
+
683
+ $remainder = substr($Line['text'], $length);
684
+
685
+ if (trim($remainder) === '')
686
+ {
687
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
688
+ {
689
+ $Block['closed'] = true;
690
+
691
+ $Block['void'] = true;
692
+ }
693
+ }
694
+ else
695
+ {
696
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
697
+ {
698
+ return;
699
+ }
700
+
701
+ if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
702
+ {
703
+ $Block['closed'] = true;
704
+ }
705
+ }
706
+
707
+ return $Block;
708
+ }
709
+ }
710
+
711
+ protected function blockMarkupContinue($Line, array $Block)
712
+ {
713
+ if (isset($Block['closed']))
714
+ {
715
+ return;
716
+ }
717
+
718
+ if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
719
+ {
720
+ $Block['depth'] ++;
721
+ }
722
+
723
+ if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
724
+ {
725
+ if ($Block['depth'] > 0)
726
+ {
727
+ $Block['depth'] --;
728
+ }
729
+ else
730
+ {
731
+ $Block['closed'] = true;
732
+ }
733
+ }
734
+
735
+ if (isset($Block['interrupted']))
736
+ {
737
+ $Block['markup'] .= "\n";
738
+
739
+ unset($Block['interrupted']);
740
+ }
741
+
742
+ $Block['markup'] .= "\n".$Line['body'];
743
+
744
+ return $Block;
745
+ }
746
+
747
+ #
748
+ # Reference
749
+
750
+ protected function blockReference($Line)
751
+ {
752
+ if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
753
+ {
754
+ $id = strtolower($matches[1]);
755
+
756
+ $Data = array(
757
+ 'url' => $matches[2],
758
+ 'title' => null,
759
+ );
760
+
761
+ if (isset($matches[3]))
762
+ {
763
+ $Data['title'] = $matches[3];
764
+ }
765
+
766
+ $this->DefinitionData['Reference'][$id] = $Data;
767
+
768
+ $Block = array(
769
+ 'hidden' => true,
770
+ );
771
+
772
+ return $Block;
773
+ }
774
+ }
775
+
776
+ #
777
+ # Table
778
+
779
+ protected function blockTable($Line, array $Block = null)
780
+ {
781
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
782
+ {
783
+ return;
784
+ }
785
+
786
+ if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
787
+ {
788
+ $alignments = array();
789
+
790
+ $divider = $Line['text'];
791
+
792
+ $divider = trim($divider);
793
+ $divider = trim($divider, '|');
794
+
795
+ $dividerCells = explode('|', $divider);
796
+
797
+ foreach ($dividerCells as $dividerCell)
798
+ {
799
+ $dividerCell = trim($dividerCell);
800
+
801
+ if ($dividerCell === '')
802
+ {
803
+ continue;
804
+ }
805
+
806
+ $alignment = null;
807
+
808
+ if ($dividerCell[0] === ':')
809
+ {
810
+ $alignment = 'left';
811
+ }
812
+
813
+ if (substr($dividerCell, - 1) === ':')
814
+ {
815
+ $alignment = $alignment === 'left' ? 'center' : 'right';
816
+ }
817
+
818
+ $alignments []= $alignment;
819
+ }
820
+
821
+ # ~
822
+
823
+ $HeaderElements = array();
824
+
825
+ $header = $Block['element']['text'];
826
+
827
+ $header = trim($header);
828
+ $header = trim($header, '|');
829
+
830
+ $headerCells = explode('|', $header);
831
+
832
+ foreach ($headerCells as $index => $headerCell)
833
+ {
834
+ $headerCell = trim($headerCell);
835
+
836
+ $HeaderElement = array(
837
+ 'name' => 'th',
838
+ 'text' => $headerCell,
839
+ 'handler' => 'line',
840
+ );
841
+
842
+ if (isset($alignments[$index]))
843
+ {
844
+ $alignment = $alignments[$index];
845
+
846
+ $HeaderElement['attributes'] = array(
847
+ 'style' => 'text-align: '.$alignment.';',
848
+ );
849
+ }
850
+
851
+ $HeaderElements []= $HeaderElement;
852
+ }
853
+
854
+ # ~
855
+
856
+ $Block = array(
857
+ 'alignments' => $alignments,
858
+ 'identified' => true,
859
+ 'element' => array(
860
+ 'name' => 'table',
861
+ 'handler' => 'elements',
862
+ ),
863
+ );
864
+
865
+ $Block['element']['text'] []= array(
866
+ 'name' => 'thead',
867
+ 'handler' => 'elements',
868
+ );
869
+
870
+ $Block['element']['text'] []= array(
871
+ 'name' => 'tbody',
872
+ 'handler' => 'elements',
873
+ 'text' => array(),
874
+ );
875
+
876
+ $Block['element']['text'][0]['text'] []= array(
877
+ 'name' => 'tr',
878
+ 'handler' => 'elements',
879
+ 'text' => $HeaderElements,
880
+ );
881
+
882
+ return $Block;
883
+ }
884
+ }
885
+
886
+ protected function blockTableContinue($Line, array $Block)
887
+ {
888
+ if (isset($Block['interrupted']))
889
+ {
890
+ return;
891
+ }
892
+
893
+ if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
894
+ {
895
+ $Elements = array();
896
+
897
+ $row = $Line['text'];
898
+
899
+ $row = trim($row);
900
+ $row = trim($row, '|');
901
+
902
+ preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
903
+
904
+ foreach ($matches[0] as $index => $cell)
905
+ {
906
+ $cell = trim($cell);
907
+
908
+ $Element = array(
909
+ 'name' => 'td',
910
+ 'handler' => 'line',
911
+ 'text' => $cell,
912
+ );
913
+
914
+ if (isset($Block['alignments'][$index]))
915
+ {
916
+ $Element['attributes'] = array(
917
+ 'style' => 'text-align: '.$Block['alignments'][$index].';',
918
+ );
919
+ }
920
+
921
+ $Elements []= $Element;
922
+ }
923
+
924
+ $Element = array(
925
+ 'name' => 'tr',
926
+ 'handler' => 'elements',
927
+ 'text' => $Elements,
928
+ );
929
+
930
+ $Block['element']['text'][1]['text'] []= $Element;
931
+
932
+ return $Block;
933
+ }
934
+ }
935
+
936
+ #
937
+ # ~
938
+ #
939
+
940
+ protected function paragraph($Line)
941
+ {
942
+ $Block = array(
943
+ 'element' => array(
944
+ 'name' => 'p',
945
+ 'text' => $Line['text'],
946
+ 'handler' => 'line',
947
+ ),
948
+ );
949
+
950
+ return $Block;
951
+ }
952
+
953
+ #
954
+ # Inline Elements
955
+ #
956
+
957
+ protected $InlineTypes = array(
958
+ '"' => array('SpecialCharacter'),
959
+ '!' => array('Image'),
960
+ '&' => array('SpecialCharacter'),
961
+ '*' => array('Emphasis'),
962
+ ':' => array('Url'),
963
+ '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
964
+ '>' => array('SpecialCharacter'),
965
+ '[' => array('Link'),
966
+ '_' => array('Emphasis'),
967
+ '`' => array('Code'),
968
+ '~' => array('Strikethrough'),
969
+ '\\' => array('EscapeSequence'),
970
+ );
971
+
972
+ # ~
973
+
974
+ protected $inlineMarkerList = '!"*_&[:<>`~\\';
975
+
976
+ #
977
+ # ~
978
+ #
979
+
980
+ public function line($text)
981
+ {
982
+ $markup = '';
983
+
984
+ # $excerpt is based on the first occurrence of a marker
985
+
986
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
987
+ {
988
+ $marker = $excerpt[0];
989
+
990
+ $markerPosition = strpos($text, $marker);
991
+
992
+ $Excerpt = array('text' => $excerpt, 'context' => $text);
993
+
994
+ foreach ($this->InlineTypes[$marker] as $inlineType)
995
+ {
996
+ $Inline = $this->{'inline'.$inlineType}($Excerpt);
997
+
998
+ if ( ! isset($Inline))
999
+ {
1000
+ continue;
1001
+ }
1002
+
1003
+ # makes sure that the inline belongs to "our" marker
1004
+
1005
+ if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1006
+ {
1007
+ continue;
1008
+ }
1009
+
1010
+ # sets a default inline position
1011
+
1012
+ if ( ! isset($Inline['position']))
1013
+ {
1014
+ $Inline['position'] = $markerPosition;
1015
+ }
1016
+
1017
+ # the text that comes before the inline
1018
+ $unmarkedText = substr($text, 0, $Inline['position']);
1019
+
1020
+ # compile the unmarked text
1021
+ $markup .= $this->unmarkedText($unmarkedText);
1022
+
1023
+ # compile the inline
1024
+ $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
1025
+
1026
+ # remove the examined text
1027
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
1028
+
1029
+ continue 2;
1030
+ }
1031
+
1032
+ # the marker does not belong to an inline
1033
+
1034
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
1035
+
1036
+ $markup .= $this->unmarkedText($unmarkedText);
1037
+
1038
+ $text = substr($text, $markerPosition + 1);
1039
+ }
1040
+
1041
+ $markup .= $this->unmarkedText($text);
1042
+
1043
+ return $markup;
1044
+ }
1045
+
1046
+ #
1047
+ # ~
1048
+ #
1049
+
1050
+ protected function inlineCode($Excerpt)
1051
+ {
1052
+ $marker = $Excerpt['text'][0];
1053
+
1054
+ if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1055
+ {
1056
+ $text = $matches[2];
1057
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
1058
+ $text = preg_replace("/[ ]*\n/", ' ', $text);
1059
+
1060
+ return array(
1061
+ 'extent' => strlen($matches[0]),
1062
+ 'element' => array(
1063
+ 'name' => 'code',
1064
+ 'text' => $text,
1065
+ ),
1066
+ );
1067
+ }
1068
+ }
1069
+
1070
+ protected function inlineEmailTag($Excerpt)
1071
+ {
1072
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
1073
+ {
1074
+ $url = $matches[1];
1075
+
1076
+ if ( ! isset($matches[2]))
1077
+ {
1078
+ $url = 'mailto:' . $url;
1079
+ }
1080
+
1081
+ return array(
1082
+ 'extent' => strlen($matches[0]),
1083
+ 'element' => array(
1084
+ 'name' => 'a',
1085
+ 'text' => $matches[1],
1086
+ 'attributes' => array(
1087
+ 'href' => $url,
1088
+ ),
1089
+ ),
1090
+ );
1091
+ }
1092
+ }
1093
+
1094
+ protected function inlineEmphasis($Excerpt)
1095
+ {
1096
+ if ( ! isset($Excerpt['text'][1]))
1097
+ {
1098
+ return;
1099
+ }
1100
+
1101
+ $marker = $Excerpt['text'][0];
1102
+
1103
+ if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1104
+ {
1105
+ $emphasis = 'strong';
1106
+ }
1107
+ elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1108
+ {
1109
+ $emphasis = 'em';
1110
+ }
1111
+ else
1112
+ {
1113
+ return;
1114
+ }
1115
+
1116
+ return array(
1117
+ 'extent' => strlen($matches[0]),
1118
+ 'element' => array(
1119
+ 'name' => $emphasis,
1120
+ 'handler' => 'line',
1121
+ 'text' => $matches[1],
1122
+ ),
1123
+ );
1124
+ }
1125
+
1126
+ protected function inlineEscapeSequence($Excerpt)
1127
+ {
1128
+ if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1129
+ {
1130
+ return array(
1131
+ 'markup' => $Excerpt['text'][1],
1132
+ 'extent' => 2,
1133
+ );
1134
+ }
1135
+ }
1136
+
1137
+ protected function inlineImage($Excerpt)
1138
+ {
1139
+ if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1140
+ {
1141
+ return;
1142
+ }
1143
+
1144
+ $Excerpt['text']= substr($Excerpt['text'], 1);
1145
+
1146
+ $Link = $this->inlineLink($Excerpt);
1147
+
1148
+ if ($Link === null)
1149
+ {
1150
+ return;
1151
+ }
1152
+
1153
+ $Inline = array(
1154
+ 'extent' => $Link['extent'] + 1,
1155
+ 'element' => array(
1156
+ 'name' => 'img',
1157
+ 'attributes' => array(
1158
+ 'src' => $Link['element']['attributes']['href'],
1159
+ 'alt' => $Link['element']['text'],
1160
+ ),
1161
+ ),
1162
+ );
1163
+
1164
+ $Inline['element']['attributes'] += $Link['element']['attributes'];
1165
+
1166
+ unset($Inline['element']['attributes']['href']);
1167
+
1168
+ return $Inline;
1169
+ }
1170
+
1171
+ protected function inlineLink($Excerpt)
1172
+ {
1173
+ $Element = array(
1174
+ 'name' => 'a',
1175
+ 'handler' => 'line',
1176
+ 'text' => null,
1177
+ 'attributes' => array(
1178
+ 'href' => null,
1179
+ 'title' => null,
1180
+ ),
1181
+ );
1182
+
1183
+ $extent = 0;
1184
+
1185
+ $remainder = $Excerpt['text'];
1186
+
1187
+ if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
1188
+ {
1189
+ $Element['text'] = $matches[1];
1190
+
1191
+ $extent += strlen($matches[0]);
1192
+
1193
+ $remainder = substr($remainder, $extent);
1194
+ }
1195
+ else
1196
+ {
1197
+ return;
1198
+ }
1199
+
1200
+ if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
1201
+ {
1202
+ $Element['attributes']['href'] = $matches[1];
1203
+
1204
+ if (isset($matches[2]))
1205
+ {
1206
+ $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1207
+ }
1208
+
1209
+ $extent += strlen($matches[0]);
1210
+ }
1211
+ else
1212
+ {
1213
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1214
+ {
1215
+ $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
1216
+ $definition = strtolower($definition);
1217
+
1218
+ $extent += strlen($matches[0]);
1219
+ }
1220
+ else
1221
+ {
1222
+ $definition = strtolower($Element['text']);
1223
+ }
1224
+
1225
+ if ( ! isset($this->DefinitionData['Reference'][$definition]))
1226
+ {
1227
+ return;
1228
+ }
1229
+
1230
+ $Definition = $this->DefinitionData['Reference'][$definition];
1231
+
1232
+ $Element['attributes']['href'] = $Definition['url'];
1233
+ $Element['attributes']['title'] = $Definition['title'];
1234
+ }
1235
+
1236
+ $Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
1237
+
1238
+ return array(
1239
+ 'extent' => $extent,
1240
+ 'element' => $Element,
1241
+ );
1242
+ }
1243
+
1244
+ protected function inlineMarkup($Excerpt)
1245
+ {
1246
+ if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
1247
+ {
1248
+ return;
1249
+ }
1250
+
1251
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
1252
+ {
1253
+ return array(
1254
+ 'markup' => $matches[0],
1255
+ 'extent' => strlen($matches[0]),
1256
+ );
1257
+ }
1258
+
1259
+ if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
1260
+ {
1261
+ return array(
1262
+ 'markup' => $matches[0],
1263
+ 'extent' => strlen($matches[0]),
1264
+ );
1265
+ }
1266
+
1267
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
1268
+ {
1269
+ return array(
1270
+ 'markup' => $matches[0],
1271
+ 'extent' => strlen($matches[0]),
1272
+ );
1273
+ }
1274
+ }
1275
+
1276
+ protected function inlineSpecialCharacter($Excerpt)
1277
+ {
1278
+ if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
1279
+ {
1280
+ return array(
1281
+ 'markup' => '&amp;',
1282
+ 'extent' => 1,
1283
+ );
1284
+ }
1285
+
1286
+ $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
1287
+
1288
+ if (isset($SpecialCharacter[$Excerpt['text'][0]]))
1289
+ {
1290
+ return array(
1291
+ 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
1292
+ 'extent' => 1,
1293
+ );
1294
+ }
1295
+ }
1296
+
1297
+ protected function inlineStrikethrough($Excerpt)
1298
+ {
1299
+ if ( ! isset($Excerpt['text'][1]))
1300
+ {
1301
+ return;
1302
+ }
1303
+
1304
+ if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1305
+ {
1306
+ return array(
1307
+ 'extent' => strlen($matches[0]),
1308
+ 'element' => array(
1309
+ 'name' => 'del',
1310
+ 'text' => $matches[1],
1311
+ 'handler' => 'line',
1312
+ ),
1313
+ );
1314
+ }
1315
+ }
1316
+
1317
+ protected function inlineUrl($Excerpt)
1318
+ {
1319
+ if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1320
+ {
1321
+ return;
1322
+ }
1323
+
1324
+ if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
1325
+ {
1326
+ $Inline = array(
1327
+ 'extent' => strlen($matches[0][0]),
1328
+ 'position' => $matches[0][1],
1329
+ 'element' => array(
1330
+ 'name' => 'a',
1331
+ 'text' => $matches[0][0],
1332
+ 'attributes' => array(
1333
+ 'href' => $matches[0][0],
1334
+ ),
1335
+ ),
1336
+ );
1337
+
1338
+ return $Inline;
1339
+ }
1340
+ }
1341
+
1342
+ protected function inlineUrlTag($Excerpt)
1343
+ {
1344
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
1345
+ {
1346
+ $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
1347
+
1348
+ return array(
1349
+ 'extent' => strlen($matches[0]),
1350
+ 'element' => array(
1351
+ 'name' => 'a',
1352
+ 'text' => $url,
1353
+ 'attributes' => array(
1354
+ 'href' => $url,
1355
+ ),
1356
+ ),
1357
+ );
1358
+ }
1359
+ }
1360
+
1361
+ # ~
1362
+
1363
+ protected function unmarkedText($text)
1364
+ {
1365
+ if ($this->breaksEnabled)
1366
+ {
1367
+ $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
1368
+ }
1369
+ else
1370
+ {
1371
+ $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
1372
+ $text = str_replace(" \n", "\n", $text);
1373
+ }
1374
+
1375
+ return $text;
1376
+ }
1377
+
1378
+ #
1379
+ # Handlers
1380
+ #
1381
+
1382
+ protected function element(array $Element)
1383
+ {
1384
+ $markup = '<'.$Element['name'];
1385
+
1386
+ if (isset($Element['attributes']))
1387
+ {
1388
+ foreach ($Element['attributes'] as $name => $value)
1389
+ {
1390
+ if ($value === null)
1391
+ {
1392
+ continue;
1393
+ }
1394
+
1395
+ $markup .= ' '.$name.'="'.$value.'"';
1396
+ }
1397
+ }
1398
+
1399
+ if (isset($Element['text']))
1400
+ {
1401
+ $markup .= '>';
1402
+
1403
+ if (isset($Element['handler']))
1404
+ {
1405
+ $markup .= $this->{$Element['handler']}($Element['text']);
1406
+ }
1407
+ else
1408
+ {
1409
+ $markup .= $Element['text'];
1410
+ }
1411
+
1412
+ $markup .= '</'.$Element['name'].'>';
1413
+ }
1414
+ else
1415
+ {
1416
+ $markup .= ' />';
1417
+ }
1418
+
1419
+ return $markup;
1420
+ }
1421
+
1422
+ protected function elements(array $Elements)
1423
+ {
1424
+ $markup = '';
1425
+
1426
+ foreach ($Elements as $Element)
1427
+ {
1428
+ $markup .= "\n" . $this->element($Element);
1429
+ }
1430
+
1431
+ $markup .= "\n";
1432
+
1433
+ return $markup;
1434
+ }
1435
+
1436
+ # ~
1437
+
1438
+ protected function li($lines)
1439
+ {
1440
+ $markup = $this->lines($lines);
1441
+
1442
+ $trimmedMarkup = trim($markup);
1443
+
1444
+ if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
1445
+ {
1446
+ $markup = $trimmedMarkup;
1447
+ $markup = substr($markup, 3);
1448
+
1449
+ $position = strpos($markup, "</p>");
1450
+
1451
+ $markup = substr_replace($markup, '', $position, 4);
1452
+ }
1453
+
1454
+ return $markup;
1455
+ }
1456
+
1457
+ #
1458
+ # Deprecated Methods
1459
+ #
1460
+
1461
+ function parse($text)
1462
+ {
1463
+ $markup = $this->text($text);
1464
+
1465
+ return $markup;
1466
+ }
1467
+
1468
+ #
1469
+ # Static Methods
1470
+ #
1471
+
1472
+ static function instance($name = 'default')
1473
+ {
1474
+ if (isset(self::$instances[$name]))
1475
+ {
1476
+ return self::$instances[$name];
1477
+ }
1478
+
1479
+ $instance = new static();
1480
+
1481
+ self::$instances[$name] = $instance;
1482
+
1483
+ return $instance;
1484
+ }
1485
+
1486
+ private static $instances = array();
1487
+
1488
+ #
1489
+ # Fields
1490
+ #
1491
+
1492
+ protected $DefinitionData;
1493
+
1494
+ #
1495
+ # Read-Only
1496
+
1497
+ protected $specialCharacters = array(
1498
+ '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
1499
+ );
1500
+
1501
+ protected $StrongRegex = array(
1502
+ '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
1503
+ '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
1504
+ );
1505
+
1506
+ protected $EmRegex = array(
1507
+ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1508
+ '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1509
+ );
1510
+
1511
+ protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
1512
+
1513
+ protected $voidElements = array(
1514
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1515
+ );
1516
+
1517
+ protected $textLevelElements = array(
1518
+ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1519
+ 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1520
+ 'i', 'rp', 'del', 'code', 'strike', 'marquee',
1521
+ 'q', 'rt', 'ins', 'font', 'strong',
1522
+ 's', 'tt', 'sub', 'mark',
1523
+ 'u', 'xm', 'sup', 'nobr',
1524
+ 'var', 'ruby',
1525
+ 'wbr', 'span',
1526
+ 'time',
1527
+ );
1528
  }
framework/core/components/extensions/manager/static/extension-page.css CHANGED
@@ -1,23 +1,23 @@
1
- #fw-extension-page .fw-extension-page-title {
2
- padding-right: 0;
3
- }
4
-
5
- #fw-extension-page .fw-extension-page-title .button,
6
- #fw-extension-page .fw-extension-page-title .button-primary,
7
- #fw-extension-page .fw-extension-page-title .button-primary:active {
8
- vertical-align: middle;
9
- }
10
-
11
- #fw-extension-page .fw-flash-message + br {
12
- display: none;
13
- }
14
-
15
-
16
- #fw-extension-docs.fw-postbox > .insider {
17
- margin-top: 0 !important;
18
- }
19
-
20
- #fw-extension-docs hr {
21
- margin-left: -27px;
22
- margin-right: -27px;
23
  }
1
+ #fw-extension-page .fw-extension-page-title {
2
+ padding-right: 0;
3
+ }
4
+
5
+ #fw-extension-page .fw-extension-page-title .button,
6
+ #fw-extension-page .fw-extension-page-title .button-primary,
7
+ #fw-extension-page .fw-extension-page-title .button-primary:active {
8
+ vertical-align: middle;
9
+ }
10
+
11
+ #fw-extension-page .fw-flash-message + br {
12
+ display: none;
13
+ }
14
+
15
+
16
+ #fw-extension-docs.fw-postbox > .insider {
17
+ margin-top: 0 !important;
18
+ }
19
+
20
+ #fw-extension-docs hr {
21
+ margin-left: -27px;
22
+ margin-right: -27px;
23
  }
framework/core/components/extensions/manager/static/extension-page.js CHANGED
@@ -1,3 +1,3 @@
1
- jQuery(function($){
2
- $('#fw-extension-docs a:fw-external').attr('target', '_blank');
3
  });
1
+ jQuery(function($){
2
+ $('#fw-extension-docs a:fw-external').attr('target', '_blank');
3
  });
framework/core/components/extensions/manager/static/extensions-page.css CHANGED
@@ -1,236 +1,236 @@
1
- .fw-extensions-list a {
2
- text-decoration: none;
3
- }
4
-
5
- .fw-extensions-no-active {
6
- margin: 75px 0;
7
- }
8
-
9
- .fw-extensions-no-active .fw-text-muted {
10
- color: #9d9d9d;
11
- }
12
-
13
- .fw-extensions-no-active .fw-extensions-title-icon .dashicons {
14
- color: #d3d3d3;
15
- font-size: 46px;
16
- width: auto;
17
- height: auto;
18
- line-height: 35px;
19
- }
20
-
21
- .fw-extensions-list .fw-extensions-list-item {
22
- padding: 0 15px 15px 0;
23
- vertical-align: top;
24
- display: inline-block;
25
- float: none;
26
- margin: 0 -1px;
27
- }
28
-
29
- .fw-extensions-list .fw-extensions-list-item > .inner {
30
- padding: 20px;
31
- background-color: #ffffff;
32
- border: 1px solid #dedede;
33
- position: relative;
34
- }
35
-
36
- .fw-extensions-list .fw-extensions-list-item > .inner > p:last-child {
37
- margin-bottom: 0;
38
- }
39
-
40
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-thumbnail-wrapper {
41
- height: 128px;
42
- width: 128px;
43
- text-align: center;
44
- background: url('img/thumbnail-bg.jpg');
45
- background-size: 128px;
46
- }
47
-
48
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-thumbnail-wrapper img.fw-extensions-list-item-thumbnail {
49
- display: block;
50
- height: 100%;
51
- }
52
-
53
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-thumbnail-wrapper span.fw-extensions-list-item-thumbnail {
54
- display: inline;
55
- vertical-align: middle;
56
- color: #fff;
57
- line-height: 128px;
58
- font-size: 42px;
59
- }
60
-
61
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-thumbnail-wrapper span.fw-extensions-list-item-thumbnail.dashicons {
62
- font-size: 48px;
63
- }
64
-
65
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title {
66
- margin-top: 0;
67
- }
68
-
69
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title:last-child {
70
- margin-bottom: 0;
71
- }
72
-
73
-
74
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table {
75
- display: table;
76
- width: 100%;
77
- }
78
-
79
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row {
80
- display: table-row;
81
- }
82
-
83
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell {
84
- display: table-cell;
85
- vertical-align: top;
86
- }
87
-
88
- /*.fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-2,
89
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 {
90
- vertical-align: middle;
91
- }*/
92
-
93
- @media (max-width: 782px) {
94
- .fw-extensions-list .fw-extensions-list-item {
95
- padding-right: 0;
96
- }
97
-
98
- .fw-extensions-list .fw-extensions-list-item .fw-text-center {
99
- text-align: left;
100
- }
101
-
102
- body.rtl .fw-extensions-list .fw-extensions-list-item .fw-text-center {
103
- text-align: right;
104
- }
105
-
106
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title {
107
- margin-top: 20px;
108
- }
109
-
110
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table,
111
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row,
112
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell {
113
- display: block;
114
- }
115
-
116
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form,
117
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form .button {
118
- margin-bottom: 0;
119
- }
120
- }
121
-
122
- @media (min-width: 783px) {
123
- .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title {
124
- margin-top: 5px;
125
- }
126
-
127
- hr.fw-extensions-lists-separator {
128
- margin: 22px 0 30px;
129
- margin-right: 15px; /* same as .fw-extensions-list .fw-extensions-list-item padding-right */
130
- }
131
-
132
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:first-child,
133
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:last-child {
134
- width: 10px;
135
- }
136
-
137
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:not(:first-child) {
138
- padding-left: 20px;
139
- }
140
-
141
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell > p:last-child {
142
- margin-bottom: 0;
143
- }
144
-
145
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 {
146
- display: block;
147
- position: absolute;
148
- top: 20px;
149
- right: 20px;
150
- width: auto;
151
- }
152
-
153
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form {
154
- display: inline-block;
155
- }
156
-
157
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form.extension-delete-form {
158
- padding: 0 0 4px 15px;
159
- vertical-align: bottom;
160
- }
161
-
162
- .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form.extension-delete-form .btn-icon {
163
- font-size: 16px;
164
- }
165
- }
166
-
167
-
168
- /* disabled style */
169
-
170
- .fw-extensions-list .fw-extensions-list-item .fw-extension-disabled {
171
- display: none;
172
- background: rgba(255, 255, 255, 0.5) url("img/disabled-bg.png");
173
- border: 1px solid #ffffff;
174
- position: absolute;
175
- top: 0;
176
- left: 0;
177
- width: 100%;
178
- height: 100%;
179
- }
180
-
181
- .fw-extensions-list .fw-extensions-list-item.disabled .fw-extension-disabled {
182
- display: block;
183
- }
184
-
185
- .fw-extensions-list .fw-extensions-list-item .fw-extension-disabled .fw-extension-disabled-panel {
186
- position: absolute;
187
- left: 0;
188
- bottom: 0;
189
- width: 100%;
190
- background: #ffffff;
191
- padding: 20px;
192
- line-height: 28px;
193
- }
194
-
195
- @media (max-width: 782px) {
196
- .fw-extensions-list .fw-extensions-list-item.disabled > .inner {
197
- min-height: 320px;
198
- }
199
- }
200
-
201
- /* end: disabled style */
202
-
203
-
204
- /* tip content */
205
-
206
- .fw-extension-tip-content {
207
- padding: 15px;
208
- max-width: 380px;
209
- }
210
-
211
- .fw-extension-tip-content ul.fw-extension-requirements {
212
- margin: 0;
213
- }
214
-
215
- .fw-extension-tip-content ul.fw-extension-requirements li:last-child {
216
- margin-bottom: 0;
217
- }
218
-
219
- /* end: tip content */
220
-
221
-
222
- /* form ajax loading */
223
-
224
- .fw-extensions-list .fw-extensions-list-item .ajax-form-loading {
225
- display: inline-block;
226
- margin: -2px 0;
227
- padding-left: 7px;
228
- }
229
-
230
- .fw-extensions-list .fw-extensions-list-item .ajax-form-loading img {
231
- vertical-align: middle;
232
- display: inline-block;
233
- margin-top: -2px;
234
- }
235
-
236
- /* end: form ajax loading */
1
+ .fw-extensions-list a {
2
+ text-decoration: none;
3
+ }
4
+
5
+ .fw-extensions-no-active {
6
+ margin: 75px 0;
7
+ }
8
+
9
+ .fw-extensions-no-active .fw-text-muted {
10
+ color: #9d9d9d;
11
+ }
12
+
13
+ .fw-extensions-no-active .fw-extensions-title-icon .dashicons {
14
+ color: #d3d3d3;
15
+ font-size: 46px;
16
+ width: auto;
17
+ height: auto;
18
+ line-height: 35px;
19
+ }
20
+
21
+ .fw-extensions-list .fw-extensions-list-item {
22
+ padding: 0 15px 15px 0;
23
+ vertical-align: top;
24
+ display: inline-block;
25
+ float: none;
26
+ margin: 0 -1px;
27
+ }
28
+
29
+ .fw-extensions-list .fw-extensions-list-item > .inner {
30
+ padding: 20px;
31
+ background-color: #ffffff;
32
+ border: 1px solid #dedede;
33
+ position: relative;
34
+ }
35
+
36
+ .fw-extensions-list .fw-extensions-list-item > .inner > p:last-child {
37
+ margin-bottom: 0;
38
+ }
39
+
40
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-thumbnail-wrapper {
41
+ height: 128px;
42
+ width: 128px;
43
+ text-align: center;
44
+ background: url('img/thumbnail-bg.jpg');
45
+ background-size: 128px;
46
+ }
47
+
48
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-thumbnail-wrapper img.fw-extensions-list-item-thumbnail {
49
+ display: block;
50
+ height: 100%;
51
+ }
52
+
53
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-thumbnail-wrapper span.fw-extensions-list-item-thumbnail {
54
+ display: inline;
55
+ vertical-align: middle;
56
+ color: #fff;
57
+ line-height: 128px;
58
+ font-size: 42px;
59
+ }
60
+
61
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-thumbnail-wrapper span.fw-extensions-list-item-thumbnail.dashicons {
62
+ font-size: 48px;
63
+ }
64
+
65
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title {
66
+ margin-top: 0;
67
+ }
68
+
69
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title:last-child {
70
+ margin-bottom: 0;
71
+ }
72
+
73
+
74
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table {
75
+ display: table;
76
+ width: 100%;
77
+ }
78
+
79
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row {
80
+ display: table-row;
81
+ }
82
+
83
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell {
84
+ display: table-cell;
85
+ vertical-align: top;
86
+ }
87
+
88
+ /*.fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-2,
89
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 {
90
+ vertical-align: middle;
91
+ }*/
92
+
93
+ @media (max-width: 782px) {
94
+ .fw-extensions-list .fw-extensions-list-item {
95
+ padding-right: 0;
96
+ }
97
+
98
+ .fw-extensions-list .fw-extensions-list-item .fw-text-center {
99
+ text-align: left;
100
+ }
101
+
102
+ body.rtl .fw-extensions-list .fw-extensions-list-item .fw-text-center {
103
+ text-align: right;
104
+ }
105
+
106
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title {
107
+ margin-top: 20px;
108
+ }
109
+
110
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table,
111
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row,
112
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell {
113
+ display: block;
114
+ }
115
+
116
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form,
117
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form .button {
118
+ margin-bottom: 0;
119
+ }
120
+ }
121
+
122
+ @media (min-width: 783px) {
123
+ .fw-extensions-list .fw-extensions-list-item .fw-extensions-list-item-title {
124
+ margin-top: 5px;
125
+ }
126
+
127
+ hr.fw-extensions-lists-separator {
128
+ margin: 22px 0 30px;
129
+ margin-right: 15px; /* same as .fw-extensions-list .fw-extensions-list-item padding-right */
130
+ }
131
+
132
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:first-child,
133
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:last-child {
134
+ width: 10px;
135
+ }
136
+
137
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell:not(:first-child) {
138
+ padding-left: 20px;
139
+ }
140
+
141
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell > p:last-child {
142
+ margin-bottom: 0;
143
+ }
144
+
145
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 {
146
+ display: block;
147
+ position: absolute;
148
+ top: 20px;
149
+ right: 20px;
150
+ width: auto;
151
+ }
152
+
153
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form {
154
+ display: inline-block;
155
+ }
156
+
157
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form.extension-delete-form {
158
+ padding: 0 0 4px 15px;
159
+ vertical-align: bottom;
160
+ }
161
+
162
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-list-item-table > .fw-extension-list-item-table-row > .fw-extension-list-item-table-cell.cell-3 form.extension-delete-form .btn-icon {
163
+ font-size: 16px;
164
+ }
165
+ }
166
+
167
+
168
+ /* disabled style */
169
+
170
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-disabled {
171
+ display: none;
172
+ background: rgba(255, 255, 255, 0.5) url("img/disabled-bg.png");
173
+ border: 1px solid #ffffff;
174
+ position: absolute;
175
+ top: 0;
176
+ left: 0;
177
+ width: 100%;
178
+ height: 100%;
179
+ }
180
+
181
+ .fw-extensions-list .fw-extensions-list-item.disabled .fw-extension-disabled {
182
+ display: block;
183
+ }
184
+
185
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-disabled .fw-extension-disabled-panel {
186
+ position: absolute;
187
+ left: 0;
188
+ bottom: 0;
189
+ width: 100%;
190
+ background: #ffffff;
191
+ padding: 20px;
192
+ line-height: 28px;
193
+ }
194
+
195
+ @media (max-width: 782px) {
196
+ .fw-extensions-list .fw-extensions-list-item.disabled > .inner {
197
+ min-height: 320px;
198
+ }
199
+ }
200
+
201
+ /* end: disabled style */
202
+
203
+
204
+ /* tip content */
205
+
206
+ .fw-extension-tip-content {
207
+ padding: 15px;
208
+ max-width: 380px;
209
+ }
210
+
211
+ .fw-extension-tip-content ul.fw-extension-requirements {
212
+ margin: 0;
213
+ }
214
+
215
+ .fw-extension-tip-content ul.fw-extension-requirements li:last-child {
216
+ margin-bottom: 0;
217
+ }
218
+
219
+ /* end: tip content */
220
+
221
+
222
+ /* form ajax loading */
223
+
224
+ .fw-extensions-list .fw-extensions-list-item .ajax-form-loading {
225
+ display: inline-block;
226
+ margin: -2px 0;
227
+ padding-left: 7px;
228
+ }
229
+
230
+ .fw-extensions-list .fw-extensions-list-item .ajax-form-loading img {
231
+ vertical-align: middle;
232
+ display: inline-block;
233
+ margin-top: -2px;
234
+ }
235
+
236
+ /* end: form ajax loading */
framework/core/components/extensions/manager/static/extensions-page.js CHANGED
@@ -1,108 +1,108 @@
1
- jQuery(function ($) {
2
- fw.qtip( $('.fw-extensions-list .fw-extensions-list-item .fw-extension-tip') );
3
- });
4
-
5
- /**
6
- * Install/Remove/... via popup if has direct filesystem access (no ftp credentials required)
7
- */
8
- jQuery(function($){
9
- var inst = {
10
- isBusy: false,
11
- eventNamespace: '.fw-extension',
12
- $wrapper: $('.wrap'),
13
- listenSubmit: function() {
14
- this.$wrapper.on('submit'+ this.eventNamespace, 'form.fw-extension-ajax-form', this.onSubmit);
15
- },
16
- stopListeningSubmit: function() {
17
- this.$wrapper.off('submit'+ this.eventNamespace, 'form.fw-extension-ajax-form');
18
- },
19
- onSubmit: function(e) {
20
- e.preventDefault();
21
-
22
- if (inst.isBusy) {
23
- alert('Working... Please try again later');
24
- return;
25
- }
26
-
27
- var $form = $(this);
28
-
29
- var confirmMessage = $form.attr('data-confirm-message');
30
-
31
- inst.isBusy = true;
32
- inst.loading($form, true);
33
-
34
- $.ajax({
35
- url: ajaxurl,
36
- type: 'POST',
37
- data: {
38
- action: 'fw_extensions_check_direct_fs_access'
39
- },
40
- dataType: 'json'
41
- }).done(function(data){
42
- if (data.success) {
43
- if (confirmMessage) {
44
- if (!confirm(confirmMessage)) {
45
- inst.isBusy = false;
46
- inst.loading($form, false);
47
- }
48
- }
49
-
50
- $.ajax({
51
- url: ajaxurl,
52
- type: 'POST',
53
- data: {
54
- action: 'fw_extensions_'+ $form.attr('data-extension-action'),
55
- extension: $form.attr('data-extension-name')
56
- },
57
- dataType: 'json'
58
- }).done(function(r) {
59
- if (r.success) {
60
- window.location.reload();
61
- } else {
62
- var error = r.data ? r.data.pop().message : 'Error';
63
-
64
- fw.soleModal.show(
65
- 'fw-extension-install-error',
66
- '<p class="fw-text-danger">'+ error +'</p>'
67
- );
68
- }
69
- }).fail(function(jqXHR, textStatus, errorThrown){
70
- fw.soleModal.show(
71
- 'fw-extension-install-error',
72
- '<p class="fw-text-danger">'+ String(errorThrown) +'</p>'
73
- );
74
- inst.isBusy = false;
75
- inst.loading($form, false);
76
- });
77
- } else {
78
- inst.stopListeningSubmit();
79
- $form.submit();
80
- }
81
- }).fail(function(jqXHR, textStatus, errorThrown){
82
- inst.stopListeningSubmit();
83
- $form.submit();
84
- });
85
- },
86
- loading: function($form, show) {
87
- var $loadingContainer = $form.closest('.fw-extensions-list-item').find('.fw-extensions-list-item-title').first();
88
- var $loading = $loadingContainer.find('.ajax-form-loading');
89
-
90
- if (!$loading.length) {
91
- $loadingContainer.append(
92
- '<span class="ajax-form-loading fw-text-center fw-hidden">'+
93
- '<img src="'+ fw.img.loadingSpinner +'" />'+
94
- '</span>'
95
- );
96
- $loading = $loadingContainer.find('.ajax-form-loading');
97
- }
98
-
99
- if (show) {
100
- $loading.removeClass('fw-hidden');
101
- } else {
102
- $loading.addClass('fw-hidden');
103
- }
104
- }
105
- };
106
-
107
- inst.listenSubmit();
108
  });
1
+ jQuery(function ($) {
2
+ fw.qtip( $('.fw-extensions-list .fw-extensions-list-item .fw-extension-tip') );
3
+ });
4
+
5
+ /**
6
+ * Install/Remove/... via popup if has direct filesystem access (no ftp credentials required)
7
+ */
8
+ jQuery(function($){
9
+ var inst = {
10
+ isBusy: false,
11
+ eventNamespace: '.fw-extension',
12
+ $wrapper: $('.wrap'),
13
+ listenSubmit: function() {
14
+ this.$wrapper.on('submit'+ this.eventNamespace, 'form.fw-extension-ajax-form', this.onSubmit);
15
+ },
16
+ stopListeningSubmit: function() {
17
+ this.$wrapper.off('submit'+ this.eventNamespace, 'form.fw-extension-ajax-form');
18
+ },
19
+ onSubmit: function(e) {
20
+ e.preventDefault();
21
+
22
+ if (inst.isBusy) {
23
+ alert('Working... Please try again later');
24
+ return;
25
+ }
26
+
27
+ var $form = $(this);
28
+
29
+ var confirmMessage = $form.attr('data-confirm-message');
30
+
31
+ inst.isBusy = true;
32
+ inst.loading($form, true);
33
+
34
+ $.ajax({
35
+ url: ajaxurl,
36
+ type: 'POST',
37
+ data: {
38
+ action: 'fw_extensions_check_direct_fs_access'
39
+ },
40
+ dataType: 'json'
41
+ }).done(function(data){
42
+ if (data.success) {
43
+ if (confirmMessage) {
44
+ if (!confirm(confirmMessage)) {
45
+ inst.isBusy = false;
46
+ inst.loading($form, false);
47
+ }
48
+ }
49
+
50
+ $.ajax({
51
+ url: ajaxurl,
52
+ type: 'POST',
53
+ data: {
54
+ action: 'fw_extensions_'+ $form.attr('data-extension-action'),
55
+ extension: $form.attr('data-extension-name')
56
+ },
57
+ dataType: 'json'
58
+ }).done(function(r) {
59
+ if (r.success) {
60
+ window.location.reload();
61
+ } else {
62
+ var error = r.data ? r.data.pop().message : 'Error';
63
+
64
+ fw.soleModal.show(
65
+ 'fw-extension-install-error',
66
+ '<p class="fw-text-danger">'+ error +'</p>'
67
+ );
68
+ }
69
+ }).fail(function(jqXHR, textStatus, errorThrown){
70
+ fw.soleModal.show(
71
+ 'fw-extension-install-error',
72
+ '<p class="fw-text-danger">'+ String(errorThrown) +'</p>'
73
+ );
74
+ inst.isBusy = false;
75
+ inst.loading($form, false);
76
+ });
77
+ } else {
78
+ inst.stopListeningSubmit();
79
+ $form.submit();
80
+ }
81
+ }).fail(function(jqXHR, textStatus, errorThrown){
82
+ inst.stopListeningSubmit();
83
+ $form.submit();
84
+ });
85
+ },
86
+ loading: function($form, show) {
87
+ var $loadingContainer = $form.closest('.fw-extensions-list-item').find('.fw-extensions-list-item-title').first();
88
+ var $loading = $loadingContainer.find('.ajax-form-loading');
89
+
90
+ if (!$loading.length) {
91
+ $loadingContainer.append(
92
+ '<span class="ajax-form-loading fw-text-center fw-hidden">'+
93
+ '<img src="'+ fw.img.loadingSpinner +'" />'+
94
+ '</span>'
95
+ );
96
+ $loading = $loadingContainer.find('.ajax-form-loading');
97
+ }
98
+
99
+ if (show) {
100
+ $loading.removeClass('fw-hidden');
101
+ } else {
102
+ $loading.addClass('fw-hidden');
103
+ }
104
+ }
105
+ };
106
+
107
+ inst.listenSubmit();
108
  });
framework/core/components/extensions/manager/static/unyson-font-icon/fonts/icomoon.svg CHANGED
@@ -1,11 +1,11 @@
1
- <?xml version="1.0" standalone="no"?>
2
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
3
- <svg xmlns="http://www.w3.org/2000/svg">
4
- <metadata>Generated by IcoMoon</metadata>
5
- <defs>
6
- <font id="icomoon" horiz-adv-x="1024">
7
- <font-face units-per-em="1024" ascent="960" descent="-64" />
8
- <missing-glyph horiz-adv-x="1024" />
9
- <glyph unicode="&#x20;" d="" horiz-adv-x="512" />
10
- <glyph unicode="&#xe600;" d="M402.322 641.307c8.195 177.384 154.668 318.693 334.198 318.693 184.794 0 334.597-149.702 334.597-334.367 0-5.258-0.166-10.476-0.408-15.674 0 0 0.408 0 0.408 0s0-611.265 0-611.265c0 0-156.842-62.693-156.842-62.693s-146.385 62.693-146.385 62.693c0 0 0 568.788 0 568.788s0 115.621 0 115.621c0 0-0.132 0-0.132 0-0.87 37.687-20.304 70.747-49.501 90.476-27.849-18.815-46.846-49.73-49.306-85.253 0 0-0.396 0-0.396 0s0-433.633 0-433.633zM668.399 254.693c-8.192-177.384-154.577-318.693-334-318.693-184.684 0-334.399 149.702-334.399 334.367 0 5.268 0.295 10.463 0.537 15.674 0 0-0.537 0-0.537 0s0 611.265 0 611.265c0 0 156.747 62.693 156.747 62.693s146.3-62.693 146.3-62.693c0 0 0-568.788 0-568.788s0-115.621 0-115.621c0 0 0.132 0 0.132 0 0.87-37.687 20.292-70.747 49.472-90.476 27.83 18.815 47.867 50.192 50.327 85.714 0 0-0.656-0.462-0.656-0.462s0 433.633 0 433.633z" horiz-adv-x="1071" />
11
  </font></defs></svg>
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
3
+ <svg xmlns="http://www.w3.org/2000/svg">
4
+ <metadata>Generated by IcoMoon</metadata>
5
+ <defs>
6
+ <font id="icomoon" horiz-adv-x="1024">
7
+ <font-face units-per-em="1024" ascent="960" descent="-64" />
8
+ <missing-glyph horiz-adv-x="1024" />
9
+ <glyph unicode="&#x20;" d="" horiz-adv-x="512" />
10
+ <glyph unicode="&#xe600;" d="M402.322 641.307c8.195 177.384 154.668 318.693 334.198 318.693 184.794 0 334.597-149.702 334.597-334.367 0-5.258-0.166-10.476-0.408-15.674 0 0 0.408 0 0.408 0s0-611.265 0-611.265c0 0-156.842-62.693-156.842-62.693s-146.385 62.693-146.385 62.693c0 0 0 568.788 0 568.788s0 115.621 0 115.621c0 0-0.132 0-0.132 0-0.87 37.687-20.304 70.747-49.501 90.476-27.849-18.815-46.846-49.73-49.306-85.253 0 0-0.396 0-0.396 0s0-433.633 0-433.633zM668.399 254.693c-8.192-177.384-154.577-318.693-334-318.693-184.684 0-334.399 149.702-334.399 334.367 0 5.268 0.295 10.463 0.537 15.674 0 0-0.537 0-0.537 0s0 611.265 0 611.265c0 0 156.747 62.693 156.747 62.693s146.3-62.693 146.3-62.693c0 0 0-568.788 0-568.788s0-115.621 0-115.621c0 0 0.132 0 0.132 0 0.87-37.687 20.292-70.747 49.472-90.476 27.83 18.815 47.867 50.192 50.327 85.714 0 0-0.656-0.462-0.656-0.462s0 433.633 0 433.633z" horiz-adv-x="1071" />
11
  </font></defs></svg>
framework/core/components/extensions/manager/static/unyson-font-icon/style.css CHANGED
@@ -1,28 +1,28 @@
1
- @font-face {
2
- font-family: 'unyson-font-icon';
3
- src:url('fonts/icomoon.eot?iganyx');
4
- src:url('fonts/icomoon.eot?#iefixiganyx') format('embedded-opentype'),
5
- url('fonts/icomoon.woff?iganyx') format('woff'),
6
- url('fonts/icomoon.ttf?iganyx') format('truetype'),
7
- url('fonts/icomoon.svg?iganyx#icomoon') format('svg');
8
- font-weight: normal;
9
- font-style: normal;
10
- }
11
-
12
- .toplevel_page_fw-extensions > .wp-menu-image:before {
13
- font-family: 'unyson-font-icon';
14
- speak: none;
15
- font-style: normal;
16
- font-weight: normal;
17
- font-variant: normal;
18
- text-transform: none;
19
- line-height: 1;
20
-
21
- /* Better Font Rendering =========== */
22
- -webkit-font-smoothing: antialiased;
23
- -moz-osx-font-smoothing: grayscale;
24
-
25
- content: "\e600";
26
- font-size: 14px;
27
- line-height: 20px;
28
- }
1
+ @font-face {
2
+ font-family: 'unyson-font-icon';
3
+ src:url('fonts/icomoon.eot?iganyx');
4
+ src:url('fonts/icomoon.eot?#iefixiganyx') format('embedded-opentype'),
5
+ url('fonts/icomoon.woff?iganyx') format('woff'),
6
+ url('fonts/icomoon.ttf?iganyx') format('truetype'),
7
+ url('fonts/icomoon.svg?iganyx#icomoon') format('svg');
8
+ font-weight: normal;
9
+ font-style: normal;
10
+ }
11
+
12
+ .toplevel_page_fw-extensions > .wp-menu-image:before {
13
+ font-family: 'unyson-font-icon';
14
+ speak: none;
15
+ font-style: normal;
16
+ font-weight: normal;
17
+ font-variant: normal;
18
+ text-transform: none;
19
+ line-height: 1;
20
+
21
+ /* Better Font Rendering =========== */
22
+ -webkit-font-smoothing: antialiased;
23
+ -moz-osx-font-smoothing: grayscale;
24
+
25
+ content: "\e600";
26
+ font-size: 14px;
27
+ line-height: 20px;
28
+ }
framework/core/components/extensions/manager/views/delete-form.php CHANGED
@@ -1,55 +1,55 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
- /**
3
- * @var array $extension_names
4
- * @var array $installed_extensions
5
- * @var array $list_page_link
6
- */
7
-
8
- $count = count($extension_names);
9
- ?>
10
-
11
- <p><?php echo _n(
12
- 'You are about to remove the following extension:',
13
- 'You are about to remove the following extensions:',
14
- $count,
15
- 'fw'
16
- ) ?></p>
17
-
18
- <ul class="ul-disc">
19
- <?php foreach ($extension_names as $extension_name): ?>
20
- <li><strong><?php echo fw()->extensions->manager->get_extension_title($extension_name); ?></strong></li>
21
- <?php endforeach; ?>
22
- </ul>
23
-
24
- <p><?php
25
- echo _n(
26
- 'Are you sure you wish to delete this extension?',
27
- 'Are you sure you wish to delete these extensions?',
28
- $count,
29
- 'fw'
30
- )
31
- ?></p>
32
-
33
- <input type="submit" name="submit" id="submit" class="button" value="<?php
34
- echo esc_attr( _n(
35
- 'Yes, Delete this extension',
36
- 'Yes, Delete these extensions',
37
- $count,
38
- 'fw'
39
- ) )
40
- ?>">
41
-
42
- <a class="button" href="<?php echo esc_attr($list_page_link) ?>" ><?php _e('No, Return me to the extension list', 'fw') ?></a>
43
-
44
- <p>
45
- <a href="#" onclick="jQuery('#files-list').toggle(); return false;"><?php _e('Click to view entire list of directories which will be deleted', 'fw') ?></a>
46
- </p>
47
- <div id="files-list" style="display: none;">
48
- <ul class="code">
49
- <?php $replace_regex = '/^'. preg_quote(fw_get_framework_directory('/extensions'), '/') .'/'; ?>
50
- <?php foreach ($extension_names as $extension_name): ?>
51
- <?php if (!isset($installed_extensions[$extension_name])) continue; ?>
52
- <li><?php echo preg_replace($replace_regex, '', $installed_extensions[$extension_name]['path']) ?>/</li>
53
- <?php endforeach; ?>
54
- </ul>
55
  </div>
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+ /**
3
+ * @var array $extension_names
4
+ * @var array $installed_extensions
5
+ * @var array $list_page_link
6
+ */
7
+
8
+ $count = count($extension_names);
9
+ ?>
10
+
11
+ <p><?php echo _n(
12
+ 'You are about to remove the following extension:',
13
+ 'You are about to remove the following extensions:',
14
+ $count,
15
+ 'fw'
16
+ ) ?></p>
17
+
18
+ <ul class="ul-disc">
19
+ <?php foreach ($extension_names as $extension_name): ?>
20
+ <li><strong><?php echo fw()->extensions->manager->get_extension_title($extension_name); ?></strong></li>
21
+ <?php endforeach; ?>
22
+ </ul>
23
+
24
+ <p><?php
25
+ echo _n(
26
+ 'Are you sure you wish to delete this extension?',
27
+ 'Are you sure you wish to delete these extensions?',
28
+ $count,
29
+ 'fw'
30
+ )
31
+ ?></p>
32
+
33
+ <input type="submit" name="submit" id="submit" class="button" value="<?php
34
+ echo esc_attr( _n(
35
+ 'Yes, Delete this extension',
36
+ 'Yes, Delete these extensions',
37
+ $count,
38
+ 'fw'
39
+ ) )
40
+ ?>">
41
+
42
+ <a class="button" href="<?php echo esc_attr($list_page_link) ?>" ><?php _e('No, Return me to the extension list', 'fw') ?></a>
43
+
44
+ <p>
45
+ <a href="#" onclick="jQuery('#files-list').toggle(); return false;"><?php _e('Click to view entire list of directories which will be deleted', 'fw') ?></a>
46
+ </p>
47
+ <div id="files-list" style="display: none;">
48
+ <ul class="code">
49
+ <?php $replace_regex = '/^'. preg_quote(fw_get_framework_directory('/extensions'), '/') .'/'; ?>
50
+ <?php foreach ($extension_names as $extension_name): ?>
51
+ <?php if (!isset($installed_extensions[$extension_name])) continue; ?>
52
+ <li><?php echo preg_replace($replace_regex, '', $installed_extensions[$extension_name]['path']) ?>/</li>
53
+ <?php endforeach; ?>
54
+ </ul>
55
  </div>
framework/core/components/extensions/manager/views/extension-page-header.php CHANGED
@@ -1,50 +1,50 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
- /**
3
- * @var string $extension_name
4
- * @var array $extension_data
5
- * @var string $extension_title
6
- * @var string $link_delete
7
- * @var string $link_extension
8
- * @var string $tab
9
- * @var bool $is_supported
10
- */
11
-
12
- ?>
13
- <h2 class="fw-extension-page-title">
14
- <span class="fw-pull-right">
15
- <?php
16
- switch ($tab) {
17
- case 'settings':
18
- if (!file_exists($extension_data['path'] .'/readme.md.php')) {
19
- break;
20
- }
21
- if ($is_supported) {
22
- // do not show install instructions for supported extensions
23
- break;
24
- }
25
- ?><a href="<?php echo esc_attr($link_extension) ?>&extension=<?php echo esc_attr($extension_name) ?>&tab=docs" class="button-primary"><?php _e('Install Instructions', 'fw') ?></a><?php
26
- break;
27
- case 'docs':
28
- if (!fw()->extensions->get($extension_name) || !fw()->extensions->get($extension_name)->get_settings_options()) {
29
- break;
30
- }
31
- ?><a href="<?php echo esc_attr($link_extension) ?>&extension=<?php echo esc_attr($extension_name) ?>" class="button-primary"><?php _e('Settings', 'fw') ?></a><?php
32
- break;
33
- }
34
- ?>
35
- </span>
36
-
37
- <?php
38
- switch ($tab) {
39
- case 'settings':
40
- echo sprintf(__('%s Settings', 'fw'), $extension_title);
41
- break;
42
- case 'docs':
43
- echo sprintf(__('%s Install Instructions', 'fw'), $extension_title);
44
- break;
45
- default:
46
- echo __('Unknown tab:', 'fw') . ' ' . fw_htmlspecialchars($tab);
47
- }
48
- ?>
49
- </h2>
50
- <br/>
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+ /**
3
+ * @var string $extension_name
4
+ * @var array $extension_data
5
+ * @var string $extension_title
6
+ * @var string $link_delete
7
+ * @var string $link_extension
8
+ * @var string $tab
9
+ * @var bool $is_supported
10
+ */
11
+
12
+ ?>
13
+ <h2 class="fw-extension-page-title">
14
+ <span class="fw-pull-right">
15
+ <?php
16
+ switch ($tab) {
17
+ case 'settings':
18
+ if (!file_exists($extension_data['path'] .'/readme.md.php')) {
19
+ break;
20
+ }
21
+ if ($is_supported) {
22
+ // do not show install instructions for supported extensions
23
+ break;
24
+ }
25
+ ?><a href="<?php echo esc_attr($link_extension) ?>&extension=<?php echo esc_attr($extension_name) ?>&tab=docs" class="button-primary"><?php _e('Install Instructions', 'fw') ?></a><?php
26
+ break;
27
+ case 'docs':
28
+ if (!fw()->extensions->get($extension_name) || !fw()->extensions->get($extension_name)->get_settings_options()) {
29
+ break;
30
+ }
31
+ ?><a href="<?php echo esc_attr($link_extension) ?>&extension=<?php echo esc_attr($extension_name) ?>" class="button-primary"><?php _e('Settings', 'fw') ?></a><?php
32
+ break;
33
+ }
34
+ ?>
35
+ </span>
36
+
37
+ <?php
38
+ switch ($tab) {
39
+ case 'settings':
40
+ echo sprintf(__('%s Settings', 'fw'), $extension_title);
41
+ break;
42
+ case 'docs':
43
+ echo sprintf(__('%s Install Instructions', 'fw'), $extension_title);
44
+ break;
45
+ default:
46
+ echo __('Unknown tab:', 'fw') . ' ' . fw_htmlspecialchars($tab);
47
+ }
48
+ ?>
49
+ </h2>
50
+ <br/>
framework/core/components/extensions/manager/views/extension.php CHANGED
@@ -1,360 +1,360 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
- /**
3
- * Display extension in list on the extensions page
4
- * @var string $name
5
- * @var string $title
6
- * @var string $description
7
- * @var string $link
8
- * @var array $lists
9
- * @var array $nonces
10
- * @var string $default_thumbnail
11
- * @var bool $can_install
12
- */
13
-
14
- $is_active = (bool)fw()->extensions->get($name);
15
-
16
- if (isset($lists['installed'][$name])) {
17
- $installed_data = &$lists['installed'][$name];
18
- } else {
19
- $installed_data = false;
20
- }
21
-
22
- if (isset($lists['available'][$name])) {
23
- $available_data = &$lists['available'][$name];
24
- } else {
25
- $available_data = false;
26
- }
27
-
28
- {
29
- $thumbnail = $default_thumbnail;
30
-
31
- if (isset($lists['available'][$name])) {
32
- $thumbnail = $lists['available'][$name]['thumbnail'];
33
- }
34
-
35
- if (isset($lists['installed'][$name])) {
36
- $thumbnail = fw_akg('thumbnail', $lists['installed'][$name]['manifest'], $thumbnail);
37
- }
38
- }
39
-
40
- $is_compatible =
41
- isset($lists['supported'][$name]) // is listed in the supported extensions list in theme manifest
42
- ||
43
- ($installed_data && $installed_data['is']['theme']); // is located in the theme
44
-
45
- $wrapper_class = 'fw-col-xs-12 fw-col-lg-6 fw-extensions-list-item';
46
-
47
- if ($installed_data && !$is_active) {
48
- $wrapper_class .= ' disabled';
49
- }
50
-
51
- if (!$installed_data && !$is_compatible) {
52
- $wrapper_class .= ' not-compatible';
53
- }
54
- ?>
55
- <div class="<?php echo esc_attr($wrapper_class) ?>" id="fw-ext-<?php echo esc_attr($name) ?>">
56
- <div class="inner">
57
- <div class="fw-extension-list-item-table">
58
- <div class="fw-extension-list-item-table-row">
59
- <div class="fw-extension-list-item-table-cell cell-1">
60
- <div class="fw-extensions-list-item-thumbnail-wrapper">
61
- <?php echo fw_string_to_icon_html($thumbnail, array('class' => 'fw-extensions-list-item-thumbnail')); ?>
62
- </div>
63
- </div>
64
- <div class="fw-extension-list-item-table-cell cell-2">
65
- <h3 class="fw-extensions-list-item-title"<?php if ($is_active): ?> title="v<?php echo esc_attr(fw()->extensions->get($name)->manifest->get_version()) ?>"<?php endif; ?>><?php
66
- if ($is_active && ($extension_link = fw()->extensions->get($name)->_get_link())) {
67
- echo fw_html_tag('a', array('href' => $extension_link), $title);
68
- } else {
69
- echo $title;
70
- }
71
- ?></h3>
72
-
73
- <?php if ($description): ?>
74
- <p class="fw-extensions-list-item-desc"><?php echo $description; ?></p>
75
- <?php endif; ?>
76
-
77
- <?php
78
- if ($installed_data) {
79
- $_links = array();
80
-
81
- if ( $is_active && fw()->extensions->get( $name )->get_settings_options() ) {
82
- $_links[] = '<a href="' . esc_attr( $link ) . '&sub-page=extension&extension=' . esc_attr( $name ) . '">' . __( 'Settings', 'fw' ) . '</a>';
83
- }
84
-
85
- if ( $is_active && file_exists( $installed_data['path'] . '/readme.md.php' ) ) {
86
- if ( isset($lists['supported'][$name]) ) {
87
- // no sense to teach how to install the extension if theme is already configured and the is extension marked as compatible
88
- } else {
89
- $_links[] = '<a href="' . esc_attr( $link ) . '&sub-page=extension&extension=' . esc_attr( $name ) . '&tab=docs">' . __( 'Install Instructions', 'fw' ) . '</a>';
90
- }
91
- }
92
-
93
- if ( ! empty( $_links ) ):
94
- ?><p
95
- class="fw-extensions-list-item-links"><?php echo implode( ' <span class="fw-text-muted">|</span> ', $_links ); ?></p><?php
96
- endif;
97
-
98
- unset( $_links );
99
- }
100
- ?>
101
- <?php if ($is_compatible): ?>
102
- <p><em><strong><span class="dashicons dashicons-yes"></span> <?php _e('Compatible', 'fw') ?></strong> <?php _e('with your current theme', 'fw') ?></em></p>
103
- <?php endif; ?>
104
- </div>
105
- <div class="fw-extension-list-item-table-cell cell-3">
106
- <?php if ($is_active): ?>
107
- <form action="<?php echo esc_attr($link) ?>&sub-page=deactivate&extension=<?php echo esc_attr($name) ?>" method="post">
108
- <?php wp_nonce_field($nonces['deactivate']['action'], $nonces['deactivate']['name']); ?>
109
- <input class="button" type="submit" value="<?php esc_attr_e('Deactivate', 'fw'); ?>"/>
110
- </form>
111
- <?php elseif ($installed_data): ?>
112
- <div class="fw-text-center">
113
- <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($name) ?>"
114
- method="post"
115
- class="extension-activate-form"
116
- >
117
- <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
118
- <input class="button" type="submit" value="<?php esc_attr_e('Activate', 'fw'); ?>"/>
119
- </form>
120
- <?php
121
- /**
122
- * Do not show the "Delete extension" button if the extension is not in the available list.
123
- * If you delete such extension you will not be able to install it back.
124
- * Most often these will be extensions located in the theme.
125
- */
126
- if ($can_install && $available_data):
127
- ?>
128
- <form action="<?php echo esc_attr($link) ?>&sub-page=delete&extension=<?php echo esc_attr($name) ?>"
129
- method="post"
130
- class="fw-extension-ajax-form extension-delete-form"
131
- data-confirm-message="<?php esc_attr_e('Are you sure you want to remove this extension?', 'fw') ?>"
132
- data-extension-name="<?php echo esc_attr($name) ?>"
133
- data-extension-action="uninstall"
134
- >
135
- <?php wp_nonce_field($nonces['delete']['action'], $nonces['delete']['name']); ?>
136
- <p class="fw-visible-xs-block"></p>
137
- <a href="#"
138
- onclick="jQuery(this).closest('form').submit(); return false;"
139
- data-remove-extension="<?php echo esc_attr($name) ?>"
140
- title="<?php echo esc_attr_e('Remove', 'fw'); ?>"
141
- ><span class="btn-text fw-visible-xs-inline"><?php _e('Remove', 'fw'); ?></span><span class="btn-icon unycon unycon-trash fw-hidden-xs"></span></a>
142
- </form>
143
- <?php endif; ?>
144
- </div>
145
- <?php elseif ($can_install && $available_data): ?>
146
- <form action="<?php echo esc_attr($link) ?>&sub-page=install&extension=<?php echo esc_attr($name) ?>"
147
- method="post"
148
- class="fw-extension-ajax-form"
149
- data-extension-name="<?php echo esc_attr($name) ?>"
150
- data-extension-action="install"
151
- >
152
- <?php wp_nonce_field($nonces['install']['action'], $nonces['install']['name']); ?>
153
- <input type="submit" class="button" value="<?php esc_attr_e('Download', 'fw') ?>">
154
- </form>
155
- <?php endif; ?>
156
- </div>
157
- </div>
158
- </div>
159
- <?php if ($installed_data): ?>
160
- <?php if (!$is_active): ?>
161
- <?php if (!fw()->extensions->_get_db_active_extensions($name)): ?>
162
- <span><!-- Is not set as active in db --></span>
163
- <?php elseif ($installed_data['parent'] && !fw()->extensions->get($installed_data['parent'])): ?>
164
- <?php
165
- $parent_extension_name = $installed_data['parent'];
166
- $parent_extension_title = fw_id_to_title($parent_extension_name);
167
-
168
- if (isset($lists['installed'][$parent_extension_name])) {
169
- $parent_extension_title = fw_akg('name', $lists['installed'][$parent_extension_name]['manifest'], $parent_extension_title);
170
- } elseif (isset($lists['available'][$parent_extension_name])) {
171
- $parent_extension_title = $lists['available'][$parent_extension_name]['name'];
172
- }
173
- ?>
174
- <p class="fw-text-muted"><?php echo sprintf(__('Parent extension "%s" is disabled', 'fw'), $parent_extension_title); ?></p>
175
- <?php else: ?>
176
- <div class="fw-extension-disabled fw-border-box-sizing">
177
- <div class="fw-extension-disabled-panel fw-border-box-sizing">
178
- <div class="fw-row">
179
- <div class="fw-col-xs-12 fw-col-sm-3">
180
- <span class="fw-text-danger">!&nbsp;<?php _e('Disabled', 'fw'); ?></span>
181
- </div>
182
- <div class="fw-col-xs-12 fw-col-sm-9 fw-text-right">
183
- <?php
184
- $requirements = array();
185
-
186
- foreach (fw_akg('requirements', $installed_data['manifest'], array()) as $req_name => $req_data) {
187
- switch ($req_name) {
188
- case 'wordpress':
189
- if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
190
- break;
191
- }
192
-
193
- global $wp_version;
194
-
195
- if ( ! empty( $req_data['min_version'] ) ) {
196
- if (!version_compare($req_data['min_version'], $wp_version, '<=')) {
197
- if ($can_install) {
198
- $requirements[] = sprintf(
199
- __( 'You need to update WordPress to %s: %s', 'fw' ),
200
- $req_data['min_version'],
201
- fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ), __( 'Update WordPress', 'fw' ) )
202
- );
203
- } else {
204
- $requirements[] = sprintf(
205
- __( 'WordPress needs to be updated to %s', 'fw' ),
206
- $req_data['min_version']
207
- );
208
- }
209
- }
210
- }
211
-
212
- if ( ! empty( $req_data['max_version'] ) ) {
213
- if (!version_compare($req_data['max_version'], $wp_version, '>=')) {
214
- $requirements[] = sprintf(
215
- __('Maximum supported WordPress version is %s', 'fw'),
216
- $req_data['max_version']
217
- );
218
- }
219
- }
220
- break;
221
- case 'framework':
222
- if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
223
- break;
224
- }
225
-
226
- if ( ! empty( $req_data['min_version'] ) ) {
227
- if (!version_compare($req_data['min_version'], fw()->manifest->get_version(), '<=')) {
228
- if ($can_install) {
229
- $requirements[] = sprintf(
230
- __( 'You need to update %s to %s: %s', 'fw' ),
231
- fw()->manifest->get_name(),
232
- $req_data['min_version'],
233
- fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ),
234
- sprintf( __( 'Update %s', 'fw' ), fw()->manifest->get_name() )
235
- )
236
- );
237
- } else {
238
- $requirements[] = sprintf(
239
- __( '%s needs to be updated to %s', 'fw' ),
240
- fw()->manifest->get_name(),
241
- $req_data['min_version']
242
- );
243
- }
244
- }
245
- }
246
-
247
- if ( ! empty( $req_data['max_version'] ) ) {
248
- if (!version_compare($req_data['max_version'], fw()->manifest->get_version(), '>=')) {
249
- $requirements[] = sprintf(
250
- __( 'Maximum supported %s version is %s', 'fw' ),
251
- fw()->manifest->get_name(),
252
- $req_data['max_version']
253
- );
254
- }
255
- }
256
- break;
257
- case 'extensions':
258
- foreach ($req_data as $req_ext => $req_ext_data) {
259
- if ($ext = fw()->extensions->get($req_ext)) {
260
- if (empty($req_ext_data['min_version']) && empty($req_ext_data['max_version'])) {
261
- continue;
262
- }
263
-
264
- if ( ! empty( $req_ext_data['min_version'] ) ) {
265
- if (!version_compare($req_ext_data['min_version'], $ext->manifest->get_version(), '<=')) {
266
- if ($can_install) {
267
- $requirements[] = sprintf(
268
- __('You need to update the %s extension to %s: %s', 'fw'),
269
- $ext->manifest->get_name(),
270
- $req_ext_data['min_version'],
271
- fw_html_tag('a', array('href' => self_admin_url('update-core.php')),
272
- sprintf(__('Update %s', 'fw'), $ext->manifest->get_name())
273
- )
274
- );
275
- } else {
276
- $requirements[] = sprintf(
277
- __('The %s extension needs to be updated to %s', 'fw'),
278
- $ext->manifest->get_name(),
279
- $req_ext_data['min_version']
280
- );
281
- }
282
- }
283
- }
284
-
285
- if ( ! empty( $req_ext_data['max_version'] ) ) {
286
- if (!version_compare($req_ext_data['max_version'], $ext->manifest->get_version(), '>=')) {
287
- $requirements[] = sprintf(
288
- __( 'Maximum supported %s extension version is %s', 'fw' ),
289
- $ext->manifest->get_name(),
290
- $req_ext_data['max_version']
291
- );
292
- }
293
- }
294
- } else {
295
- $ext_title = fw_id_to_title($req_ext);
296
-
297
- if (isset($lists['installed'][$req_ext])) {
298
- $ext_title = fw_akg('name', $lists['installed'][$req_ext]['manifest'], $ext_title);
299
-
300
- ob_start(); ?>
301
- <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($req_ext) ?>" method="post" style="display: inline;">
302
- <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
303
- <?php echo sprintf(__( 'The %s extension is disabled', 'fw' ), $ext_title); ?>:
304
- <a href="#" onclick="jQuery(this).closest('form').submit(); return false;"><?php echo sprintf(__('Activate %s', 'fw'), $ext_title); ?></a>
305
- </form>
306
- <?php
307
- $requirements[] = ob_get_clean();
308
- } else {
309
- if ($can_install && isset($lists['available'][$req_ext])) {
310
- $ext_title = $lists['available'][ $req_ext ]['name'];
311
-
312
- $requirements[] = sprintf(
313
- __( 'The %s extension is not installed: %s', 'fw' ),
314
- $ext_title,
315
- fw_html_tag( 'a', array( 'href' => $link . '&sub-page=install&extension=' . $req_ext ),
316
- sprintf( __( 'Install %s', 'fw' ), $ext_title )
317
- )
318
- );
319
- } else {
320
- $requirements[] = sprintf(
321
- __( 'The %s extension is not installed', 'fw' ),
322
- $ext_title
323
- );
324
- }
325
- }
326
- }
327
- }
328
- break;
329
- default:
330
- trigger_error('Invalid requirement: '. $req_name, E_USER_WARNING);
331
- continue;
332
- }
333
- }
334
- ?>
335
- <a onclick="return false;" href="#" class="fw-extension-tip" title="<?php
336
- echo fw_htmlspecialchars(
337
- '<div class="fw-extension-tip-content">'.
338
- '<ul class="fw-extension-requirements"><li>- '. implode('</li><li>- ', $requirements) .'</li></ul>'.
339
- '</div>'
340
- );
341
- unset($requirements);
342
- ?>"><?php _e('View Requirements', 'fw') ?></a>
343
- &nbsp; <p class="fw-visible-xs-block"></p><?php
344
- if ($can_install):
345
- ?><a href="<?php echo esc_attr($link) ?>&sub-page=delete&extension=<?php echo esc_attr($name) ?>" class="button" ><?php _e('Remove', 'fw'); ?></a><?php
346
- endif;
347
- ?>
348
- </div>
349
- </div>
350
- </div>
351
- </div>
352
- <?php endif; ?>
353
- <?php endif; ?>
354
- <?php elseif ($available_data): ?>
355
- <!-- -->
356
- <?php else: ?>
357
- <!-- -->
358
- <?php endif; ?>
359
- </div>
360
- </div>
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+ /**
3
+ * Display extension in list on the extensions page
4
+ * @var string $name
5
+ * @var string $title
6
+ * @var string $description
7
+ * @var string $link
8
+ * @var array $lists
9
+ * @var array $nonces
10
+ * @var string $default_thumbnail
11
+ * @var bool $can_install
12
+ */
13
+
14
+ $is_active = (bool)fw()->extensions->get($name);
15
+
16
+ if (isset($lists['installed'][$name])) {
17
+ $installed_data = &$lists['installed'][$name];
18
+ } else {
19
+ $installed_data = false;
20
+ }
21
+
22
+ if (isset($lists['available'][$name])) {
23
+ $available_data = &$lists['available'][$name];
24
+ } else {
25
+ $available_data = false;
26
+ }
27
+
28
+ {
29
+ $thumbnail = $default_thumbnail;
30
+
31
+ if (isset($lists['available'][$name])) {
32
+ $thumbnail = $lists['available'][$name]['thumbnail'];
33
+ }
34
+
35
+ if (isset($lists['installed'][$name])) {
36
+ $thumbnail = fw_akg('thumbnail', $lists['installed'][$name]['manifest'], $thumbnail);
37
+ }
38
+ }
39
+
40
+ $is_compatible =
41
+ isset($lists['supported'][$name]) // is listed in the supported extensions list in theme manifest
42
+ ||
43
+ ($installed_data && $installed_data['is']['theme']); // is located in the theme
44
+
45
+ $wrapper_class = 'fw-col-xs-12 fw-col-lg-6 fw-extensions-list-item';
46
+
47
+ if ($installed_data && !$is_active) {
48
+ $wrapper_class .= ' disabled';
49
+ }
50
+
51
+ if (!$installed_data && !$is_compatible) {
52
+ $wrapper_class .= ' not-compatible';
53
+ }
54
+ ?>
55
+ <div class="<?php echo esc_attr($wrapper_class) ?>" id="fw-ext-<?php echo esc_attr($name) ?>">
56
+ <div class="inner">
57
+ <div class="fw-extension-list-item-table">
58
+ <div class="fw-extension-list-item-table-row">
59
+ <div class="fw-extension-list-item-table-cell cell-1">
60
+ <div class="fw-extensions-list-item-thumbnail-wrapper">
61
+ <?php echo fw_string_to_icon_html($thumbnail, array('class' => 'fw-extensions-list-item-thumbnail')); ?>
62
+ </div>
63
+ </div>
64
+ <div class="fw-extension-list-item-table-cell cell-2">
65
+ <h3 class="fw-extensions-list-item-title"<?php if ($is_active): ?> title="v<?php echo esc_attr(fw()->extensions->get($name)->manifest->get_version()) ?>"<?php endif; ?>><?php
66
+ if ($is_active && ($extension_link = fw()->extensions->get($name)->_get_link())) {
67
+ echo fw_html_tag('a', array('href' => $extension_link), $title);
68
+ } else {
69
+ echo $title;
70
+ }
71
+ ?></h3>
72
+
73
+ <?php if ($description): ?>
74
+ <p class="fw-extensions-list-item-desc"><?php echo esc_html($description); ?></p>
75
+ <?php endif; ?>
76
+
77
+ <?php
78
+ if ($installed_data) {
79
+ $_links = array();
80
+
81
+ if ( $is_active && fw()->extensions->get( $name )->get_settings_options() ) {
82
+ $_links[] = '<a href="' . esc_attr( $link ) . '&sub-page=extension&extension=' . esc_attr( $name ) . '">' . __( 'Settings', 'fw' ) . '</a>';
83
+ }
84
+
85
+ if ( $is_active && file_exists( $installed_data['path'] . '/readme.md.php' ) ) {
86
+ if ( isset($lists['supported'][$name]) ) {
87
+ // no sense to teach how to install the extension if theme is already configured and the is extension marked as compatible
88
+ } else {
89
+ $_links[] = '<a href="' . esc_attr( $link ) . '&sub-page=extension&extension=' . esc_attr( $name ) . '&tab=docs">' . __( 'Install Instructions', 'fw' ) . '</a>';
90
+ }
91
+ }
92
+
93
+ if ( ! empty( $_links ) ):
94
+ ?><p
95
+ class="fw-extensions-list-item-links"><?php echo implode( ' <span class="fw-text-muted">|</span> ', $_links ); ?></p><?php
96
+ endif;
97
+
98
+ unset( $_links );
99
+ }
100
+ ?>
101
+ <?php if ($is_compatible): ?>
102
+ <p><em><strong><span class="dashicons dashicons-yes"></span> <?php _e('Compatible', 'fw') ?></strong> <?php _e('with your current theme', 'fw') ?></em></p>
103
+ <?php endif; ?>
104
+ </div>
105
+ <div class="fw-extension-list-item-table-cell cell-3">
106
+ <?php if ($is_active): ?>
107
+ <form action="<?php echo esc_attr($link) ?>&sub-page=deactivate&extension=<?php echo esc_attr($name) ?>" method="post">
108
+ <?php wp_nonce_field($nonces['deactivate']['action'], $nonces['deactivate']['name']); ?>
109
+ <input class="button" type="submit" value="<?php esc_attr_e('Deactivate', 'fw'); ?>"/>
110
+ </form>
111
+ <?php elseif ($installed_data): ?>
112
+ <div class="fw-text-center">
113
+ <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($name) ?>"
114
+ method="post"
115
+ class="extension-activate-form"
116
+ >
117
+ <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
118
+ <input class="button" type="submit" value="<?php esc_attr_e('Activate', 'fw'); ?>"/>
119
+ </form>
120
+ <?php
121
+ /**
122
+ * Do not show the "Delete extension" button if the extension is not in the available list.
123
+ * If you delete such extension you will not be able to install it back.
124
+ * Most often these will be extensions located in the theme.
125
+ */
126
+ if ($can_install && $available_data):
127
+ ?>
128
+ <form action="<?php echo esc_attr($link) ?>&sub-page=delete&extension=<?php echo esc_attr($name) ?>"
129
+ method="post"
130
+ class="fw-extension-ajax-form extension-delete-form"
131
+ data-confirm-message="<?php esc_attr_e('Are you sure you want to remove this extension?', 'fw') ?>"
132
+ data-extension-name="<?php echo esc_attr($name) ?>"
133
+ data-extension-action="uninstall"
134
+ >
135
+ <?php wp_nonce_field($nonces['delete']['action'], $nonces['delete']['name']); ?>
136
+ <p class="fw-visible-xs-block"></p>
137
+ <a href="#"
138
+ onclick="jQuery(this).closest('form').submit(); return false;"
139
+ data-remove-extension="<?php echo esc_attr($name) ?>"
140
+ title="<?php echo esc_attr_e('Remove', 'fw'); ?>"
141
+ ><span class="btn-text fw-visible-xs-inline"><?php _e('Remove', 'fw'); ?></span><span class="btn-icon unycon unycon-trash fw-hidden-xs"></span></a>
142
+ </form>
143
+ <?php endif; ?>
144
+ </div>
145
+ <?php elseif ($can_install && $available_data): ?>
146
+ <form action="<?php echo esc_attr($link) ?>&sub-page=install&extension=<?php echo esc_attr($name) ?>"
147
+ method="post"
148
+ class="fw-extension-ajax-form"
149
+ data-extension-name="<?php echo esc_attr($name) ?>"
150
+ data-extension-action="install"
151
+ >
152
+ <?php wp_nonce_field($nonces['install']['action'], $nonces['install']['name']); ?>
153
+ <input type="submit" class="button" value="<?php esc_attr_e('Download', 'fw') ?>">
154
+ </form>
155
+ <?php endif; ?>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ <?php if ($installed_data): ?>
160
+ <?php if (!$is_active): ?>
161
+ <?php if (!fw()->extensions->_get_db_active_extensions($name)): ?>
162
+ <span><!-- Is not set as active in db --></span>
163
+ <?php elseif ($installed_data['parent'] && !fw()->extensions->get($installed_data['parent'])): ?>
164
+ <?php
165
+ $parent_extension_name = $installed_data['parent'];
166
+ $parent_extension_title = fw_id_to_title($parent_extension_name);
167
+
168
+ if (isset($lists['installed'][$parent_extension_name])) {
169
+ $parent_extension_title = fw_akg('name', $lists['installed'][$parent_extension_name]['manifest'], $parent_extension_title);
170
+ } elseif (isset($lists['available'][$parent_extension_name])) {
171
+ $parent_extension_title = $lists['available'][$parent_extension_name]['name'];
172
+ }
173
+ ?>
174
+ <p class="fw-text-muted"><?php echo sprintf(__('Parent extension "%s" is disabled', 'fw'), $parent_extension_title); ?></p>
175
+ <?php else: ?>
176
+ <div class="fw-extension-disabled fw-border-box-sizing">
177
+ <div class="fw-extension-disabled-panel fw-border-box-sizing">
178
+ <div class="fw-row">
179
+ <div class="fw-col-xs-12 fw-col-sm-3">
180
+ <span class="fw-text-danger">!&nbsp;<?php _e('Disabled', 'fw'); ?></span>
181
+ </div>
182
+ <div class="fw-col-xs-12 fw-col-sm-9 fw-text-right">
183
+ <?php
184
+ $requirements = array();
185
+
186
+ foreach (fw_akg('requirements', $installed_data['manifest'], array()) as $req_name => $req_data) {
187
+ switch ($req_name) {
188
+ case 'wordpress':
189
+ if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
190
+ break;
191
+ }
192
+
193
+ global $wp_version;
194
+
195
+ if ( ! empty( $req_data['min_version'] ) ) {
196
+ if (!version_compare($req_data['min_version'], $wp_version, '<=')) {
197
+ if ($can_install) {
198
+ $requirements[] = sprintf(
199
+ __( 'You need to update WordPress to %s: %s', 'fw' ),
200
+ $req_data['min_version'],
201
+ fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ), __( 'Update WordPress', 'fw' ) )
202
+ );
203
+ } else {
204
+ $requirements[] = sprintf(
205
+ __( 'WordPress needs to be updated to %s', 'fw' ),
206
+ $req_data['min_version']
207
+ );
208
+ }
209
+ }
210
+ }
211
+
212
+ if ( ! empty( $req_data['max_version'] ) ) {
213
+ if (!version_compare($req_data['max_version'], $wp_version, '>=')) {
214
+ $requirements[] = sprintf(
215
+ __('Maximum supported WordPress version is %s', 'fw'),
216
+ $req_data['max_version']
217
+ );
218
+ }
219
+ }
220
+ break;
221
+ case 'framework':
222
+ if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
223
+ break;
224
+ }
225
+
226
+ if ( ! empty( $req_data['min_version'] ) ) {
227
+ if (!version_compare($req_data['min_version'], fw()->manifest->get_version(), '<=')) {
228
+ if ($can_install) {
229
+ $requirements[] = sprintf(
230
+ __( 'You need to update %s to %s: %s', 'fw' ),
231
+ fw()->manifest->get_name(),
232
+ $req_data['min_version'],
233
+ fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ),
234
+ sprintf( __( 'Update %s', 'fw' ), fw()->manifest->get_name() )
235
+ )
236
+ );
237
+ } else {
238
+ $requirements[] = sprintf(
239
+ __( '%s needs to be updated to %s', 'fw' ),
240
+ fw()->manifest->get_name(),
241
+ $req_data['min_version']
242
+ );
243
+ }
244
+ }
245
+ }
246
+
247
+ if ( ! empty( $req_data['max_version'] ) ) {
248
+ if (!version_compare($req_data['max_version'], fw()->manifest->get_version(), '>=')) {
249
+ $requirements[] = sprintf(
250
+ __( 'Maximum supported %s version is %s', 'fw' ),
251
+ fw()->manifest->get_name(),
252
+ $req_data['max_version']
253
+ );
254
+ }
255
+ }
256
+ break;
257
+ case 'extensions':
258
+ foreach ($req_data as $req_ext => $req_ext_data) {
259
+ if ($ext = fw()->extensions->get($req_ext)) {
260
+ if (empty($req_ext_data['min_version']) && empty($req_ext_data['max_version'])) {
261
+ continue;
262
+ }
263
+
264
+ if ( ! empty( $req_ext_data['min_version'] ) ) {
265
+ if (!version_compare($req_ext_data['min_version'], $ext->manifest->get_version(), '<=')) {
266
+ if ($can_install) {
267
+ $requirements[] = sprintf(
268
+ __('You need to update the %s extension to %s: %s', 'fw'),
269
+ $ext->manifest->get_name(),
270
+ $req_ext_data['min_version'],
271
+ fw_html_tag('a', array('href' => self_admin_url('update-core.php')),
272
+ sprintf(__('Update %s', 'fw'), $ext->manifest->get_name())
273
+ )
274
+ );
275
+ } else {
276
+ $requirements[] = sprintf(
277
+ __('The %s extension needs to be updated to %s', 'fw'),
278
+ $ext->manifest->get_name(),
279
+ $req_ext_data['min_version']
280
+ );
281
+ }
282
+ }
283
+ }
284
+
285
+ if ( ! empty( $req_ext_data['max_version'] ) ) {
286
+ if (!version_compare($req_ext_data['max_version'], $ext->manifest->get_version(), '>=')) {
287
+ $requirements[] = sprintf(
288
+ __( 'Maximum supported %s extension version is %s', 'fw' ),
289
+ $ext->manifest->get_name(),
290
+ $req_ext_data['max_version']
291
+ );
292
+ }
293
+ }
294
+ } else {
295
+ $ext_title = fw_id_to_title($req_ext);
296
+
297
+ if (isset($lists['installed'][$req_ext])) {
298
+ $ext_title = fw_akg('name', $lists['installed'][$req_ext]['manifest'], $ext_title);
299
+
300
+ ob_start(); ?>
301
+ <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($req_ext) ?>" method="post" style="display: inline;">
302
+ <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
303
+ <?php echo sprintf(__( 'The %s extension is disabled', 'fw' ), $ext_title); ?>:
304
+ <a href="#" onclick="jQuery(this).closest('form').submit(); return false;"><?php echo sprintf(__('Activate %s', 'fw'), $ext_title); ?></a>
305
+ </form>
306
+ <?php
307
+ $requirements[] = ob_get_clean();
308
+ } else {
309
+ if ($can_install && isset($lists['available'][$req_ext])) {
310
+ $ext_title = $lists['available'][ $req_ext ]['name'];
311
+
312
+ $requirements[] = sprintf(
313
+ __( 'The %s extension is not installed: %s', 'fw' ),
314
+ $ext_title,
315
+ fw_html_tag( 'a', array( 'href' => $link . '&sub-page=install&extension=' . $req_ext ),
316
+ sprintf( __( 'Install %s', 'fw' ), $ext_title )
317
+ )
318
+ );
319
+ } else {
320
+ $requirements[] = sprintf(
321
+ __( 'The %s extension is not installed', 'fw' ),
322
+ $ext_title
323
+ );
324
+ }
325
+ }
326
+ }
327
+ }
328
+ break;
329
+ default:
330
+ trigger_error('Invalid requirement: '. $req_name, E_USER_WARNING);
331
+ continue;
332
+ }
333
+ }
334
+ ?>
335
+ <a onclick="return false;" href="#" class="fw-extension-tip" title="<?php
336
+ echo fw_htmlspecialchars(
337
+ '<div class="fw-extension-tip-content">'.
338
+ '<ul class="fw-extension-requirements"><li>- '. implode('</li><li>- ', $requirements) .'</li></ul>'.
339
+ '</div>'
340
+ );
341
+ unset($requirements);
342
+ ?>"><?php _e('View Requirements', 'fw') ?></a>
343
+ &nbsp; <p class="fw-visible-xs-block"></p><?php
344
+ if ($can_install):
345
+ ?><a href="<?php echo esc_attr($link) ?>&sub-page=delete&extension=<?php echo esc_attr($name) ?>" class="button" ><?php _e('Remove', 'fw'); ?></a><?php
346
+ endif;
347
+ ?>
348
+ </div>
349
+ </div>
350
+ </div>
351
+ </div>
352
+ <?php endif; ?>
353
+ <?php endif; ?>
354
+ <?php elseif ($available_data): ?>
355
+ <!-- -->
356
+ <?php else: ?>
357
+ <!-- -->
358
+ <?php endif; ?>
359
+ </div>
360
+ </div>
framework/core/components/extensions/manager/views/extensions-page.php CHANGED
@@ -1,218 +1,218 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
- /**
3
- * @var array $lists
4
- * @var string $link
5
- * @var array $nonces
6
- * @var mixed $display_default_value
7
- * @var string $default_thumbnail
8
- * @var bool $can_install
9
- */
10
-
11
- $dir = dirname(__FILE__);
12
- $extension_view_path = $dir .'/extension.php';
13
-
14
- $displayed = array();
15
- ?>
16
-
17
- <h3><?php _e('Active Extensions', 'fw') ?></h3>
18
- <?php
19
- $display_active_extensions = array();
20
-
21
- foreach ($lists['active'] as $name => &$data) {
22
- if (true !== fw_akg('display', $data['manifest'], $display_default_value)) {
23
- continue;
24
- }
25
-
26
- $display_active_extensions[$name] = &$data;
27
- }
28
- unset($data);
29
- ?>
30
- <?php if (empty($display_active_extensions)): ?>
31
- <div class="fw-extensions-no-active">
32
- <div class="fw-text-center fw-extensions-title-icon"><span class="dashicons dashicons-screenoptions"></span></div>
33
- <p class="fw-text-center fw-text-muted"><em><?php _e('No extensions activated yet', 'fw'); ?><br/><?php _e('Check the available extensions below', 'fw'); ?></em></p>
34
- </div>
35
- <?php else: ?>
36
- <div class="fw-row fw-extensions-list">
37
- <?php
38
- foreach ($display_active_extensions as $name => &$data) {
39
- fw_render_view($extension_view_path, array(
40
- 'name' => $name,
41
- 'title' => fw_ext($name)->manifest->get_name(),
42
- 'description' => fw_ext($name)->manifest->get('description'),
43
- 'link' => $link,
44
- 'lists' => &$lists,
45
- 'nonces' => $nonces,
46
- 'default_thumbnail' => $default_thumbnail,
47
- 'can_install' => $can_install,
48
- ), false);
49
-
50
- $displayed[$name] = true;
51
- }
52
- unset($data);
53
- ?>
54
- </div>
55
- <?php endif; ?>
56
-
57
- <div id="fw-extensions-list-available">
58
- <hr class="fw-extensions-lists-separator"/>
59
- <h3><?php _e('Available Extensions', 'fw') ?></h3><!-- This "available" differs from technical "available" -->
60
- <div class="fw-row fw-extensions-list">
61
- <?php $something_displayed = false; ?>
62
- <?php
63
- {
64
- $theme_extensions = array();
65
-
66
- foreach ($lists['disabled'] as $name => &$data) {
67
- if (!$data['is']['theme']) {
68
- continue;
69
- }
70
-
71
- $theme_extensions[$name] = array(
72
- 'name' => fw_akg('name', $data['manifest'], fw_id_to_title($name)),
73
- 'description' => fw_akg('description', $data['manifest'], '')
74
- );
75
- }
76
- unset($data);
77
-
78
- foreach ($theme_extensions + $lists['supported'] as $name => $data) {
79
- if (isset($displayed[$name])) {
80
- continue;
81
- } elseif (isset($lists['installed'][$name])) {
82
- if (true !== fw_akg('display', $lists['installed'][$name]['manifest'], $display_default_value)) {
83
- continue;
84
- }
85
- } else {
86
- if (isset($lists['available'][$name])) {
87
- if (!$can_install) {
88
- continue;
89
- }
90
- } else {
91
- //trigger_error(sprintf(__('Supported extension "%s" is not available.', 'fw'), $name));
92
- continue;
93
- }
94
- }
95
-
96
- fw_render_view($extension_view_path, array(
97
- 'name' => $name,
98
- 'title' => $data['name'],
99
- 'description' => $data['description'],
100
- 'link' => $link,
101
- 'lists' => &$lists,
102
- 'nonces' => $nonces,
103
- 'default_thumbnail' => $default_thumbnail,
104
- 'can_install' => $can_install,
105
- ), false);
106
-
107
- $displayed[$name] = $something_displayed = true;
108
- }
109
-
110
- unset($theme_extensions);
111
- }
112
-
113
- foreach ($lists['disabled'] as $name => &$data) {
114
- if (isset($displayed[$name])) {
115
- continue;
116
- } elseif (true !== fw_akg('display', $data['manifest'], $display_default_value)) {
117
- continue;
118
- }
119
-
120
- fw_render_view($extension_view_path, array(
121
- 'name' => $name,
122
- 'title' => fw_akg('name', $data['manifest'], fw_id_to_title($name)),
123
- 'description' => fw_akg('description', $data['manifest'], ''),
124
- 'link' => $link,
125
- 'lists' => &$lists,
126
- 'nonces' => $nonces,
127
- 'default_thumbnail' => $default_thumbnail,
128
- 'can_install' => $can_install,
129
- ), false);
130
-
131
- $displayed[$name] = $something_displayed = true;
132
- }
133
- unset($data);
134
-
135
- if ($can_install) {
136
- foreach ( $lists['available'] as $name => &$data ) {
137
- if ( isset( $displayed[ $name ] ) ) {
138
- continue;
139
- } elseif ( isset( $lists['installed'][ $name ] ) ) {
140
- continue;
141
- } elseif ( $data['display'] !== true ) {
142
- continue;
143
- }
144
-
145
- /**
146
- * fixme: remove this in the future when this extensions will look good on any theme
147
- */
148
- if ( in_array( $name, array( 'styling', 'megamenu' ) ) ) {
149
- if ( isset( $lists['supported'][ $name ] ) || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
150
- } else {
151
- continue;
152
- }
153
- }
154
-
155
- fw_render_view( $extension_view_path, array(
156
- 'name' => $name,
157
- 'title' => $data['name'],
158
- 'description' => $data['description'],
159
- 'link' => $link,
160
- 'lists' => &$lists,
161
- 'nonces' => $nonces,
162
- 'default_thumbnail' => $default_thumbnail,
163
- 'can_install' => $can_install,
164
- ), false );
165
-
166
- $something_displayed = true;
167
- }
168
- unset($data);
169
- }
170
- ?>
171
- </div>
172
-
173
- <?php if ($something_displayed && apply_filters('fw_extensions_page_show_other_extensions', true)): ?>
174
- <!-- show/hide not compatible extensions -->
175
- <p class="fw-text-center toggle-not-compat-ext-btn-wrapper"><?php
176
- echo fw_html_tag(
177
- 'a',
178
- array(
179
- 'href' => '#',
180
- 'onclick' => 'return false;',
181
- 'class' => 'button toggle-not-compat-ext-btn',
182
- 'style' => 'box-shadow:none;'
183
- ),
184
- '<span class="the-show-text">'. __('Show other extensions', 'fw') .'</span>'.
185
- '<span class="the-hide-text fw-hidden">'. __('Hide other extensions', 'fw') .'</span>'
186
- );
187
- ?></p>
188
- <script type="text/javascript">
189
- jQuery(function($){
190
- if (
191
- !$('.fw-extensions-list .fw-extensions-list-item.not-compatible').length
192
- ||
193
- <?php echo empty($lists['supported']) ? 'true' : 'false' ?>
194
- ) {
195
- // disable the show/hide feature
196
- $('#fw-extensions-list-wrapper .toggle-not-compat-ext-btn-wrapper').addClass('fw-hidden');
197
- } else {
198
- $('#fw-extensions-list-wrapper .fw-extensions-list .fw-extensions-list-item.not-compatible').fadeOut('fast');
199
-
200
- $('#fw-extensions-list-wrapper .toggle-not-compat-ext-btn-wrapper').on('click', function(){
201
- $('#fw-extensions-list-wrapper .fw-extensions-list .fw-extensions-list-item.not-compatible')[
202
- $(this).find('.the-hide-text').hasClass('fw-hidden') ? 'fadeIn' : 'fadeOut'
203
- ]();
204
-
205
- $(this).find('.the-show-text, .the-hide-text').toggleClass('fw-hidden');
206
- });
207
- }
208
- });
209
- </script>
210
- <!-- end: show/hide not compatible extensions -->
211
- <?php else: ?>
212
- <script type="text/javascript">
213
- jQuery(function($){
214
- $('#fw-extensions-list-available').remove();
215
- });
216
- </script>
217
- <?php endif; ?>
218
- </div>
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+ /**
3
+ * @var array $lists
4
+ * @var string $link
5
+ * @var array $nonces
6
+ * @var mixed $display_default_value
7
+ * @var string $default_thumbnail
8
+ * @var bool $can_install
9
+ */
10
+
11
+ $dir = dirname(__FILE__);
12
+ $extension_view_path = $dir .'/extension.php';
13
+
14
+ $displayed = array();
15
+ ?>
16
+
17
+ <h3><?php _e('Active Extensions', 'fw') ?></h3>
18
+ <?php
19
+ $display_active_extensions = array();
20
+
21
+ foreach ($lists['active'] as $name => &$data) {
22
+ if (true !== fw_akg('display', $data['manifest'], $display_default_value)) {
23
+ continue;
24
+ }
25
+
26
+ $display_active_extensions[$name] = &$data;
27
+ }
28
+ unset($data);
29
+ ?>
30
+ <?php if (empty($display_active_extensions)): ?>
31
+ <div class="fw-extensions-no-active">
32
+ <div class="fw-text-center fw-extensions-title-icon"><span class="dashicons dashicons-screenoptions"></span></div>
33
+ <p class="fw-text-center fw-text-muted"><em><?php _e('No extensions activated yet', 'fw'); ?><br/><?php _e('Check the available extensions below', 'fw'); ?></em></p>
34
+ </div>
35
+ <?php else: ?>
36
+ <div class="fw-row fw-extensions-list">
37
+ <?php
38
+ foreach ($display_active_extensions as $name => &$data) {
39
+ fw_render_view($extension_view_path, array(
40
+ 'name' => $name,
41
+ 'title' => fw_ext($name)->manifest->get_name(),
42
+ 'description' => fw_ext($name)->manifest->get('description'),
43
+ 'link' => $link,
44
+ 'lists' => &$lists,
45
+ 'nonces' => $nonces,
46
+ 'default_thumbnail' => $default_thumbnail,
47
+ 'can_install' => $can_install,
48
+ ), false);
49
+
50
+ $displayed[$name] = true;
51
+ }
52
+ unset($data);
53
+ ?>
54
+ </div>
55
+ <?php endif; ?>
56
+
57
+ <div id="fw-extensions-list-available">
58
+ <hr class="fw-extensions-lists-separator"/>
59
+ <h3><?php _e('Available Extensions', 'fw') ?></h3><!-- This "available" differs from technical "available" -->
60
+ <div class="fw-row fw-extensions-list">
61
+ <?php $something_displayed = false; ?>
62
+ <?php
63
+ {
64
+ $theme_extensions = array();
65
+
66
+ foreach ($lists['disabled'] as $name => &$data) {
67
+ if (!$data['is']['theme']) {
68
+ continue;
69
+ }
70
+
71
+ $theme_extensions[$name] = array(
72
+ 'name' => fw_akg('name', $data['manifest'], fw_id_to_title($name)),
73
+ 'description' => fw_akg('description', $data['manifest'], '')
74
+ );
75
+ }
76
+ unset($data);
77
+
78
+ foreach ($theme_extensions + $lists['supported'] as $name => $data) {
79
+ if (isset($displayed[$name])) {
80
+ continue;
81
+ } elseif (isset($lists['installed'][$name])) {
82
+ if (true !== fw_akg('display', $lists['installed'][$name]['manifest'], $display_default_value)) {
83
+ continue;
84
+ }
85
+ } else {
86
+ if (isset($lists['available'][$name])) {
87
+ if (!$can_install) {
88
+ continue;
89
+ }
90
+ } else {
91
+ //trigger_error(sprintf(__('Supported extension "%s" is not available.', 'fw'), $name));
92
+ continue;
93
+ }
94
+ }
95
+
96
+ fw_render_view($extension_view_path, array(
97
+ 'name' => $name,
98
+ 'title' => $data['name'],
99
+ 'description' => $data['description'],
100
+ 'link' => $link,
101
+ 'lists' => &$lists,
102
+ 'nonces' => $nonces,
103
+ 'default_thumbnail' => $default_thumbnail,
104
+ 'can_install' => $can_install,
105
+ ), false);
106
+
107
+ $displayed[$name] = $something_displayed = true;
108
+ }
109
+
110
+ unset($theme_extensions);
111
+ }
112
+
113
+ foreach ($lists['disabled'] as $name => &$data) {
114
+ if (isset($displayed[$name])) {
115
+ continue;
116
+ } elseif (true !== fw_akg('display', $data['manifest'], $display_default_value)) {
117
+ continue;
118
+ }
119
+
120
+ fw_render_view($extension_view_path, array(
121
+ 'name' => $name,
122
+ 'title' => fw_akg('name', $data['manifest'], fw_id_to_title($name)),
123
+ 'description' => fw_akg('description', $data['manifest'], ''),
124
+ 'link' => $link,
125
+ 'lists' => &$lists,
126
+ 'nonces' => $nonces,
127
+ 'default_thumbnail' => $default_thumbnail,
128
+ 'can_install' => $can_install,
129
+ ), false);
130
+
131
+ $displayed[$name] = $something_displayed = true;
132
+ }
133
+ unset($data);
134
+
135
+ if ($can_install) {
136
+ foreach ( $lists['available'] as $name => &$data ) {
137
+ if ( isset( $displayed[ $name ] ) ) {
138
+ continue;
139
+ } elseif ( isset( $lists['installed'][ $name ] ) ) {
140
+ continue;
141
+ } elseif ( $data['display'] !== true ) {
142
+ continue;
143
+ }
144
+
145
+ /**
146
+ * fixme: remove this in the future when this extensions will look good on any theme
147
+ */
148
+ if ( in_array( $name, array( 'styling', 'megamenu' ) ) ) {
149
+ if ( isset( $lists['supported'][ $name ] ) || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
150
+ } else {
151
+ continue;
152
+ }
153
+ }
154
+
155
+ fw_render_view( $extension_view_path, array(
156
+ 'name' => $name,
157
+ 'title' => $data['name'],
158
+ 'description' => $data['description'],
159
+ 'link' => $link,
160
+ 'lists' => &$lists,
161
+ 'nonces' => $nonces,
162
+ 'default_thumbnail' => $default_thumbnail,
163
+ 'can_install' => $can_install,
164
+ ), false );
165
+
166
+ $something_displayed = true;
167
+ }
168
+ unset($data);
169
+ }
170
+ ?>
171
+ </div>
172
+
173
+ <?php if ($something_displayed && apply_filters('fw_extensions_page_show_other_extensions', true)): ?>
174
+ <!-- show/hide not compatible extensions -->
175
+ <p class="fw-text-center toggle-not-compat-ext-btn-wrapper"><?php
176
+ echo fw_html_tag(
177
+ 'a',
178
+ array(
179
+ 'href' => '#',
180
+ 'onclick' => 'return false;',
181
+ 'class' => 'button toggle-not-compat-ext-btn',
182
+ 'style' => 'box-shadow:none;'
183
+ ),
184
+ '<span class="the-show-text">'. __('Show other extensions', 'fw') .'</span>'.
185
+ '<span class="the-hide-text fw-hidden">'. __('Hide other extensions', 'fw') .'</span>'
186
+ );
187
+ ?></p>
188
+ <script type="text/javascript">
189
+ jQuery(function($){
190
+ if (
191
+ !$('.fw-extensions-list .fw-extensions-list-item.not-compatible').length
192
+ ||
193
+ <?php echo empty($lists['supported']) ? 'true' : 'false' ?>
194
+ ) {
195
+ // disable the show/hide feature
196
+ $('#fw-extensions-list-wrapper .toggle-not-compat-ext-btn-wrapper').addClass('fw-hidden');
197
+ } else {
198
+ $('#fw-extensions-list-wrapper .fw-extensions-list .fw-extensions-list-item.not-compatible').fadeOut('fast');
199
+
200
+ $('#fw-extensions-list-wrapper .toggle-not-compat-ext-btn-wrapper').on('click', function(){
201
+ $('#fw-extensions-list-wrapper .fw-extensions-list .fw-extensions-list-item.not-compatible')[
202
+ $(this).find('.the-hide-text').hasClass('fw-hidden') ? 'fadeIn' : 'fadeOut'
203
+ ]();
204
+
205
+ $(this).find('.the-show-text, .the-hide-text').toggleClass('fw-hidden');
206
+ });
207
+ }
208
+ });
209
+ </script>
210
+ <!-- end: show/hide not compatible extensions -->
211
+ <?php else: ?>
212
+ <script type="text/javascript">
213
+ jQuery(function($){
214
+ $('#fw-extensions-list-available').remove();
215
+ });
216
+ </script>
217
+ <?php endif; ?>
218
+ </div>
framework/core/components/extensions/manager/views/install-form.php CHANGED
@@ -1,51 +1,51 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
- /**
3
- * @var array $extension_titles
4
- * @var array $list_page_link
5
- * @var bool $supported
6
- */
7
-
8
- $count = count($extension_titles);
9
- ?>
10
-
11
- <?php if ($supported): ?>
12
- <p><?php echo _n(
13
- 'We\'ve detected that your current theme is compatible with the following extension and it is recommended that you install it to fully benefit from your theme.',
14
- 'We\'ve detected that your current theme is compatible with the following extensions and it is recommended that you install them to fully benefit from your theme.',
15
- $count,
16
- 'fw'
17
- ) ?></p>
18
- <?php else: ?>
19
- <p><?php echo _n(
20
- 'You are about to install the following extension:',
21
- 'You are about to install the following extensions:',
22
- $count,
23
- 'fw'
24
- ) ?></p>
25
- <?php endif; ?>
26
-
27
- <ul class="ul-disc">
28
- <?php foreach ($extension_titles as $extension_title): ?>
29
- <li><strong><?php echo $extension_title; ?></strong></li>
30
- <?php endforeach; ?>
31
- </ul>
32
-
33
- <p><?php
34
- echo _n(
35
- 'Are you sure you wish to install this extension?',
36
- 'Are you sure you wish to install these extensions?',
37
- $count,
38
- 'fw'
39
- )
40
- ?></p>
41
-
42
- <input type="submit" name="submit" id="submit" class="button" value="<?php
43
- echo esc_attr( _n(
44
- 'Yes, Install this extension',
45
- 'Yes, Install these extensions',
46
- $count,
47
- 'fw'
48
- ) )
49
- ?>">
50
-
51
- <a class="button" href="<?php echo esc_attr($list_page_link) ?>" ><?php _e('No, Return me to the extension list', 'fw') ?></a>
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+ /**
3
+ * @var array $extension_titles
4
+ * @var array $list_page_link
5
+ * @var bool $supported
6
+ */
7
+
8
+ $count = count($extension_titles);
9
+ ?>
10
+
11
+ <?php if ($supported): ?>
12
+ <p><?php echo _n(
13
+ 'We\'ve detected that your current theme is compatible with the following extension and it is recommended that you install it to fully benefit from your theme.',
14
+ 'We\'ve detected that your current theme is compatible with the following extensions and it is recommended that you install them to fully benefit from your theme.',
15
+ $count,
16
+ 'fw'
17
+ ) ?></p>
18
+ <?php else: ?>
19
+ <p><?php echo _n(
20
+ 'You are about to install the following extension:',
21
+ 'You are about to install the following extensions:',
22
+ $count,
23
+ 'fw'
24
+ ) ?></p>
25
+ <?php endif; ?>
26
+
27
+ <ul class="ul-disc">
28
+ <?php foreach ($extension_titles as $extension_title): ?>
29
+ <li><strong><?php echo $extension_title; ?></strong></li>
30
+ <?php endforeach; ?>
31
+ </ul>
32
+
33
+ <p><?php
34
+ echo _n(
35
+ 'Are you sure you wish to install this extension?',
36
+ 'Are you sure you wish to install these extensions?',
37
+ $count,
38
+ 'fw'
39
+ )
40
+ ?></p>
41
+
42
+ <input type="submit" name="submit" id="submit" class="button" value="<?php
43
+ echo esc_attr( _n(
44
+ 'Yes, Install this extension',
45
+ 'Yes, Install these extensions',
46
+ $count,
47
+ 'fw'
48
+ ) )
49
+ ?>">
50
+
51
+ <a class="button" href="<?php echo esc_attr($list_page_link) ?>" ><?php _e('No, Return me to the extension list', 'fw') ?></a>
framework/core/components/theme.php CHANGED
@@ -1,211 +1,211 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Theme Component
5
- * Works with framework customizations / theme directory
6
- */
7
- final class _FW_Component_Theme
8
- {
9
- private static $cache_key = 'fw_theme';
10
-
11
- /**
12
- * @var FW_Theme_Manifest
13
- */
14
- public $manifest;
15
-
16
- public function __construct()
17
- {
18
- {
19
- $manifest = array();
20
-
21
- @include fw_get_template_customizations_directory('/theme/manifest.php');
22
-
23
- $this->manifest = new FW_Theme_Manifest($manifest);
24
- }
25
- }
26
-
27
- /**
28
- * @internal
29
- */
30
- public function _init()
31
- {
32
- add_action('fw_extensions_init', array($this, '_action_fw_extensions_init'));
33
- }
34
-
35
- /**
36
- * @internal
37
- */
38
- public function _after_components_init()
39
- {
40
- }
41
-
42
- /**
43
- * Search relative path in: child theme -> parent "theme" directory and return full path
44
- * @param string $rel_path
45
- * @return false|string
46
- */
47
- public function locate_path($rel_path)
48
- {
49
- if (is_child_theme() && file_exists(fw_get_stylesheet_customizations_directory('/theme'. $rel_path))) {
50
- return fw_get_stylesheet_customizations_directory('/theme'. $rel_path);
51
- }
52
-
53
- if (file_exists(fw_get_template_customizations_directory('/theme'. $rel_path))) {
54
- return fw_get_template_customizations_directory('/theme'. $rel_path);
55
- }
56
-
57
- return false;
58
- }
59
-
60
- /**
61
- * Return array with options from specified name/path
62
- * @param string $name '{theme}/framework-customizations/theme/options/{$name}.php'
63
- * @param array $variables These will be available in options file (like variables for view)
64
- * @return array
65
- */
66
- public function get_options($name, array $variables = array())
67
- {
68
- $path = $this->locate_path('/options/'. $name .'.php');
69
-
70
- if (!$path) {
71
- return array();
72
- }
73
-
74
- $variables = fw_get_variables_from_file($path, array('options' => array()), $variables);
75
-
76
- return $variables['options'];
77
- }
78
-
79
- public function get_settings_options()
80
- {
81
- $cache_key = self::$cache_key .'/options/settings';
82
-
83
- try {
84
- return FW_Cache::get($cache_key);
85
- } catch (FW_Cache_Not_Found_Exception $e) {
86
- $options = apply_filters('fw_settings_options', $this->get_options('settings'));
87
-
88
- FW_Cache::set($cache_key, $options);
89
-
90
- return $options;
91
- }
92
- }
93
-
94
- public function get_customizer_options()
95
- {
96
- $cache_key = self::$cache_key .'/options/customizer';
97
-
98
- try {
99
- return FW_Cache::get($cache_key);
100
- } catch (FW_Cache_Not_Found_Exception $e) {
101
- $options = apply_filters('fw_customizer_options', $this->get_options('customizer'));
102
-
103
- FW_Cache::set($cache_key, $options);
104
-
105
- return $options;
106
- }
107
- }
108
-
109
- public function get_post_options($post_type)
110
- {
111
- $cache_key = self::$cache_key .'/options/posts/'. $post_type;
112
-
113
- try {
114
- return FW_Cache::get($cache_key);
115
- } catch (FW_Cache_Not_Found_Exception $e) {
116
- $options = apply_filters('fw_post_options', $this->get_options('posts/'. $post_type), $post_type);
117
-
118
- FW_Cache::set($cache_key, $options);
119
-
120
- return $options;
121
- }
122
- }
123
-
124
- public function get_taxonomy_options($taxonomy)
125
- {
126
- $cache_key = self::$cache_key .'/options/taxonomies/'. $taxonomy;
127
-
128
- try {
129
- return FW_Cache::get($cache_key);
130
- } catch (FW_Cache_Not_Found_Exception $e) {
131
- $options = apply_filters('fw_taxonomy_options',
132
- $this->get_options('taxonomies/'. $taxonomy),
133
- $taxonomy
134
- );
135
-
136
- FW_Cache::set($cache_key, $options);
137
-
138
- return $options;
139
- }
140
- }
141
-
142
- /**
143
- * Return config key value, or entire config array
144
- * Config array is merged from child configs
145
- * @param string|null $key Multi key format accepted: 'a/b/c'
146
- * @param mixed $default_value
147
- * @return mixed|null
148
- */
149
- final public function get_config($key = null, $default_value = null)
150
- {
151
- $cache_key = self::$cache_key .'/config';
152
-
153
- try {
154
- $config = FW_Cache::get($cache_key);
155
- } catch (FW_Cache_Not_Found_Exception $e) {
156
- // default values
157
- $config = array(
158
- /** Toggle Theme Settings form ajax submit */
159
- 'settings_form_ajax_submit' => true,
160
- /** Toggle Theme Settings side tabs */
161
- 'settings_form_side_tabs' => false,
162
- /** Toggle Tabs rendered all at once, or initialized only on open/display */
163
- 'lazy_tabs' => true,
164
- );
165
-
166
- if (file_exists(fw_get_template_customizations_directory('/theme/config.php'))) {
167
- $variables = fw_get_variables_from_file(fw_get_template_customizations_directory('/theme/config.php'), array('cfg' => null));
168
-
169
- if (!empty($variables['cfg'])) {
170
- $config = array_merge($config, $variables['cfg']);
171
- unset($variables);
172
- }
173
- }
174
-
175
- if (is_child_theme() && file_exists(fw_get_stylesheet_customizations_directory('/theme/config.php'))) {
176
- $variables = fw_get_variables_from_file(fw_get_stylesheet_customizations_directory('/theme/config.php'), array('cfg' => null));
177
-
178
- if (!empty($variables['cfg'])) {
179
- $config = array_merge($config, $variables['cfg']);
180
- unset($variables);
181
- }
182
- }
183
-
184
- unset($path);
185
-
186
- FW_Cache::set($cache_key, $config);
187
- }
188
-
189
- return $key === null ? $config : fw_akg($key, $config, $default_value);
190
- }
191
-
192
- /**
193
- * @internal
194
- */
195
- public function _action_fw_extensions_init()
196
- {
197
- if (
198
- is_admin()
199
- &&
200
- !fw()->theme->manifest->check_requirements()
201
- &&
202
- current_user_can('manage_options')
203
- ) {
204
- FW_Flash_Messages::add(
205
- 'fw_theme_requirements',
206
- __('Theme requirements not met:', 'fw') .' '. fw()->theme->manifest->get_not_met_requirement_text(),
207
- 'warning'
208
- );
209
- }
210
- }
211
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Theme Component
5
+ * Works with framework customizations / theme directory
6
+ */
7
+ final class _FW_Component_Theme
8
+ {
9
+ private static $cache_key = 'fw_theme';
10
+
11
+ /**
12
+ * @var FW_Theme_Manifest
13
+ */
14
+ public $manifest;
15
+
16
+ public function __construct()
17
+ {
18
+ {
19
+ $manifest = array();
20
+
21
+ @include fw_get_template_customizations_directory('/theme/manifest.php');
22
+
23
+ $this->manifest = new FW_Theme_Manifest($manifest);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @internal
29
+ */
30
+ public function _init()
31
+ {
32
+ add_action('fw_extensions_init', array($this, '_action_fw_extensions_init'));
33
+ }
34
+
35
+ /**
36
+ * @internal
37
+ */
38
+ public function _after_components_init()
39
+ {
40
+ }
41
+
42
+ /**
43
+ * Search relative path in: child theme -> parent "theme" directory and return full path
44
+ * @param string $rel_path
45
+ * @return false|string
46
+ */
47
+ public function locate_path($rel_path)
48
+ {
49
+ if (is_child_theme() && file_exists(fw_get_stylesheet_customizations_directory('/theme'. $rel_path))) {
50
+ return fw_get_stylesheet_customizations_directory('/theme'. $rel_path);
51
+ }
52
+
53
+ if (file_exists(fw_get_template_customizations_directory('/theme'. $rel_path))) {
54
+ return fw_get_template_customizations_directory('/theme'. $rel_path);
55
+ }
56
+
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * Return array with options from specified name/path
62
+ * @param string $name '{theme}/framework-customizations/theme/options/{$name}.php'
63
+ * @param array $variables These will be available in options file (like variables for view)
64
+ * @return array
65
+ */
66
+ public function get_options($name, array $variables = array())
67
+ {
68
+ $path = $this->locate_path('/options/'. $name .'.php');
69
+
70
+ if (!$path) {
71
+ return array();
72
+ }
73
+
74
+ $variables = fw_get_variables_from_file($path, array('options' => array()), $variables);
75
+
76
+ return $variables['options'];
77
+ }
78
+
79
+ public function get_settings_options()
80
+ {
81
+ $cache_key = self::$cache_key .'/options/settings';
82
+
83
+ try {
84
+ return FW_Cache::get($cache_key);
85
+ } catch (FW_Cache_Not_Found_Exception $e) {
86
+ $options = apply_filters('fw_settings_options', $this->get_options('settings'));
87
+
88
+ FW_Cache::set($cache_key, $options);
89
+
90
+ return $options;
91
+ }
92
+ }
93
+
94
+ public function get_customizer_options()
95
+ {
96
+ $cache_key = self::$cache_key .'/options/customizer';
97
+
98
+ try {
99
+ return FW_Cache::get($cache_key);
100
+ } catch (FW_Cache_Not_Found_Exception $e) {
101
+ $options = apply_filters('fw_customizer_options', $this->get_options('customizer'));
102
+
103
+ FW_Cache::set($cache_key, $options);
104
+
105
+ return $options;
106
+ }
107
+ }
108
+
109
+ public function get_post_options($post_type)
110
+ {
111
+ $cache_key = self::$cache_key .'/options/posts/'. $post_type;
112
+
113
+ try {
114
+ return FW_Cache::get($cache_key);
115
+ } catch (FW_Cache_Not_Found_Exception $e) {
116
+ $options = apply_filters('fw_post_options', $this->get_options('posts/'. $post_type), $post_type);
117
+
118
+ FW_Cache::set($cache_key, $options);
119
+
120
+ return $options;
121
+ }
122
+ }
123
+
124
+ public function get_taxonomy_options($taxonomy)
125
+ {
126
+ $cache_key = self::$cache_key .'/options/taxonomies/'. $taxonomy;
127
+
128
+ try {
129
+ return FW_Cache::get($cache_key);
130
+ } catch (FW_Cache_Not_Found_Exception $e) {
131
+ $options = apply_filters('fw_taxonomy_options',
132
+ $this->get_options('taxonomies/'. $taxonomy),
133
+ $taxonomy
134
+ );
135
+
136
+ FW_Cache::set($cache_key, $options);
137
+
138
+ return $options;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Return config key value, or entire config array
144
+ * Config array is merged from child configs
145
+ * @param string|null $key Multi key format accepted: 'a/b/c'
146
+ * @param mixed $default_value
147
+ * @return mixed|null
148
+ */
149
+ final public function get_config($key = null, $default_value = null)
150
+ {
151
+ $cache_key = self::$cache_key .'/config';
152
+
153
+ try {
154
+ $config = FW_Cache::get($cache_key);
155
+ } catch (FW_Cache_Not_Found_Exception $e) {
156
+ // default values
157
+ $config = array(
158
+ /** Toggle Theme Settings form ajax submit */
159
+ 'settings_form_ajax_submit' => true,
160
+ /** Toggle Theme Settings side tabs */
161
+ 'settings_form_side_tabs' => false,
162
+ /** Toggle Tabs rendered all at once, or initialized only on open/display */
163
+ 'lazy_tabs' => true,
164
+ );
165
+
166
+ if (file_exists(fw_get_template_customizations_directory('/theme/config.php'))) {
167
+ $variables = fw_get_variables_from_file(fw_get_template_customizations_directory('/theme/config.php'), array('cfg' => null));
168
+
169
+ if (!empty($variables['cfg'])) {
170
+ $config = array_merge($config, $variables['cfg']);
171
+ unset($variables);
172
+ }
173
+ }
174
+
175
+ if (is_child_theme() && file_exists(fw_get_stylesheet_customizations_directory('/theme/config.php'))) {
176
+ $variables = fw_get_variables_from_file(fw_get_stylesheet_customizations_directory('/theme/config.php'), array('cfg' => null));
177
+
178
+ if (!empty($variables['cfg'])) {
179
+ $config = array_merge($config, $variables['cfg']);
180
+ unset($variables);
181
+ }
182
+ }
183
+
184
+ unset($path);
185
+
186
+ FW_Cache::set($cache_key, $config);
187
+ }
188
+
189
+ return $key === null ? $config : fw_akg($key, $config, $default_value);
190
+ }
191
+
192
+ /**
193
+ * @internal
194
+ */
195
+ public function _action_fw_extensions_init()
196
+ {
197
+ if (
198
+ is_admin()
199
+ &&
200
+ !fw()->theme->manifest->check_requirements()
201
+ &&
202
+ current_user_can('manage_options')
203
+ ) {
204
+ FW_Flash_Messages::add(
205
+ 'fw_theme_requirements',
206
+ __('Theme requirements not met:', 'fw') .' '. fw()->theme->manifest->get_not_met_requirement_text(),
207
+ 'warning'
208
+ );
209
+ }
210
+ }
211
+ }
framework/core/extends/class-fw-container-type.php CHANGED
@@ -1,233 +1,233 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Backend option container
5
- */
6
- abstract class FW_Container_Type
7
- {
8
- /**
9
- * Container's unique type, used in option array in 'type' key
10
- * @return string
11
- */
12
- abstract public function get_type();
13
-
14
- /**
15
- * Overwrite this method to enqueue scripts and styles
16
- *
17
- * @param string $id
18
- * @param array $option
19
- * @param array $values Options values (in db format, returned from get_value_from_input())
20
- * @param array $data
21
- * @param bool Return true to call this method again on the next enqueue,
22
- * if you have some functionality in it that depends on option parameters.
23
- * By default this method is called only once for performance reasons.
24
- */
25
- abstract protected function _enqueue_static($id, $option, $values, $data);
26
-
27
- /**
28
- * Generate html
29
- * @param array $containers array('option_id' => array(), ...)
30
- * - Options arrays are merged with _get_defaults()
31
- * - All options are 100% only current container type, no need to check if ($option['type'] === $this->get_type())
32
- * - Are sent multiple options instead of one, because tabs (and maybe other feature containers)
33
- * can't be rendered separately (only as a collection).
34
- * Instead of having render_option() for those that can be rendered separately,
35
- * and render_options() for those like tabs, was decided to make a compromise,
36
- * only one method that always will receive an array of options,
37
- * instead of two methods when things may become confuse and complicated.
38
- * @param array $values Options values (in db format, returned from get_value_from_input())
39
- * @param array $data {id_prefix => '...', name_prefix => '...'}
40
- * @return string HTML
41
- * @internal
42
- */
43
- abstract protected function _render($containers, $values, $data);
44
-
45
- /**
46
- * Default option array
47
- *
48
- * This makes possible a container option array to have required only two parameters:
49
- * array('type' => '...', 'options' => array(...))
50
- * Other parameters are merged with the array returned by this method.
51
- *
52
- * @return array
53
- *
54
- * array(
55
- * 'type' => '...',
56
- * ...
57
- * 'options' => array(...),
58
- * )
59
- * @internal
60
- */
61
- abstract protected function _get_defaults();
62
-
63
- /**
64
- * Prevent execute enqueue multiple times
65
- * @var bool
66
- */
67
- private $static_enqueued = false;
68
-
69
- final public function __construct()
70
- {
71
- // does nothing at the moment, but maybe in the future will do something
72
- }
73
-
74
- /**
75
- * @param FW_Access_Key $access_key
76
- * @internal
77
- * This must be called right after an instance of container type has been created
78
- * and was added to the registered array
79
- */
80
- final public function _call_init($access_key)
81
- {
82
- if ($access_key->get_key() !== 'fw_backend') {
83
- trigger_error('Method call not allowed', E_USER_ERROR);
84
- }
85
-
86
- if (method_exists($this, '_init')) {
87
- $this->_init();
88
- }
89
- }
90
-
91
- /**
92
- * Fixes and prepare defaults
93
- *
94
- * @param string $id
95
- * @param array $option
96
- * @param array $data
97
- * @return array
98
- */
99
- private function prepare($id, &$option, &$data)
100
- {
101
- $data = array_merge(
102
- array(
103
- 'id_prefix' => FW_Option_Type::get_default_id_prefix(), // attribute id prefix
104
- 'name_prefix' => FW_Option_Type::get_default_name_prefix(), // attribute name prefix
105
- ),
106
- $data
107
- );
108
-
109
- $option = array_merge(
110
- $this->get_defaults(),
111
- $option,
112
- array(
113
- 'type' => $this->get_type(),
114
- )
115
- );
116
-
117
- if (!isset($option['attr'])) {
118
- $option['attr'] = array();
119
- }
120
-
121
- if (!isset($option['title'])) {
122
- $option['title'] = fw_id_to_title($id);
123
- }
124
-
125
- $option['attr']['class'] = 'fw-container fw-container-type-'. $option['type'] .(
126
- isset($option['attr']['class'])
127
- ? ' '. $option['attr']['class']
128
- : ''
129
- );
130
- }
131
-
132
- /**
133
- * Generate html
134
- * @param array $options array('container_id' => array(...container option...))
135
- * @param array $values Options values (in db format, returned from get_value_from_input())
136
- * @param array $data {'id_prefix' => '...', 'name_prefix' => '...'}
137
- * @return string HTML
138
- */
139
- final public function render($options, $values = array(), $data = array())
140
- {
141
- $containers = array();
142
-
143
- foreach ($options as $id => &$option) {
144
- if (
145
- !isset($option['options'])
146
- ||
147
- !isset($option['type'])
148
- ||
149
- $option['type'] !== $this->get_type()
150
- ) {
151
- continue;
152
- }
153
-
154
- $this->prepare($id, $option, $data);
155
-
156
- $this->enqueue_static($id, $option, $data);
157
-
158
- $containers[$id] = &$option;
159
- }
160
-
161
- return $this->_render($containers, $values, $data);
162
- }
163
-
164
- /**
165
- * Enqueue container type scripts and styles
166
- *
167
- * All parameters are optional and will be populated with defaults
168
- *
169
- * @param string $id
170
- * @param array $option
171
- * @param array $values Options values (in db format, returned from get_value_from_input())
172
- * @param array $data
173
- * @return bool
174
- */
175
- final public function enqueue_static($id = '', $option = array(), $values = array(), $data = array())
176
- {
177
- if (
178
- !doing_action('admin_enqueue_scripts')
179
- &&
180
- !did_action('admin_enqueue_scripts')
181
- ) {
182
- /**
183
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
184
- * and maybe they are used in dependencies in handles that are going to be enqueued.
185
- * So as a result some handles will not be equeued because of not registered dependecies.
186
- */
187
- return;
188
- }
189
-
190
- if ($this->static_enqueued) {
191
- return false;
192
- }
193
-
194
- $this->prepare($id, $option, $data);
195
-
196
- $call_next_time = $this->_enqueue_static($id, $option, $values, $data);
197
-
198
- $this->static_enqueued = !$call_next_time;
199
-
200
- return $call_next_time;
201
- }
202
-
203
- /**
204
- * Default option array
205
- *
206
- * @return array
207
- * 'type' => '...'
208
- * 'title' => '...'
209
- * 'attr' => array(...)
210
- */
211
- final public function get_defaults()
212
- {
213
- $option = $this->_get_defaults();
214
-
215
- $option['type'] = $this->get_type();
216
-
217
- return $option;
218
- }
219
-
220
- /**
221
- * Use this method to register a new container type
222
- * @param string|FW_Container_Type $container_type_class
223
- */
224
- final public static function register($container_type_class) {
225
- static $registration_access_key = null;
226
-
227
- if ($registration_access_key === null) {
228
- $registration_access_key = new FW_Access_Key('fw_container_type');
229
- }
230
-
231
- fw()->backend->_register_container_type($registration_access_key, $container_type_class);
232
- }
233
  }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Backend option container
5
+ */
6
+ abstract class FW_Container_Type
7
+ {
8
+ /**
9
+ * Container's unique type, used in option array in 'type' key
10
+ * @return string
11
+ */
12
+ abstract public function get_type();
13
+
14
+ /**
15
+ * Overwrite this method to enqueue scripts and styles
16
+ *
17
+ * @param string $id
18
+ * @param array $option
19
+ * @param array $values Options values (in db format, returned from get_value_from_input())
20
+ * @param array $data
21
+ * @param bool Return true to call this method again on the next enqueue,
22
+ * if you have some functionality in it that depends on option parameters.
23
+ * By default this method is called only once for performance reasons.
24
+ */
25
+ abstract protected function _enqueue_static($id, $option, $values, $data);
26
+
27
+ /**
28
+ * Generate html
29
+ * @param array $containers array('option_id' => array(), ...)
30
+ * - Options arrays are merged with _get_defaults()
31
+ * - All options are 100% only current container type, no need to check if ($option['type'] === $this->get_type())
32
+ * - Are sent multiple options instead of one, because tabs (and maybe other feature containers)
33
+ * can't be rendered separately (only as a collection).
34
+ * Instead of having render_option() for those that can be rendered separately,
35
+ * and render_options() for those like tabs, was decided to make a compromise,
36
+ * only one method that always will receive an array of options,
37
+ * instead of two methods when things may become confuse and complicated.
38
+ * @param array $values Options values (in db format, returned from get_value_from_input())
39
+ * @param array $data {id_prefix => '...', name_prefix => '...'}
40
+ * @return string HTML
41
+ * @internal
42
+ */
43
+ abstract protected function _render($containers, $values, $data);
44
+
45
+ /**
46
+ * Default option array
47
+ *
48
+ * This makes possible a container option array to have required only two parameters:
49
+ * array('type' => '...', 'options' => array(...))
50
+ * Other parameters are merged with the array returned by this method.
51
+ *
52
+ * @return array
53
+ *
54
+ * array(
55
+ * 'type' => '...',
56
+ * ...
57
+ * 'options' => array(...),
58
+ * )
59
+ * @internal
60
+ */
61
+ abstract protected function _get_defaults();
62
+
63
+ /**
64
+ * Prevent execute enqueue multiple times
65
+ * @var bool
66
+ */
67
+ private $static_enqueued = false;
68
+
69
+ final public function __construct()
70
+ {
71
+ // does nothing at the moment, but maybe in the future will do something
72
+ }
73
+
74
+ /**
75
+ * @param FW_Access_Key $access_key
76
+ * @internal
77
+ * This must be called right after an instance of container type has been created
78
+ * and was added to the registered array
79
+ */
80
+ final public function _call_init($access_key)
81
+ {
82
+ if ($access_key->get_key() !== 'fw_backend') {
83
+ trigger_error('Method call not allowed', E_USER_ERROR);
84
+ }
85
+
86
+ if (method_exists($this, '_init')) {
87
+ $this->_init();
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Fixes and prepare defaults
93
+ *
94
+ * @param string $id
95
+ * @param array $option
96
+ * @param array $data
97
+ * @return array
98
+ */
99
+ private function prepare($id, &$option, &$data)
100
+ {
101
+ $data = array_merge(
102
+ array(
103
+ 'id_prefix' => FW_Option_Type::get_default_id_prefix(), // attribute id prefix
104
+ 'name_prefix' => FW_Option_Type::get_default_name_prefix(), // attribute name prefix
105
+ ),
106
+ $data
107
+ );
108
+
109
+ $option = array_merge(
110
+ $this->get_defaults(),
111
+ $option,
112
+ array(
113
+ 'type' => $this->get_type(),
114
+ )
115
+ );
116
+
117
+ if (!isset($option['attr'])) {
118
+ $option['attr'] = array();
119
+ }
120
+
121
+ if (!isset($option['title'])) {
122
+ $option['title'] = fw_id_to_title($id);
123
+ }
124
+
125
+ $option['attr']['class'] = 'fw-container fw-container-type-'. $option['type'] .(
126
+ isset($option['attr']['class'])
127
+ ? ' '. $option['attr']['class']
128
+ : ''
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Generate html
134
+ * @param array $options array('container_id' => array(...container option...))
135
+ * @param array $values Options values (in db format, returned from get_value_from_input())
136
+ * @param array $data {'id_prefix' => '...', 'name_prefix' => '...'}
137
+ * @return string HTML
138
+ */
139
+ final public function render($options, $values = array(), $data = array())
140
+ {
141
+ $containers = array();
142
+
143
+ foreach ($options as $id => &$option) {
144
+ if (
145
+ !isset($option['options'])
146
+ ||
147
+ !isset($option['type'])
148
+ ||
149
+ $option['type'] !== $this->get_type()
150
+ ) {
151
+ continue;
152
+ }
153
+
154
+ $this->prepare($id, $option, $data);
155
+
156
+ $this->enqueue_static($id, $option, $data);
157
+
158
+ $containers[$id] = &$option;
159
+ }
160
+
161
+ return $this->_render($containers, $values, $data);
162
+ }
163
+
164
+ /**
165
+ * Enqueue container type scripts and styles
166
+ *
167
+ * All parameters are optional and will be populated with defaults
168
+ *
169
+ * @param string $id
170
+ * @param array $option
171
+ * @param array $values Options values (in db format, returned from get_value_from_input())
172
+ * @param array $data
173
+ * @return bool
174
+ */
175
+ final public function enqueue_static($id = '', $option = array(), $values = array(), $data = array())
176
+ {
177
+ if (
178
+ !doing_action('admin_enqueue_scripts')
179
+ &&
180
+ !did_action('admin_enqueue_scripts')
181
+ ) {
182
+ /**
183
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
184
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
185
+ * So as a result some handles will not be equeued because of not registered dependecies.
186
+ */
187
+ return;
188
+ }
189
+
190
+ if ($this->static_enqueued) {
191
+ return false;
192
+ }
193
+
194
+ $this->prepare($id, $option, $data);
195
+
196
+ $call_next_time = $this->_enqueue_static($id, $option, $values, $data);
197
+
198
+ $this->static_enqueued = !$call_next_time;
199
+
200
+ return $call_next_time;
201
+ }
202
+
203
+ /**
204
+ * Default option array
205
+ *
206
+ * @return array
207
+ * 'type' => '...'
208
+ * 'title' => '...'
209
+ * 'attr' => array(...)
210
+ */
211
+ final public function get_defaults()
212
+ {
213
+ $option = $this->_get_defaults();
214
+
215
+ $option['type'] = $this->get_type();
216
+
217
+ return $option;
218
+ }
219
+
220
+ /**
221
+ * Use this method to register a new container type
222
+ * @param string|FW_Container_Type $container_type_class
223
+ */
224
+ final public static function register($container_type_class) {
225
+ static $registration_access_key = null;
226
+
227
+ if ($registration_access_key === null) {
228
+ $registration_access_key = new FW_Access_Key('fw_container_type');
229
+ }
230
+
231
+ fw()->backend->_register_container_type($registration_access_key, $container_type_class);
232
+ }
233
  }
framework/core/extends/class-fw-extension.php CHANGED
@@ -1,507 +1,507 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * All framework extensions should extend this
5
- */
6
- abstract class FW_Extension
7
- {
8
- /**
9
- * Called after all extensions instances was created
10
- * @internal
11
- */
12
- abstract protected function _init();
13
-
14
- /** @var FW_Extension_Manifest */
15
- public $manifest;
16
-
17
- /** @var string Key used in FW_Cache to store data about extensions */
18
- private static $cache_key = 'fw_ext';
19
-
20
- /** @var FW_Access_Key */
21
- private static $access_key;
22
-
23
- /**
24
- * Extension name, equal to directory name
25
- * @var string
26
- */
27
- private $name;
28
-
29
- /**
30
- * Parent extension instance
31
- * @var FW_Extension|null
32
- */
33
- private $parent;
34
-
35
- /**
36
- * @var string
37
- */
38
- private $rel_path;
39
-
40
- /**
41
- * @var string
42
- */
43
- private $path;
44
-
45
- /**
46
- * @var string
47
- */
48
- private $uri;
49
-
50
- /**
51
- * On what directory depth is the extension
52
- *
53
- * 1 - Root extension
54
- * 2 - Their children
55
- * 3 - Sub children
56
- * ...
57
- *
58
- * @var int
59
- */
60
- private $depth;
61
-
62
- /**
63
- * Locations where the extension can look for customizations (overwrite views, options; extend config)
64
- * @var array {'/path' => 'https://uri.to/path'}
65
- */
66
- private $customizations_locations;
67
-
68
- final public function __construct($data)
69
- {
70
- if (!self::$access_key) {
71
- self::$access_key = new FW_Access_Key('extension');
72
- }
73
-
74
- $this->rel_path = $data['rel_path'];
75
- $this->path = $data['path'];
76
- $this->uri = $data['uri'];
77
- $this->parent = $data['parent'];
78
- $this->depth = $data['depth'];
79
- $this->customizations_locations = $data['customizations_locations'];
80
-
81
- {
82
- $variables = fw_get_variables_from_file($this->path .'/manifest.php', array('manifest' => array()));
83
- $manifest = $variables['manifest'];
84
- unset($variables);
85
-
86
- if (empty($manifest['name'])) {
87
- $manifest['name'] = fw_id_to_title($this->get_name());
88
- }
89
-
90
- $this->manifest = new FW_Extension_Manifest($manifest);
91
- }
92
- }
93
-
94
- /**
95
- * Cache key for this extension
96
- *
97
- * Usage:
98
- * FW_Cache::get( $this->get_cache_key('/some/key') )
99
- *
100
- * @param string $sub_key
101
- * @return string
102
- */
103
- final public function get_cache_key($sub_key = '')
104
- {
105
- return self::$cache_key .'/'. $this->get_name() . $sub_key;
106
- }
107
-
108
- /**
109
- * @param string $name View file name (without .php) from <extension>/views directory
110
- * @param array $view_variables Keys will be variables names within view
111
- * @param bool $return In some cases, for memory saving reasons, you can disable the use of output buffering
112
- * @return string HTML
113
- */
114
- final protected function render_view($name, $view_variables = array(), $return = true)
115
- {
116
- $full_path = $this->locate_path('/views/'. $name .'.php');
117
-
118
- if (!$full_path) {
119
- trigger_error('Extension view not found: '. $name, E_USER_WARNING);
120
- return;
121
- }
122
-
123
- return fw_render_view($full_path, $view_variables, $return);
124
- }
125
-
126
- /**
127
- * @internal
128
- * @param FW_Access_Key $access_key
129
- * @return mixed
130
- */
131
- final public function _call_init($access_key)
132
- {
133
- if ($access_key->get_key() !== 'fw_extensions') {
134
- trigger_error(__METHOD__ .' denied', E_USER_ERROR);
135
- }
136
-
137
- return $this->_init();
138
- }
139
-
140
- /**
141
- * Tree array with all sub extensions
142
- * @return array
143
- */
144
- final public function get_tree()
145
- {
146
- return fw()->extensions->_get_extension_tree(self::$access_key, $this->get_name());
147
- }
148
-
149
- /**
150
- * @param string $rel_path '/views/test.php'
151
- * @return false|string '/var/www/.../extensions/<extension>/views/test.php'
152
- */
153
- final public function locate_path($rel_path)
154
- {
155
- $locations = $this->customizations_locations;
156
- $locations[$this->get_path()] = $this->get_uri();
157
-
158
- foreach ($locations as $path => $uri) {
159
- if (file_exists($path . $rel_path)) {
160
- return $path . $rel_path;
161
- }
162
- }
163
-
164
- return false;
165
- }
166
-
167
- /**
168
- * @param string $rel_path E.g. '/static/js/scripts.js'
169
- * @return string URI E.g. 'http: //wordpress.com/.../extensions/<extension>/static/js/scripts.js'
170
- */
171
- final public function locate_URI($rel_path)
172
- {
173
- $locations = $this->customizations_locations;
174
- $locations[$this->get_path()] = $this->get_uri();
175
-
176
- foreach ($locations as $path => $uri) {
177
- if (file_exists($path . $rel_path)) {
178
- return $uri . $rel_path;
179
- }
180
- }
181
-
182
- return false;
183
- }
184
-
185
- /**
186
- * @return FW_Extension|null if has no parent extension
187
- */
188
- final public function get_parent()
189
- {
190
- return $this->parent;
191
- }
192
-
193
- /**
194
- * @return string
195
- */
196
- final public function get_name()
197
- {
198
- if ($this->name === null) {
199
- $this->name = basename($this->path);
200
- }
201
-
202
- return $this->name;
203
- }
204
-
205
- /**
206
- * @return string
207
- * @deprecated
208
- */
209
- final public function get_declared_source()
210
- {
211
- return 'deprecated';
212
- }
213
-
214
- /**
215
- * @param string $append_rel_path E.g. '/includes/something.php'
216
- * @return string
217
- * @deprecated
218
- */
219
- final public function get_declared_path($append_rel_path = '')
220
- {
221
- return $this->get_path($append_rel_path);
222
- }
223
-
224
- final public function get_path($append_rel_path = '')
225
- {
226
- return $this->path . $append_rel_path;
227
- }
228
-
229
- /**
230
- * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
231
- * @return string
232
- * @deprecated
233
- */
234
- final public function get_declared_URI($append_rel_path = '')
235
- {
236
- return $this->get_uri($append_rel_path);
237
- }
238
-
239
- /**
240
- * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
241
- * @return string
242
- */
243
- final public function get_uri($append_rel_path = '')
244
- {
245
- return $this->uri . $append_rel_path;
246
- }
247
-
248
- /**
249
- * @param string $child_extension_name
250
- * @return FW_Extension|null
251
- */
252
- final public function get_child($child_extension_name)
253
- {
254
- $active_tree = $this->get_tree();
255
-
256
- if (isset($active_tree[$child_extension_name])) {
257
- return fw()->extensions->get($child_extension_name);
258
- } else {
259
- return null;
260
- }
261
- }
262
-
263
- /**
264
- * Return all child extensions
265
- * Only one level, not all sub levels
266
- * @return FW_Extension[]
267
- */
268
- final public function get_children()
269
- {
270
- $active_tree = $this->get_tree();
271
-
272
- $result = array();
273
-
274
- foreach ($active_tree as $extension_name => &$sub_extensions) {
275
- $result[$extension_name] = fw()->extensions->get($extension_name);
276
- }
277
- unset($sub_extensions);
278
-
279
- return $result;
280
- }
281
-
282
- /**
283
- * Return config key value, or entire config array
284
- * Config array is merged from child configs
285
- * @param string|null $key Multi key format accepted: 'a/b/c'
286
- * @return mixed|null
287
- */
288
- final public function get_config($key = null)
289
- {
290
- $cache_key = $this->get_cache_key() .'/config';
291
-
292
- try {
293
- $config = FW_Cache::get($cache_key);
294
- } catch (FW_Cache_Not_Found_Exception $e) {
295
- $config = array();
296
-
297
- $locations = $this->customizations_locations;
298
- $locations[$this->get_path()] = $this->get_uri();
299
-
300
- foreach (array_reverse($locations) as $path => $uri) {
301
- $config_path = $path .'/config.php';
302
-
303
- if (file_exists($config_path)) {
304
- $variables = fw_get_variables_from_file($config_path, array('cfg' => null));
305
-
306
- if (!empty($variables['cfg'])) {
307
- $config = array_merge($config, $variables['cfg']);
308
- unset($variables);
309
- }
310
- }
311
- }
312
-
313
- FW_Cache::set($cache_key, $config);
314
- }
315
-
316
- return $key === null ? $config : fw_akg($key, $config);
317
- }
318
-
319
- /**
320
- * Return array with options from specified name/path
321
- * @param string $name Examples: 'framework', 'posts/portfolio'
322
- * @param array $variables These will be available in options file (like variables for view)
323
- * @return array
324
- */
325
- final public function get_options($name, array $variables = array())
326
- {
327
- $path = $this->locate_path('/options/'. $name .'.php');
328
-
329
- if (!$path) {
330
- return array();
331
- }
332
-
333
- $variables = fw_get_variables_from_file($path, array('options' => array()), $variables);
334
-
335
- return $variables['options'];
336
- }
337
-
338
- final public function get_settings_options()
339
- {
340
- $cache_key = $this->get_cache_key() .'/settings_options';
341
-
342
- try {
343
- return FW_Cache::get($cache_key);
344
- } catch (FW_Cache_Not_Found_Exception $e) {
345
- $path = $this->get_path('/settings-options.php');
346
-
347
- if (!file_exists($path)) {
348
- FW_Cache::set($cache_key, array());
349
- return array();
350
- }
351
-
352
- $variables = fw_get_variables_from_file($path, array('options' => array()));
353
-
354
- FW_Cache::set($cache_key, $variables['options']);
355
-
356
- return $variables['options'];
357
- }
358
- }
359
-
360
- /**
361
- * Get extension's settings option value from the database
362
- *
363
- * @param string|null $option_id
364
- * @param null|mixed $default_value If no option found in the database, this value will be returned
365
- * @param null|bool $get_original_value Original value is that with no translations and other changes
366
- *
367
- * @return mixed|null
368
- */
369
- final public function get_db_settings_option( $option_id = null, $default_value = null, $get_original_value = null ) {
370
- return fw_get_db_ext_settings_option( $this->get_name(), $option_id, $default_value, $get_original_value );
371
- }
372
-
373
- /**
374
- * Set extension's setting option value in database
375
- *
376
- * @param string|null $option_id
377
- * @param mixed $value
378
- */
379
- final public function set_db_settings_option( $option_id = null, $value ) {
380
- fw_set_db_ext_settings_option( $this->get_name(), $option_id, $value );
381
- }
382
-
383
- /**
384
- * Get extension's data from the database
385
- *
386
- * @param string|null $multi_key The key of the data you want to get. null - all data
387
- * @param null|mixed $default_value If no option found in the database, this value will be returned
388
- * @param null|bool $get_original_value Original value is that with no translations and other changes
389
- *
390
- * @return mixed|null
391
- */
392
- final public function get_db_data( $multi_key = null, $default_value = null, $get_original_value = null ) {
393
- return fw_get_db_extension_data( $this->get_name(), $multi_key, $default_value, $get_original_value );
394
- }
395
-
396
- /**
397
- * Set some extension's data in database
398
- *
399
- * @param string|null $multi_key The key of the data you want to set. null - all data
400
- * @param mixed $value
401
- */
402
- final public function set_db_data( $multi_key = null, $value ) {
403
- fw_set_db_extension_data( $this->get_name(), $multi_key, $value );
404
- }
405
-
406
- /**
407
- * Get extension's data from user meta
408
- *
409
- * @param int $user_id
410
- * @param string|null $keys
411
- *
412
- * @return mixed|null
413
- */
414
- final public function get_user_data( $user_id, $keys = null ) {
415
- return fw_get_db_extension_user_data($user_id, $this->get_name(), $keys);
416
- }
417
-
418
- /**
419
- * et some extension's data in user meta
420
- *
421
- * @param int $user_id
422
- * @param mixed $value
423
- * @param string|null $keys
424
- *
425
- * @return bool|int
426
- */
427
- final public function set_user_data( $user_id, $value, $keys = null ) {
428
- return fw_set_db_extension_user_data($user_id, $this->get_name(), $value, $keys);
429
- }
430
-
431
- final public function get_post_options($post_type)
432
- {
433
- return $this->get_options('posts/'. $post_type);
434
- }
435
-
436
- final public function get_taxonomy_options($taxonomy)
437
- {
438
- return $this->get_options('taxonomies/'. $taxonomy);
439
- }
440
-
441
- /**
442
- * @param string $name File name without extension, located in <extension>/static/js/$name.js
443
- * @return string URI
444
- */
445
- final public function locate_js_URI($name)
446
- {
447
- return $this->locate_URI('/static/js/'. $name .'.js');
448
- }
449
-
450
- /**
451
- * @param string $name File name without extension, located in <extension>/static/js/$name.js
452
- * @return string URI
453
- */
454
- final public function locate_css_URI($name)
455
- {
456
- return $this->locate_URI('/static/css/'. $name .'.css');
457
- }
458
-
459
- /**
460
- * @param string $name File name without extension, located in <extension>/views/$name.php
461
- * @return false|string
462
- */
463
- final public function locate_view_path($name)
464
- {
465
- return $this->locate_path('/views/'. $name .'.php');
466
- }
467
-
468
- final public function get_depth()
469
- {
470
- return $this->depth;
471
- }
472
-
473
- final public function get_customizations_locations()
474
- {
475
- return $this->customizations_locations;
476
- }
477
-
478
- final public function get_rel_path()
479
- {
480
- return $this->rel_path;
481
- }
482
-
483
- /**
484
- * Check if child extension is valid
485
- *
486
- * Used for special cases when an extension requires its child extensions to extend some special class
487
- *
488
- * @param FW_Extension $child_extension_instance
489
- * @return bool
490
- * @internal
491
- */
492
- public function _child_extension_is_valid($child_extension_instance)
493
- {
494
- return is_subclass_of($child_extension_instance, 'FW_Extension');
495
- }
496
-
497
- /**
498
- * Get link to the page created by this extension in dashboard
499
- * (Used on the extensions page)
500
- * @internal
501
- * @return string
502
- */
503
- public function _get_link()
504
- {
505
- return false;
506
- }
507
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * All framework extensions should extend this
5
+ */
6
+ abstract class FW_Extension
7
+ {
8
+ /**
9
+ * Called after all extensions instances was created
10
+ * @internal
11
+ */
12
+ abstract protected function _init();
13
+
14
+ /** @var FW_Extension_Manifest */
15
+ public $manifest;
16
+
17
+ /** @var string Key used in FW_Cache to store data about extensions */
18
+ private static $cache_key = 'fw_ext';
19
+
20
+ /** @var FW_Access_Key */
21
+ private static $access_key;
22
+
23
+ /**
24
+ * Extension name, equal to directory name
25
+ * @var string
26
+ */
27
+ private $name;
28
+
29
+ /**
30
+ * Parent extension instance
31
+ * @var FW_Extension|null
32
+ */
33
+ private $parent;
34
+
35
+ /**
36
+ * @var string
37
+ */
38
+ private $rel_path;
39
+
40
+ /**
41
+ * @var string
42
+ */
43
+ private $path;
44
+
45
+ /**
46
+ * @var string
47
+ */
48
+ private $uri;
49
+
50
+ /**
51
+ * On what directory depth is the extension
52
+ *
53
+ * 1 - Root extension
54
+ * 2 - Their children
55
+ * 3 - Sub children
56
+ * ...
57
+ *
58
+ * @var int
59
+ */
60
+ private $depth;
61
+
62
+ /**
63
+ * Locations where the extension can look for customizations (overwrite views, options; extend config)
64
+ * @var array {'/path' => 'https://uri.to/path'}
65
+ */
66
+ private $customizations_locations;
67
+
68
+ final public function __construct($data)
69
+ {
70
+ if (!self::$access_key) {
71
+ self::$access_key = new FW_Access_Key('extension');
72
+ }
73
+
74
+ $this->rel_path = $data['rel_path'];
75
+ $this->path = $data['path'];
76
+ $this->uri = $data['uri'];
77
+ $this->parent = $data['parent'];
78
+ $this->depth = $data['depth'];
79
+ $this->customizations_locations = $data['customizations_locations'];
80
+
81
+ {
82
+ $variables = fw_get_variables_from_file($this->path .'/manifest.php', array('manifest' => array()));
83
+ $manifest = $variables['manifest'];
84
+ unset($variables);
85
+
86
+ if (empty($manifest['name'])) {
87
+ $manifest['name'] = fw_id_to_title($this->get_name());
88
+ }
89
+
90
+ $this->manifest = new FW_Extension_Manifest($manifest);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Cache key for this extension
96
+ *
97
+ * Usage:
98
+ * FW_Cache::get( $this->get_cache_key('/some/key') )
99
+ *
100
+ * @param string $sub_key
101
+ * @return string
102
+ */
103
+ final public function get_cache_key($sub_key = '')
104
+ {
105
+ return self::$cache_key .'/'. $this->get_name() . $sub_key;
106
+ }
107
+
108
+ /**
109
+ * @param string $name View file name (without .php) from <extension>/views directory
110
+ * @param array $view_variables Keys will be variables names within view
111
+ * @param bool $return In some cases, for memory saving reasons, you can disable the use of output buffering
112
+ * @return string HTML
113
+ */
114
+ final protected function render_view($name, $view_variables = array(), $return = true)
115
+ {
116
+ $full_path = $this->locate_path('/views/'. $name .'.php');
117
+
118
+ if (!$full_path) {
119
+ trigger_error('Extension view not found: '. $name, E_USER_WARNING);
120
+ return;
121
+ }
122
+
123
+ return fw_render_view($full_path, $view_variables, $return);
124
+ }
125
+
126
+ /**
127
+ * @internal
128
+ * @param FW_Access_Key $access_key
129
+ * @return mixed
130
+ */
131
+ final public function _call_init($access_key)
132
+ {
133
+ if ($access_key->get_key() !== 'fw_extensions') {
134
+ trigger_error(__METHOD__ .' denied', E_USER_ERROR);
135
+ }
136
+
137
+ return $this->_init();
138
+ }
139
+
140
+ /**
141
+ * Tree array with all sub extensions
142
+ * @return array
143
+ */
144
+ final public function get_tree()
145
+ {
146
+ return fw()->extensions->_get_extension_tree(self::$access_key, $this->get_name());
147
+ }
148
+
149
+ /**
150
+ * @param string $rel_path '/views/test.php'
151
+ * @return false|string '/var/www/.../extensions/<extension>/views/test.php'
152
+ */
153
+ final public function locate_path($rel_path)
154
+ {
155
+ $locations = $this->customizations_locations;
156
+ $locations[$this->get_path()] = $this->get_uri();
157
+
158
+ foreach ($locations as $path => $uri) {
159
+ if (file_exists($path . $rel_path)) {
160
+ return $path . $rel_path;
161
+ }
162
+ }
163
+
164
+ return false;
165
+ }
166
+
167
+ /**
168
+ * @param string $rel_path E.g. '/static/js/scripts.js'
169
+ * @return string URI E.g. 'http: //wordpress.com/.../extensions/<extension>/static/js/scripts.js'
170
+ */
171
+ final public function locate_URI($rel_path)
172
+ {
173
+ $locations = $this->customizations_locations;
174
+ $locations[$this->get_path()] = $this->get_uri();
175
+
176
+ foreach ($locations as $path => $uri) {
177
+ if (file_exists($path . $rel_path)) {
178
+ return $uri . $rel_path;
179
+ }
180
+ }
181
+
182
+ return false;
183
+ }
184
+
185
+ /**
186
+ * @return FW_Extension|null if has no parent extension
187
+ */
188
+ final public function get_parent()
189
+ {
190
+ return $this->parent;
191
+ }
192
+
193
+ /**
194
+ * @return string
195
+ */
196
+ final public function get_name()
197
+ {
198
+ if ($this->name === null) {
199
+ $this->name = basename($this->path);
200
+ }
201
+
202
+ return $this->name;
203
+ }
204
+
205
+ /**
206
+ * @return string
207
+ * @deprecated
208
+ */
209
+ final public function get_declared_source()
210
+ {
211
+ return 'deprecated';
212
+ }
213
+
214
+ /**
215
+ * @param string $append_rel_path E.g. '/includes/something.php'
216
+ * @return string
217
+ * @deprecated
218
+ */
219
+ final public function get_declared_path($append_rel_path = '')
220
+ {
221
+ return $this->get_path($append_rel_path);
222
+ }
223
+
224
+ final public function get_path($append_rel_path = '')
225
+ {
226
+ return $this->path . $append_rel_path;
227
+ }
228
+
229
+ /**
230
+ * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
231
+ * @return string
232
+ * @deprecated
233
+ */
234
+ final public function get_declared_URI($append_rel_path = '')
235
+ {
236
+ return $this->get_uri($append_rel_path);
237
+ }
238
+
239
+ /**
240
+ * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
241
+ * @return string
242
+ */
243
+ final public function get_uri($append_rel_path = '')
244
+ {
245
+ return $this->uri . $append_rel_path;
246
+ }
247
+
248
+ /**
249
+ * @param string $child_extension_name
250
+ * @return FW_Extension|null
251
+ */
252
+ final public function get_child($child_extension_name)
253
+ {
254
+ $active_tree = $this->get_tree();
255
+
256
+ if (isset($active_tree[$child_extension_name])) {
257
+ return fw()->extensions->get($child_extension_name);
258
+ } else {
259
+ return null;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Return all child extensions
265
+ * Only one level, not all sub levels
266
+ * @return FW_Extension[]
267
+ */
268
+ final public function get_children()
269
+ {
270
+ $active_tree = $this->get_tree();
271
+
272
+ $result = array();
273
+
274
+ foreach ($active_tree as $extension_name => &$sub_extensions) {
275
+ $result[$extension_name] = fw()->extensions->get($extension_name);
276
+ }
277
+ unset($sub_extensions);
278
+
279
+ return $result;
280
+ }
281
+
282
+ /**
283
+ * Return config key value, or entire config array
284
+ * Config array is merged from child configs
285
+ * @param string|null $key Multi key format accepted: 'a/b/c'
286
+ * @return mixed|null
287
+ */
288
+ final public function get_config($key = null)
289
+ {
290
+ $cache_key = $this->get_cache_key() .'/config';
291
+
292
+ try {
293
+ $config = FW_Cache::get($cache_key);
294
+ } catch (FW_Cache_Not_Found_Exception $e) {
295
+ $config = array();
296
+
297
+ $locations = $this->customizations_locations;
298
+ $locations[$this->get_path()] = $this->get_uri();
299
+
300
+ foreach (array_reverse($locations) as $path => $uri) {
301
+ $config_path = $path .'/config.php';
302
+
303
+ if (file_exists($config_path)) {
304
+ $variables = fw_get_variables_from_file($config_path, array('cfg' => null));
305
+
306
+ if (!empty($variables['cfg'])) {
307
+ $config = array_merge($config, $variables['cfg']);
308
+ unset($variables);
309
+ }
310
+ }
311
+ }
312
+
313
+ FW_Cache::set($cache_key, $config);
314
+ }
315
+
316
+ return $key === null ? $config : fw_akg($key, $config);
317
+ }
318
+
319
+ /**
320
+ * Return array with options from specified name/path
321
+ * @param string $name Examples: 'framework', 'posts/portfolio'
322
+ * @param array $variables These will be available in options file (like variables for view)
323
+ * @return array
324
+ */
325
+ final public function get_options($name, array $variables = array())
326
+ {
327
+ $path = $this->locate_path('/options/'. $name .'.php');
328
+
329
+ if (!$path) {
330
+ return array();
331
+ }
332
+
333
+ $variables = fw_get_variables_from_file($path, array('options' => array()), $variables);
334
+
335
+ return $variables['options'];
336
+ }
337
+
338
+ final public function get_settings_options()
339
+ {
340
+ $cache_key = $this->get_cache_key() .'/settings_options';
341
+
342
+ try {
343
+ return FW_Cache::get($cache_key);
344
+ } catch (FW_Cache_Not_Found_Exception $e) {
345
+ $path = $this->get_path('/settings-options.php');
346
+
347
+ if (!file_exists($path)) {
348
+ FW_Cache::set($cache_key, array());
349
+ return array();
350
+ }
351
+
352
+ $variables = fw_get_variables_from_file($path, array('options' => array()));
353
+
354
+ FW_Cache::set($cache_key, $variables['options']);
355
+
356
+ return $variables['options'];
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Get extension's settings option value from the database
362
+ *
363
+ * @param string|null $option_id
364
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
365
+ * @param null|bool $get_original_value Original value is that with no translations and other changes
366
+ *
367
+ * @return mixed|null
368
+ */
369
+ final public function get_db_settings_option( $option_id = null, $default_value = null, $get_original_value = null ) {
370
+ return fw_get_db_ext_settings_option( $this->get_name(), $option_id, $default_value, $get_original_value );
371
+ }
372
+
373
+ /**
374
+ * Set extension's setting option value in database
375
+ *
376
+ * @param string|null $option_id
377
+ * @param mixed $value
378
+ */
379
+ final public function set_db_settings_option( $option_id = null, $value ) {
380
+ fw_set_db_ext_settings_option( $this->get_name(), $option_id, $value );
381
+ }
382
+
383
+ /**
384
+ * Get extension's data from the database
385
+ *
386
+ * @param string|null $multi_key The key of the data you want to get. null - all data
387
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
388
+ * @param null|bool $get_original_value Original value is that with no translations and other changes
389
+ *
390
+ * @return mixed|null
391
+ */
392
+ final public function get_db_data( $multi_key = null, $default_value = null, $get_original_value = null ) {
393
+ return fw_get_db_extension_data( $this->get_name(), $multi_key, $default_value, $get_original_value );
394
+ }
395
+
396
+ /**
397
+ * Set some extension's data in database
398
+ *
399
+ * @param string|null $multi_key The key of the data you want to set. null - all data
400
+ * @param mixed $value
401
+ */
402
+ final public function set_db_data( $multi_key = null, $value ) {
403
+ fw_set_db_extension_data( $this->get_name(), $multi_key, $value );
404
+ }
405
+
406
+ /**
407
+ * Get extension's data from user meta
408
+ *
409
+ * @param int $user_id
410
+ * @param string|null $keys
411
+ *
412
+ * @return mixed|null
413
+ */
414
+ final public function get_user_data( $user_id, $keys = null ) {
415
+ return fw_get_db_extension_user_data($user_id, $this->get_name(), $keys);
416
+ }
417
+
418
+ /**
419
+ * et some extension's data in user meta
420
+ *
421
+ * @param int $user_id
422
+ * @param mixed $value
423
+ * @param string|null $keys
424
+ *
425
+ * @return bool|int
426
+ */
427
+ final public function set_user_data( $user_id, $value, $keys = null ) {
428
+ return fw_set_db_extension_user_data($user_id, $this->get_name(), $value, $keys);
429
+ }
430
+
431
+ final public function get_post_options($post_type)
432
+ {
433
+ return $this->get_options('posts/'. $post_type);
434
+ }
435
+
436
+ final public function get_taxonomy_options($taxonomy)
437
+ {
438
+ return $this->get_options('taxonomies/'. $taxonomy);
439
+ }
440
+
441
+ /**
442
+ * @param string $name File name without extension, located in <extension>/static/js/$name.js
443
+ * @return string URI
444
+ */
445
+ final public function locate_js_URI($name)
446
+ {
447
+ return $this->locate_URI('/static/js/'. $name .'.js');
448
+ }
449
+
450
+ /**
451
+ * @param string $name File name without extension, located in <extension>/static/js/$name.js
452
+ * @return string URI
453
+ */
454
+ final public function locate_css_URI($name)
455
+ {
456
+ return $this->locate_URI('/static/css/'. $name .'.css');
457
+ }
458
+
459
+ /**
460
+ * @param string $name File name without extension, located in <extension>/views/$name.php
461
+ * @return false|string
462
+ */
463
+ final public function locate_view_path($name)
464
+ {
465
+ return $this->locate_path('/views/'. $name .'.php');
466
+ }
467
+
468
+ final public function get_depth()
469
+ {
470
+ return $this->depth;
471
+ }
472
+
473
+ final public function get_customizations_locations()
474
+ {
475
+ return $this->customizations_locations;
476
+ }
477
+
478
+ final public function get_rel_path()
479
+ {
480
+ return $this->rel_path;
481
+ }
482
+
483
+ /**
484
+ * Check if child extension is valid
485
+ *
486
+ * Used for special cases when an extension requires its child extensions to extend some special class
487
+ *
488
+ * @param FW_Extension $child_extension_instance
489
+ * @return bool
490
+ * @internal
491
+ */
492
+ public function _child_extension_is_valid($child_extension_instance)
493
+ {
494
+ return is_subclass_of($child_extension_instance, 'FW_Extension');
495
+ }
496
+
497
+ /**
498
+ * Get link to the page created by this extension in dashboard
499
+ * (Used on the extensions page)
500
+ * @internal
501
+ * @return string
502
+ */
503
+ public function _get_link()
504
+ {
505
+ return false;
506
+ }
507
+ }
framework/core/extends/class-fw-option-type.php CHANGED
@@ -1,386 +1,398 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Backend option
5
- */
6
- abstract class FW_Option_Type
7
- {
8
- /**
9
- * Option's unique type, used in option array in 'type' key
10
- * @return string
11
- */
12
- abstract public function get_type();
13
-
14
- /**
15
- * Overwrite this method to enqueue scripts and styles
16
- *
17
- * This method would be abstract but was added after the framework release,
18
- * and to prevent fatal errors from new option types created by users we can't make it abstract.
19
- *
20
- * @param string $id
21
- * @param array $option
22
- * @param array $data
23
- * @param bool Return true to call this method again on the next enqueue,
24
- * if you have some functionality in it that depends on option parameters.
25
- * By default this method is called only once for performance reasons.
26
- */
27
- protected function _enqueue_static($id, $option, $data) {}
28
-
29
- /**
30
- * Generate html
31
- * @param string $id
32
- * @param array $option Option array merged with _get_defaults()
33
- * @param array $data {value => _get_value_from_input(), id_prefix => ..., name_prefix => ...}
34
- * @return string HTML
35
- * @internal
36
- */
37
- abstract protected function _render($id, $option, $data);
38
-
39
- /**
40
- * Extract correct value for $option['value'] from input array
41
- * If input value is empty, will be returned $option['value']
42
- * @param array $option Option array merged with _get_defaults()
43
- * @param array|string|null $input_value
44
- * @return string|array|int|bool Correct value
45
- * @internal
46
- */
47
- abstract protected function _get_value_from_input($option, $input_value);
48
-
49
- /**
50
- * Default option array
51
- *
52
- * This makes possible an option array to have required only one parameter: array('type' => '...')
53
- * Other parameters are merged with the array returned by this method.
54
- *
55
- * @return array
56
- *
57
- * array(
58
- * 'value' => '',
59
- * ...
60
- * )
61
- * @internal
62
- */
63
- abstract protected function _get_defaults();
64
-
65
- /**
66
- * Prevent execute enqueue multiple times
67
- * @var bool
68
- */
69
- private $static_enqueued = false;
70
-
71
- /**
72
- * Used as prefix for attribute id="{prefix}{option-id}"
73
- * @return string
74
- */
75
- final public static function get_default_id_prefix()
76
- {
77
- return 'fw-option-';
78
- }
79
-
80
- /**
81
- * Used as default prefix for attribute name="prefix[name]"
82
- * Cannot contain [], it is used for $_POST[ self::get_default_name_prefix() ]
83
- * @return string
84
- */
85
- final public static function get_default_name_prefix()
86
- {
87
- return 'fw_options';
88
- }
89
-
90
- final public function __construct()
91
- {
92
- // does nothing at the moment, but maybe in the future will do something
93
- }
94
-
95
- /**
96
- * @param FW_Access_Key $access_key
97
- * @internal
98
- * This must be called right after an instance of option type has been created
99
- * and was added to the registered array, so it is available through
100
- * fw()->backend->option_type($this->get_type())
101
- */
102
- final public function _call_init($access_key)
103
- {
104
- if ($access_key->get_key() !== 'fw_backend') {
105
- trigger_error('Method call not allowed', E_USER_ERROR);
106
- }
107
-
108
- if (method_exists($this, '_init')) {
109
- $this->_init();
110
- }
111
- }
112
-
113
- /**
114
- * Fixes and prepare defaults
115
- *
116
- * @param string $id
117
- * @param array $option
118
- * @param array $data
119
- * @return array
120
- */
121
- private function prepare(&$id, &$option, &$data)
122
- {
123
- $data = array_merge(
124
- array(
125
- 'id_prefix' => self::get_default_id_prefix(), // attribute id prefix
126
- 'name_prefix' => self::get_default_name_prefix(), // attribute name prefix
127
- ),
128
- $data
129
- );
130
-
131
- $defaults = $this->get_defaults();
132
- $merge_attr = !empty($option['attr']) && !empty($defaults['attr']);
133
-
134
- $option = array_merge($defaults, $option, array(
135
- 'type' => $this->get_type()
136
- ));
137
-
138
- if ($merge_attr) {
139
- $option['attr'] = array_merge($defaults['attr'], $option['attr']);
140
- }
141
-
142
- if (!isset($data['value'])) {
143
- // if no input value, use default
144
- $data['value'] = $option['value'];
145
- }
146
-
147
- if (!isset($option['attr'])) {
148
- $option['attr'] = array();
149
- }
150
-
151
- $option['attr']['name'] = $data['name_prefix'] .'['. $id .']';
152
- $option['attr']['id'] = $data['id_prefix'] . $id;
153
- $option['attr']['class'] = 'fw-option fw-option-type-'. $option['type'] .(
154
- isset($option['attr']['class'])
155
- ? ' '. $option['attr']['class']
156
- : ''
157
- );
158
- $option['attr']['value'] = is_array($option['value']) ? '' : $option['value'];
159
-
160
- /**
161
- * Remove some blacklisted attributes
162
- * They should be added only by the render method
163
- */
164
- {
165
- unset($option['attr']['type']);
166
- unset($option['attr']['checked']);
167
- unset($option['attr']['selected']);
168
- }
169
- }
170
-
171
- /**
172
- * Generate option's html from option array
173
- * @param string $id
174
- * @param array $option
175
- * @param array $data {value => $this->get_value_from_input()}
176
- * @return string HTML
177
- */
178
- final public function render($id, $option, $data = array())
179
- {
180
- $this->prepare($id, $option, $data);
181
-
182
- $this->enqueue_static($id, $option, $data);
183
-
184
- return $this->_render($id, $option, $data);
185
- }
186
-
187
- /**
188
- * Enqueue option type scripts and styles
189
- *
190
- * All parameters are optional and will be populated with defaults
191
- * @param string $id
192
- * @param array $option
193
- * @param array $data
194
- * @return bool
195
- */
196
- final public function enqueue_static($id = '', $option = array(), $data = array())
197
- {
198
- if (
199
- !doing_action('admin_enqueue_scripts')
200
- &&
201
- !did_action('admin_enqueue_scripts')
202
- ) {
203
- /**
204
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
205
- * and maybe they are used in dependencies in handles that are going to be enqueued.
206
- * So as a result some handles will not be equeued because of not registered dependecies.
207
- */
208
- return;
209
- }
210
-
211
- {
212
- static $option_types_static_enqueued = false;
213
-
214
- if (!$option_types_static_enqueued) {
215
- wp_enqueue_style(
216
- 'fw-option-types',
217
- fw_get_framework_directory_uri('/static/css/option-types.css'),
218
- array('fw', 'qtip'),
219
- fw()->manifest->get_version()
220
- );
221
- wp_enqueue_script(
222
- 'fw-option-types',
223
- fw_get_framework_directory_uri('/static/js/option-types.js'),
224
- array('fw-events', 'qtip'),
225
- fw()->manifest->get_version(),
226
- true
227
- );
228
-
229
- $option_types_static_enqueued = true;
230
- }
231
- }
232
-
233
- if ($this->static_enqueued) {
234
- return false;
235
- }
236
-
237
- $this->prepare($id, $option, $data);
238
-
239
- $call_next_time = $this->_enqueue_static($id, $option, $data);
240
-
241
- $this->static_enqueued = !$call_next_time;
242
-
243
- return $call_next_time;
244
- }
245
-
246
- /**
247
- * Extract correct value for $option['value'] from input array
248
- * If input value is empty, will be returned $option['value']
249
- * @param array $option
250
- * @param mixed|null $input_value Option's value from $_POST or elsewhere. If is null, it means it does not exists
251
- * @return array|string
252
- */
253
- final public function get_value_from_input($option, $input_value)
254
- {
255
- $option = array_merge(
256
- $this->get_defaults(),
257
- $option,
258
- array(
259
- 'type' => $this->get_type()
260
- )
261
- );
262
-
263
- return $this->_get_value_from_input($option, $input_value);
264
- }
265
-
266
- /**
267
- * Default option array
268
- *
269
- * This makes possible an option array to have required only one parameter: array('type' => '...')
270
- * Other parameters are merged with array returned from this method
271
- *
272
- * @return array
273
- */
274
- final public function get_defaults()
275
- {
276
- $option = $this->_get_defaults();
277
-
278
- $option['type'] = $this->get_type();
279
-
280
- if (!array_key_exists('value', $option)) {
281
- FW_Flash_Messages::add(
282
- 'fw-option-type-no-default-value',
283
- sprintf(__('Option type %s has no default value', 'fw'), $this->get_type()),
284
- 'warning'
285
- );
286
-
287
- $option['value'] = array();
288
- }
289
-
290
- return $option;
291
- }
292
-
293
- /**
294
- * Exist 3 types of options widths:
295
- * - auto (float left real width of the option (minimal) )
296
- * - fixed (inputs, select, textarea, and others - they have same width)
297
- * - full (100% . eg. html option should expand to maximum width)
298
- * Options can override this method to return another value
299
- * @return bool
300
- * @internal
301
- */
302
- public function _get_backend_width_type()
303
- {
304
- return 'fixed';
305
- }
306
-
307
- /**
308
- * Use this method to register a new option type
309
- * @param string|FW_Option_Type $option_type_class
310
- */
311
- final public static function register($option_type_class) {
312
- static $registration_access_key = null;
313
-
314
- if ($registration_access_key === null) {
315
- $registration_access_key = new FW_Access_Key('fw_option_type');
316
- }
317
-
318
- fw()->backend->_register_option_type($registration_access_key, $option_type_class);
319
- }
320
-
321
- /**
322
- * If the option is composed of more options (added by user) which values are stored in database
323
- * the option must call fw_db_option_storage_load() for each sub-option
324
- * because some of them may have configured the save to be done in separate place (post meta, wp option, etc.)
325
- * @param string $id
326
- * @param array $option
327
- * @param mixed $value
328
- * @param array $params
329
- * @return mixed
330
- * @since 2.5.0
331
- */
332
- final public function storage_load($id, array $option, $value, array $params = array()) {
333
- if ($this->get_type() === $option['type']) {
334
- return $this->_storage_load($id, $option, $value, $params);
335
- } else {
336
- return $value;
337
- }
338
- }
339
-
340
- /**
341
- * @see storage_load
342
- * @param string $id
343
- * @param array $option
344
- * @param mixed $value
345
- * @param array $params
346
- * @return mixed
347
- * @since 2.5.0
348
- * @internal
349
- */
350
- protected function _storage_load($id, array $option, $value, array $params) {
351
- return fw_db_option_storage_load($id, $option, $value, $params);
352
- }
353
-
354
- /**
355
- * If the option is composed of more options (added by user) which values are stored in database
356
- * the option must call fw_db_option_storage_save() for each sub-option
357
- * because some of them may have configured the save to be done in separate place (post meta, wp option, etc.)
358
- * @param string $id
359
- * @param array $option
360
- * @param mixed $value
361
- * @param array $params
362
- * @return mixed
363
- * @since 2.5.0
364
- */
365
- final public function storage_save($id, array $option, $value, array $params = array()) {
366
- if ($this->get_type() === $option['type']) {
367
- return $this->_storage_save($id, $option, $value, $params);
368
- } else {
369
- return $value;
370
- }
371
- }
372
-
373
- /**
374
- * @see storage_save
375
- * @param string $id
376
- * @param array $option
377
- * @param mixed $value
378
- * @param array $params
379
- * @return mixed
380
- * @since 2.5.0
381
- * @internal
382
- */
383
- protected function _storage_save($id, array $option, $value, array $params) {
384
- return fw_db_option_storage_save($id, $option, $value, $params);
385
- }
 
 
 
 
 
 
 
 
 
 
 
 
386
  }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Backend option
5
+ */
6
+ abstract class FW_Option_Type
7
+ {
8
+ /**
9
+ * Option's unique type, used in option array in 'type' key
10
+ * @return string
11
+ */
12
+ abstract public function get_type();
13
+
14
+ /**
15
+ * Overwrite this method to enqueue scripts and styles
16
+ *
17
+ * This method would be abstract but was added after the framework release,
18
+ * and to prevent fatal errors from new option types created by users we can't make it abstract.
19
+ *
20
+ * @param string $id
21
+ * @param array $option
22
+ * @param array $data
23
+ * @param bool Return true to call this method again on the next enqueue,
24
+ * if you have some functionality in it that depends on option parameters.
25
+ * By default this method is called only once for performance reasons.
26
+ */
27
+ protected function _enqueue_static($id, $option, $data) {}
28
+
29
+ /**
30
+ * Generate html
31
+ * @param string $id
32
+ * @param array $option Option array merged with _get_defaults()
33
+ * @param array $data {value => _get_value_from_input(), id_prefix => ..., name_prefix => ...}
34
+ * @return string HTML
35
+ * @internal
36
+ */
37
+ abstract protected function _render($id, $option, $data);
38
+
39
+ /**
40
+ * Extract correct value for $option['value'] from input array
41
+ * If input value is empty, will be returned $option['value']
42
+ * @param array $option Option array merged with _get_defaults()
43
+ * @param array|string|null $input_value
44
+ * @return string|array|int|bool Correct value
45
+ * @internal
46
+ */
47
+ abstract protected function _get_value_from_input($option, $input_value);
48
+
49
+ /**
50
+ * Default option array
51
+ *
52
+ * This makes possible an option array to have required only one parameter: array('type' => '...')
53
+ * Other parameters are merged with the array returned by this method.
54
+ *
55
+ * @return array
56
+ *
57
+ * array(
58
+ * 'value' => '',
59
+ * ...
60
+ * )
61
+ * @internal
62
+ */
63
+ abstract protected function _get_defaults();
64
+
65
+ /**
66
+ * Prevent execute enqueue multiple times
67
+ * @var bool
68
+ */
69
+ private $static_enqueued = false;
70
+
71
+ /**
72
+ * Used as prefix for attribute id="{prefix}{option-id}"
73
+ * @return string
74
+ */
75
+ final public static function get_default_id_prefix()
76
+ {
77
+ return 'fw-option-';
78
+ }
79
+
80
+ /**
81
+ * Used as default prefix for attribute name="prefix[name]"
82
+ * Cannot contain [], it is used for $_POST[ self::get_default_name_prefix() ]
83
+ * @return string
84
+ */
85
+ final public static function get_default_name_prefix()
86
+ {
87
+ return 'fw_options';
88
+ }
89
+
90
+ final public function __construct()
91
+ {
92
+ // does nothing at the moment, but maybe in the future will do something
93
+ }
94
+
95
+ /**
96
+ * @param FW_Access_Key $access_key
97
+ * @internal
98
+ * This must be called right after an instance of option type has been created
99
+ * and was added to the registered array, so it is available through
100
+ * fw()->backend->option_type($this->get_type())
101
+ */
102
+ final public function _call_init($access_key)
103
+ {
104
+ if ($access_key->get_key() !== 'fw_backend') {
105
+ trigger_error('Method call not allowed', E_USER_ERROR);
106
+ }
107
+
108
+ if (method_exists($this, '_init')) {
109
+ $this->_init();
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Fixes and prepare defaults
115
+ *
116
+ * @param string $id
117
+ * @param array $option
118
+ * @param array $data
119
+ * @return array
120
+ */
121
+ private function prepare(&$id, &$option, &$data)
122
+ {
123
+ $data = array_merge(
124
+ array(
125
+ 'id_prefix' => self::get_default_id_prefix(), // attribute id prefix
126
+ 'name_prefix' => self::get_default_name_prefix(), // attribute name prefix
127
+ ),
128
+ $data
129
+ );
130
+
131
+ $defaults = $this->get_defaults();
132
+ $merge_attr = !empty($option['attr']) && !empty($defaults['attr']);
133
+
134
+ $option = array_merge($defaults, $option, array(
135
+ 'type' => $this->get_type()
136
+ ));
137
+
138
+ if ($merge_attr) {
139
+ $option['attr'] = array_merge($defaults['attr'], $option['attr']);
140
+ }
141
+
142
+ if (!isset($data['value'])) {
143
+ // if no input value, use default
144
+ $data['value'] = $option['value'];
145
+ }
146
+
147
+ if (!isset($option['attr'])) {
148
+ $option['attr'] = array();
149
+ }
150
+
151
+ $option['attr']['name'] = $data['name_prefix'] .'['. $id .']';
152
+ $option['attr']['id'] = $data['id_prefix'] . $id;
153
+ $option['attr']['class'] = 'fw-option fw-option-type-'. $option['type'] .(
154
+ isset($option['attr']['class'])
155
+ ? ' '. $option['attr']['class']
156
+ : ''
157
+ );
158
+ $option['attr']['value'] = is_array($option['value']) ? '' : $option['value'];
159
+
160
+ /**
161
+ * Remove some blacklisted attributes
162
+ * They should be added only by the render method
163
+ */
164
+ {
165
+ unset($option['attr']['type']);
166
+ unset($option['attr']['checked']);
167
+ unset($option['attr']['selected']);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Generate option's html from option array
173
+ * @param string $id
174
+ * @param array $option
175
+ * @param array $data {value => $this->get_value_from_input()}
176
+ * @return string HTML
177
+ */
178
+ final public function render($id, $option, $data = array())
179
+ {
180
+ $this->prepare($id, $option, $data);
181
+
182
+ $this->enqueue_static($id, $option, $data);
183
+
184
+ return $this->_render($id, $option, $data);
185
+ }
186
+
187
+ /**
188
+ * Enqueue option type scripts and styles
189
+ *
190
+ * All parameters are optional and will be populated with defaults
191
+ * @param string $id
192
+ * @param array $option
193
+ * @param array $data
194
+ * @return bool
195
+ */
196
+ final public function enqueue_static($id = '', $option = array(), $data = array())
197
+ {
198
+ if (
199
+ !doing_action('admin_enqueue_scripts')
200
+ &&
201
+ !did_action('admin_enqueue_scripts')
202
+ ) {
203
+ /**
204
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
205
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
206
+ * So as a result some handles will not be equeued because of not registered dependecies.
207
+ */
208
+ return;
209
+ }
210
+
211
+ {
212
+ static $option_types_static_enqueued = false;
213
+
214
+ if (!$option_types_static_enqueued) {
215
+ wp_enqueue_style(
216
+ 'fw-option-types',
217
+ fw_get_framework_directory_uri('/static/css/option-types.css'),
218
+ array('fw', 'qtip'),
219
+ fw()->manifest->get_version()
220
+ );
221
+ wp_enqueue_script(
222
+ 'fw-option-types',
223
+ fw_get_framework_directory_uri('/static/js/option-types.js'),
224
+ array('fw-events', 'qtip'),
225
+ fw()->manifest->get_version(),
226
+ true
227
+ );
228
+
229
+ $option_types_static_enqueued = true;
230
+ }
231
+ }
232
+
233
+ if ($this->static_enqueued) {
234
+ return false;
235
+ }
236
+
237
+ $this->prepare($id, $option, $data);
238
+
239
+ $call_next_time = $this->_enqueue_static($id, $option, $data);
240
+
241
+ $this->static_enqueued = !$call_next_time;
242
+
243
+ return $call_next_time;
244
+ }
245
+
246
+ /**
247
+ * Extract correct value for $option['value'] from input array
248
+ * If input value is empty, will be returned $option['value']
249
+ * @param array $option
250
+ * @param mixed|null $input_value Option's value from $_POST or elsewhere. If is null, it means it does not exists
251
+ * @return array|string
252
+ */
253
+ final public function get_value_from_input($option, $input_value)
254
+ {
255
+ $option = array_merge(
256
+ $this->get_defaults(),
257
+ $option,
258
+ array(
259
+ 'type' => $this->get_type()
260
+ )
261
+ );
262
+
263
+ return $this->_get_value_from_input($option, $input_value);
264
+ }
265
+
266
+ /**
267
+ * Default option array
268
+ *
269
+ * This makes possible an option array to have required only one parameter: array('type' => '...')
270
+ * Other parameters are merged with array returned from this method
271
+ *
272
+ * @return array
273
+ */
274
+ final public function get_defaults()
275
+ {
276
+ $option = $this->_get_defaults();
277
+
278
+ $option['type'] = $this->get_type();
279
+
280
+ if (!array_key_exists('value', $option)) {
281
+ FW_Flash_Messages::add(
282
+ 'fw-option-type-no-default-value',
283
+ sprintf(__('Option type %s has no default value', 'fw'), $this->get_type()),
284
+ 'warning'
285
+ );
286
+
287
+ $option['value'] = array();
288
+ }
289
+
290
+ return $option;
291
+ }
292
+
293
+ /**
294
+ * Exist 3 types of options widths:
295
+ * - auto (float left real width of the option (minimal) )
296
+ * - fixed (inputs, select, textarea, and others - they have same width)
297
+ * - full (100% . eg. html option should expand to maximum width)
298
+ * Options can override this method to return another value
299
+ * @return bool
300
+ * @internal
301
+ */
302
+ public function _get_backend_width_type()
303
+ {
304
+ return 'fixed';
305
+ }
306
+
307
+ /**
308
+ * Use this method to register a new option type
309
+ * @param string|FW_Option_Type $option_type_class
310
+ */
311
+ final public static function register($option_type_class) {
312
+ static $registration_access_key = null;
313
+
314
+ if ($registration_access_key === null) {
315
+ $registration_access_key = new FW_Access_Key('fw_option_type');
316
+ }
317
+
318
+ fw()->backend->_register_option_type($registration_access_key, $option_type_class);
319
+ }
320
+
321
+ /**
322
+ * If the option is composed of more options (added by user) which values are stored in database
323
+ * the option must call fw_db_option_storage_load() for each sub-option
324
+ * because some of them may have configured the save to be done in separate place (post meta, wp option, etc.)
325
+ * @param string $id
326
+ * @param array $option
327
+ * @param mixed $value
328
+ * @param array $params
329
+ * @return mixed
330
+ * @since 2.5.0
331
+ */
332
+ final public function storage_load($id, array $option, $value, array $params = array()) {
333
+ if (
334
+ $this->get_type() === $option['type']
335
+ &&
336
+ ($option = array_merge($this->get_defaults(), $option))
337
+ ) {
338
+ if (is_null($value)) {
339
+ $value = fw()->backend->option_type($option['type'])->get_value_from_input($option, $value);
340
+ }
341
+
342
+ return $this->_storage_load($id, $option, $value, $params);
343
+ } else {
344
+ return $value;
345
+ }
346
+ }
347
+
348
+ /**
349
+ * @see storage_load()
350
+ * @param string $id
351
+ * @param array $option
352
+ * @param mixed $value
353
+ * @param array $params
354
+ * @return mixed
355
+ * @since 2.5.0
356
+ * @internal
357
+ */
358
+ protected function _storage_load($id, array $option, $value, array $params) {
359
+ return fw_db_option_storage_load($id, $option, $value, $params);
360
+ }
361
+
362
+ /**
363
+ * If the option is composed of more options (added by user) which values are stored in database
364
+ * the option must call fw_db_option_storage_save() for each sub-option
365
+ * because some of them may have configured the save to be done in separate place (post meta, wp option, etc.)
366
+ * @param string $id
367
+ * @param array $option
368
+ * @param mixed $value
369
+ * @param array $params
370
+ * @return mixed
371
+ * @since 2.5.0
372
+ */
373
+ final public function storage_save($id, array $option, $value, array $params = array()) {
374
+ if (
375
+ $this->get_type() === $option['type']
376
+ &&
377
+ ($option = array_merge($this->get_defaults(), $option))
378
+ ) {
379
+ return $this->_storage_save($id, $option, $value, $params);
380
+ } else {
381
+ return $value;
382
+ }
383
+ }
384
+
385
+ /**
386
+ * @see storage_save()
387
+ * @param string $id
388
+ * @param array $option
389
+ * @param mixed $value
390
+ * @param array $params
391
+ * @return mixed
392
+ * @since 2.5.0
393
+ * @internal
394
+ */
395
+ protected function _storage_save($id, array $option, $value, array $params) {
396
+ return fw_db_option_storage_save($id, $option, $value, $params);
397
+ }
398
  }
framework/core/extends/interface-fw-option-handler.php CHANGED
@@ -1,12 +1,12 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * @deprecated since 2.5.0
5
- */
6
- interface FW_Option_Handler
7
- {
8
- function get_option_value($option_id, $option, $data = array());
9
-
10
- function save_option_value($option_id, $option, $value, $data = array());
11
- }
12
-
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * @deprecated since 2.5.0
5
+ */
6
+ interface FW_Option_Handler
7
+ {
8
+ function get_option_value($option_id, $option, $data = array());
9
+
10
+ function save_option_value($option_id, $option, $value, $data = array());
11
+ }
12
+
framework/extensions/blog/class-fw-extension-blog.php CHANGED
@@ -1,90 +1,90 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- class FW_Extension_Blog extends FW_Extension {
6
- private $post_type = 'post';
7
-
8
- /**
9
- * @internal
10
- */
11
- public function _init() {
12
- if ( is_admin() ) {
13
- add_action( 'admin_menu', array( $this, '_admin_action_rename_post_menu' ) );
14
- add_action( 'init', array( $this, '_admin_action_change_post_labels' ), 999 );
15
- } else {
16
- add_action( 'init', array( $this, '_theme_action_change_post_labels' ), 999 );
17
- }
18
- }
19
-
20
- /**
21
- * Changes the labels value od the posts type: post from Post to Blog Post
22
- * @internal
23
- */
24
- public function _theme_action_change_post_labels() {
25
- global $wp_post_types;
26
- $p = $this->post_type;
27
-
28
- // Someone has changed this post type, always check for that!
29
- if ( empty ( $wp_post_types[ $p ] )
30
- or ! is_object( $wp_post_types[ $p ] )
31
- or empty ( $wp_post_types[ $p ]->labels )
32
- ) {
33
- return;
34
- }
35
-
36
- $wp_post_types[ $p ]->labels->name = __( 'Blog', 'fw' );
37
- $wp_post_types[ $p ]->labels->singular_name = __( 'Blog', 'fw' );
38
- $wp_post_types[ $p ]->labels->add_new = __( 'Add blog post', 'fw' );
39
- $wp_post_types[ $p ]->labels->add_new_item = __( 'Add new blog post', 'fw' );
40
- $wp_post_types[ $p ]->labels->all_items = __( 'All blog posts', 'fw' );
41
- $wp_post_types[ $p ]->labels->edit_item = __( 'Edit blog post', 'fw' );
42
- $wp_post_types[ $p ]->labels->name_admin_bar = __( 'Blog Post', 'fw' );
43
- $wp_post_types[ $p ]->labels->menu_name = __( 'Blog Post', 'fw' );
44
- $wp_post_types[ $p ]->labels->new_item = __( 'New blog post', 'fw' );
45
- $wp_post_types[ $p ]->labels->not_found = __( 'No blog posts found', 'fw' );
46
- $wp_post_types[ $p ]->labels->not_found_in_trash = __( 'No blog posts found in trash', 'fw' );
47
- $wp_post_types[ $p ]->labels->search_items = __( 'Search blog posts', 'fw' );
48
- $wp_post_types[ $p ]->labels->view_item = __( 'View blog post', 'fw' );
49
- }
50
-
51
- /**
52
- * Changes the labels value od the posts type: post from Post to Blog Post
53
- * @internal
54
- */
55
- public function _admin_action_change_post_labels() {
56
- global $wp_post_types, $wp_taxonomies;
57
- $p = $this->post_type;
58
-
59
- // Someone has changed this post type, always check for that!
60
- if ( empty ( $wp_post_types[ $p ] )
61
- or ! is_object( $wp_post_types[ $p ] )
62
- or empty ( $wp_post_types[ $p ]->labels )
63
- ) {
64
- return;
65
- }
66
-
67
- $wp_post_types[ $p ]->labels->name = __( 'Blog Posts', 'fw' );
68
-
69
- if ( empty ( $wp_taxonomies['category'] )
70
- or ! is_object( $wp_taxonomies['category'] )
71
- or empty ( $wp_taxonomies['category']->labels )
72
- ) {
73
- return;
74
- }
75
-
76
- $wp_taxonomies['category']->labels->name = __( 'Blog Categories', 'fw' );
77
- }
78
-
79
- /**
80
- * Changes the name in admin menu from Post to Blog Post
81
- * @internal
82
- */
83
- public function _admin_action_rename_post_menu() {
84
- global $menu;
85
-
86
- if ( isset( $menu[5] ) ) {
87
- $menu[5][0] = __( 'Blog Posts', 'fw' );
88
- }
89
- }
90
  }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ class FW_Extension_Blog extends FW_Extension {
6
+ private $post_type = 'post';
7
+
8
+ /**
9
+ * @internal
10
+ */
11
+ public function _init() {
12
+ if ( is_admin() ) {
13
+ add_action( 'admin_menu', array( $this, '_admin_action_rename_post_menu' ) );
14
+ add_action( 'init', array( $this, '_admin_action_change_post_labels' ), 999 );
15
+ } else {
16
+ add_action( 'init', array( $this, '_theme_action_change_post_labels' ), 999 );
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Changes the labels value od the posts type: post from Post to Blog Post
22
+ * @internal
23
+ */
24
+ public function _theme_action_change_post_labels() {
25
+ global $wp_post_types;
26
+ $p = $this->post_type;
27
+
28
+ // Someone has changed this post type, always check for that!
29
+ if ( empty ( $wp_post_types[ $p ] )
30
+ or ! is_object( $wp_post_types[ $p ] )
31
+ or empty ( $wp_post_types[ $p ]->labels )
32
+ ) {
33
+ return;
34
+ }
35
+
36
+ $wp_post_types[ $p ]->labels->name = __( 'Blog', 'fw' );
37
+ $wp_post_types[ $p ]->labels->singular_name = __( 'Blog', 'fw' );
38
+ $wp_post_types[ $p ]->labels->add_new = __( 'Add blog post', 'fw' );
39
+ $wp_post_types[ $p ]->labels->add_new_item = __( 'Add new blog post', 'fw' );
40
+ $wp_post_types[ $p ]->labels->all_items = __( 'All blog posts', 'fw' );
41
+ $wp_post_types[ $p ]->labels->edit_item = __( 'Edit blog post', 'fw' );
42
+ $wp_post_types[ $p ]->labels->name_admin_bar = __( 'Blog Post', 'fw' );
43
+ $wp_post_types[ $p ]->labels->menu_name = __( 'Blog Post', 'fw' );
44
+ $wp_post_types[ $p ]->labels->new_item = __( 'New blog post', 'fw' );
45
+ $wp_post_types[ $p ]->labels->not_found = __( 'No blog posts found', 'fw' );
46
+ $wp_post_types[ $p ]->labels->not_found_in_trash = __( 'No blog posts found in trash', 'fw' );
47
+ $wp_post_types[ $p ]->labels->search_items = __( 'Search blog posts', 'fw' );
48
+ $wp_post_types[ $p ]->labels->view_item = __( 'View blog post', 'fw' );
49
+ }
50
+
51
+ /**
52
+ * Changes the labels value od the posts type: post from Post to Blog Post
53
+ * @internal
54
+ */
55
+ public function _admin_action_change_post_labels() {
56
+ global $wp_post_types, $wp_taxonomies;
57
+ $p = $this->post_type;
58
+
59
+ // Someone has changed this post type, always check for that!
60
+ if ( empty ( $wp_post_types[ $p ] )
61
+ or ! is_object( $wp_post_types[ $p ] )
62
+ or empty ( $wp_post_types[ $p ]->labels )
63
+ ) {
64
+ return;
65
+ }
66
+
67
+ $wp_post_types[ $p ]->labels->name = __( 'Blog Posts', 'fw' );
68
+
69
+ if ( empty ( $wp_taxonomies['category'] )
70
+ or ! is_object( $wp_taxonomies['category'] )
71
+ or empty ( $wp_taxonomies['category']->labels )
72
+ ) {
73
+ return;
74
+ }
75
+
76
+ $wp_taxonomies['category']->labels->name = __( 'Blog Categories', 'fw' );
77
+ }
78
+
79
+ /**
80
+ * Changes the name in admin menu from Post to Blog Post
81
+ * @internal
82
+ */
83
+ public function _admin_action_rename_post_menu() {
84
+ global $menu;
85
+
86
+ if ( isset( $menu[5] ) ) {
87
+ $menu[5][0] = __( 'Blog Posts', 'fw' );
88
+ }
89
+ }
90
  }
framework/extensions/blog/manifest.php CHANGED
@@ -1,13 +1,13 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- $manifest = array();
6
-
7
- $manifest['name'] = __( 'Blog Posts', 'fw' );
8
- $manifest['description'] = __( 'Blog Posts', 'fw' );
9
- $manifest['version'] = '1.0.1';
10
- $manifest['display'] = false;
11
- $manifest['standalone'] = true;
12
-
13
- $manifest['github_update'] = 'ThemeFuse/Unyson-Blog-Extension';
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ $manifest = array();
6
+
7
+ $manifest['name'] = __( 'Blog Posts', 'fw' );
8
+ $manifest['description'] = __( 'Blog Posts', 'fw' );
9
+ $manifest['version'] = '1.0.1';
10
+ $manifest['display'] = false;
11
+ $manifest['standalone'] = true;
12
+
13
+ $manifest['github_update'] = 'ThemeFuse/Unyson-Blog-Extension';
framework/extensions/update/class-fw-extension-update.php CHANGED
@@ -1,941 +1,982 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- require dirname(__FILE__) .'/includes/extends/class-fw-ext-update-service.php';
4
-
5
- class FW_Extension_Update extends FW_Extension
6
- {
7
- /**
8
- * {@inheritdoc}
9
- */
10
- public function _child_extension_is_valid($child_extension_instance)
11
- {
12
- return is_subclass_of($child_extension_instance, 'FW_Ext_Update_Service');
13
- }
14
-
15
- /**
16
- * File names to skip (do not delete or change) during the update process
17
- * @var array
18
- */
19
- private $skip_file_names = array('.git');
20
-
21
- /**
22
- * @internal
23
- */
24
- protected function _init()
25
- {
26
- {
27
- $has_access = (current_user_can('update_themes') || current_user_can('update_plugins'));
28
-
29
- if ($has_access) {
30
- if (is_multisite() && !is_network_admin()) {
31
- // only network admin can change files that affects the entire network
32
- $has_access = false;
33
- }
34
- }
35
-
36
- if (!$has_access) {
37
- return false; // prevent child extensions activation
38
- }
39
- }
40
-
41
- $this->add_actions();
42
- $this->add_filters();
43
- }
44
-
45
- private function add_actions()
46
- {
47
- add_action('core_upgrade_preamble', array($this, '_action_updates_page_footer'));
48
-
49
- add_action('update-core-custom_'. 'fw-update-framework', array($this, '_action_update_framework'));
50
- add_action('update-core-custom_'. 'fw-update-theme', array($this, '_action_update_theme'));
51
- add_action('update-core-custom_'. 'fw-update-extensions', array($this, '_action_update_extensions'));
52
- }
53
-
54
- private function add_filters()
55
- {
56
- add_filter('wp_get_update_data', array($this, '_filter_update_data'), 10, 2);
57
- }
58
-
59
- private function get_fixed_version($version)
60
- {
61
- // remove from the beginning everything that is not a number: 'v1.2.3' -> '1.2.3', 'ver1.0.0' -> '1.0.0'
62
- return preg_replace('/^[^0-9]+/i', '', $version);;
63
- }
64
-
65
- private function get_wp_fs_tmp_dir()
66
- {
67
- return FW_WP_Filesystem::real_path_to_filesystem_path(
68
- apply_filters('fw_tmp_dir', fw_fix_path(WP_CONTENT_DIR) .'/tmp')
69
- );
70
- }
71
-
72
- /**
73
- * @internal
74
- */
75
- public function _action_updates_page_footer()
76
- {
77
- echo $this->render_view('updates-list', array(
78
- 'updates' => $this->get_updates(!empty($_GET['force-check']))
79
- ));
80
- }
81
-
82
- /**
83
- * @internal
84
- */
85
- public function _filter_update_data($data, $titles)
86
- {
87
- $updates = $this->get_updates(!empty($_GET['force-check']));
88
-
89
- if ($updates['framework'] && !is_wp_error($updates['framework'])) {
90
- $data['counts']['total']++;
91
- }
92
-
93
- if ($updates['theme'] && !is_wp_error($updates['theme'])) {
94
- $data['counts']['total']++;
95
- }
96
-
97
- if (!empty($updates['extensions'])) {
98
- foreach ( $updates['extensions'] as $ext_name => $ext_update ) {
99
- if ( is_wp_error( $ext_update ) ) {
100
- continue;
101
- }
102
-
103
- $data['counts']['total'] ++;
104
-
105
- if ($this->get_config('extensions_as_one_update')) {
106
- // no matter how many extensions, display as one update
107
- break;
108
- }
109
- }
110
- }
111
-
112
- return $data;
113
- }
114
-
115
- private function get_updates($force_check = false)
116
- {
117
- $cache_key = 'fw_ext_update/updates';
118
-
119
- // use cache because this method may be called multiple times (to prevent useless requests to update servers)
120
-
121
- try {
122
- return FW_Cache::get($cache_key);
123
- } catch (FW_Cache_Not_Found_Exception $e) {
124
- $updates = array(
125
- 'framework' => $this->get_framework_update($force_check),
126
- 'theme' => $this->get_theme_update($force_check),
127
- 'extensions' => $this->get_extensions_with_updates($force_check)
128
- );
129
-
130
- FW_Cache::set($cache_key, $updates);
131
-
132
- return $updates;
133
- }
134
- }
135
-
136
- /**
137
- * Collect extensions that has new versions available
138
- * @param bool $force_check
139
- * @return array {ext_name => update_data}
140
- */
141
- private function get_extensions_with_updates($force_check = false)
142
- {
143
- $updates = array();
144
-
145
- $services = $this->get_children();
146
-
147
- foreach (fw()->extensions->get_all() as $ext_name => $extension) {
148
- /** @var FW_Extension $extension */
149
-
150
- /**
151
- * Ask each service if it knows how to update the extension
152
- */
153
- foreach ($services as $service_name => $service) {
154
- /** @var $service FW_Ext_Update_Service */
155
-
156
- $latest_version = $service->_get_extension_latest_version($extension, $force_check);
157
-
158
- if ($latest_version === false) {
159
- // It said that it doesn't know how to update it
160
- continue;
161
- }
162
-
163
- if (is_wp_error($latest_version)) {
164
- $updates[$ext_name] = $latest_version;
165
- break;
166
- }
167
-
168
- $fixed_latest_version = $this->get_fixed_version($latest_version);
169
-
170
- if (!version_compare($fixed_latest_version, $extension->manifest->get_version(), '>')) {
171
- // we already have latest version
172
- continue;
173
- }
174
-
175
- $updates[$ext_name] = array(
176
- 'service' => $service_name,
177
- 'latest_version' => $latest_version,
178
- 'fixed_latest_version' => $fixed_latest_version
179
- );
180
-
181
- break;
182
- }
183
- }
184
-
185
- return $updates;
186
- }
187
-
188
- /**
189
- * @param bool $force_check
190
- * @return array|false|WP_Error
191
- */
192
- private function get_framework_update($force_check = false)
193
- {
194
- /**
195
- * Ask each service if it knows how to update the framework
196
- */
197
- foreach ($this->get_children() as $service_name => $service) {
198
- /** @var $service FW_Ext_Update_Service */
199
-
200
- $latest_version = $service->_get_framework_latest_version($force_check);
201
-
202
- if ($latest_version === false) {
203
- // It said that it doesn't know how to update it
204
- continue;
205
- }
206
-
207
- if (is_wp_error($latest_version)) {
208
- return $latest_version;
209
- }
210
-
211
- $fixed_latest_version = $this->get_fixed_version($latest_version);
212
-
213
- if (!version_compare($fixed_latest_version, fw()->manifest->get_version(), '>')) {
214
- // we already have latest version
215
- continue;
216
- }
217
-
218
- return array(
219
- 'service' => $service_name,
220
- 'latest_version' => $latest_version,
221
- 'fixed_latest_version' => $fixed_latest_version
222
- );
223
- }
224
-
225
- return false;
226
- }
227
-
228
- /**
229
- * @param bool $force_check
230
- * @return array|false|WP_Error
231
- */
232
- private function get_theme_update($force_check = false)
233
- {
234
- /**
235
- * Ask each service if it knows how to update the theme
236
- */
237
- foreach ($this->get_children() as $service_name => $service) {
238
- /** @var $service FW_Ext_Update_Service */
239
-
240
- $latest_version = $service->_get_theme_latest_version($force_check);
241
-
242
- if ($latest_version === false) {
243
- // It said that it doesn't know how to update it
244
- continue;
245
- }
246
-
247
- if (is_wp_error($latest_version)) {
248
- return $latest_version;
249
- }
250
-
251
- $fixed_latest_version = $this->get_fixed_version($latest_version);
252
-
253
- if (!version_compare($fixed_latest_version, fw()->theme->manifest->get_version(), '>')) {
254
- // we already have latest version
255
- continue;
256
- }
257
-
258
- return array(
259
- 'service' => $service_name,
260
- 'latest_version' => $latest_version,
261
- 'fixed_latest_version' => $fixed_latest_version
262
- );
263
- }
264
-
265
- return false;
266
- }
267
-
268
- /**
269
- * Turn on/off the maintenance mode
270
- * @param bool $enable
271
- */
272
- private function maintenance_mode($enable = false)
273
- {
274
- /** @var WP_Filesystem_Base $wp_filesystem */
275
- global $wp_filesystem;
276
-
277
- if (!$wp_filesystem) {
278
- return;
279
- }
280
-
281
- $file_path = $wp_filesystem->abspath() . '.maintenance';
282
-
283
- if ($wp_filesystem->exists($file_path)) {
284
- if (!$wp_filesystem->delete($file_path)) {
285
- trigger_error(__('Cannot delete: ', 'fw') . $file_path, E_USER_WARNING);
286
- }
287
- }
288
-
289
- if ($enable) {
290
- // Create maintenance file to signal that we are upgrading
291
- if (!$wp_filesystem->put_contents($file_path, '<?php $upgrading = ' . time() . '; ?>', FS_CHMOD_FILE)) {
292
- trigger_error(__('Cannot create: ', 'fw') . $file_path, E_USER_WARNING);
293
- }
294
- }
295
- }
296
-
297
- /**
298
- * Download and install new version files
299
- *
300
- * global $wp_filesystem; must be initialized before calling this method
301
- *
302
- * @param array $data
303
- * @param bool $merge_extensions The extensions/ directory will not be replaced entirely,
304
- * only extensions that comes with the update will be replaced
305
- * @return null|WP_Error
306
- */
307
- private function update($data, $merge_extensions = false)
308
- {
309
- $required_data_keys = array(
310
- 'wp_fs_destination_dir' => true,
311
- 'download_callback' => true,
312
- 'download_callback_args' => true,
313
- 'skin' => true,
314
- 'title' => true,
315
- );
316
-
317
- if (count($required_data_keys) > count(array_intersect_key($required_data_keys, $data))) {
318
- trigger_error('Some required keys are not present', E_USER_ERROR);
319
- }
320
-
321
- // move manually every key to variable, so IDE will understand better them
322
- {
323
- /**
324
- * Replace all files in this directory with downloaded
325
- * @var string $wp_fs_destination_dir
326
- */
327
- $wp_fs_destination_dir = $data['wp_fs_destination_dir'];
328
-
329
- /**
330
- * Called to download new version files to $this->get_wp_fs_tmp_dir()
331
- * @var callable $download_callback
332
- */
333
- $download_callback = $data['download_callback'];
334
-
335
- /**
336
- * @var array
337
- */
338
- $download_callback_args = $data['download_callback_args'];
339
-
340
- /**
341
- * @var WP_Upgrader_Skin $skin
342
- */
343
- $skin = $data['skin'];
344
-
345
- /**
346
- * Used in text messages
347
- * @var string $title
348
- */
349
- $title = $data['title'];
350
-
351
- unset($data);
352
- }
353
-
354
- /**
355
- * @var string|WP_Error
356
- */
357
- $error = false;
358
-
359
- $tmp_download_dir = $this->get_wp_fs_tmp_dir();
360
-
361
- do {
362
- /** @var WP_Filesystem_Base $wp_filesystem */
363
- global $wp_filesystem;
364
-
365
- // create temporary directory
366
- {
367
- if ($wp_filesystem->exists($tmp_download_dir)) {
368
- // just in case it already exists, clear everything, it may contain old files
369
- if (!$wp_filesystem->rmdir($tmp_download_dir, true)) {
370
- $error = __('Cannot remove old temporary directory: ', 'fw') . $tmp_download_dir;
371
- break;
372
- }
373
- }
374
-
375
- if (!FW_WP_Filesystem::mkdir_recursive($tmp_download_dir)) {
376
- $error = __('Cannot create directory: ', 'fw') . $tmp_download_dir;
377
- break;
378
- }
379
- }
380
-
381
- $skin->feedback(sprintf(__('Downloading the %s...', 'fw'), $title));
382
- {
383
- $downloaded_dir = call_user_func_array($download_callback, $download_callback_args);
384
-
385
- if (!$downloaded_dir) {
386
- $error = sprintf(__('Cannot download the %s.', 'fw'), $title);
387
- break;
388
- } elseif (is_wp_error($downloaded_dir)) {
389
- $error = $downloaded_dir;
390
- break;
391
- }
392
- }
393
-
394
- $this->maintenance_mode(true);
395
-
396
- $skin->feedback(sprintf(__('Installing the %s...', 'fw'), $title));
397
- {
398
- // remove all files from destination directory
399
- {
400
- $dir_files = $wp_filesystem->dirlist($wp_fs_destination_dir, true);
401
- if ($dir_files === false) {
402
- $error =__('Cannot access directory: ', 'fw') . $wp_fs_destination_dir;
403
- break;
404
- }
405
-
406
- foreach ($dir_files as $file) {
407
- if (in_array($file['name'], $this->skip_file_names)) {
408
- continue;
409
- }
410
-
411
- if ($merge_extensions) {
412
- if ($file['name'] === 'extensions' && $file['type'] === 'd') {
413
- // do not remove extensions, will be merged later
414
- continue;
415
- }
416
- }
417
-
418
- $file_path = $wp_fs_destination_dir .'/'. $file['name'];
419
-
420
- if (!$wp_filesystem->delete($file_path, true, $file['type'])) {
421
- $error = __('Cannot remove: ', 'fw') . $file_path;
422
- break 2;
423
- }
424
- }
425
- }
426
-
427
- // move all files from the temporary directory to the destination directory
428
- {
429
- $dir_files = $wp_filesystem->dirlist($downloaded_dir, true);
430
- if ($dir_files === false) {
431
- $error = __('Cannot access directory: ', 'fw') . $downloaded_dir;
432
- break;
433
- }
434
-
435
- foreach ($dir_files as $file) {
436
- if (in_array($file['name'], $this->skip_file_names)) {
437
- continue;
438
- }
439
-
440
- $downloaded_file_path = $downloaded_dir .'/'. $file['name'];
441
- $destination_file_path = $wp_fs_destination_dir .'/'. $file['name'];
442
-
443
- if ($merge_extensions) {
444
- if ($file['name'] === 'extensions' && $file['type'] === 'd') {
445
- // merge extensions/ after all other files was moved
446
- $merge_extensions_data = array(
447
- 'source' => $downloaded_file_path,
448
- 'destination' => $destination_file_path,
449
- );
450
- continue;
451
- }
452
- }
453
-
454
- if (!$wp_filesystem->move($downloaded_file_path, $destination_file_path)) {
455
- $error = sprintf(
456
- __('Cannot move "%s" to "%s"', 'fw'),
457
- $downloaded_file_path, $destination_file_path
458
- );
459
- break 2;
460
- }
461
- }
462
-
463
- if ($merge_extensions) {
464
- if (!empty($merge_extensions_data)) {
465
- $merge_result = $this->merge_extensions(
466
- $merge_extensions_data['source'],
467
- $merge_extensions_data['destination']
468
- );
469
-
470
- if ($merge_result === false) {
471
- $error = sprintf(
472
- __('Cannot merge "%s" with "%s"', 'fw'),
473
- $downloaded_file_path, $destination_file_path
474
- );
475
- break;
476
- } elseif (is_wp_error($merge_result)) {
477
- $error = $merge_result;
478
- break;
479
- }
480
- }
481
- }
482
- }
483
- }
484
-
485
- $skin->feedback(sprintf(__('The %s has been successfully updated.', 'fw'), $title));
486
- } while(false);
487
-
488
- $this->maintenance_mode(false);
489
-
490
- if ($wp_filesystem->exists($tmp_download_dir)) {
491
- if ( ! $wp_filesystem->delete( $tmp_download_dir, true, 'd' ) ) {
492
- $error = sprintf( __( 'Cannot remove temporary directory "%s".', 'fw' ), $tmp_download_dir );
493
- }
494
- }
495
-
496
- if ($error) {
497
- if (!is_wp_error($error)) {
498
- $error = new WP_Error( 'fw_ext_update_failed', (string)$error );
499
- }
500
-
501
- return $error;
502
- }
503
- }
504
-
505
- /**
506
- * Merge two extensions/ directories
507
- * @param string $source_dir WP_Filesystem dir '/a/b/c/extensions'
508
- * @param string $destination_dir WP_Filesystem dir '/a/b/d/extensions'
509
- * @return bool|WP_Error
510
- */
511
- private function merge_extensions($source_dir, $destination_dir)
512
- {
513
- /** @var WP_Filesystem_Base $wp_filesystem */
514
- global $wp_filesystem;
515
-
516
- $wp_error_id = 'fw_ext_update_merge_extensions';
517
-
518
- if (!$wp_filesystem->exists($destination_dir)) {
519
- // do a simple move if destination does not exist
520
- if (!$wp_filesystem->move($source_dir, $destination_dir)) {
521
- return new WP_Error($wp_error_id,
522
- sprintf(__('Cannot move "%s" to "%s"', 'fw'), $source_dir, $destination_dir)
523
- );
524
- }
525
- return true;
526
- }
527
-
528
- $source_ext_dirs = $wp_filesystem->dirlist($source_dir, true);
529
- if ($source_ext_dirs === false) {
530
- return new WP_Error($wp_error_id,
531
- __('Cannot access directory: ', 'fw') . $source_dir
532
- );
533
- }
534
-
535
- foreach ($source_ext_dirs as $ext_dir) {
536
- if (in_array($ext_dir['name'], $this->skip_file_names)) {
537
- continue;
538
- }
539
-
540
- if ($ext_dir['type'] !== 'd') {
541
- // process only directories from the extensions/ directory
542
- continue;
543
- }
544
-
545
- $source_extension_dir = $source_dir .'/'. $ext_dir['name'];
546
- $destination_extension_dir = $destination_dir .'/'. $ext_dir['name'];
547
-
548
- {
549
- $source_ext_files = $wp_filesystem->dirlist($source_extension_dir, true);
550
- if ($source_ext_files === false) {
551
- return new WP_Error($wp_error_id,
552
- __('Cannot access directory: ', 'fw') . $source_extension_dir
553
- );
554
- }
555
-
556
- if (empty($source_ext_files)) {
557
- /**
558
- * Source extension directory is empty, do nothing.
559
- * This happens when the extension is a git submodule in repository
560
- * but in zip it comes as an empty directory.
561
- */
562
- continue;
563
- }
564
- }
565
-
566
- // prepare destination
567
- {
568
- // create if not exists
569
- if (!$wp_filesystem->exists($destination_extension_dir)) {
570
- if (!FW_WP_Filesystem::mkdir_recursive($destination_extension_dir)) {
571
- return new WP_Error($wp_error_id,
572
- __('Cannot create directory: ', 'fw') . $destination_extension_dir
573
- );
574
- }
575
- }
576
-
577
- // remove everything except the extensions/ dir
578
- {
579
- $dest_ext_files = $wp_filesystem->dirlist($destination_extension_dir, true);
580
- if ($dest_ext_files === false) {
581
- return new WP_Error($wp_error_id,
582
- __('Cannot access directory: ', 'fw') . $destination_extension_dir
583
- );
584
- }
585
-
586
- $destination_has_extensions_dir = false;
587
-
588
- foreach ($dest_ext_files as $dest_ext_file) {
589
- if (in_array($dest_ext_file['name'], $this->skip_file_names)) {
590
- continue;
591
- }
592
-
593
- if ($dest_ext_file['name'] === 'extensions' && $dest_ext_file['type'] === 'd') {
594
- $destination_has_extensions_dir = true;
595
- continue;
596
- }
597
-
598
- $dest_ext_file_path = $destination_extension_dir .'/'. $dest_ext_file['name'];
599
-
600
- if (!$wp_filesystem->delete($dest_ext_file_path, true, $dest_ext_file['type'])) {
601
- return new WP_Error($wp_error_id,
602
- __('Cannot delete: ', 'fw') . $dest_ext_file_path
603
- );
604
- }
605
- }
606
- }
607
- }
608
-
609
- // move files from source to destination extension directory
610
- {
611
- $source_has_extensions_dir = false;
612
-
613
- foreach ($source_ext_files as $source_ext_file) {
614
- if (in_array($source_ext_file['name'], $this->skip_file_names)) {
615
- continue;
616
- }
617
-
618
- if ($source_ext_file['name'] === 'extensions' && $source_ext_file['type'] === 'd') {
619
- $source_has_extensions_dir = true;
620
- continue;
621
- }
622
-
623
- $source_ext_file_path = $source_extension_dir .'/'. $source_ext_file['name'];
624
- $dest_ext_file_path = $destination_extension_dir .'/'. $source_ext_file['name'];
625
-
626
- if (!$wp_filesystem->move($source_ext_file_path, $dest_ext_file_path)) {
627
- return new WP_Error($wp_error_id,
628
- sprintf(__('Cannot move "%s" to "%s"', 'fw'),
629
- $source_ext_file_path, $dest_ext_file_path
630
- )
631
- );
632
- }
633
- }
634
- }
635
-
636
- if ($source_has_extensions_dir) {
637
- if ($destination_has_extensions_dir) {
638
- $merge_result = $this->merge_extensions(
639
- $source_extension_dir .'/extensions',
640
- $destination_extension_dir .'/extensions'
641
- );
642
-
643
- if ($merge_result !== true) {
644
- return $merge_result;
645
- }
646
- } else {
647
- if (!$wp_filesystem->move(
648
- $source_extension_dir .'/extensions',
649
- $destination_extension_dir .'/extensions'
650
- )) {
651
- return new WP_Error($wp_error_id,
652
- sprintf(__('Cannot move "%s" to "%s"', 'fw'),
653
- $source_extension_dir .'/extensions',
654
- $destination_extension_dir .'/extensions'
655
- )
656
- );
657
- }
658
- }
659
- }
660
- }
661
-
662
- return true;
663
- }
664
-
665
- /**
666
- * @internal
667
- */
668
- public function _action_update_framework()
669
- {
670
- $nonce_name = '_nonce_fw_ext_update_framework';
671
- if (!isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name])) {
672
- wp_die(__('Invalid nonce.', 'fw'));
673
- }
674
-
675
- {
676
- if (!class_exists('_FW_Ext_Update_Framework_Upgrader_Skin')) {
677
- fw_include_file_isolated(
678
- $this->get_declared_path('/includes/classes/class--fw-ext-update-framework-upgrader-skin.php')
679
- );
680
- }
681
-
682
- $skin = new _FW_Ext_Update_Framework_Upgrader_Skin(array(
683
- 'title' => __('Framework Update', 'fw'),
684
- ));
685
- }
686
-
687
- require_once ABSPATH .'wp-admin/admin-header.php';
688
-
689
- $skin->header();
690
-
691
- do {
692
- if (!FW_WP_Filesystem::request_access(fw_get_framework_directory(), fw_current_url(), array($nonce_name))) {
693
- break;
694
- }
695
-
696
- $update = $this->get_framework_update();
697
-
698
- if ($update === false) {
699
- $skin->error(__('Failed to get framework latest version.', 'fw'));
700
- break;
701
- } elseif (is_wp_error($update)) {
702
- $skin->error($update);
703
- break;
704
- }
705
-
706
- /** @var FW_Ext_Update_Service $service */
707
- $service = $this->get_child($update['service']);
708
-
709
- $update_result = $this->update(array(
710
- 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
711
- fw_get_framework_directory()
712
- ),
713
- 'download_callback' => array($service, '_download_framework'),
714
- 'download_callback_args' => array($update['latest_version'], $this->get_wp_fs_tmp_dir()),
715
- 'skin' => $skin,
716
- 'title' => __('Framework', 'fw'),
717
- ));
718
-
719
- if (is_wp_error($update_result)) {
720
- $skin->error($update_result);
721
- break;
722
- }
723
-
724
- $skin->set_result(true);
725
- $skin->after();
726
- } while(false);
727
-
728
- $skin->footer();
729
-
730
- require_once(ABSPATH . 'wp-admin/admin-footer.php');
731
- }
732
-
733
- /**
734
- * @internal
735
- */
736
- public function _action_update_theme()
737
- {
738
- $nonce_name = '_nonce_fw_ext_update_theme';
739
- if (!isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name])) {
740
- wp_die(__('Invalid nonce.', 'fw'));
741
- }
742
-
743
- {
744
- if (!class_exists('_FW_Ext_Update_Theme_Upgrader_Skin')) {
745
- fw_include_file_isolated(
746
- $this->get_declared_path('/includes/classes/class--fw-ext-update-theme-upgrader-skin.php')
747
- );
748
- }
749
-
750
- $skin = new _FW_Ext_Update_Theme_Upgrader_Skin(array(
751
- 'title' => __('Theme Update', 'fw'),
752
- ));
753
- }
754
-
755
- require_once(ABSPATH . 'wp-admin/admin-header.php');
756
-
757
- $skin->header();
758
-
759
- do {
760
- if (!FW_WP_Filesystem::request_access(get_template_directory(), fw_current_url(), array($nonce_name))) {
761
- break;
762
- }
763
-
764
- $update = $this->get_theme_update();
765
-
766
- if ($update === false) {
767
- $skin->error(__('Failed to get theme latest version.', 'fw'));
768
- break;
769
- } elseif (is_wp_error($update)) {
770
- $skin->error($update);
771
- break;
772
- }
773
-
774
- /** @var FW_Ext_Update_Service $service */
775
- $service = $this->get_child($update['service']);
776
-
777
- $update_result = $this->update(array(
778
- 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
779
- get_template_directory()
780
- ),
781
- 'download_callback' => array($service, '_download_theme'),
782
- 'download_callback_args' => array($update['latest_version'], $this->get_wp_fs_tmp_dir()),
783
- 'skin' => $skin,
784
- 'title' => __('Theme', 'fw'),
785
- ));
786
-
787
- if (is_wp_error($update_result)) {
788
- $skin->error($update_result);
789
- break;
790
- }
791
-
792
- $skin->set_result(true);
793
- $skin->after();
794
- } while(false);
795
-
796
- $skin->footer();
797
-
798
- require_once(ABSPATH . 'wp-admin/admin-footer.php');
799
- }
800
-
801
- /**
802
- * @internal
803
- */
804
- public function _action_update_extensions()
805
- {
806
- $nonce_name = '_nonce_fw_ext_update_extensions';
807
- if (!isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name])) {
808
- wp_die(__('Invalid nonce.', 'fw'));
809
- }
810
-
811
- $form_input_name = 'extensions';
812
- $extensions_list = FW_Request::POST($form_input_name);
813
-
814
- if (empty($extensions_list)) {
815
- FW_Flash_Messages::add(
816
- 'fw_ext_update',
817
- __('Please check the extensions you want to update.', 'fw'),
818
- 'warning'
819
- );
820
- wp_redirect(self_admin_url('update-core.php'));
821
- exit;
822
- }
823
-
824
- // handle changes by the hack below
825
- {
826
- if (is_string($extensions_list)) {
827
- $extensions_list = json_decode($extensions_list);
828
- } else {
829
- $extensions_list = array_keys($extensions_list);
830
- }
831
- }
832
-
833
- {
834
- if (!class_exists('_FW_Ext_Update_Extensions_Upgrader_Skin')) {
835
- fw_include_file_isolated(
836
- $this->get_declared_path('/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php')
837
- );
838
- }
839
-
840
- $skin = new _FW_Ext_Update_Extensions_Upgrader_Skin(array(
841
- 'title' => __('Extensions Update', 'fw'),
842
- ));
843
- }
844
-
845
- require_once(ABSPATH . 'wp-admin/admin-header.php');
846
-
847
- $skin->header();
848
-
849
- do {
850
- /**
851
- * Hack for the ftp credentials template that does not support array post values
852
- * https://github.com/WordPress/WordPress/blob/3949a8b6cc50a021ed93798287b4ef9ea8a560d9/wp-admin/includes/file.php#L1144
853
- */
854
- {
855
- $original_post_value = $_POST[$form_input_name];
856
- $_POST[$form_input_name] = wp_slash(json_encode($extensions_list));
857
- }
858
-
859
- if (!FW_WP_Filesystem::request_access(
860
- fw_get_framework_directory('/extensions'),
861
- fw_current_url(),
862
- array($nonce_name, $form_input_name))
863
- ) {
864
- { // revert hack changes
865
- $_POST[$form_input_name] = $original_post_value;
866
- unset($original_post_value);
867
- }
868
- break;
869
- }
870
-
871
- { // revert hack changes
872
- $_POST[$form_input_name] = $original_post_value;
873
- unset($original_post_value);
874
- }
875
-
876
- $updates = $this->get_extensions_with_updates();
877
-
878
- if (empty($updates)) {
879
- $skin->error(__('No extensions updates found.', 'fw'));
880
- break;
881
- }
882
-
883
- foreach ($extensions_list as $extension_name) {
884
- if (!($extension = fw()->extensions->get($extension_name))) {
885
- $skin->error(
886
- sprintf(__('Extension "%s" does not exist or is disabled.', 'fw'), $extension_name)
887
- );
888
- continue;
889
- }
890
-
891
- if (!isset($updates[$extension_name])) {
892
- $skin->error(
893
- sprintf(__('No update found for the "%s" extension.', 'fw'), $extension->manifest->get_name())
894
- );
895
- continue;
896
- }
897
-
898
- $update = $updates[$extension_name];
899
-
900
- if (is_wp_error($update)) {
901
- $skin->error($update);
902
- continue;
903
- }
904
-
905
- /** @var FW_Ext_Update_Service $service */
906
- $service = $this->get_child($update['service']);
907
-
908
- $update_result = $this->update(array(
909
- 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
910
- $extension->get_declared_path()
911
- ),
912
- 'download_callback' => array($service, '_download_extension'),
913
- 'download_callback_args' => array($extension, $update['latest_version'], $this->get_wp_fs_tmp_dir()),
914
- 'skin' => $skin,
915
- 'title' => sprintf(__('%s extension', 'fw'), $extension->manifest->get_name()),
916
- ), true);
917
-
918
- if (is_wp_error($update_result)) {
919
- $skin->error($update_result);
920
- continue;
921
- }
922
-
923
- $skin->set_result(true);
924
-
925
- if (!$this->get_config('extensions_as_one_update')) {
926
- $skin->decrement_extension_update_count( $extension_name );
927
- }
928
- }
929
-
930
- if ($this->get_config('extensions_as_one_update')) {
931
- $skin->decrement_extension_update_count( $extension_name );
932
- }
933
-
934
- $skin->after();
935
- } while(false);
936
-
937
- $skin->footer();
938
-
939
- require_once(ABSPATH . 'wp-admin/admin-footer.php');
940
- }
941
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ require dirname(__FILE__) .'/includes/extends/class-fw-ext-update-service.php';
4
+
5
+ class FW_Extension_Update extends FW_Extension
6
+ {
7
+ /**
8
+ * {@inheritdoc}
9
+ */
10
+ public function _child_extension_is_valid($child_extension_instance)
11
+ {
12
+ return is_subclass_of($child_extension_instance, 'FW_Ext_Update_Service');
13
+ }
14
+
15
+ /**
16
+ * File names to skip (do not delete or change) during the update process
17
+ * @var array
18
+ */
19
+ private $skip_file_names = array('.git');
20
+
21
+ /**
22
+ * @internal
23
+ */
24
+ protected function _init()
25
+ {
26
+ {
27
+ $has_access = (current_user_can('update_themes') || current_user_can('update_plugins'));
28
+
29
+ if ($has_access) {
30
+ if (is_multisite() && !is_network_admin()) {
31
+ // only network admin can change files that affects the entire network
32
+ $has_access = false;
33
+ }
34
+ }
35
+
36
+ if (!$has_access) {
37
+ return false; // prevent child extensions activation
38
+ }
39
+ }
40
+
41
+ $this->add_actions();
42
+ $this->add_filters();
43
+ }
44
+
45
+ private function add_actions()
46
+ {
47
+ add_action('core_upgrade_preamble', array($this, '_action_updates_page_footer'));
48
+
49
+ add_action('update-core-custom_'. 'fw-update-framework', array($this, '_action_update_framework'));
50
+ add_action('update-core-custom_'. 'fw-update-theme', array($this, '_action_update_theme'));
51
+ add_action('update-core-custom_'. 'fw-update-extensions', array($this, '_action_update_extensions'));
52
+
53
+ add_action('admin_notices', array($this, '_action_admin_notices'));
54
+ }
55
+
56
+ private function add_filters()
57
+ {
58
+ add_filter('wp_get_update_data', array($this, '_filter_update_data'), 10, 2);
59
+ }
60
+
61
+ private function get_fixed_version($version)
62
+ {
63
+ // remove from the beginning everything that is not a number: 'v1.2.3' -> '1.2.3', 'ver1.0.0' -> '1.0.0'
64
+ return preg_replace('/^[^0-9]+/i', '', $version);;
65
+ }
66
+
67
+ private function get_wp_fs_tmp_dir()
68
+ {
69
+ return FW_WP_Filesystem::real_path_to_filesystem_path(
70
+ apply_filters('fw_tmp_dir', fw_fix_path(WP_CONTENT_DIR) .'/tmp')
71
+ );
72
+ }
73
+
74
+ /**
75
+ * @internal
76
+ */
77
+ public function _action_updates_page_footer()
78
+ {
79
+ echo $this->render_view('updates-list', array(
80
+ 'updates' => $this->get_updates(!empty($_GET['force-check']))
81
+ ));
82
+ }
83
+
84
+ /**
85
+ * @internal
86
+ */
87
+ public function _filter_update_data($data, $titles)
88
+ {
89
+ $updates = $this->get_updates(!empty($_GET['force-check']));
90
+
91
+ if ($updates['framework'] && !is_wp_error($updates['framework'])) {
92
+ ++$data['counts']['total'];
93
+ }
94
+
95
+ if ($updates['theme'] && !is_wp_error($updates['theme'])) {
96
+ ++$data['counts']['total'];
97
+ }
98
+
99
+ if (!empty($updates['extensions'])) {
100
+ foreach ( $updates['extensions'] as $ext_name => $ext_update ) {
101
+ if ( is_wp_error( $ext_update ) ) {
102
+ continue;
103
+ }
104
+
105
+ ++$data['counts']['total'];
106
+
107
+ if ($this->get_config('extensions_as_one_update')) {
108
+ // no matter how many extensions, display as one update
109
+ break;
110
+ }
111
+ }
112
+ }
113
+
114
+ return $data;
115
+ }
116
+
117
+ private function get_updates($force_check = false)
118
+ {
119
+ $cache_key = 'fw_ext_update/updates';
120
+
121
+ // use cache because this method may be called multiple times (to prevent useless requests to update servers)
122
+
123
+ try {
124
+ return FW_Cache::get($cache_key);
125
+ } catch (FW_Cache_Not_Found_Exception $e) {
126
+ $updates = array(
127
+ 'framework' => $this->get_framework_update($force_check),
128
+ 'theme' => $this->get_theme_update($force_check),
129
+ 'extensions' => $this->get_extensions_with_updates($force_check)
130
+ );
131
+
132
+ FW_Cache::set($cache_key, $updates);
133
+
134
+ return $updates;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Collect extensions that has new versions available
140
+ * @param bool $force_check
141
+ * @return array {ext_name => update_data}
142
+ */
143
+ private function get_extensions_with_updates($force_check = false)
144
+ {
145
+ $updates = array();
146
+ $services = $this->get_children();
147
+ $theme_ext_requirements = fw()->theme->manifest->get('requirements/extensions');
148
+
149
+ foreach (fw()->extensions->get_all() as $ext_name => $extension) {
150
+ /** @var FW_Extension $extension */
151
+
152
+ /**
153
+ * Ask each service if it knows how to update the extension
154
+ */
155
+ foreach ($services as $service_name => $service) {
156
+ /** @var $service FW_Ext_Update_Service */
157
+
158
+ $latest_version = $service->_get_extension_latest_version($extension, $force_check);
159
+
160
+ if ($latest_version === false) {
161
+ // It said that it doesn't know how to update it
162
+ continue;
163
+ }
164
+
165
+ if (is_wp_error($latest_version)) {
166
+ $updates[$ext_name] = $latest_version;
167
+ break;
168
+ }
169
+
170
+ $fixed_latest_version = $this->get_fixed_version($latest_version);
171
+
172
+ if (!version_compare($fixed_latest_version, $extension->manifest->get_version(), '>')) {
173
+ // we already have latest version
174
+ continue;
175
+ }
176
+
177
+ if (
178
+ isset($theme_ext_requirements[$ext_name])
179
+ &&
180
+ isset($theme_ext_requirements[$ext_name]['max_version'])
181
+ &&
182
+ version_compare($fixed_latest_version, $theme_ext_requirements[$ext_name]['max_version'], '>')
183
+ ) {
184
+ continue; // do not allow update if it exceeds max_version
185
+ }
186
+
187
+ $updates[$ext_name] = array(
188
+ 'service' => $service_name,
189
+ 'latest_version' => $latest_version,
190
+ 'fixed_latest_version' => $fixed_latest_version
191
+ );
192
+
193
+ break;
194
+ }
195
+ }
196
+
197
+ return $updates;
198
+ }
199
+
200
+ /**
201
+ * @param bool $force_check
202
+ * @return array|false|WP_Error
203
+ */
204
+ private function get_framework_update($force_check = false)
205
+ {
206
+ /**
207
+ * Ask each service if it knows how to update the framework
208
+ */
209
+ foreach ($this->get_children() as $service_name => $service) {
210
+ /** @var $service FW_Ext_Update_Service */
211
+
212
+ $latest_version = $service->_get_framework_latest_version($force_check);
213
+
214
+ if ($latest_version === false) {
215
+ // It said that it doesn't know how to update it
216
+ continue;
217
+ }
218
+
219
+ if (is_wp_error($latest_version)) {
220
+ return $latest_version;
221
+ }
222
+
223
+ $fixed_latest_version = $this->get_fixed_version($latest_version);
224
+
225
+ if (!version_compare($fixed_latest_version, fw()->manifest->get_version(), '>')) {
226
+ // we already have latest version
227
+ continue;
228
+ }
229
+
230
+ return array(
231
+ 'service' => $service_name,
232
+ 'latest_version' => $latest_version,
233
+ 'fixed_latest_version' => $fixed_latest_version
234
+ );
235
+ }
236
+
237
+ return false;
238
+ }
239
+
240
+ /**
241
+ * @param bool $force_check
242
+ * @return array|false|WP_Error
243
+ */
244
+ private function get_theme_update($force_check = false)
245
+ {
246
+ /**
247
+ * Ask each service if it knows how to update the theme
248
+ */
249
+ foreach ($this->get_children() as $service_name => $service) {
250
+ /** @var $service FW_Ext_Update_Service */
251
+
252
+ $latest_version = $service->_get_theme_latest_version($force_check);
253
+
254
+ if ($latest_version === false) {
255
+ // It said that it doesn't know how to update it
256
+ continue;
257
+ }
258
+
259
+ if (is_wp_error($latest_version)) {
260
+ return $latest_version;
261
+ }
262
+
263
+ $fixed_latest_version = $this->get_fixed_version($latest_version);
264
+
265
+ if (!version_compare($fixed_latest_version, fw()->theme->manifest->get_version(), '>')) {
266
+ // we already have latest version
267
+ continue;
268
+ }
269
+
270
+ return array(
271
+ 'service' => $service_name,
272
+ 'latest_version' => $latest_version,
273
+ 'fixed_latest_version' => $fixed_latest_version
274
+ );
275
+ }
276
+
277
+ return false;
278
+ }
279
+
280
+ /**
281
+ * Turn on/off the maintenance mode
282
+ * @param bool $enable
283
+ */
284
+ private function maintenance_mode($enable = false)
285
+ {
286
+ /** @var WP_Filesystem_Base $wp_filesystem */
287
+ global $wp_filesystem;
288
+
289
+ if (!$wp_filesystem || (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())) {
290
+ return;
291
+ }
292
+
293
+ $file_path = $wp_filesystem->abspath() . '.maintenance';
294
+
295
+ if ($wp_filesystem->exists($file_path)) {
296
+ if (!$wp_filesystem->delete($file_path)) {
297
+ trigger_error(__('Cannot delete: ', 'fw') . $file_path, E_USER_WARNING);
298
+ }
299
+ }
300
+
301
+ if ($enable) {
302
+ // Create maintenance file to signal that we are upgrading
303
+ if (!$wp_filesystem->put_contents($file_path, '<?php $upgrading = ' . time() . '; ?>', FS_CHMOD_FILE)) {
304
+ trigger_error(__('Cannot create: ', 'fw') . $file_path, E_USER_WARNING);
305
+ }
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Download and install new version files
311
+ *
312
+ * global $wp_filesystem; must be initialized before calling this method
313
+ *
314
+ * @param array $data
315
+ * @param bool $merge_extensions The extensions/ directory will not be replaced entirely,
316
+ * only extensions that comes with the update will be replaced
317
+ * @return null|WP_Error
318
+ */
319
+ private function update($data, $merge_extensions = false)
320
+ {
321
+ $required_data_keys = array(
322
+ 'wp_fs_destination_dir' => true,
323
+ 'download_callback' => true,
324
+ 'download_callback_args' => true,
325
+ 'skin' => true,
326
+ 'title' => true,
327
+ );
328
+
329
+ if (count($required_data_keys) > count(array_intersect_key($required_data_keys, $data))) {
330
+ trigger_error('Some required keys are not present', E_USER_ERROR);
331
+ }
332
+
333
+ // move manually every key to variable, so IDE will understand better them
334
+ {
335
+ /**
336
+ * Replace all files in this directory with downloaded
337
+ * @var string $wp_fs_destination_dir
338
+ */
339
+ $wp_fs_destination_dir = $data['wp_fs_destination_dir'];
340
+
341
+ /**
342
+ * Called to download new version files to $this->get_wp_fs_tmp_dir()
343
+ * @var callable $download_callback
344
+ */
345
+ $download_callback = $data['download_callback'];
346
+
347
+ /**
348
+ * @var array
349
+ */
350
+ $download_callback_args = $data['download_callback_args'];
351
+
352
+ /**
353
+ * @var WP_Upgrader_Skin $skin
354
+ */
355
+ $skin = $data['skin'];
356
+
357
+ /**
358
+ * Used in text messages
359
+ * @var string $title
360
+ */
361
+ $title = $data['title'];
362
+
363
+ unset($data);
364
+ }
365
+
366
+ /**
367
+ * @var string|WP_Error
368
+ */
369
+ $error = false;
370
+
371
+ $tmp_download_dir = $this->get_wp_fs_tmp_dir();
372
+
373
+ do {
374
+ /** @var WP_Filesystem_Base $wp_filesystem */
375
+ global $wp_filesystem;
376
+
377
+ // create temporary directory
378
+ {
379
+ if ($wp_filesystem->exists($tmp_download_dir)) {
380
+ // just in case it already exists, clear everything, it may contain old files
381
+ if (!$wp_filesystem->rmdir($tmp_download_dir, true)) {
382
+ $error = __('Cannot remove old temporary directory: ', 'fw') . $tmp_download_dir;
383
+ break;
384
+ }
385
+ }
386
+
387
+ if (!FW_WP_Filesystem::mkdir_recursive($tmp_download_dir)) {
388
+ $error = __('Cannot create directory: ', 'fw') . $tmp_download_dir;
389
+ break;
390
+ }
391
+ }
392
+
393
+ $skin->feedback(sprintf(__('Downloading the %s...', 'fw'), $title));
394
+ {
395
+ $downloaded_dir = call_user_func_array($download_callback, $download_callback_args);
396
+
397
+ if (!$downloaded_dir) {
398
+ $error = sprintf(__('Cannot download the %s.', 'fw'), $title);
399
+ break;
400
+ } elseif (is_wp_error($downloaded_dir)) {
401
+ $error = $downloaded_dir;
402
+ break;
403
+ }
404
+ }
405
+
406
+ $this->maintenance_mode(true);
407
+
408
+ $skin->feedback(sprintf(__('Installing the %s...', 'fw'), $title));
409
+ {
410
+ // remove all files from destination directory
411
+ {
412
+ $dir_files = $wp_filesystem->dirlist($wp_fs_destination_dir, true);
413
+ if ($dir_files === false) {
414
+ $error =__('Cannot access directory: ', 'fw') . $wp_fs_destination_dir;
415
+ break;
416
+ }
417
+
418
+ foreach ($dir_files as $file) {
419
+ if (in_array($file['name'], $this->skip_file_names)) {
420
+ continue;
421
+ }
422
+
423
+ if ($merge_extensions) {
424
+ if ($file['name'] === 'extensions' && $file['type'] === 'd') {
425
+ // do not remove extensions, will be merged later
426
+ continue;
427
+ }
428
+ }
429
+
430
+ $file_path = $wp_fs_destination_dir .'/'. $file['name'];
431
+
432
+ if (!$wp_filesystem->delete($file_path, true, $file['type'])) {
433
+ $error = __('Cannot remove: ', 'fw') . $file_path;
434
+ break 2;
435
+ }
436
+ }
437
+ }
438
+
439
+ // move all files from the temporary directory to the destination directory
440
+ {
441
+ $dir_files = $wp_filesystem->dirlist($downloaded_dir, true);
442
+ if ($dir_files === false) {
443
+ $error = __('Cannot access directory: ', 'fw') . $downloaded_dir;
444
+ break;
445
+ }
446
+
447
+ foreach ($dir_files as $file) {
448
+ if (in_array($file['name'], $this->skip_file_names)) {
449
+ continue;
450
+ }
451
+
452
+ $downloaded_file_path = $downloaded_dir .'/'. $file['name'];
453
+ $destination_file_path = $wp_fs_destination_dir .'/'. $file['name'];
454
+
455
+ if ($merge_extensions) {
456
+ if ($file['name'] === 'extensions' && $file['type'] === 'd') {
457
+ // merge extensions/ after all other files was moved
458
+ $merge_extensions_data = array(
459
+ 'source' => $downloaded_file_path,
460
+ 'destination' => $destination_file_path,
461
+ );
462
+ continue;
463
+ }
464
+ }
465
+
466
+ if (!$wp_filesystem->move($downloaded_file_path, $destination_file_path)) {
467
+ $error = sprintf(
468
+ __('Cannot move "%s" to "%s"', 'fw'),
469
+ $downloaded_file_path, $destination_file_path
470
+ );
471
+ break 2;
472
+ }
473
+ }
474
+
475
+ if ($merge_extensions) {
476
+ if (!empty($merge_extensions_data)) {
477
+ $merge_result = $this->merge_extensions(
478
+ $merge_extensions_data['source'],
479
+ $merge_extensions_data['destination']
480
+ );
481
+
482
+ if ($merge_result === false) {
483
+ $error = sprintf(
484
+ __('Cannot merge "%s" with "%s"', 'fw'),
485
+ $downloaded_file_path, $destination_file_path
486
+ );
487
+ break;
488
+ } elseif (is_wp_error($merge_result)) {
489
+ $error = $merge_result;
490
+ break;
491
+ }
492
+ }
493
+ }
494
+ }
495
+ }
496
+
497
+ $skin->feedback(sprintf(__('The %s has been successfully updated.', 'fw'), $title));
498
+ } while(false);
499
+
500
+ $this->maintenance_mode(false);
501
+
502
+ if ($wp_filesystem->exists($tmp_download_dir)) {
503
+ if ( ! $wp_filesystem->delete( $tmp_download_dir, true, 'd' ) ) {
504
+ $error = sprintf( __( 'Cannot remove temporary directory "%s".', 'fw' ), $tmp_download_dir );
505
+ }
506
+ }
507
+
508
+ if ($error) {
509
+ if (!is_wp_error($error)) {
510
+ $error = new WP_Error( 'fw_ext_update_failed', (string)$error );
511
+ }
512
+
513
+ return $error;
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Merge two extensions/ directories
519
+ * @param string $source_dir WP_Filesystem dir '/a/b/c/extensions'
520
+ * @param string $destination_dir WP_Filesystem dir '/a/b/d/extensions'
521
+ * @return bool|WP_Error
522
+ */
523
+ private function merge_extensions($source_dir, $destination_dir)
524
+ {
525
+ /** @var WP_Filesystem_Base $wp_filesystem */
526
+ global $wp_filesystem;
527
+
528
+ $wp_error_id = 'fw_ext_update_merge_extensions';
529
+
530
+ if (!$wp_filesystem->exists($destination_dir)) {
531
+ // do a simple move if destination does not exist
532
+ if (!$wp_filesystem->move($source_dir, $destination_dir)) {
533
+ return new WP_Error($wp_error_id,
534
+ sprintf(__('Cannot move "%s" to "%s"', 'fw'), $source_dir, $destination_dir)
535
+ );
536
+ }
537
+ return true;
538
+ }
539
+
540
+ $source_ext_dirs = $wp_filesystem->dirlist($source_dir, true);
541
+ if ($source_ext_dirs === false) {
542
+ return new WP_Error($wp_error_id,
543
+ __('Cannot access directory: ', 'fw') . $source_dir
544
+ );
545
+ }
546
+
547
+ foreach ($source_ext_dirs as $ext_dir) {
548
+ if (in_array($ext_dir['name'], $this->skip_file_names)) {
549
+ continue;
550
+ }
551
+
552
+ if ($ext_dir['type'] !== 'd') {
553
+ // process only directories from the extensions/ directory
554
+ continue;
555
+ }
556
+
557
+ $source_extension_dir = $source_dir .'/'. $ext_dir['name'];
558
+ $destination_extension_dir = $destination_dir .'/'. $ext_dir['name'];
559
+
560
+ {
561
+ $source_ext_files = $wp_filesystem->dirlist($source_extension_dir, true);
562
+ if ($source_ext_files === false) {
563
+ return new WP_Error($wp_error_id,
564
+ __('Cannot access directory: ', 'fw') . $source_extension_dir
565
+ );
566
+ }
567
+
568
+ if (empty($source_ext_files)) {
569
+ /**
570
+ * Source extension directory is empty, do nothing.
571
+ * This happens when the extension is a git submodule in repository
572
+ * but in zip it comes as an empty directory.
573
+ */
574
+ continue;
575
+ }
576
+ }
577
+
578
+ // prepare destination
579
+ {
580
+ // create if not exists
581
+ if (!$wp_filesystem->exists($destination_extension_dir)) {
582
+ if (!FW_WP_Filesystem::mkdir_recursive($destination_extension_dir)) {
583
+ return new WP_Error($wp_error_id,
584
+ __('Cannot create directory: ', 'fw') . $destination_extension_dir
585
+ );
586
+ }
587
+ }
588
+
589
+ // remove everything except the extensions/ dir
590
+ {
591
+ $dest_ext_files = $wp_filesystem->dirlist($destination_extension_dir, true);
592
+ if ($dest_ext_files === false) {
593
+ return new WP_Error($wp_error_id,
594
+ __('Cannot access directory: ', 'fw') . $destination_extension_dir
595
+ );
596
+ }
597
+
598
+ $destination_has_extensions_dir = false;
599
+
600
+ foreach ($dest_ext_files as $dest_ext_file) {
601
+ if (in_array($dest_ext_file['name'], $this->skip_file_names)) {
602
+ continue;
603
+ }
604
+
605
+ if ($dest_ext_file['name'] === 'extensions' && $dest_ext_file['type'] === 'd') {
606
+ $destination_has_extensions_dir = true;
607
+ continue;
608
+ }
609
+
610
+ $dest_ext_file_path = $destination_extension_dir .'/'. $dest_ext_file['name'];
611
+
612
+ if (!$wp_filesystem->delete($dest_ext_file_path, true, $dest_ext_file['type'])) {
613
+ return new WP_Error($wp_error_id,
614
+ __('Cannot delete: ', 'fw') . $dest_ext_file_path
615
+ );
616
+ }
617
+ }
618
+ }
619
+ }
620
+
621
+ // move files from source to destination extension directory
622
+ {
623
+ $source_has_extensions_dir = false;
624
+
625
+ foreach ($source_ext_files as $source_ext_file) {
626
+ if (in_array($source_ext_file['name'], $this->skip_file_names)) {
627
+ continue;
628
+ }
629
+
630
+ if ($source_ext_file['name'] === 'extensions' && $source_ext_file['type'] === 'd') {
631
+ $source_has_extensions_dir = true;
632
+ continue;
633
+ }
634
+
635
+ $source_ext_file_path = $source_extension_dir .'/'. $source_ext_file['name'];
636
+ $dest_ext_file_path = $destination_extension_dir .'/'. $source_ext_file['name'];
637
+
638
+ if (!$wp_filesystem->move($source_ext_file_path, $dest_ext_file_path)) {
639
+ return new WP_Error($wp_error_id,
640
+ sprintf(__('Cannot move "%s" to "%s"', 'fw'),
641
+ $source_ext_file_path, $dest_ext_file_path
642
+ )
643
+ );
644
+ }
645
+ }
646
+ }
647
+
648
+ if ($source_has_extensions_dir) {
649
+ if ($destination_has_extensions_dir) {
650
+ $merge_result = $this->merge_extensions(
651
+ $source_extension_dir .'/extensions',
652
+ $destination_extension_dir .'/extensions'
653
+ );
654
+
655
+ if ($merge_result !== true) {
656
+ return $merge_result;
657
+ }
658
+ } else {
659
+ if (!$wp_filesystem->move(
660
+ $source_extension_dir .'/extensions',
661
+ $destination_extension_dir .'/extensions'
662
+ )) {
663
+ return new WP_Error($wp_error_id,
664
+ sprintf(__('Cannot move "%s" to "%s"', 'fw'),
665
+ $source_extension_dir .'/extensions',
666
+ $destination_extension_dir .'/extensions'
667
+ )
668
+ );
669
+ }
670
+ }
671
+ }
672
+ }
673
+
674
+ return true;
675
+ }
676
+
677
+ /**
678
+ * @internal
679
+ */
680
+ public function _action_update_framework()
681
+ {
682
+ $nonce_name = '_nonce_fw_ext_update_framework';
683
+ if (!isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name])) {
684
+ wp_die(__('Invalid nonce.', 'fw'));
685
+ }
686
+
687
+ {
688
+ if (!class_exists('_FW_Ext_Update_Framework_Upgrader_Skin')) {
689
+ fw_include_file_isolated(
690
+ $this->get_declared_path('/includes/classes/class--fw-ext-update-framework-upgrader-skin.php')
691
+ );
692
+ }
693
+
694
+ $skin = new _FW_Ext_Update_Framework_Upgrader_Skin(array(
695
+ 'title' => __('Framework Update', 'fw'),
696
+ ));
697
+ }
698
+
699
+ require_once ABSPATH .'wp-admin/admin-header.php';
700
+
701
+ $skin->header();
702
+
703
+ do {
704
+ if (!FW_WP_Filesystem::request_access(fw_get_framework_directory(), fw_current_url(), array($nonce_name))) {
705
+ break;
706
+ }
707
+
708
+ $update = $this->get_framework_update();
709
+
710
+ if ($update === false) {
711
+ $skin->error(__('Failed to get framework latest version.', 'fw'));
712
+ break;
713
+ } elseif (is_wp_error($update)) {
714
+ $skin->error($update);
715
+ break;
716
+ }
717
+
718
+ /** @var FW_Ext_Update_Service $service */
719
+ $service = $this->get_child($update['service']);
720
+
721
+ $update_result = $this->update(array(
722
+ 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
723
+ fw_get_framework_directory()
724
+ ),
725
+ 'download_callback' => array($service, '_download_framework'),
726
+ 'download_callback_args' => array($update['latest_version'], $this->get_wp_fs_tmp_dir()),
727
+ 'skin' => $skin,
728
+ 'title' => __('Framework', 'fw'),
729
+ ));
730
+
731
+ if (is_wp_error($update_result)) {
732
+ $skin->error($update_result);
733
+ break;
734
+ }
735
+
736
+ $skin->set_result(true);
737
+ $skin->after();
738
+ } while(false);
739
+
740
+ $skin->footer();
741
+
742
+ require_once(ABSPATH . 'wp-admin/admin-footer.php');
743
+ }
744
+
745
+ /**
746
+ * @internal
747
+ */
748
+ public function _action_update_theme()
749
+ {
750
+ $nonce_name = '_nonce_fw_ext_update_theme';
751
+ if (!isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name])) {
752
+ wp_die(__('Invalid nonce.', 'fw'));
753
+ }
754
+
755
+ {
756
+ if (!class_exists('_FW_Ext_Update_Theme_Upgrader_Skin')) {
757
+ fw_include_file_isolated(
758
+ $this->get_declared_path('/includes/classes/class--fw-ext-update-theme-upgrader-skin.php')
759
+ );
760
+ }
761
+
762
+ $skin = new _FW_Ext_Update_Theme_Upgrader_Skin(array(
763
+ 'title' => __('Theme Update', 'fw'),
764
+ ));
765
+ }
766
+
767
+ require_once(ABSPATH . 'wp-admin/admin-header.php');
768
+
769
+ $skin->header();
770
+
771
+ do {
772
+ if (!FW_WP_Filesystem::request_access(get_template_directory(), fw_current_url(), array($nonce_name))) {
773
+ break;
774
+ }
775
+
776
+ $update = $this->get_theme_update();
777
+
778
+ if ($update === false) {
779
+ $skin->error(__('Failed to get theme latest version.', 'fw'));
780
+ break;
781
+ } elseif (is_wp_error($update)) {
782
+ $skin->error($update);
783
+ break;
784
+ }
785
+
786
+ /** @var FW_Ext_Update_Service $service */
787
+ $service = $this->get_child($update['service']);
788
+
789
+ $update_result = $this->update(array(
790
+ 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
791
+ get_template_directory()
792
+ ),
793
+ 'download_callback' => array($service, '_download_theme'),
794
+ 'download_callback_args' => array($update['latest_version'], $this->get_wp_fs_tmp_dir()),
795
+ 'skin' => $skin,
796
+ 'title' => __('Theme', 'fw'),
797
+ ));
798
+
799
+ if (is_wp_error($update_result)) {
800
+ $skin->error($update_result);
801
+ break;
802
+ }
803
+
804
+ $skin->set_result(true);
805
+ $skin->after();
806
+ } while(false);
807
+
808
+ $skin->footer();
809
+
810
+ require_once(ABSPATH . 'wp-admin/admin-footer.php');
811
+ }
812
+
813
+ /**
814
+ * @internal
815
+ */
816
+ public function _action_update_extensions()
817
+ {
818
+ $nonce_name = '_nonce_fw_ext_update_extensions';
819
+ if (!isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name])) {
820
+ wp_die(__('Invalid nonce.', 'fw'));
821
+ }
822
+
823
+ $form_input_name = 'extensions';
824
+ $extensions_list = FW_Request::POST($form_input_name);
825
+
826
+ if (empty($extensions_list)) {
827
+ FW_Flash_Messages::add(
828
+ 'fw_ext_update',
829
+ __('Please check the extensions you want to update.', 'fw'),
830
+ 'warning'
831
+ );
832
+ wp_redirect(self_admin_url('update-core.php'));
833
+ exit;
834
+ }
835
+
836
+ // handle changes by the hack below
837
+ {
838
+ if (is_string($extensions_list)) {
839
+ $extensions_list = json_decode($extensions_list);
840
+ } else {
841
+ $extensions_list = array_keys($extensions_list);
842
+ }
843
+ }
844
+
845
+ {
846
+ if (!class_exists('_FW_Ext_Update_Extensions_Upgrader_Skin')) {
847
+ fw_include_file_isolated(
848
+ $this->get_declared_path('/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php')
849
+ );
850
+ }
851
+
852
+ $skin = new _FW_Ext_Update_Extensions_Upgrader_Skin(array(
853
+ 'title' => __('Extensions Update', 'fw'),
854
+ ));
855
+ }
856
+
857
+ require_once(ABSPATH . 'wp-admin/admin-header.php');
858
+
859
+ $skin->header();
860
+
861
+ do {
862
+ /**
863
+ * Hack for the ftp credentials template that does not support array post values
864
+ * https://github.com/WordPress/WordPress/blob/3949a8b6cc50a021ed93798287b4ef9ea8a560d9/wp-admin/includes/file.php#L1144
865
+ */
866
+ {
867
+ $original_post_value = $_POST[$form_input_name];
868
+ $_POST[$form_input_name] = wp_slash(json_encode($extensions_list));
869
+ }
870
+
871
+ if (!FW_WP_Filesystem::request_access(
872
+ fw_get_framework_directory('/extensions'),
873
+ fw_current_url(),
874
+ array($nonce_name, $form_input_name))
875
+ ) {
876
+ { // revert hack changes
877
+ $_POST[$form_input_name] = $original_post_value;
878
+ unset($original_post_value);
879
+ }
880
+ break;
881
+ }
882
+
883
+ { // revert hack changes
884
+ $_POST[$form_input_name] = $original_post_value;
885
+ unset($original_post_value);
886
+ }
887
+
888
+ $updates = $this->get_extensions_with_updates();
889
+
890
+ if (empty($updates)) {
891
+ $skin->error(__('No extensions updates found.', 'fw'));
892
+ break;
893
+ }
894
+
895
+ foreach ($extensions_list as $extension_name) {
896
+ if (!($extension = fw()->extensions->get($extension_name))) {
897
+ $skin->error(
898
+ sprintf(__('Extension "%s" does not exist or is disabled.', 'fw'), $extension_name)
899
+ );
900
+ continue;
901
+ }
902
+
903
+ if (!isset($updates[$extension_name])) {
904
+ $skin->error(
905
+ sprintf(__('No update found for the "%s" extension.', 'fw'), $extension->manifest->get_name())
906
+ );
907
+ continue;
908
+ }
909
+
910
+ $update = $updates[$extension_name];
911
+
912
+ if (is_wp_error($update)) {
913
+ $skin->error($update);
914
+ continue;
915
+ }
916
+
917
+ /** @var FW_Ext_Update_Service $service */
918
+ $service = $this->get_child($update['service']);
919
+
920
+ $update_result = $this->update(array(
921
+ 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
922
+ $extension->get_declared_path()
923
+ ),
924
+ 'download_callback' => array($service, '_download_extension'),
925
+ 'download_callback_args' => array($extension, $update['latest_version'], $this->get_wp_fs_tmp_dir()),
926
+ 'skin' => $skin,
927
+ 'title' => sprintf(__('%s extension', 'fw'), $extension->manifest->get_name()),
928
+ ), true);
929
+
930
+ if (is_wp_error($update_result)) {
931
+ $skin->error($update_result);
932
+ continue;
933
+ }
934
+
935
+ $skin->set_result(true);
936
+
937
+ if (!$this->get_config('extensions_as_one_update')) {
938
+ $skin->decrement_extension_update_count( $extension_name );
939
+ }
940
+ }
941
+
942
+ if ($this->get_config('extensions_as_one_update')) {
943
+ $skin->decrement_extension_update_count( $extension_name );
944
+ }
945
+
946
+ $skin->after();
947
+ } while(false);
948
+
949
+ $skin->footer();
950
+
951
+ require_once(ABSPATH . 'wp-admin/admin-footer.php');
952
+ }
953
+
954
+ public function _action_admin_notices() {
955
+ if (
956
+ get_current_screen()->parent_base === fw()->extensions->manager->get_page_slug()
957
+ &&
958
+ ($updates = $this->get_updates())
959
+ &&
960
+ !empty($updates['extensions'])
961
+ ) { /* ok */ } else {
962
+ return;
963
+ }
964
+
965
+ foreach ($updates['extensions'] as $ext_name => $ext_update) {
966
+ if ( is_wp_error( $ext_update ) ) {
967
+ return;
968
+ }
969
+
970
+ break;
971
+ }
972
+
973
+ echo '<div class="notice notice-warning"><p>'
974
+ . sprintf(
975
+ esc_html__('New extensions updates available. %s', 'fw'),
976
+ fw_html_tag('a', array(
977
+ 'href' => self_admin_url('update-core.php') .'#fw-ext-update-extensions',
978
+ ), esc_html__('Go to Updates page', 'fw'))
979
+ )
980
+ . '</p></div>';
981
+ }
982
+ }
framework/extensions/update/config.php CHANGED
@@ -1,9 +1,9 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- $cfg = array();
4
-
5
- /**
6
- * Do not show details about each extension update, but show it as one update
7
- * (simplify users life)
8
- */
9
- $cfg['extensions_as_one_update'] = true;
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ $cfg = array();
4
+
5
+ /**
6
+ * Do not show details about each extension update, but show it as one update
7
+ * (simplify users life)
8
+ */
9
+ $cfg['extensions_as_one_update'] = true;
framework/extensions/update/extensions/github-update/class-fw-extension-github-update.php CHANGED
@@ -1,443 +1,443 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Github Update
5
- *
6
- * Add {'github_update' => 'user/repo'} to your manifest and this extension will handle it
7
- */
8
- class FW_Extension_Github_Update extends FW_Ext_Update_Service
9
- {
10
- /**
11
- * Handle framework, theme and extensions that has this key in manifest
12
- * @var string
13
- */
14
- private $manifest_key = 'github_update';
15
-
16
- /**
17
- * Check if manifest key format is correct 'user/repo'
18
- * @var string
19
- */
20
- private $manifest_key_regex = '/^([^\s\/]+)\/([^\s\/]+)$/';
21
-
22
- /**
23
- * How long to cache server responses
24
- * @var int seconds
25
- */
26
- private $transient_expiration = DAY_IN_SECONDS;
27
-
28
- private $download_timeout = 300;
29
-
30
- /**
31
- * Used when there is internet connection problems
32
- * To prevent site being blocked on every refresh, this fake version will be cached in the transient
33
- * @var string
34
- */
35
- private $fake_latest_version = '0.0.0';
36
-
37
- /**
38
- * @internal
39
- */
40
- protected function _init()
41
- {
42
- }
43
-
44
- /**
45
- * @param string $append '/foo/bar'
46
- * @return string
47
- */
48
- private function get_github_api_url($append)
49
- {
50
- return apply_filters('fw_github_api_url', 'https://api.github.com') . $append;
51
- }
52
-
53
- private function fetch_latest_version($user_slash_repo)
54
- {
55
- /**
56
- * If at least one request failed, do not do any other requests, to prevent site being blocked on every refresh.
57
- * This may happen on localhost when develop your theme and you have no internet connection.
58
- * Then this method will return a fake '0.0.0' version, it will be cached by the transient
59
- * and will not bother you until the transient will expire, then a new request will be made.
60
- * @var bool
61
- */
62
- static $no_internet_connection = false;
63
-
64
- if ($no_internet_connection) {
65
- return $this->fake_latest_version;
66
- }
67
-
68
- $http = new WP_Http();
69
-
70
- $response = $http->get(
71
- $this->get_github_api_url('/repos/'. $user_slash_repo .'/releases/latest')
72
- );
73
-
74
- unset($http);
75
-
76
- if (is_wp_error($response)) {
77
- if ($response->get_error_code() === 'http_request_failed') {
78
- $no_internet_connection = true;
79
- }
80
-
81
- return $response;
82
- }
83
-
84
- if (($response_code = intval(wp_remote_retrieve_response_code($response))) !== 200) {
85
- if ($response_code === 403) {
86
- $json_response = json_decode($response['body'], true);
87
-
88
- if ($json_response) {
89
- return new WP_Error(
90
- 'fw_ext_update_github_fetch_releases_failed',
91
- __('Github error:', 'fw') .' '. $json_response['message']
92
- );
93
- }
94
- }
95
-
96
- if ($response_code) {
97
- return new WP_Error(
98
- 'fw_ext_update_github_fetch_releases_failed',
99
- sprintf(
100
- __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
101
- $user_slash_repo, $response_code
102
- )
103
- );
104
- } else {
105
- return new WP_Error(
106
- 'fw_ext_update_github_fetch_releases_failed',
107
- sprintf(
108
- __( 'Failed to access Github repository "%s" releases.', 'fw' ),
109
- $user_slash_repo
110
- )
111
- );
112
- }
113
- }
114
-
115
- $release = json_decode($response['body'], true);
116
-
117
- unset($response);
118
-
119
- if (empty($release)) {
120
- return new WP_Error(
121
- 'fw_ext_update_github_fetch_no_releases',
122
- sprintf(__('No releases found in repository "%s".', 'fw'), $user_slash_repo)
123
- );
124
- }
125
-
126
- return $release['tag_name'];
127
- }
128
-
129
- /**
130
- * Get repository latest release version
131
- *
132
- * @param string $user_slash_repo Github 'user/repo'
133
- * @param bool $force_check Bypass cache
134
- * @param string $title Used in messages
135
- *
136
- * @return string|WP_Error
137
- */
138
- private function get_latest_version($user_slash_repo, $force_check, $title)
139
- {
140
- if (!preg_match($this->manifest_key_regex, $user_slash_repo)) {
141
- return new WP_Error('fw_ext_update_github_manifest_invalid',
142
- sprintf(
143
- __('%s manifest has invalid "github_update" parameter. Please use "user/repo" format.', 'fw'),
144
- $title
145
- )
146
- );
147
- }
148
-
149
- $transient_id = 'fw_ext_upd_gh_fw'; // the length must be 45 characters or less
150
-
151
- if ($force_check) {
152
- delete_site_transient($transient_id);
153
-
154
- $cache = array();
155
- } else {
156
- $cache = get_site_transient($transient_id);
157
-
158
- if ($cache === false) {
159
- $cache = array();
160
- } elseif (isset($cache[$user_slash_repo])) {
161
- return $cache[$user_slash_repo];
162
- }
163
- }
164
-
165
- $latest_version = $this->fetch_latest_version($user_slash_repo);
166
-
167
- if (empty($latest_version)) {
168
- return new WP_Error(
169
- 'fw_ext_update_github_failed_fetch_latest_version',
170
- sprintf(
171
- __('Failed to fetch %s latest version from github "%s".', 'fw'),
172
- $title, $user_slash_repo
173
- )
174
- );
175
- }
176
-
177
- if (is_wp_error($latest_version)) {
178
- /**
179
- * Internet connection problems or Github API requests limit reached.
180
- * Cache fake version to prevent requests to Github API on every refresh.
181
- */
182
- $cache = array_merge($cache, array($user_slash_repo => $this->fake_latest_version));
183
-
184
- /**
185
- * Show the error to the user because it is not visible elsewhere
186
- */
187
- FW_Flash_Messages::add(
188
- 'fw_ext_github_update_error',
189
- $latest_version->get_error_message(),
190
- 'error'
191
- );
192
- } else {
193
- $cache = array_merge($cache, array($user_slash_repo => $latest_version));
194
- }
195
-
196
- set_site_transient(
197
- $transient_id,
198
- $cache,
199
- $this->transient_expiration
200
- );
201
-
202
- return $latest_version;
203
- }
204
-
205
- /**
206
- * @param string $user_slash_repo Github 'user/repo'
207
- * @param string $version Requested version to download
208
- * @param string $wp_filesystem_download_directory Allocated temporary empty directory
209
- * @param string $title Used in messages
210
- *
211
- * @return string|WP_Error Path to the downloaded directory
212
- */
213
- private function download($user_slash_repo, $version, $wp_filesystem_download_directory, $title)
214
- {
215
- $http = new WP_Http();
216
-
217
- $response = $http->get(
218
- $this->get_github_api_url('/repos/'. $user_slash_repo .'/releases/tags/'. $version)
219
- );
220
-
221
- unset($http);
222
-
223
- $response_code = intval(wp_remote_retrieve_response_code($response));
224
-
225
- if ($response_code !== 200) {
226
- if ($response_code === 403) {
227
- $json_response = json_decode($response['body'], true);
228
-
229
- if ($json_response) {
230
- return new WP_Error(
231
- 'fw_ext_update_github_download_releases_failed',
232
- __('Github error:', 'fw') .' '. $json_response['message']
233
- );
234
- }
235
- }
236
-
237
- if ($response_code) {
238
- return new WP_Error(
239
- 'fw_ext_update_github_download_releases_failed',
240
- sprintf(
241
- __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
242
- $user_slash_repo, $response_code
243
- )
244
- );
245
- } else {
246
- return new WP_Error(
247
- 'fw_ext_update_github_download_releases_failed',
248
- sprintf(
249
- __( 'Failed to access Github repository "%s" releases.', 'fw' ),
250
- $user_slash_repo
251
- )
252
- );
253
- }
254
- }
255
-
256
- $release = json_decode($response['body'], true);
257
-
258
- unset($response);
259
-
260
- if (empty($release)) {
261
- return new WP_Error(
262
- 'fw_ext_update_github_download_no_release',
263
- sprintf(
264
- __('%s github repository "%s" does not have the "%s" release.', 'fw'),
265
- $title, $user_slash_repo, $version
266
- )
267
- );
268
- }
269
-
270
- $http = new WP_Http();
271
-
272
- $response = $http->request(
273
- 'https://github.com/'. $user_slash_repo .'/archive/'. $release['tag_name'] .'.zip',
274
- array(
275
- 'timeout' => $this->download_timeout,
276
- )
277
- );
278
-
279
- unset($http);
280
-
281
- if (intval(wp_remote_retrieve_response_code($response)) !== 200) {
282
- return new WP_Error(
283
- 'fw_ext_update_github_download_failed',
284
- sprintf(__('Cannot download %s zip.', 'fw'), $title)
285
- );
286
- }
287
-
288
- /** @var WP_Filesystem_Base $wp_filesystem */
289
- global $wp_filesystem;
290
-
291
- $zip_path = $wp_filesystem_download_directory .'/temp.zip';
292
-
293
- // save zip to file
294
- if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
295
- return new WP_Error(
296
- 'fw_ext_update_github_save_download_failed',
297
- sprintf(__('Cannot save %s zip.', 'fw'), $title)
298
- );
299
- }
300
-
301
- unset($response);
302
-
303
- $unzip_result = unzip_file(
304
- FW_WP_Filesystem::filesystem_path_to_real_path($zip_path),
305
- $wp_filesystem_download_directory
306
- );
307
-
308
- if (is_wp_error($unzip_result)) {
309
- return $unzip_result;
310
- }
311
-
312
- // remove zip file
313
- if (!$wp_filesystem->delete($zip_path, false, 'f')) {
314
- return new WP_Error(
315
- 'fw_ext_update_github_remove_downloaded_zip_failed',
316
- sprintf(__('Cannot remove %s zip.', 'fw'), $title)
317
- );
318
- }
319
-
320
- $unzipped_dir_files = $wp_filesystem->dirlist($wp_filesystem_download_directory);
321
-
322
- if (!$unzipped_dir_files) {
323
- return new WP_Error(
324
- 'fw_ext_update_github_unzipped_dir_fail',
325
- __('Cannot access the unzipped directory files.', 'fw')
326
- );
327
- }
328
-
329
- /**
330
- * get first found directory
331
- * (if everything worked well, there should be only one directory)
332
- */
333
- foreach ($unzipped_dir_files as $file) {
334
- if ($file['type'] == 'd') {
335
- return $wp_filesystem_download_directory .'/'. $file['name'];
336
- }
337
- }
338
-
339
- return new WP_Error(
340
- 'fw_ext_update_github_unzipped_dir_not_found',
341
- sprintf(__('The unzipped %s directory not found.', 'fw'), $title)
342
- );
343
- }
344
-
345
- /**
346
- * {@inheritdoc}
347
- * @internal
348
- */
349
- public function _get_framework_latest_version($force_check)
350
- {
351
- $user_slash_repo = fw()->manifest->get($this->manifest_key);
352
-
353
- if (empty($user_slash_repo)) {
354
- return false;
355
- }
356
-
357
- return $this->get_latest_version(
358
- $user_slash_repo,
359
- $force_check,
360
- __('Framework', 'fw')
361
- );
362
- }
363
-
364
- /**
365
- * {@inheritdoc}
366
- * @internal
367
- */
368
- public function _download_framework($version, $wp_filesystem_download_directory)
369
- {
370
- return $this->download(
371
- fw()->manifest->get($this->manifest_key),
372
- $version,
373
- $wp_filesystem_download_directory,
374
- __('Framework', 'fw')
375
- );
376
- }
377
-
378
- /**
379
- * {@inheritdoc}
380
- * @internal
381
- */
382
- public function _get_theme_latest_version($force_check)
383
- {
384
- $user_slash_repo = fw()->theme->manifest->get($this->manifest_key);
385
-
386
- if (empty($user_slash_repo)) {
387
- return false;
388
- }
389
-
390
- return $this->get_latest_version(
391
- $user_slash_repo,
392
- $force_check,
393
- __('Theme', 'fw')
394
- );
395
- }
396
-
397
- /**
398
- * {@inheritdoc}
399
- * @internal
400
- */
401
- public function _download_theme($version, $wp_filesystem_download_directory)
402
- {
403
- return $this->download(
404
- fw()->theme->manifest->get($this->manifest_key),
405
- $version,
406
- $wp_filesystem_download_directory,
407
- __('Theme', 'fw')
408
- );
409
- }
410
-
411
- /**
412
- * {@inheritdoc}
413
- * @internal
414
- */
415
- public function _get_extension_latest_version(FW_Extension $extension, $force_check)
416
- {
417
- $user_slash_repo = $extension->manifest->get($this->manifest_key);
418
-
419
- if (empty($user_slash_repo)) {
420
- return false;
421
- }
422
-
423
- return $this->get_latest_version(
424
- $user_slash_repo,
425
- $force_check,
426
- sprintf(__('%s extension', 'fw'), $extension->manifest->get_name())
427
- );
428
- }
429
-
430
- /**
431
- * {@inheritdoc}
432
- * @internal
433
- */
434
- public function _download_extension(FW_Extension $extension, $version, $wp_filesystem_download_directory)
435
- {
436
- return $this->download(
437
- $extension->manifest->get($this->manifest_key),
438
- $version,
439
- $wp_filesystem_download_directory,
440
- sprintf(__('%s extension', 'fw'), $extension->manifest->get_name())
441
- );
442
- }
443
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Github Update
5
+ *
6
+ * Add {'github_update' => 'user/repo'} to your manifest and this extension will handle it
7
+ */
8
+ class FW_Extension_Github_Update extends FW_Ext_Update_Service
9
+ {
10
+ /**
11
+ * Handle framework, theme and extensions that has this key in manifest
12
+ * @var string
13
+ */
14
+ private $manifest_key = 'github_update';
15
+
16
+ /**
17
+ * Check if manifest key format is correct 'user/repo'
18
+ * @var string
19
+ */
20
+ private $manifest_key_regex = '/^([^\s\/]+)\/([^\s\/]+)$/';
21
+
22
+ /**
23
+ * How long to cache server responses
24
+ * @var int seconds
25
+ */
26
+ private $transient_expiration = DAY_IN_SECONDS;
27
+
28
+ private $download_timeout = 300;
29
+
30
+ /**
31
+ * Used when there is internet connection problems
32
+ * To prevent site being blocked on every refresh, this fake version will be cached in the transient
33
+ * @var string
34
+ */
35
+ private $fake_latest_version = '0.0.0';
36
+
37
+ /**
38
+ * @internal
39
+ */
40
+ protected function _init()
41
+ {
42
+ }
43
+
44
+ /**
45
+ * @param string $append '/foo/bar'
46
+ * @return string
47
+ */
48
+ private function get_github_api_url($append)
49
+ {
50
+ return apply_filters('fw_github_api_url', 'https://api.github.com') . $append;
51
+ }
52
+
53
+ private function fetch_latest_version($user_slash_repo)
54
+ {
55
+ /**
56
+ * If at least one request failed, do not do any other requests, to prevent site being blocked on every refresh.
57
+ * This may happen on localhost when develop your theme and you have no internet connection.
58
+ * Then this method will return a fake '0.0.0' version, it will be cached by the transient
59
+ * and will not bother you until the transient will expire, then a new request will be made.
60
+ * @var bool
61
+ */
62
+ static $no_internet_connection = false;
63
+
64
+ if ($no_internet_connection) {
65
+ return $this->fake_latest_version;
66
+ }
67
+
68
+ $http = new WP_Http();
69
+
70
+ $response = $http->get(
71
+ $this->get_github_api_url('/repos/'. $user_slash_repo .'/releases/latest')
72
+ );
73
+
74
+ unset($http);
75
+
76
+ if (is_wp_error($response)) {
77
+ if ($response->get_error_code() === 'http_request_failed') {
78
+ $no_internet_connection = true;
79
+ }
80
+
81
+ return $response;
82
+ }
83
+
84
+ if (($response_code = intval(wp_remote_retrieve_response_code($response))) !== 200) {
85
+ if ($response_code === 403) {
86
+ $json_response = json_decode($response['body'], true);
87
+
88
+ if ($json_response) {
89
+ return new WP_Error(
90
+ 'fw_ext_update_github_fetch_releases_failed',
91
+ __('Github error:', 'fw') .' '. $json_response['message']
92
+ );
93
+ }
94
+ }
95
+
96
+ if ($response_code) {
97
+ return new WP_Error(
98
+ 'fw_ext_update_github_fetch_releases_failed',
99
+ sprintf(
100
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
101
+ $user_slash_repo, $response_code
102
+ )
103
+ );
104
+ } else {
105
+ return new WP_Error(
106
+ 'fw_ext_update_github_fetch_releases_failed',
107
+ sprintf(
108
+ __( 'Failed to access Github repository "%s" releases.', 'fw' ),
109
+ $user_slash_repo
110
+ )
111
+ );
112
+ }
113
+ }
114
+
115
+ $release = json_decode($response['body'], true);
116
+
117
+ unset($response);
118
+
119
+ if (empty($release)) {
120
+ return new WP_Error(
121
+ 'fw_ext_update_github_fetch_no_releases',
122
+ sprintf(__('No releases found in repository "%s".', 'fw'), $user_slash_repo)
123
+ );
124
+ }
125
+
126
+ return $release['tag_name'];
127
+ }
128
+
129
+ /**
130
+ * Get repository latest release version
131
+ *
132
+ * @param string $user_slash_repo Github 'user/repo'
133
+ * @param bool $force_check Bypass cache
134
+ * @param string $title Used in messages
135
+ *
136
+ * @return string|WP_Error
137
+ */
138
+ private function get_latest_version($user_slash_repo, $force_check, $title)
139
+ {
140
+ if (!preg_match($this->manifest_key_regex, $user_slash_repo)) {
141
+ return new WP_Error('fw_ext_update_github_manifest_invalid',
142
+ sprintf(
143
+ __('%s manifest has invalid "github_update" parameter. Please use "user/repo" format.', 'fw'),
144
+ $title
145
+ )
146
+ );
147
+ }
148
+
149
+ $transient_id = 'fw_ext_upd_gh_fw'; // the length must be 45 characters or less
150
+
151
+ if ($force_check) {
152
+ delete_site_transient($transient_id);
153
+
154
+ $cache = array();
155
+ } else {
156
+ $cache = get_site_transient($transient_id);
157
+
158
+ if ($cache === false) {
159
+ $cache = array();
160
+ } elseif (isset($cache[$user_slash_repo])) {
161
+ return $cache[$user_slash_repo];
162
+ }
163
+ }
164
+
165
+ $latest_version = $this->fetch_latest_version($user_slash_repo);
166
+
167
+ if (empty($latest_version)) {
168
+ return new WP_Error(
169
+ 'fw_ext_update_github_failed_fetch_latest_version',
170
+ sprintf(
171
+ __('Failed to fetch %s latest version from github "%s".', 'fw'),
172
+ $title, $user_slash_repo
173
+ )
174
+ );
175
+ }
176
+
177
+ if (is_wp_error($latest_version)) {
178
+ /**
179
+ * Internet connection problems or Github API requests limit reached.
180
+ * Cache fake version to prevent requests to Github API on every refresh.
181
+ */
182
+ $cache = array_merge($cache, array($user_slash_repo => $this->fake_latest_version));
183
+
184
+ /**
185
+ * Show the error to the user because it is not visible elsewhere
186
+ */
187
+ FW_Flash_Messages::add(
188
+ 'fw_ext_github_update_error',
189
+ $latest_version->get_error_message(),
190
+ 'error'
191
+ );
192
+ } else {
193
+ $cache = array_merge($cache, array($user_slash_repo => $latest_version));
194
+ }
195
+
196
+ set_site_transient(
197
+ $transient_id,
198
+ $cache,
199
+ $this->transient_expiration
200
+ );
201
+
202
+ return $latest_version;
203
+ }
204
+
205
+ /**
206
+ * @param string $user_slash_repo Github 'user/repo'
207
+ * @param string $version Requested version to download
208
+ * @param string $wp_filesystem_download_directory Allocated temporary empty directory
209
+ * @param string $title Used in messages
210
+ *
211
+ * @return string|WP_Error Path to the downloaded directory
212
+ */
213
+ private function download($user_slash_repo, $version, $wp_filesystem_download_directory, $title)
214
+ {
215
+ $http = new WP_Http();
216
+
217
+ $response = $http->get(
218
+ $this->get_github_api_url('/repos/'. $user_slash_repo .'/releases/tags/'. $version)
219
+ );
220
+
221
+ unset($http);
222
+
223
+ $response_code = intval(wp_remote_retrieve_response_code($response));
224
+
225
+ if ($response_code !== 200) {
226
+ if ($response_code === 403) {
227
+ $json_response = json_decode($response['body'], true);
228
+
229
+ if ($json_response) {
230
+ return new WP_Error(
231
+ 'fw_ext_update_github_download_releases_failed',
232
+ __('Github error:', 'fw') .' '. $json_response['message']
233
+ );
234
+ }
235
+ }
236
+
237
+ if ($response_code) {
238
+ return new WP_Error(
239
+ 'fw_ext_update_github_download_releases_failed',
240
+ sprintf(
241
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
242
+ $user_slash_repo, $response_code
243
+ )
244
+ );
245
+ } else {
246
+ return new WP_Error(
247
+ 'fw_ext_update_github_download_releases_failed',
248
+ sprintf(
249
+ __( 'Failed to access Github repository "%s" releases.', 'fw' ),
250
+ $user_slash_repo
251
+ )
252
+ );
253
+ }
254
+ }
255
+
256
+ $release = json_decode($response['body'], true);
257
+
258
+ unset($response);
259
+
260
+ if (empty($release)) {
261
+ return new WP_Error(
262
+ 'fw_ext_update_github_download_no_release',
263
+ sprintf(
264
+ __('%s github repository "%s" does not have the "%s" release.', 'fw'),
265
+ $title, $user_slash_repo, $version
266
+ )
267
+ );
268
+ }
269
+
270
+ $http = new WP_Http();
271
+
272
+ $response = $http->request(
273
+ 'https://github.com/'. $user_slash_repo .'/archive/'. $release['tag_name'] .'.zip',
274
+ array(
275
+ 'timeout' => $this->download_timeout,
276
+ )
277
+ );
278
+
279
+ unset($http);
280
+
281
+ if (intval(wp_remote_retrieve_response_code($response)) !== 200) {
282
+ return new WP_Error(
283
+ 'fw_ext_update_github_download_failed',
284
+ sprintf(__('Cannot download %s zip.', 'fw'), $title)
285
+ );
286
+ }
287
+
288
+ /** @var WP_Filesystem_Base $wp_filesystem */
289
+ global $wp_filesystem;
290
+
291
+ $zip_path = $wp_filesystem_download_directory .'/temp.zip';
292
+
293
+ // save zip to file
294
+ if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
295
+ return new WP_Error(
296
+ 'fw_ext_update_github_save_download_failed',
297
+ sprintf(__('Cannot save %s zip.', 'fw'), $title)
298
+ );
299
+ }
300
+
301
+ unset($response);
302
+
303
+ $unzip_result = unzip_file(
304
+ FW_WP_Filesystem::filesystem_path_to_real_path($zip_path),
305
+ $wp_filesystem_download_directory
306
+ );
307
+
308
+ if (is_wp_error($unzip_result)) {
309
+ return $unzip_result;
310
+ }
311
+
312
+ // remove zip file
313
+ if (!$wp_filesystem->delete($zip_path, false, 'f')) {
314
+ return new WP_Error(
315
+ 'fw_ext_update_github_remove_downloaded_zip_failed',
316
+ sprintf(__('Cannot remove %s zip.', 'fw'), $title)
317
+ );
318
+ }
319
+
320
+ $unzipped_dir_files = $wp_filesystem->dirlist($wp_filesystem_download_directory);
321
+
322
+ if (!$unzipped_dir_files) {
323
+ return new WP_Error(
324
+ 'fw_ext_update_github_unzipped_dir_fail',
325
+ __('Cannot access the unzipped directory files.', 'fw')
326
+ );
327
+ }
328
+
329
+ /**
330
+ * get first found directory
331
+ * (if everything worked well, there should be only one directory)
332
+ */
333
+ foreach ($unzipped_dir_files as $file) {
334
+ if ($file['type'] == 'd') {
335
+ return $wp_filesystem_download_directory .'/'. $file['name'];
336
+ }
337
+ }
338
+
339
+ return new WP_Error(
340
+ 'fw_ext_update_github_unzipped_dir_not_found',
341
+ sprintf(__('The unzipped %s directory not found.', 'fw'), $title)
342
+ );
343
+ }
344
+
345
+ /**
346
+ * {@inheritdoc}
347
+ * @internal
348
+ */
349
+ public function _get_framework_latest_version($force_check)
350
+ {
351
+ $user_slash_repo = fw()->manifest->get($this->manifest_key);
352
+
353
+ if (empty($user_slash_repo)) {
354
+ return false;
355
+ }
356
+
357
+ return $this->get_latest_version(
358
+ $user_slash_repo,
359
+ $force_check,
360
+ __('Framework', 'fw')
361
+ );
362
+ }
363
+
364
+ /**
365
+ * {@inheritdoc}
366
+ * @internal
367
+ */
368
+ public function _download_framework($version, $wp_filesystem_download_directory)
369
+ {
370
+ return $this->download(
371
+ fw()->manifest->get($this->manifest_key),
372
+ $version,
373
+ $wp_filesystem_download_directory,
374
+ __('Framework', 'fw')
375
+ );
376
+ }
377
+
378
+ /**
379
+ * {@inheritdoc}
380
+ * @internal
381
+ */
382
+ public function _get_theme_latest_version($force_check)
383
+ {
384
+ $user_slash_repo = fw()->theme->manifest->get($this->manifest_key);
385
+
386
+ if (empty($user_slash_repo)) {
387
+ return false;
388
+ }
389
+
390
+ return $this->get_latest_version(
391
+ $user_slash_repo,
392
+ $force_check,
393
+ __('Theme', 'fw')
394
+ );
395
+ }
396
+
397
+ /**
398
+ * {@inheritdoc}
399
+ * @internal
400
+ */
401
+ public function _download_theme($version, $wp_filesystem_download_directory)
402
+ {
403
+ return $this->download(
404
+ fw()->theme->manifest->get($this->manifest_key),
405
+ $version,
406
+ $wp_filesystem_download_directory,
407
+ __('Theme', 'fw')
408
+ );
409
+ }
410
+
411
+ /**
412
+ * {@inheritdoc}
413
+ * @internal
414
+ */
415
+ public function _get_extension_latest_version(FW_Extension $extension, $force_check)
416
+ {
417
+ $user_slash_repo = $extension->manifest->get($this->manifest_key);
418
+
419
+ if (empty($user_slash_repo)) {
420
+ return false;
421
+ }
422
+
423
+ return $this->get_latest_version(
424
+ $user_slash_repo,
425
+ $force_check,
426
+ sprintf(__('%s extension', 'fw'), $extension->manifest->get_name())
427
+ );
428
+ }
429
+
430
+ /**
431
+ * {@inheritdoc}
432
+ * @internal
433
+ */
434
+ public function _download_extension(FW_Extension $extension, $version, $wp_filesystem_download_directory)
435
+ {
436
+ return $this->download(
437
+ $extension->manifest->get($this->manifest_key),
438
+ $version,
439
+ $wp_filesystem_download_directory,
440
+ sprintf(__('%s extension', 'fw'), $extension->manifest->get_name())
441
+ );
442
+ }
443
+ }
framework/extensions/update/extensions/github-update/manifest.php CHANGED
@@ -1,5 +1,5 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- $manifest = array();
4
-
5
- $manifest['standalone'] = true;
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ $manifest = array();
4
+
5
+ $manifest['standalone'] = true;
framework/extensions/update/includes/classes/class--fw-ext-update-extensions-list-table.php CHANGED
@@ -1,128 +1,128 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Display extensions with updates on the Update Page
5
- */
6
- class _FW_Ext_Update_Extensions_List_Table extends WP_List_Table
7
- {
8
- private $items_pre_page = 1000;
9
-
10
- private $total_items = null;
11
-
12
- private $_extensions = array();
13
-
14
- private $_table_columns = array();
15
- private $_table_columns_count = 0;
16
-
17
- public function __construct($args)
18
- {
19
- parent::__construct(array(
20
- 'screen' => 'fw-ext-update-extensions-update'
21
- ));
22
-
23
- $this->_extensions = $args['extensions'];
24
-
25
- $this->_table_columns = array(
26
- 'cb' => '<input type="checkbox" />',
27
- 'details' => fw_html_tag(
28
- 'a',
29
- array(
30
- 'href' => '#',
31
- 'onclick' => "jQuery(this).closest('tr').find('input[type=\"checkbox\"]:first').trigger('click'); return false;"
32
- ),
33
- __('Select All', 'fw')
34
- ),
35
- );
36
- $this->_table_columns_count = count($this->_table_columns);
37
- }
38
-
39
- public function get_columns()
40
- {
41
- return $this->_table_columns;
42
- }
43
-
44
- public function prepare_items()
45
- {
46
- if ($this->total_items !== null) {
47
- return;
48
- }
49
-
50
- $this->total_items = count($this->_extensions);
51
-
52
- $this->set_pagination_args(array(
53
- 'total_items' => $this->total_items,
54
- 'per_page' => $this->items_pre_page,
55
- ));
56
-
57
- $page_num = $this->get_pagenum();
58
- $offset = ($page_num - 1) * $this->items_pre_page;
59
-
60
- /**
61
- * Prepare items for output
62
- */
63
- foreach ($this->_extensions as $ext_name => $ext_update) {
64
- $extension = fw()->extensions->get($ext_name);
65
-
66
- if (is_wp_error($ext_update)) {
67
- $this->items[] = array(
68
- 'cb' => '<input type="checkbox" disabled />',
69
- 'details' =>
70
- '<p>'.
71
- '<strong>'. fw_htmlspecialchars($extension->manifest->get_name()) .'</strong>'.
72
- '<br/>'.
73
- '<span class="wp-ui-text-notification">'. $ext_update->get_error_message() .'</span>'.
74
- '</p>',
75
- );
76
- } else {
77
- $this->items[] = array(
78
- 'cb' => '<input type="checkbox" name="extensions['. esc_attr($ext_name) .']" />',
79
- 'details' =>
80
- '<p>'.
81
- '<strong>'. fw_htmlspecialchars($extension->manifest->get_name()) .'</strong>'.
82
- '<br/>'.
83
- sprintf(
84
- __('You have version %s installed. Update to %s.', 'fw'),
85
- $extension->manifest->get_version(), fw_htmlspecialchars($ext_update['fixed_latest_version'])
86
- ).
87
- '</p>',
88
- );
89
- }
90
- }
91
- }
92
-
93
- public function has_items()
94
- {
95
- $this->prepare_items();
96
-
97
- return $this->total_items;
98
- }
99
-
100
- /**
101
- * (override parent)
102
- */
103
- function single_row($item)
104
- {
105
- static $row_class = '';
106
-
107
- $row_class = ( $row_class == '' ? ' class="alternate"' : '' );
108
-
109
- echo '<tr' . $row_class . '>';
110
- echo $this->single_row_columns( $item );
111
- echo '</tr>';
112
- }
113
-
114
- protected function column_cb($item)
115
- {
116
- echo $item['cb'];
117
- }
118
-
119
- protected function column_default($item, $column_name)
120
- {
121
- echo $item[$column_name];
122
- }
123
-
124
- function no_items()
125
- {
126
- _e('No Extensions for update.', 'fw');
127
- }
128
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Display extensions with updates on the Update Page
5
+ */
6
+ class _FW_Ext_Update_Extensions_List_Table extends WP_List_Table
7
+ {
8
+ private $items_pre_page = 1000;
9
+
10
+ private $total_items = null;
11
+
12
+ private $_extensions = array();
13
+
14
+ private $_table_columns = array();
15
+ private $_table_columns_count = 0;
16
+
17
+ public function __construct($args)
18
+ {
19
+ parent::__construct(array(
20
+ 'screen' => 'fw-ext-update-extensions-update'
21
+ ));
22
+
23
+ $this->_extensions = $args['extensions'];
24
+
25
+ $this->_table_columns = array(
26
+ 'cb' => '<input type="checkbox" />',
27
+ 'details' => fw_html_tag(
28
+ 'a',
29
+ array(
30
+ 'href' => '#',
31
+ 'onclick' => "jQuery(this).closest('tr').find('input[type=\"checkbox\"]:first').trigger('click'); return false;"
32
+ ),
33
+ __('Select All', 'fw')
34
+ ),
35
+ );
36
+ $this->_table_columns_count = count($this->_table_columns);
37
+ }
38
+
39
+ public function get_columns()
40
+ {
41
+ return $this->_table_columns;
42
+ }
43
+
44
+ public function prepare_items()
45
+ {
46
+ if ($this->total_items !== null) {
47
+ return;
48
+ }
49
+
50
+ $this->total_items = count($this->_extensions);
51
+
52
+ $this->set_pagination_args(array(
53
+ 'total_items' => $this->total_items,
54
+ 'per_page' => $this->items_pre_page,
55
+ ));
56
+
57
+ $page_num = $this->get_pagenum();
58
+ $offset = ($page_num - 1) * $this->items_pre_page;
59
+
60
+ /**
61
+ * Prepare items for output
62
+ */
63
+ foreach ($this->_extensions as $ext_name => $ext_update) {
64
+ $extension = fw()->extensions->get($ext_name);
65
+
66
+ if (is_wp_error($ext_update)) {
67
+ $this->items[] = array(
68
+ 'cb' => '<input type="checkbox" disabled />',
69
+ 'details' =>
70
+ '<p>'.
71
+ '<strong>'. fw_htmlspecialchars($extension->manifest->get_name()) .'</strong>'.
72
+ '<br/>'.
73
+ '<span class="wp-ui-text-notification">'. $ext_update->get_error_message() .'</span>'.
74
+ '</p>',
75
+ );
76
+ } else {
77
+ $this->items[] = array(
78
+ 'cb' => '<input type="checkbox" name="extensions['. esc_attr($ext_name) .']" />',
79
+ 'details' =>
80
+ '<p>'.
81
+ '<strong>'. fw_htmlspecialchars($extension->manifest->get_name()) .'</strong>'.
82
+ '<br/>'.
83
+ sprintf(
84
+ __('You have version %s installed. Update to %s.', 'fw'),
85
+ $extension->manifest->get_version(), fw_htmlspecialchars($ext_update['fixed_latest_version'])
86
+ ).
87
+ '</p>',
88
+ );
89
+ }
90
+ }
91
+ }
92
+
93
+ public function has_items()
94
+ {
95
+ $this->prepare_items();
96
+
97
+ return $this->total_items;
98
+ }
99
+
100
+ /**
101
+ * (override parent)
102
+ */
103
+ function single_row($item)
104
+ {
105
+ static $row_class = '';
106
+
107
+ $row_class = ( $row_class == '' ? ' class="alternate"' : '' );
108
+
109
+ echo '<tr' . $row_class . '>';
110
+ echo $this->single_row_columns( $item );
111
+ echo '</tr>';
112
+ }
113
+
114
+ protected function column_cb($item)
115
+ {
116
+ echo $item['cb'];
117
+ }
118
+
119
+ protected function column_default($item, $column_name)
120
+ {
121
+ echo $item[$column_name];
122
+ }
123
+
124
+ function no_items()
125
+ {
126
+ _e('No Extensions for update.', 'fw');
127
+ }
128
+ }
framework/extensions/update/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php CHANGED
@@ -1,36 +1,36 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
-
5
- class _FW_Ext_Update_Extensions_Upgrader_Skin extends WP_Upgrader_Skin
6
- {
7
- public function after()
8
- {
9
- $update_actions = array(
10
- 'updates_page' => fw_html_tag(
11
- 'a',
12
- array(
13
- 'href' => self_admin_url('update-core.php'),
14
- 'title' => __('Go to updates page', 'fw'),
15
- 'target' => '_parent',
16
- ),
17
- __('Return to Updates page', 'fw')
18
- )
19
- );
20
-
21
- /**
22
- * Filter the list of action links available following extensions update.
23
- * @param array $update_actions Array of plugin action links.
24
- */
25
- $update_actions = apply_filters('fw_ext_update_extensions_complete_actions', $update_actions);
26
-
27
- if (!empty($update_actions)) {
28
- $this->feedback(implode(' | ', (array)$update_actions));
29
- }
30
- }
31
-
32
- public function decrement_extension_update_count($extension_name)
33
- {
34
- $this->decrement_update_count('fw:extension:'. $extension_name);
35
- }
36
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
+
5
+ class _FW_Ext_Update_Extensions_Upgrader_Skin extends WP_Upgrader_Skin
6
+ {
7
+ public function after()
8
+ {
9
+ $update_actions = array(
10
+ 'updates_page' => fw_html_tag(
11
+ 'a',
12
+ array(
13
+ 'href' => self_admin_url('update-core.php'),
14
+ 'title' => __('Go to updates page', 'fw'),
15
+ 'target' => '_parent',
16
+ ),
17
+ __('Return to Updates page', 'fw')
18
+ )
19
+ );
20
+
21
+ /**
22
+ * Filter the list of action links available following extensions update.
23
+ * @param array $update_actions Array of plugin action links.
24
+ */
25
+ $update_actions = apply_filters('fw_ext_update_extensions_complete_actions', $update_actions);
26
+
27
+ if (!empty($update_actions)) {
28
+ $this->feedback(implode(' | ', (array)$update_actions));
29
+ }
30
+ }
31
+
32
+ public function decrement_extension_update_count($extension_name)
33
+ {
34
+ $this->decrement_update_count('fw:extension:'. $extension_name);
35
+ }
36
+ }
framework/extensions/update/includes/classes/class--fw-ext-update-framework-upgrader-skin.php CHANGED
@@ -1,33 +1,33 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
-
5
- class _FW_Ext_Update_Framework_Upgrader_Skin extends WP_Upgrader_Skin
6
- {
7
- public function after()
8
- {
9
- $this->decrement_update_count('fw');
10
-
11
- $update_actions = array(
12
- 'updates_page' => fw_html_tag(
13
- 'a',
14
- array(
15
- 'href' => self_admin_url('update-core.php'),
16
- 'title' => __('Go to updates page', 'fw'),
17
- 'target' => '_parent',
18
- ),
19
- __('Return to Updates page', 'fw')
20
- )
21
- );
22
-
23
- /**
24
- * Filter the list of action links available following framework update.
25
- * @param array $update_actions Array of plugin action links.
26
- */
27
- $update_actions = apply_filters('fw_ext_update_framework_complete_actions', $update_actions);
28
-
29
- if (!empty($update_actions)) {
30
- $this->feedback(implode(' | ', (array)$update_actions));
31
- }
32
- }
33
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
+
5
+ class _FW_Ext_Update_Framework_Upgrader_Skin extends WP_Upgrader_Skin
6
+ {
7
+ public function after()
8
+ {
9
+ $this->decrement_update_count('fw');
10
+
11
+ $update_actions = array(
12
+ 'updates_page' => fw_html_tag(
13
+ 'a',
14
+ array(
15
+ 'href' => self_admin_url('update-core.php'),
16
+ 'title' => __('Go to updates page', 'fw'),
17
+ 'target' => '_parent',
18
+ ),
19
+ __('Return to Updates page', 'fw')
20
+ )
21
+ );
22
+
23
+ /**
24
+ * Filter the list of action links available following framework update.
25
+ * @param array $update_actions Array of plugin action links.
26
+ */
27
+ $update_actions = apply_filters('fw_ext_update_framework_complete_actions', $update_actions);
28
+
29
+ if (!empty($update_actions)) {
30
+ $this->feedback(implode(' | ', (array)$update_actions));
31
+ }
32
+ }
33
+ }
framework/extensions/update/includes/classes/class--fw-ext-update-theme-upgrader-skin.php CHANGED
@@ -1,33 +1,33 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
-
5
- class _FW_Ext_Update_Theme_Upgrader_Skin extends WP_Upgrader_Skin
6
- {
7
- public function after()
8
- {
9
- $this->decrement_update_count('fw:theme');
10
-
11
- $update_actions = array(
12
- 'updates_page' => fw_html_tag(
13
- 'a',
14
- array(
15
- 'href' => self_admin_url('update-core.php'),
16
- 'title' => __('Go to updates page', 'fw'),
17
- 'target' => '_parent',
18
- ),
19
- __('Return to Updates page', 'fw')
20
- )
21
- );
22
-
23
- /**
24
- * Filter the list of action links available following theme update.
25
- * @param array $update_actions Array of plugin action links.
26
- */
27
- $update_actions = apply_filters('fw_ext_update_theme_complete_actions', $update_actions);
28
-
29
- if (!empty($update_actions)) {
30
- $this->feedback(implode(' | ', (array)$update_actions));
31
- }
32
- }
33
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
4
+
5
+ class _FW_Ext_Update_Theme_Upgrader_Skin extends WP_Upgrader_Skin
6
+ {
7
+ public function after()
8
+ {
9
+ $this->decrement_update_count('fw:theme');
10
+
11
+ $update_actions = array(
12
+ 'updates_page' => fw_html_tag(
13
+ 'a',
14
+ array(
15
+ 'href' => self_admin_url('update-core.php'),
16
+ 'title' => __('Go to updates page', 'fw'),
17
+ 'target' => '_parent',
18
+ ),
19
+ __('Return to Updates page', 'fw')
20
+ )
21
+ );
22
+
23
+ /**
24
+ * Filter the list of action links available following theme update.
25
+ * @param array $update_actions Array of plugin action links.
26
+ */
27
+ $update_actions = apply_filters('fw_ext_update_theme_complete_actions', $update_actions);
28
+
29
+ if (!empty($update_actions)) {
30
+ $this->feedback(implode(' | ', (array)$update_actions));
31
+ }
32
+ }
33
+ }
framework/extensions/update/includes/extends/class-fw-ext-update-service.php CHANGED
@@ -1,105 +1,105 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Extend this class if you want to create a new update service
5
- */
6
- abstract class FW_Ext_Update_Service extends FW_Extension
7
- {
8
- /**
9
- * Return latest version of the framework if this service supports framework update
10
- *
11
- * @param bool $force_check Check now, do not use cache
12
- * @return string|false|WP_Error
13
- * false Does not know how to work with extension.
14
- * WP_Error Knows how to work with it, but there is an error
15
- * string Everything is ok, here is latest version
16
- *
17
- * @internal
18
- */
19
- public function _get_framework_latest_version($force_check)
20
- {
21
- return false;
22
- }
23
-
24
- /**
25
- * Download (and extract) framework files
26
- *
27
- * ! Work with global $wp_filesystem; Do not use base php filesystem functions
28
- *
29
- * @param $version Version to download
30
- * @param string $wp_filesystem_download_directory Empty directory offered for download files in it
31
- * @return string|false|WP_Error Path to WP Filesystem directory with downloaded (and extracted) files
32
- *
33
- * @internal
34
- */
35
- public function _download_framework($version, $wp_filesystem_download_directory)
36
- {
37
- return false;
38
- }
39
-
40
- /**
41
- * Return latest version of the theme if this service supports theme update
42
- *
43
- * @param bool $force_check Check now, do not use cache
44
- * @return string|false|WP_Error
45
- * false Does not know how to work with extension.
46
- * WP_Error Knows how to work with it, but there is an error
47
- * string Everything is ok, here is latest version
48
- *
49
- * @internal
50
- */
51
- public function _get_theme_latest_version($force_check)
52
- {
53
- return false;
54
- }
55
-
56
- /**
57
- * Download (and extract) theme files
58
- *
59
- * ! Work with global $wp_filesystem; Do not use base php filesystem functions
60
- *
61
- * @param $version Version to download
62
- * @param string $wp_filesystem_download_directory Empty directory offered for download files in it
63
- * @return string|false|WP_Error Path to WP Filesystem directory with downloaded (and extracted) files
64
- *
65
- * @internal
66
- */
67
- public function _download_theme($version, $wp_filesystem_download_directory)
68
- {
69
- return false;
70
- }
71
-
72
- /**
73
- * Return latest version of the extension if this service supports extension update
74
- *
75
- * @param FW_Extension $extension
76
- * @param bool $force_check Check now, do not use cache
77
- * @return string|false|WP_Error
78
- * false Does not know how to work with extension.
79
- * WP_Error Knows how to work with it, but there is an error
80
- * string Everything is ok, here is latest version
81
- *
82
- * @internal
83
- */
84
- public function _get_extension_latest_version(FW_Extension $extension, $force_check)
85
- {
86
- return false;
87
- }
88
-
89
- /**
90
- * Download (and extract) extension
91
- *
92
- * ! Work with global $wp_filesystem; Do not use base php filesystem functions
93
- *
94
- * @param FW_Extension $extension
95
- * @param $version Version to download
96
- * @param string $wp_filesystem_download_directory Empty directory offered for download files in it
97
- * @return string|false|WP_Error Path to WP Filesystem directory with downloaded (and extracted) files
98
- *
99
- * @internal
100
- */
101
- public function _download_extension(FW_Extension $extension, $version, $wp_filesystem_download_directory)
102
- {
103
- return false;
104
- }
105
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Extend this class if you want to create a new update service
5
+ */
6
+ abstract class FW_Ext_Update_Service extends FW_Extension
7
+ {
8
+ /**
9
+ * Return latest version of the framework if this service supports framework update
10
+ *
11
+ * @param bool $force_check Check now, do not use cache
12
+ * @return string|false|WP_Error
13
+ * false Does not know how to work with extension.
14
+ * WP_Error Knows how to work with it, but there is an error
15
+ * string Everything is ok, here is latest version
16
+ *
17
+ * @internal
18
+ */
19
+ public function _get_framework_latest_version($force_check)
20
+ {
21
+ return false;
22
+ }
23
+
24
+ /**
25
+ * Download (and extract) framework files
26
+ *
27
+ * ! Work with global $wp_filesystem; Do not use base php filesystem functions
28
+ *
29
+ * @param $version Version to download
30
+ * @param string $wp_filesystem_download_directory Empty directory offered for download files in it
31
+ * @return string|false|WP_Error Path to WP Filesystem directory with downloaded (and extracted) files
32
+ *
33
+ * @internal
34
+ */
35
+ public function _download_framework($version, $wp_filesystem_download_directory)
36
+ {
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Return latest version of the theme if this service supports theme update
42
+ *
43
+ * @param bool $force_check Check now, do not use cache
44
+ * @return string|false|WP_Error
45
+ * false Does not know how to work with extension.
46
+ * WP_Error Knows how to work with it, but there is an error
47
+ * string Everything is ok, here is latest version
48
+ *
49
+ * @internal
50
+ */
51
+ public function _get_theme_latest_version($force_check)
52
+ {
53
+ return false;
54
+ }
55
+
56
+ /**
57
+ * Download (and extract) theme files
58
+ *
59
+ * ! Work with global $wp_filesystem; Do not use base php filesystem functions
60
+ *
61
+ * @param $version Version to download
62
+ * @param string $wp_filesystem_download_directory Empty directory offered for download files in it
63
+ * @return string|false|WP_Error Path to WP Filesystem directory with downloaded (and extracted) files
64
+ *
65
+ * @internal
66
+ */
67
+ public function _download_theme($version, $wp_filesystem_download_directory)
68
+ {
69
+ return false;
70
+ }
71
+
72
+ /**
73
+ * Return latest version of the extension if this service supports extension update
74
+ *
75
+ * @param FW_Extension $extension
76
+ * @param bool $force_check Check now, do not use cache
77
+ * @return string|false|WP_Error
78
+ * false Does not know how to work with extension.
79
+ * WP_Error Knows how to work with it, but there is an error
80
+ * string Everything is ok, here is latest version
81
+ *
82
+ * @internal
83
+ */
84
+ public function _get_extension_latest_version(FW_Extension $extension, $force_check)
85
+ {
86
+ return false;
87
+ }
88
+
89
+ /**
90
+ * Download (and extract) extension
91
+ *
92
+ * ! Work with global $wp_filesystem; Do not use base php filesystem functions
93
+ *
94
+ * @param FW_Extension $extension
95
+ * @param $version Version to download
96
+ * @param string $wp_filesystem_download_directory Empty directory offered for download files in it
97
+ * @return string|false|WP_Error Path to WP Filesystem directory with downloaded (and extracted) files
98
+ *
99
+ * @internal
100
+ */
101
+ public function _download_extension(FW_Extension $extension, $version, $wp_filesystem_download_directory)
102
+ {
103
+ return false;
104
+ }
105
+ }
framework/extensions/update/manifest.php CHANGED
@@ -1,10 +1,10 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- $manifest = array();
4
-
5
- $manifest['name'] = __('Update', 'fw');
6
- $manifest['description'] = __('Keep you framework, extensions and theme up to date.', 'fw');
7
- $manifest['standalone'] = true;
8
-
9
- $manifest['version'] = '1.0.8';
10
- $manifest['github_update'] = 'ThemeFuse/Unyson-Update-Extension';
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ $manifest = array();
4
+
5
+ $manifest['name'] = __('Update', 'fw');
6
+ $manifest['description'] = __('Keep you framework, extensions and theme up to date.', 'fw');
7
+ $manifest['standalone'] = true;
8
+
9
+ $manifest['version'] = '1.0.10';
10
+ $manifest['github_update'] = 'ThemeFuse/Unyson-Update-Extension';
framework/extensions/update/static.php CHANGED
@@ -1,14 +1,14 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- $extension = fw()->extensions->get('update');
4
-
5
- if (fw_current_screen_match(array('only' => array(array('id' => 'update-core'))))) {
6
- // Include only on update page
7
-
8
- wp_enqueue_style(
9
- 'fw-ext-'. $extension->get_name() .'-update-page',
10
- $extension->get_declared_URI('/static/css/admin-update-page.css'),
11
- array(),
12
- $extension->manifest->get_version()
13
- );
14
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ $extension = fw()->extensions->get('update');
4
+
5
+ if (fw_current_screen_match(array('only' => array(array('id' => 'update-core'))))) {
6
+ // Include only on update page
7
+
8
+ wp_enqueue_style(
9
+ 'fw-ext-'. $extension->get_name() .'-update-page',
10
+ $extension->get_declared_URI('/static/css/admin-update-page.css'),
11
+ array(),
12
+ $extension->manifest->get_version()
13
+ );
14
+ }
framework/extensions/update/static/css/admin-update-page.css CHANGED
@@ -1,3 +1,3 @@
1
- #fw-ext-update-extensions .tablenav {
2
- display: none;
3
  }
1
+ #fw-ext-update-extensions .tablenav {
2
+ display: none;
3
  }
framework/extensions/update/views/updates-list.php CHANGED
@@ -1,113 +1,113 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
- /**
3
- * @var array $updates
4
- */
5
- ?>
6
-
7
- <?php if ($updates['framework'] !== false): ?>
8
- <div id="fw-ext-update-framework">
9
- <a name="fw-framework"></a>
10
- <h3><?php _e('Framework', 'fw') ?></h3>
11
- <?php if (empty($updates['framework'])): ?>
12
- <p><?php echo sprintf(__('You have the latest version of %s.', 'fw'), fw()->manifest->get_name()) ?></p>
13
- <?php else: ?>
14
- <?php if (is_wp_error($updates['framework'])): ?>
15
- <p class="wp-ui-text-notification"><?php echo $updates['framework']->get_error_message() ?></p>
16
- <?php else: ?>
17
- <form id="fw-ext-update-framework" method="post" action="update-core.php?action=fw-update-framework">
18
- <p><?php
19
- _e(sprintf('You have version %s installed. Update to %s.',
20
- fw()->manifest->get_version(),
21
- $updates['framework']['fixed_latest_version']
22
- ), 'fw')
23
- ?></p>
24
- <?php wp_nonce_field(-1, '_nonce_fw_ext_update_framework'); ?>
25
- <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Framework', 'fw')) ?>" name="update"></p>
26
- </form>
27
- <?php endif; ?>
28
- <?php endif; ?>
29
- </div>
30
- <?php endif; ?>
31
-
32
- <?php if ($updates['theme'] !== false): ?>
33
- <div id="fw-ext-update-theme">
34
- <a name="fw-theme"></a>
35
- <h3><?php $theme = wp_get_theme(); _e(sprintf('%s Theme', $theme->parent()->get('Name')), 'fw') ?></h3>
36
- <?php if (empty($updates['theme'])): ?>
37
- <p><?php _e('Your theme is up to date.', 'fw') ?></p>
38
- <?php else: ?>
39
- <?php if (is_wp_error($updates['theme'])): ?>
40
- <p class="wp-ui-text-notification"><?php echo $updates['theme']->get_error_message() ?></p>
41
- <?php else: ?>
42
- <form id="fw-ext-update-theme" method="post" action="update-core.php?action=fw-update-theme">
43
- <p><?php
44
- _e(sprintf('You have version %s installed. Update to %s.',
45
- fw()->theme->manifest->get_version(),
46
- $updates['theme']['fixed_latest_version']
47
- ), 'fw')
48
- ?></p>
49
- <?php wp_nonce_field(-1, '_nonce_fw_ext_update_theme'); ?>
50
- <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Theme', 'fw')) ?>" name="update"></p>
51
- </form>
52
- <?php endif; ?>
53
- <?php endif; ?>
54
- </div>
55
- <?php endif; ?>
56
-
57
- <?php if (true): ?>
58
- <div id="fw-ext-update-extensions">
59
- <a name="fw-extensions"></a>
60
- <h3><?php echo sprintf(__('%s Extensions', 'fw'), fw()->manifest->get_name()) ?></h3>
61
- <?php if (empty($updates['extensions'])): ?>
62
- <p><?php echo sprintf(__('You have the latest version of %s Extensions.', 'fw'), fw()->manifest->get_name()); ?></p>
63
- <?php else: ?>
64
- <?php
65
- $one_update_mode = fw()->extensions->get('update')->get_config('extensions_as_one_update');
66
-
67
- foreach ($updates['extensions'] as $extension) {
68
- if (is_wp_error($extension)) {
69
- /**
70
- * Cancel the "One update mode" and display all extensions list table with details
71
- * if at least one extension has an error that needs to be visible
72
- */
73
- $one_update_mode = false;
74
- break;
75
- }
76
- }
77
- ?>
78
- <form id="fw-ext-update-extensions" method="post" action="update-core.php?action=fw-update-extensions">
79
- <div class="fw-ext-update-extensions-form-detailed" <?php if ($one_update_mode): ?>style="display: none;"<?php endif; ?>>
80
- <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Extensions', 'fw')) ?>" name="update"></p>
81
- <?php
82
- if (!class_exists('_FW_Ext_Update_Extensions_List_Table')) {
83
- fw_include_file_isolated(
84
- fw()->extensions->get('update')->get_declared_path('/includes/classes/class--fw-ext-update-extensions-list-table.php')
85
- );
86
- }
87
-
88
- $list_table = new _FW_Ext_Update_Extensions_List_Table(array(
89
- 'extensions' => $updates['extensions']
90
- ));
91
-
92
- $list_table->display();
93
- ?>
94
- <?php wp_nonce_field(-1, '_nonce_fw_ext_update_extensions'); ?>
95
- <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Extensions', 'fw')) ?>" name="update"></p>
96
- </div>
97
- <?php if ($one_update_mode): ?>
98
- <div class="fw-ext-update-extensions-form-simple">
99
- <p style="color:#d54e21;"><?php _e('New extensions updates available.', 'fw'); ?></p>
100
- <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Extensions', 'fw')) ?>" name="update"></p>
101
- <script type="text/javascript">
102
- jQuery(function($){
103
- $('form#fw-ext-update-extensions').on('submit', function(){
104
- $(this).find('.check-column input[type="checkbox"]').prop('checked', true);
105
- });
106
- });
107
- </script>
108
- </div>
109
- <?php endif; ?>
110
- </form>
111
- <?php endif; ?>
112
- </div>
113
- <?php endif; ?>
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+ /**
3
+ * @var array $updates
4
+ */
5
+ ?>
6
+
7
+ <?php if ($updates['framework'] !== false): ?>
8
+ <div id="fw-ext-update-framework">
9
+ <a name="fw-framework"></a>
10
+ <h3><?php _e('Framework', 'fw') ?></h3>
11
+ <?php if (empty($updates['framework'])): ?>
12
+ <p><?php echo sprintf(__('You have the latest version of %s.', 'fw'), fw()->manifest->get_name()) ?></p>
13
+ <?php else: ?>
14
+ <?php if (is_wp_error($updates['framework'])): ?>
15
+ <p class="wp-ui-text-notification"><?php echo $updates['framework']->get_error_message() ?></p>
16
+ <?php else: ?>
17
+ <form id="fw-ext-update-framework" method="post" action="update-core.php?action=fw-update-framework">
18
+ <p><?php
19
+ _e(sprintf('You have version %s installed. Update to %s.',
20
+ fw()->manifest->get_version(),
21
+ $updates['framework']['fixed_latest_version']
22
+ ), 'fw')
23
+ ?></p>
24
+ <?php wp_nonce_field(-1, '_nonce_fw_ext_update_framework'); ?>
25
+ <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Framework', 'fw')) ?>" name="update"></p>
26
+ </form>
27
+ <?php endif; ?>
28
+ <?php endif; ?>
29
+ </div>
30
+ <?php endif; ?>
31
+
32
+ <?php if ($updates['theme'] !== false): ?>
33
+ <div id="fw-ext-update-theme">
34
+ <a name="fw-theme"></a>
35
+ <h3><?php $theme = wp_get_theme(); _e(sprintf('%s Theme', $theme->parent()->get('Name')), 'fw') ?></h3>
36
+ <?php if (empty($updates['theme'])): ?>
37
+ <p><?php _e('Your theme is up to date.', 'fw') ?></p>
38
+ <?php else: ?>
39
+ <?php if (is_wp_error($updates['theme'])): ?>
40
+ <p class="wp-ui-text-notification"><?php echo $updates['theme']->get_error_message() ?></p>
41
+ <?php else: ?>
42
+ <form id="fw-ext-update-theme" method="post" action="update-core.php?action=fw-update-theme">
43
+ <p><?php
44
+ _e(sprintf('You have version %s installed. Update to %s.',
45
+ fw()->theme->manifest->get_version(),
46
+ $updates['theme']['fixed_latest_version']
47
+ ), 'fw')
48
+ ?></p>
49
+ <?php wp_nonce_field(-1, '_nonce_fw_ext_update_theme'); ?>
50
+ <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Theme', 'fw')) ?>" name="update"></p>
51
+ </form>
52
+ <?php endif; ?>
53
+ <?php endif; ?>
54
+ </div>
55
+ <?php endif; ?>
56
+
57
+ <?php if (true): ?>
58
+ <div id="fw-ext-update-extensions">
59
+ <a name="fw-extensions"></a>
60
+ <h3><?php echo sprintf(__('%s Extensions', 'fw'), fw()->manifest->get_name()) ?></h3>
61
+ <?php if (empty($updates['extensions'])): ?>
62
+ <p><?php echo sprintf(__('You have the latest version of %s Extensions.', 'fw'), fw()->manifest->get_name()); ?></p>
63
+ <?php else: ?>
64
+ <?php
65
+ $one_update_mode = fw()->extensions->get('update')->get_config('extensions_as_one_update');
66
+
67
+ foreach ($updates['extensions'] as $extension) {
68
+ if (is_wp_error($extension)) {
69
+ /**
70
+ * Cancel the "One update mode" and display all extensions list table with details
71
+ * if at least one extension has an error that needs to be visible
72
+ */
73
+ $one_update_mode = false;
74
+ break;
75
+ }
76
+ }
77
+ ?>
78
+ <form id="fw-ext-update-extensions" method="post" action="update-core.php?action=fw-update-extensions">
79
+ <div class="fw-ext-update-extensions-form-detailed" <?php if ($one_update_mode): ?>style="display: none;"<?php endif; ?>>
80
+ <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Extensions', 'fw')) ?>" name="update"></p>
81
+ <?php
82
+ if (!class_exists('_FW_Ext_Update_Extensions_List_Table')) {
83
+ fw_include_file_isolated(
84
+ fw()->extensions->get('update')->get_declared_path('/includes/classes/class--fw-ext-update-extensions-list-table.php')
85
+ );
86
+ }
87
+
88
+ $list_table = new _FW_Ext_Update_Extensions_List_Table(array(
89
+ 'extensions' => $updates['extensions']
90
+ ));
91
+
92
+ $list_table->display();
93
+ ?>
94
+ <?php wp_nonce_field(-1, '_nonce_fw_ext_update_extensions'); ?>
95
+ <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Extensions', 'fw')) ?>" name="update"></p>
96
+ </div>
97
+ <?php if ($one_update_mode): ?>
98
+ <div class="fw-ext-update-extensions-form-simple">
99
+ <p style="color:#d54e21;"><?php _e('New extensions updates available.', 'fw'); ?></p>
100
+ <p><input class="button" type="submit" value="<?php echo esc_attr(__('Update Extensions', 'fw')) ?>" name="update"></p>
101
+ <script type="text/javascript">
102
+ jQuery(function($){
103
+ $('form#fw-ext-update-extensions').on('submit', function(){
104
+ $(this).find('.check-column input[type="checkbox"]').prop('checked', true);
105
+ });
106
+ });
107
+ </script>
108
+ </div>
109
+ <?php endif; ?>
110
+ </form>
111
+ <?php endif; ?>
112
+ </div>
113
+ <?php endif; ?>
framework/helpers/class-fw-access-key.php CHANGED
@@ -1,43 +1,43 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Used in public callbacks to allow call only from known caller
5
- *
6
- * For e.g. Inside some class is created an instance of FW_Access_Key with unique key 'whatever',
7
- * so nobody else can create another instance with same key, only that class owns that unique key.
8
- * Some public callback (function or method) that wants to allow to be called only by that class,
9
- * sets an requirement that some parameter should be an instance on FW_Access_Key and its key should be 'whatever'
10
- *
11
- * function my_function(FW_Access_Key $key, $another_parameter) {
12
- * if ($key->get_key() !== 'whatever') {
13
- * trigger_error('Call denied', E_USER_ERROR);
14
- * }
15
- *
16
- * //...
17
- * }
18
- */
19
- final class FW_Access_Key
20
- {
21
- private static $created_keys = array();
22
-
23
- private $key;
24
-
25
- final public function get_key()
26
- {
27
- return $this->key;
28
- }
29
-
30
- /**
31
- * @param string $unique_key unique
32
- */
33
- final public function __construct($unique_key)
34
- {
35
- if (isset(self::$created_keys[$unique_key])) {
36
- trigger_error('Key "'. $unique_key .'" already defined', E_USER_ERROR);
37
- }
38
-
39
- self::$created_keys[$unique_key] = true;
40
-
41
- $this->key = $unique_key;
42
- }
43
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Used in public callbacks to allow call only from known caller
5
+ *
6
+ * For e.g. Inside some class is created an instance of FW_Access_Key with unique key 'whatever',
7
+ * so nobody else can create another instance with same key, only that class owns that unique key.
8
+ * Some public callback (function or method) that wants to allow to be called only by that class,
9
+ * sets an requirement that some parameter should be an instance on FW_Access_Key and its key should be 'whatever'
10
+ *
11
+ * function my_function(FW_Access_Key $key, $another_parameter) {
12
+ * if ($key->get_key() !== 'whatever') {
13
+ * trigger_error('Call denied', E_USER_ERROR);
14
+ * }
15
+ *
16
+ * //...
17
+ * }
18
+ */
19
+ final class FW_Access_Key
20
+ {
21
+ private static $created_keys = array();
22
+
23
+ private $key;
24
+
25
+ final public function get_key()
26
+ {
27
+ return $this->key;
28
+ }
29
+
30
+ /**
31
+ * @param string $unique_key unique
32
+ */
33
+ final public function __construct($unique_key)
34
+ {
35
+ if (isset(self::$created_keys[$unique_key])) {
36
+ trigger_error('Key "'. $unique_key .'" already defined', E_USER_ERROR);
37
+ }
38
+
39
+ self::$created_keys[$unique_key] = true;
40
+
41
+ $this->key = $unique_key;
42
+ }
43
+ }
framework/helpers/class-fw-cache.php CHANGED
@@ -1,295 +1,283 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Memory Cache
5
- * Only for internal usage in other functions/methods, because it throws exceptions
6
- *
7
- * Recommended usage example:
8
- * try {
9
- * $value = FW_Cache::get('some/key');
10
- * } catch(FW_Cache_Not_Found_Exception $e) {
11
- * $value = get_value_from_somewhere();
12
- *
13
- * FW_Cache::set('some/key', $value);
14
- *
15
- * // (!) after set, do not do this:
16
- * $value = FW_Cache::get('some/key');
17
- * // because there is no guaranty that FW_Cache::set('some/key', $value); succeeded
18
- * // trust only your $value, cache can do clean-up right after set() and remove the value you tried to set
19
- * }
20
- *
21
- * // use $value ...
22
- */
23
- class FW_Cache
24
- {
25
- /**
26
- * The actual cache
27
- * @var array
28
- */
29
- protected static $cache = array();
30
-
31
- /**
32
- * If the PHP will have less that this memory, the cache will try to delete parts from its array to free memory
33
- *
34
- * (1024 * 1024 = 1048576 = 1 Mb) * 10
35
- */
36
- protected static $min_free_memory = 10485760;
37
-
38
- /**
39
- * Max allowed memory for PHP
40
- */
41
- protected static $memory_limit = null;
42
-
43
- /**
44
- * A special value that is used to detect if value was found in cache
45
- * We can't use null|false because these can be values set by user and we can't treat them as not existing values
46
- */
47
- protected static $not_found_value;
48
-
49
- /**
50
- * The amount of times the data was already stored in the cache.
51
- * @var int
52
- * @since 2.4.17
53
- */
54
- protected static $hits = 0;
55
-
56
- /**
57
- * Amount of times the cache did not have the value in cache.
58
- * @var int
59
- * @since 2.4.17
60
- */
61
- protected static $misses = 0;
62
-
63
- /**
64
- * Amount of times the cache free was called.
65
- * @var int
66
- * @since 2.4.17
67
- */
68
- protected static $freed = 0;
69
-
70
- protected static function get_memory_limit()
71
- {
72
- if (self::$memory_limit === null) {
73
- $memory_limit = ini_get('memory_limit');
74
-
75
- if (preg_match('/^(\d+)(.)$/', $memory_limit, $matches)) {
76
- if ($matches[2] == 'M') {
77
- $memory_limit = $matches[1] * 1024 * 1024; // nnn_m -> nnn MB
78
- } else if ($matches[2] == 'K') {
79
- $memory_limit = $matches[1] * 1024; // nnn_k -> nnn KB
80
- }
81
- }
82
-
83
- self::$memory_limit = $memory_limit;
84
- }
85
-
86
- return self::$memory_limit;
87
- }
88
-
89
- protected static function memory_exceeded()
90
- {
91
- return memory_get_usage(false) >= self::get_memory_limit() - self::$min_free_memory;
92
-
93
- // about memory_get_usage(false) http://stackoverflow.com/a/16239377/1794248
94
- }
95
-
96
- /**
97
- * @internal
98
- */
99
- public static function _init()
100
- {
101
- self::$not_found_value = new FW_Cache_Not_Found_Exception();
102
-
103
- /**
104
- * Listen often triggered hooks to clear the memory
105
- * instead of tick function https://github.com/ThemeFuse/Unyson/issues/1197
106
- * @since 2.4.17
107
- */
108
- foreach (array(
109
- 'query' => true,
110
- 'plugins_loaded' => true,
111
- 'wp_get_object_terms' => true,
112
- 'created_term' => true,
113
- 'wp_upgrade' => true,
114
- 'added_option' => true,
115
- 'updated_option' => true,
116
- 'deleted_option' => true,
117
- 'wp_after_admin_bar_render' => true,
118
- 'http_response' => true,
119
- 'oembed_result' => true,
120
- 'customize_post_value_set' => true,
121
- 'customize_save_after' => true,
122
- 'customize_render_panel' => true,
123
- 'customize_render_control' => true,
124
- 'customize_render_section' => true,
125
- 'role_has_cap' => true,
126
- 'user_has_cap' => true,
127
- 'theme_page_templates' => true,
128
- 'pre_get_users' => true,
129
- 'request' => true,
130
- 'send_headers' => true,
131
- 'updated_usermeta' => true,
132
- 'added_usermeta' => true,
133
- 'image_memory_limit' => true,
134
- 'upload_dir' => true,
135
- 'wp_head' => true,
136
- 'wp_footer' => true,
137
- 'wp' => true,
138
- 'wp_init' => true,
139
- 'fw_init' => true,
140
- 'init' => true,
141
- 'updated_postmeta' => true,
142
- 'deleted_postmeta' => true,
143
- 'setted_transient' => true,
144
- 'registered_post_type' => true,
145
- 'wp_count_posts' => true,
146
- 'wp_count_attachments' => true,
147
- 'after_delete_post' => true,
148
- 'post_updated' => true,
149
- 'wp_insert_post' => true,
150
- 'deleted_post' => true,
151
- 'clean_post_cache' => true,
152
- 'wp_restore_post_revision' => true,
153
- 'wp_delete_post_revision' => true,
154
- 'get_term' => true,
155
- 'edited_term_taxonomies' => true,
156
- 'deleted_term_taxonomy' => true,
157
- 'edited_terms' => true,
158
- 'created_term' => true,
159
- 'clean_term_cache' => true,
160
- 'edited_term_taxonomy' => true,
161
- 'switch_theme' => true,
162
- 'wp_get_update_data' => true,
163
- 'clean_user_cache' => true,
164
- 'process_text_diff_html' => true,
165
- ) as $hook => $tmp) {
166
- add_filter($hook, array(__CLASS__, 'free_memory'), 9999);
167
- }
168
- }
169
-
170
- /**
171
- * This method does nothing @since 2.4.17
172
- * but we can't delete it because it's public and maybe somebody is calling it
173
- * @return bool
174
- */
175
- public static function is_enabled()
176
- {
177
- return true;
178
- }
179
-
180
- public static function free_memory($dummy = null)
181
- {
182
- while (self::memory_exceeded() && !empty(self::$cache)) {
183
- reset(self::$cache);
184
-
185
- $key = key(self::$cache);
186
-
187
- unset(self::$cache[$key]);
188
- }
189
-
190
- ++self::$freed;
191
-
192
- /**
193
- * This method is used add_filter() so to not break anything return filter value
194
- */
195
- return $dummy;
196
- }
197
-
198
- /**
199
- * @param $keys
200
- * @param $value
201
- * @param $keys_delimiter
202
- */
203
- public static function set($keys, $value, $keys_delimiter = '/')
204
- {
205
- if (!self::is_enabled()) {
206
- return;
207
- }
208
-
209
- self::free_memory();
210
-
211
- fw_aks($keys, $value, self::$cache, $keys_delimiter);
212
-
213
- self::free_memory();
214
- }
215
-
216
- /**
217
- * Unset key from cache
218
- * @param $keys
219
- * @param $keys_delimiter
220
- */
221
- public static function del($keys, $keys_delimiter = '/')
222
- {
223
- fw_aku($keys, self::$cache, $keys_delimiter);
224
-
225
- self::free_memory();
226
- }
227
-
228
- /**
229
- * @param $keys
230
- * @param $keys_delimiter
231
- * @return mixed
232
- * @throws FW_Cache_Not_Found_Exception
233
- */
234
- public static function get($keys, $keys_delimiter = '/')
235
- {
236
- $keys = (string)$keys;
237
- $keys_arr = explode($keys_delimiter, $keys);
238
-
239
- $key = $keys_arr;
240
- $key = array_shift($key);
241
-
242
- if ($key === '' || $key === null) {
243
- trigger_error('First key must not be empty', E_USER_ERROR);
244
- }
245
-
246
- self::free_memory();
247
-
248
- $value = fw_akg($keys, self::$cache, self::$not_found_value, $keys_delimiter);
249
-
250
- self::free_memory();
251
-
252
- if ($value === self::$not_found_value) {
253
- ++self::$misses;
254
-
255
- throw new FW_Cache_Not_Found_Exception();
256
- } else {
257
- ++self::$hits;
258
-
259
- return $value;
260
- }
261
- }
262
-
263
- /**
264
- * Empty the cache
265
- */
266
- public static function clear()
267
- {
268
- self::$cache = array();
269
- }
270
-
271
- /**
272
- * Debug information
273
- * <?php add_action('admin_footer', function(){ FW_Cache::stats(); });
274
- * @since 2.4.17
275
- */
276
- public static function stats() {
277
- echo '<div style="z-index: 10000; position: relative; background: #fff; padding: 15px;">';
278
- echo '<p>';
279
- echo '<strong>Cache Hits:</strong> '. self::$hits .'<br />';
280
- echo '<strong>Cache Misses:</strong> '. self::$misses .'<br />';
281
- echo '<strong>Cache Freed:</strong> '. self::$freed .'<br />';
282
- echo '<strong>PHP Memory Peak Usage:</strong> '. fw_human_bytes(memory_get_peak_usage(false)) .'<br />';
283
- echo '</p>';
284
- echo '<ul>';
285
- foreach (self::$cache as $group => $cache) {
286
- echo "<li><strong>Group:</strong> $group - ( " . number_format( strlen( serialize( $cache ) ) / KB_IN_BYTES, 2 ) . 'k )</li>';
287
- }
288
- echo '</ul>';
289
- echo '</div>';
290
- }
291
- }
292
-
293
- class FW_Cache_Not_Found_Exception extends Exception {}
294
-
295
  FW_Cache::_init();
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Memory Cache
5
+ * Only for internal usage in other functions/methods, because it throws exceptions
6
+ *
7
+ * Recommended usage example:
8
+ * try {
9
+ * $value = FW_Cache::get('some/key');
10
+ * } catch(FW_Cache_Not_Found_Exception $e) {
11
+ * $value = get_value_from_somewhere();
12
+ *
13
+ * FW_Cache::set('some/key', $value);
14
+ *
15
+ * // (!) after set, do not do this:
16
+ * $value = FW_Cache::get('some/key');
17
+ * // because there is no guaranty that FW_Cache::set('some/key', $value); succeeded
18
+ * // trust only your $value, cache can do clean-up right after set() and remove the value you tried to set
19
+ * }
20
+ *
21
+ * // use $value ...
22
+ */
23
+ class FW_Cache
24
+ {
25
+ /**
26
+ * The actual cache
27
+ * @var array
28
+ */
29
+ protected static $cache = array();
30
+
31
+ /**
32
+ * If the PHP will have less that this memory, the cache will try to delete parts from its array to free memory
33
+ *
34
+ * (1024 * 1024 = 1048576 = 1 Mb) * 10
35
+ */
36
+ protected static $min_free_memory = 10485760;
37
+
38
+ /**
39
+ * A special value that is used to detect if value was found in cache
40
+ * We can't use null|false because these can be values set by user and we can't treat them as not existing values
41
+ */
42
+ protected static $not_found_value;
43
+
44
+ /**
45
+ * The amount of times the data was already stored in the cache.
46
+ * @var int
47
+ * @since 2.4.17
48
+ */
49
+ protected static $hits = 0;
50
+
51
+ /**
52
+ * Amount of times the cache did not have the value in cache.
53
+ * @var int
54
+ * @since 2.4.17
55
+ */
56
+ protected static $misses = 0;
57
+
58
+ /**
59
+ * Amount of times the cache free was called.
60
+ * @var int
61
+ * @since 2.4.17
62
+ */
63
+ protected static $freed = 0;
64
+
65
+ protected static function get_memory_limit()
66
+ {
67
+ $memory_limit = ini_get('memory_limit');
68
+
69
+ switch (substr($memory_limit, -1)) {
70
+ case 'M': return intval($memory_limit) * 1024 * 1024;
71
+ case 'K': return intval($memory_limit) * 1024;
72
+ case 'G': return intval($memory_limit) * 1024 * 1024 * 1024;
73
+ default: return intval($memory_limit) * 1024 * 1024;
74
+ }
75
+ }
76
+
77
+ protected static function memory_exceeded()
78
+ {
79
+ return memory_get_usage(false) >= self::get_memory_limit() - self::$min_free_memory;
80
+
81
+ // about memory_get_usage(false) http://stackoverflow.com/a/16239377/1794248
82
+ }
83
+
84
+ /**
85
+ * @internal
86
+ */
87
+ public static function _init()
88
+ {
89
+ self::$not_found_value = new FW_Cache_Not_Found_Exception();
90
+
91
+ /**
92
+ * Listen often triggered hooks to clear the memory
93
+ * instead of tick function https://github.com/ThemeFuse/Unyson/issues/1197
94
+ * @since 2.4.17
95
+ */
96
+ foreach (array(
97
+ 'query' => true,
98
+ 'plugins_loaded' => true,
99
+ 'wp_get_object_terms' => true,
100
+ 'created_term' => true,
101
+ 'wp_upgrade' => true,
102
+ 'added_option' => true,
103
+ 'updated_option' => true,
104
+ 'deleted_option' => true,
105
+ 'wp_after_admin_bar_render' => true,
106
+ 'http_response' => true,
107
+ 'oembed_result' => true,
108
+ 'customize_post_value_set' => true,
109
+ 'customize_save_after' => true,
110
+ 'customize_render_panel' => true,
111
+ 'customize_render_control' => true,
112
+ 'customize_render_section' => true,
113
+ 'role_has_cap' => true,
114
+ 'user_has_cap' => true,
115
+ 'theme_page_templates' => true,
116
+ 'pre_get_users' => true,
117
+ 'request' => true,
118
+ 'send_headers' => true,
119
+ 'updated_usermeta' => true,
120
+ 'added_usermeta' => true,
121
+ 'image_memory_limit' => true,
122
+ 'upload_dir' => true,
123
+ 'wp_head' => true,
124
+ 'wp_footer' => true,
125
+ 'wp' => true,
126
+ 'wp_init' => true,
127
+ 'fw_init' => true,
128
+ 'init' => true,
129
+ 'updated_postmeta' => true,
130
+ 'deleted_postmeta' => true,
131
+ 'setted_transient' => true,
132
+ 'registered_post_type' => true,
133
+ 'wp_count_posts' => true,
134
+ 'wp_count_attachments' => true,
135
+ 'after_delete_post' => true,
136
+ 'post_updated' => true,
137
+ 'wp_insert_post' => true,
138
+ 'deleted_post' => true,
139
+ 'clean_post_cache' => true,
140
+ 'wp_restore_post_revision' => true,
141
+ 'wp_delete_post_revision' => true,
142
+ 'get_term' => true,
143
+ 'edited_term_taxonomies' => true,
144
+ 'deleted_term_taxonomy' => true,
145
+ 'edited_terms' => true,
146
+ 'created_term' => true,
147
+ 'clean_term_cache' => true,
148
+ 'edited_term_taxonomy' => true,
149
+ 'switch_theme' => true,
150
+ 'wp_get_update_data' => true,
151
+ 'clean_user_cache' => true,
152
+ 'process_text_diff_html' => true,
153
+ ) as $hook => $tmp) {
154
+ add_filter($hook, array(__CLASS__, 'free_memory'), 9999);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * This method does nothing @since 2.4.17
160
+ * but we can't delete it because it's public and maybe somebody is calling it
161
+ * @return bool
162
+ */
163
+ public static function is_enabled()
164
+ {
165
+ return true;
166
+ }
167
+
168
+ public static function free_memory($dummy = null)
169
+ {
170
+ while (self::memory_exceeded() && !empty(self::$cache)) {
171
+ reset(self::$cache);
172
+
173
+ $key = key(self::$cache);
174
+
175
+ unset(self::$cache[$key]);
176
+ }
177
+
178
+ ++self::$freed;
179
+
180
+ /**
181
+ * This method is used add_filter() so to not break anything return filter value
182
+ */
183
+ return $dummy;
184
+ }
185
+
186
+ /**
187
+ * @param $keys
188
+ * @param $value
189
+ * @param $keys_delimiter
190
+ */
191
+ public static function set($keys, $value, $keys_delimiter = '/')
192
+ {
193
+ if (!self::is_enabled()) {
194
+ return;
195
+ }
196
+
197
+ self::free_memory();
198
+
199
+ fw_aks($keys, $value, self::$cache, $keys_delimiter);
200
+
201
+ self::free_memory();
202
+ }
203
+
204
+ /**
205
+ * Unset key from cache
206
+ * @param $keys
207
+ * @param $keys_delimiter
208
+ */
209
+ public static function del($keys, $keys_delimiter = '/')
210
+ {
211
+ fw_aku($keys, self::$cache, $keys_delimiter);
212
+
213
+ self::free_memory();
214
+ }
215
+
216
+ /**
217
+ * @param $keys
218
+ * @param $keys_delimiter
219
+ * @return mixed
220
+ * @throws FW_Cache_Not_Found_Exception
221
+ */
222
+ public static function get($keys, $keys_delimiter = '/')
223
+ {
224
+ $keys = (string)$keys;
225
+ $keys_arr = explode($keys_delimiter, $keys);
226
+
227
+ $key = $keys_arr;
228
+ $key = array_shift($key);
229
+
230
+ if ($key === '' || $key === null) {
231
+ trigger_error('First key must not be empty', E_USER_ERROR);
232
+ }
233
+
234
+ self::free_memory();
235
+
236
+ $value = fw_akg($keys, self::$cache, self::$not_found_value, $keys_delimiter);
237
+
238
+ self::free_memory();
239
+
240
+ if ($value === self::$not_found_value) {
241
+ ++self::$misses;
242
+
243
+ throw new FW_Cache_Not_Found_Exception();
244
+ } else {
245
+ ++self::$hits;
246
+
247
+ return $value;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Empty the cache
253
+ */
254
+ public static function clear()
255
+ {
256
+ self::$cache = array();
257
+ }
258
+
259
+ /**
260
+ * Debug information
261
+ * <?php add_action('admin_footer', function(){ FW_Cache::stats(); });
262
+ * @since 2.4.17
263
+ */
264
+ public static function stats() {
265
+ echo '<div style="z-index: 10000; position: relative; background: #fff; padding: 15px;">';
266
+ echo '<p>';
267
+ echo '<strong>Cache Hits:</strong> '. self::$hits .'<br />';
268
+ echo '<strong>Cache Misses:</strong> '. self::$misses .'<br />';
269
+ echo '<strong>Cache Freed:</strong> '. self::$freed .'<br />';
270
+ echo '<strong>PHP Memory Peak Usage:</strong> '. fw_human_bytes(memory_get_peak_usage(false)) .'<br />';
271
+ echo '</p>';
272
+ echo '<ul>';
273
+ foreach (self::$cache as $group => $cache) {
274
+ echo "<li><strong>Group:</strong> $group - ( " . number_format( strlen( serialize( $cache ) ) / KB_IN_BYTES, 2 ) . 'k )</li>';
275
+ }
276
+ echo '</ul>';
277
+ echo '</div>';
278
+ }
279
+ }
280
+
281
+ class FW_Cache_Not_Found_Exception extends Exception {}
282
+
 
 
 
 
 
 
 
 
 
 
 
 
283
  FW_Cache::_init();
framework/helpers/class-fw-dumper.php CHANGED
@@ -1,124 +1,124 @@
1
- <?php
2
-
3
- // original source: https://code.google.com/p/prado3/source/browse/trunk/framework/Util/TVar_dumper.php
4
-
5
- /**
6
- * TVar_dumper class.
7
- *
8
- * TVar_dumper is intended to replace the buggy PHP function var_dump and print_r.
9
- * It can correctly identify the recursively referenced objects in a complex
10
- * object structure. It also has a recursive depth control to avoid indefinite
11
- * recursive display of some peculiar variables.
12
- *
13
- * TVar_dumper can be used as follows,
14
- * <code>
15
- * echo TVar_dumper::dump($var);
16
- * </code>
17
- *
18
- * @author Qiang Xue <qiang.xue@gmail.com>
19
- * @version $Id$
20
- * @package System.Util
21
- * @since 3.0
22
- */
23
- class FW_Dumper
24
- {
25
- private static $_objects;
26
- private static $_output;
27
- private static $_depth;
28
-
29
- /**
30
- * Converts a variable into a string representation.
31
- * This method achieves the similar functionality as var_dump and print_r
32
- * but is more robust when handling complex objects such as PRADO controls.
33
- * @param mixed $var Variable to be dumped
34
- * @param integer $depth Maximum depth that the dumper should go into the variable. Defaults to 10.
35
- * @return string the string representation of the variable
36
- */
37
- public static function dump($var, $depth=10)
38
- {
39
- self::reset_internals();
40
-
41
- self::$_depth=$depth;
42
- self::dump_internal($var,0);
43
-
44
- $output = self::$_output;
45
-
46
- self::reset_internals();
47
-
48
- return $output;
49
- }
50
-
51
- private static function reset_internals()
52
- {
53
- self::$_output='';
54
- self::$_objects=array();
55
- self::$_depth=10;
56
- }
57
-
58
- private static function dump_internal($var,$level)
59
- {
60
- switch(gettype($var)) {
61
- case 'boolean':
62
- self::$_output.=$var?'true':'false';
63
- break;
64
- case 'integer':
65
- self::$_output.="$var";
66
- break;
67
- case 'double':
68
- self::$_output.="$var";
69
- break;
70
- case 'string':
71
- self::$_output.="'$var'";
72
- break;
73
- case 'resource':
74
- self::$_output.='{resource}';
75
- break;
76
- case 'NULL':
77
- self::$_output.="null";
78
- break;
79
- case 'unknown type':
80
- self::$_output.='{unknown}';
81
- break;
82
- case 'array':
83
- if(self::$_depth<=$level)
84
- self::$_output.='array(...)';
85
- else if(empty($var))
86
- self::$_output.='array()';
87
- else
88
- {
89
- $keys=array_keys($var);
90
- $spaces=str_repeat(' ',$level*4);
91
- self::$_output.="array\n".$spaces.'(';
92
- foreach($keys as $key)
93
- {
94
- self::$_output.="\n".$spaces." [$key] => ";
95
- self::$_output.=self::dump_internal($var[$key],$level+1);
96
- }
97
- self::$_output.="\n".$spaces.')';
98
- }
99
- break;
100
- case 'object':
101
- if(($id=array_search($var,self::$_objects,true))!==false)
102
- self::$_output.=get_class($var).'(...)';
103
- else if(self::$_depth<=$level)
104
- self::$_output.=get_class($var).'(...)';
105
- else
106
- {
107
- $id=array_push(self::$_objects,$var);
108
- $class_name=get_class($var);
109
- $members=(array)$var;
110
- $keys=array_keys($members);
111
- $spaces=str_repeat(' ',$level*4);
112
- self::$_output.="$class_name\n".$spaces.'(';
113
- foreach($keys as $key)
114
- {
115
- $key_display=strtr(trim($key),array("\0"=>':'));
116
- self::$_output.="\n".$spaces." [$key_display] => ";
117
- self::$_output.=self::dump_internal($members[$key],$level+1);
118
- }
119
- self::$_output.="\n".$spaces.')';
120
- }
121
- break;
122
- }
123
- }
124
  }
1
+ <?php
2
+
3
+ // original source: https://code.google.com/p/prado3/source/browse/trunk/framework/Util/TVar_dumper.php
4
+
5
+ /**
6
+ * TVar_dumper class.
7
+ *
8
+ * TVar_dumper is intended to replace the buggy PHP function var_dump and print_r.
9
+ * It can correctly identify the recursively referenced objects in a complex
10
+ * object structure. It also has a recursive depth control to avoid indefinite
11
+ * recursive display of some peculiar variables.
12
+ *
13
+ * TVar_dumper can be used as follows,
14
+ * <code>
15
+ * echo TVar_dumper::dump($var);
16
+ * </code>
17
+ *
18
+ * @author Qiang Xue <qiang.xue@gmail.com>
19
+ * @version $Id$
20
+ * @package System.Util
21
+ * @since 3.0
22
+ */
23
+ class FW_Dumper
24
+ {
25
+ private static $_objects;
26
+ private static $_output;
27
+ private static $_depth;
28
+
29
+ /**
30
+ * Converts a variable into a string representation.
31
+ * This method achieves the similar functionality as var_dump and print_r
32
+ * but is more robust when handling complex objects such as PRADO controls.
33
+ * @param mixed $var Variable to be dumped
34
+ * @param integer $depth Maximum depth that the dumper should go into the variable. Defaults to 10.
35
+ * @return string the string representation of the variable
36
+ */
37
+ public static function dump($var, $depth=10)
38
+ {
39
+ self::reset_internals();
40
+
41
+ self::$_depth=$depth;
42
+ self::dump_internal($var,0);
43
+
44
+ $output = self::$_output;
45
+
46
+ self::reset_internals();
47
+
48
+ return $output;
49
+ }
50
+
51
+ private static function reset_internals()
52
+ {
53
+ self::$_output='';
54
+ self::$_objects=array();
55
+ self::$_depth=10;
56
+ }
57
+
58
+ private static function dump_internal($var,$level)
59
+ {
60
+ switch(gettype($var)) {
61
+ case 'boolean':
62
+ self::$_output.=$var?'true':'false';
63
+ break;
64
+ case 'integer':
65
+ self::$_output.="$var";
66
+ break;
67
+ case 'double':
68
+ self::$_output.="$var";
69
+ break;
70
+ case 'string':
71
+ self::$_output.="'$var'";
72
+ break;
73
+ case 'resource':
74
+ self::$_output.='{resource}';
75
+ break;
76
+ case 'NULL':
77
+ self::$_output.="null";
78
+ break;
79
+ case 'unknown type':
80
+ self::$_output.='{unknown}';
81
+ break;
82
+ case 'array':
83
+ if(self::$_depth<=$level)
84
+ self::$_output.='array(...)';
85
+ else if(empty($var))
86
+ self::$_output.='array()';
87
+ else
88
+ {
89
+ $keys=array_keys($var);
90
+ $spaces=str_repeat(' ',$level*4);
91
+ self::$_output.="array\n".$spaces.'(';
92
+ foreach($keys as $key)
93
+ {
94
+ self::$_output.="\n".$spaces." [$key] => ";
95
+ self::$_output.=self::dump_internal($var[$key],$level+1);
96
+ }
97
+ self::$_output.="\n".$spaces.')';
98
+ }
99
+ break;
100
+ case 'object':
101
+ if(($id=array_search($var,self::$_objects,true))!==false)
102
+ self::$_output.=get_class($var).'(...)';
103
+ else if(self::$_depth<=$level)
104
+ self::$_output.=get_class($var).'(...)';
105
+ else
106
+ {
107
+ $id=array_push(self::$_objects,$var);
108
+ $class_name=get_class($var);
109
+ $members=(array)$var;
110
+ $keys=array_keys($members);
111
+ $spaces=str_repeat(' ',$level*4);
112
+ self::$_output.="$class_name\n".$spaces.'(';
113
+ foreach($keys as $key)
114
+ {
115
+ $key_display=strtr(trim($key),array("\0"=>':'));
116
+ self::$_output.="\n".$spaces." [$key_display] => ";
117
+ self::$_output.=self::dump_internal($members[$key],$level+1);
118
+ }
119
+ self::$_output.="\n".$spaces.')';
120
+ }
121
+ break;
122
+ }
123
+ }
124
  }
framework/helpers/class-fw-flash-messages.php CHANGED
@@ -1,312 +1,312 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Set flash messages
5
- **
6
- * Store messages in session (to not be lost between redirects) and remove them after they were shown to the user
7
- */
8
- class FW_Flash_Messages
9
- {
10
- private static $available_types = array(
11
- // 'type' => 'backend class/type' (only 2 backend types exists: error and updated)
12
- 'error' => 'error',
13
- 'warning' => 'update-nag',
14
- 'info' => 'updated',
15
- 'success' => 'updated'
16
- );
17
-
18
- private static $session_key = 'fw_flash_messages';
19
-
20
- private static $frontend_printed = false;
21
-
22
- private static function get_messages()
23
- {
24
- $messages = FW_Session::get(self::$session_key);
25
-
26
- if (empty($messages) || !is_array($messages)) {
27
- $messages = array_fill_keys(array_keys(self::$available_types), array());
28
- }
29
-
30
- return $messages;
31
- }
32
-
33
- private static function set_messages(array $messages)
34
- {
35
- FW_Session::set(self::$session_key, $messages);
36
- }
37
-
38
- /**
39
- * Remove messages with ids from pending remove
40
- */
41
- private static function process_pending_remove_ids()
42
- {
43
- $pending_remove = array();
44
-
45
- foreach (self::get_messages() as $messages) {
46
- if (empty($messages)) {
47
- continue;
48
- }
49
-
50
- foreach ($messages as $message) {
51
- if (empty($message['remove_ids'])) {
52
- continue;
53
- }
54
-
55
- foreach ($message['remove_ids'] as $remove_id) {
56
- $pending_remove[$remove_id] = true;
57
- }
58
- }
59
- }
60
-
61
- if (empty($pending_remove)) {
62
- return;
63
- }
64
-
65
- $types = self::get_messages();
66
-
67
- foreach ($types as $type => $messages) {
68
- if (empty($messages)) {
69
- continue;
70
- }
71
-
72
- foreach ($messages as $id => $message) {
73
- if (isset($pending_remove[$id])) {
74
- unset($types[$type][$id]);
75
- }
76
- }
77
- }
78
-
79
- self::set_messages( $types );
80
- }
81
-
82
- /**
83
- * Add flash message
84
- **
85
- * @param string $id Unique id of the message
86
- * @param string $message Message (can be html)
87
- * @param string $type Type from $available_types
88
- * @param array $removed_ids Remove flashes with this id(s)
89
- * (For e.g. your message is success and some known error messages ids needs to be removed
90
- * because they are not relevant anymore, your success message suppress/cancels them)
91
- */
92
- public static function add($id, $message, $type = 'info', array $removed_ids = array())
93
- {
94
- if (!isset(self::$available_types[$type])) {
95
- trigger_error(sprintf(__('Invalid flash message type: %s', 'tfuse'), $type), E_USER_WARNING);
96
- $type = 'info';
97
- }
98
-
99
- $messages = self::get_messages();
100
-
101
- $messages[$type][$id] = array(
102
- 'message' => $message,
103
- 'remove_ids' => $removed_ids,
104
- );
105
-
106
- self::set_messages($messages);
107
- }
108
-
109
- /**
110
- * Use this method to print messages html in backend
111
- * (used in action at the end of the file)
112
- * @internal
113
- */
114
- public static function _print_backend()
115
- {
116
- self::process_pending_remove_ids();
117
-
118
- $html = array_fill_keys(array_keys(self::$available_types), '');
119
-
120
- $all_messages = self::get_messages();
121
-
122
- foreach ($all_messages as $type => $messages) {
123
- if (!empty($messages)) {
124
- foreach ($messages as $id => $data) {
125
- $html[$type] .=
126
- '<div class="'. self::$available_types[$type] .' fw-flash-message">'.
127
- '<p data-id="'. esc_attr($id) .'">'. $data['message'] .'</p>'.
128
- '</div>';
129
-
130
- unset($all_messages[$type][$id]);
131
- }
132
-
133
- $html[$type] = '<div class="fw-flash-type-'. $type .'">'. $html[$type] .'</div>';
134
- }
135
- }
136
-
137
- unset($success, $error, $info);
138
-
139
- self::set_messages($all_messages);
140
-
141
- echo '<div class="fw-flash-messages">'. implode("\n\n", $html) .'</div>';
142
- }
143
-
144
- /**
145
- * Use this method to print messages html in frontend
146
- * @return bool If some html was printed or not
147
- */
148
- public static function _print_frontend()
149
- {
150
- self::process_pending_remove_ids();
151
-
152
- $html = array_fill_keys(array_keys(self::$available_types), '');
153
- $all_messages = self::get_messages();
154
-
155
- $messages_exists = false;
156
-
157
- foreach ($all_messages as $type => $messages) {
158
- if (empty($messages)) {
159
- continue;
160
- }
161
-
162
- foreach ($messages as $id => $data) {
163
- $html[$type] .= '<li class="fw-flash-message">'. nl2br($data['message']) .'</li>';
164
-
165
- unset($all_messages[$type][$id]);
166
- }
167
-
168
- $html[$type] = '<ul class="fw-flash-type-'. $type .'">'. $html[$type] .'</ul>';
169
-
170
- $messages_exists = true;
171
- }
172
-
173
- self::set_messages($all_messages);
174
-
175
- self::$frontend_printed = true;
176
-
177
- if ($messages_exists) {
178
- echo '<div class="fw-flash-messages">';
179
- echo implode("\n\n", $html);
180
- echo '</div>';
181
-
182
- return true;
183
- } else {
184
- return false;
185
- }
186
- }
187
-
188
- public static function _frontend_printed()
189
- {
190
- return self::$frontend_printed;
191
- }
192
-
193
- public static function _get_messages($clear = false)
194
- {
195
- self::process_pending_remove_ids();
196
-
197
- $messages = self::get_messages();
198
-
199
- if ($clear) {
200
- self::set_messages(array());
201
- }
202
-
203
- return $messages;
204
- }
205
- }
206
-
207
- if (is_admin()) {
208
- /**
209
- * Start the session before the content is sent to prevent the "headers already sent" warning
210
- * @internal
211
- */
212
- function _action_fw_flash_message_backend_prepare() {
213
- if (!session_id()) {
214
- session_start();
215
- }
216
- }
217
- add_action('current_screen', '_action_fw_flash_message_backend_prepare', 9999);
218
-
219
- /**
220
- * Display flash messages in backend as notices
221
- */
222
- add_action( 'admin_notices', array( 'FW_Flash_Messages', '_print_backend' ) );
223
- } else {
224
- /**
225
- * Start the session before the content is sent to prevent the "headers already sent" warning
226
- * @internal
227
- */
228
- function _action_fw_flash_message_frontend_prepare() {
229
- if (
230
- /**
231
- * In ajax it's not possible to call flash message after headers were sent,
232
- * so there will be no "headers already sent" warning.
233
- * Also in the Backups extension, are made many internal ajax request,
234
- * each creating a new independent request that don't remember/use session cookie from previous request,
235
- * thus on server side are created many (not used) new sessions.
236
- */
237
- !(defined('DOING_AJAX') && DOING_AJAX)
238
- &&
239
- !session_id()
240
- ) {
241
- session_start();
242
- }
243
- }
244
- add_action('send_headers', '_action_fw_flash_message_frontend_prepare', 9999);
245
-
246
- /**
247
- * Print flash messages in frontend if this has not been done from theme
248
- */
249
- function _action_fw_flash_message_frontend_print() {
250
- if (FW_Flash_Messages::_frontend_printed()) {
251
- return;
252
- }
253
-
254
- if (!FW_Flash_Messages::_print_frontend()) {
255
- return;
256
- }
257
-
258
- ?>
259
- <script type="text/javascript">
260
- (function(){
261
- if (typeof jQuery === "undefined") {
262
- return;
263
- }
264
-
265
- jQuery(function($){
266
- var $container;
267
-
268
- // Try to find the content element
269
- {
270
- var selector, selectors = [
271
- '#main #content',
272
- '#content #main',
273
- '#main',
274
- '#content',
275
- '#content-container',
276
- '#container',
277
- '.container:first'
278
- ];
279
-
280
- while (selector = selectors.shift()) {
281
- $container = $(selector);
282
-
283
- if ($container.length) {
284
- break;
285
- }
286
- }
287
- }
288
-
289
- if (!$container.length) {
290
- // Try to find main page H1 container
291
- $container = $('h1:first').parent();
292
- }
293
-
294
- if (!$container.length) {
295
- // If nothing found, just add to body
296
- $container = $(document.body);
297
- }
298
-
299
- $(".fw-flash-messages").prependTo($container);
300
- });
301
- })();
302
- </script>
303
- <style type="text/css">
304
- .fw-flash-messages .fw-flash-type-error { color: #f00; }
305
- .fw-flash-messages .fw-flash-type-warning { color: #f70; }
306
- .fw-flash-messages .fw-flash-type-success { color: #070; }
307
- .fw-flash-messages .fw-flash-type-info { color: #07f; }
308
- </style>
309
- <?php
310
- }
311
- add_action('wp_footer', '_action_fw_flash_message_frontend_print', 9999);
312
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Set flash messages
5
+ **
6
+ * Store messages in session (to not be lost between redirects) and remove them after they were shown to the user
7
+ */
8
+ class FW_Flash_Messages
9
+ {
10
+ private static $available_types = array(
11
+ // 'type' => 'backend class/type' (only 2 backend types exists: error and updated)
12
+ 'error' => 'error',
13
+ 'warning' => 'update-nag',
14
+ 'info' => 'updated',
15
+ 'success' => 'updated'
16
+ );
17
+
18
+ private static $session_key = 'fw_flash_messages';
19
+
20
+ private static $frontend_printed = false;
21
+
22
+ private static function get_messages()
23
+ {
24
+ $messages = FW_Session::get(self::$session_key);
25
+
26
+ if (empty($messages) || !is_array($messages)) {
27
+ $messages = array_fill_keys(array_keys(self::$available_types), array());
28
+ }
29
+
30
+ return $messages;
31
+ }
32
+
33
+ private static function set_messages(array $messages)
34
+ {
35
+ FW_Session::set(self::$session_key, $messages);
36
+ }
37
+
38
+ /**
39
+ * Remove messages with ids from pending remove
40
+ */
41
+ private static function process_pending_remove_ids()
42
+ {
43
+ $pending_remove = array();
44
+
45
+ foreach (self::get_messages() as $messages) {
46
+ if (empty($messages)) {
47
+ continue;
48
+ }
49
+
50
+ foreach ($messages as $message) {
51
+ if (empty($message['remove_ids'])) {
52
+ continue;
53
+ }
54
+
55
+ foreach ($message['remove_ids'] as $remove_id) {
56
+ $pending_remove[$remove_id] = true;
57
+ }
58
+ }
59
+ }
60
+
61
+ if (empty($pending_remove)) {
62
+ return;
63
+ }
64
+
65
+ $types = self::get_messages();
66
+
67
+ foreach ($types as $type => $messages) {
68
+ if (empty($messages)) {
69
+ continue;
70
+ }
71
+
72
+ foreach ($messages as $id => $message) {
73
+ if (isset($pending_remove[$id])) {
74
+ unset($types[$type][$id]);
75
+ }
76
+ }
77
+ }
78
+
79
+ self::set_messages( $types );
80
+ }
81
+
82
+ /**
83
+ * Add flash message
84
+ **
85
+ * @param string $id Unique id of the message
86
+ * @param string $message Message (can be html)
87
+ * @param string $type Type from $available_types
88
+ * @param array $removed_ids Remove flashes with this id(s)
89
+ * (For e.g. your message is success and some known error messages ids needs to be removed
90
+ * because they are not relevant anymore, your success message suppress/cancels them)
91
+ */
92
+ public static function add($id, $message, $type = 'info', array $removed_ids = array())
93
+ {
94
+ if (!isset(self::$available_types[$type])) {
95
+ trigger_error(sprintf(__('Invalid flash message type: %s', 'tfuse'), $type), E_USER_WARNING);
96
+ $type = 'info';
97
+ }
98
+
99
+ $messages = self::get_messages();
100
+
101
+ $messages[$type][$id] = array(
102
+ 'message' => $message,
103
+ 'remove_ids' => $removed_ids,
104
+ );
105
+
106
+ self::set_messages($messages);
107
+ }
108
+
109
+ /**
110
+ * Use this method to print messages html in backend
111
+ * (used in action at the end of the file)
112
+ * @internal
113
+ */
114
+ public static function _print_backend()
115
+ {
116
+ self::process_pending_remove_ids();
117
+
118
+ $html = array_fill_keys(array_keys(self::$available_types), '');
119
+
120
+ $all_messages = self::get_messages();
121
+
122
+ foreach ($all_messages as $type => $messages) {
123
+ if (!empty($messages)) {
124
+ foreach ($messages as $id => $data) {
125
+ $html[$type] .=
126
+ '<div class="'. self::$available_types[$type] .' fw-flash-message">'.
127
+ '<p data-id="'. esc_attr($id) .'">'. $data['message'] .'</p>'.
128
+ '</div>';
129
+
130
+ unset($all_messages[$type][$id]);
131
+ }
132
+
133
+ $html[$type] = '<div class="fw-flash-type-'. $type .'">'. $html[$type] .'</div>';
134
+ }
135
+ }
136
+
137
+ unset($success, $error, $info);
138
+
139
+ self::set_messages($all_messages);
140
+
141
+ echo '<div class="fw-flash-messages">'. implode("\n\n", $html) .'</div>';
142
+ }
143
+
144
+ /**
145
+ * Use this method to print messages html in frontend
146
+ * @return bool If some html was printed or not
147
+ */
148
+ public static function _print_frontend()
149
+ {
150
+ self::process_pending_remove_ids();
151
+
152
+ $html = array_fill_keys(array_keys(self::$available_types), '');
153
+ $all_messages = self::get_messages();
154
+
155
+ $messages_exists = false;
156
+
157
+ foreach ($all_messages as $type => $messages) {
158
+ if (empty($messages)) {
159
+ continue;
160
+ }
161
+
162
+ foreach ($messages as $id => $data) {
163
+ $html[$type] .= '<li class="fw-flash-message">'. nl2br($data['message']) .'</li>';
164
+
165
+ unset($all_messages[$type][$id]);
166
+ }
167
+
168
+ $html[$type] = '<ul class="fw-flash-type-'. $type .'">'. $html[$type] .'</ul>';
169
+
170
+ $messages_exists = true;
171
+ }
172
+
173
+ self::set_messages($all_messages);
174
+
175
+ self::$frontend_printed = true;
176
+
177
+ if ($messages_exists) {
178
+ echo '<div class="fw-flash-messages">';
179
+ echo implode("\n\n", $html);
180
+ echo '</div>';
181
+
182
+ return true;
183
+ } else {
184
+ return false;
185
+ }
186
+ }
187
+
188
+ public static function _frontend_printed()
189
+ {
190
+ return self::$frontend_printed;
191
+ }
192
+
193
+ public static function _get_messages($clear = false)
194
+ {
195
+ self::process_pending_remove_ids();
196
+
197
+ $messages = self::get_messages();
198
+
199
+ if ($clear) {
200
+ self::set_messages(array());
201
+ }
202
+
203
+ return $messages;
204
+ }
205
+ }
206
+
207
+ if (is_admin()) {
208
+ /**
209
+ * Start the session before the content is sent to prevent the "headers already sent" warning
210
+ * @internal
211
+ */
212
+ function _action_fw_flash_message_backend_prepare() {
213
+ if (!session_id()) {
214
+ session_start();
215
+ }
216
+ }
217
+ add_action('current_screen', '_action_fw_flash_message_backend_prepare', 9999);
218
+
219
+ /**
220
+ * Display flash messages in backend as notices
221
+ */
222
+ add_action( 'admin_notices', array( 'FW_Flash_Messages', '_print_backend' ) );
223
+ } else {
224
+ /**
225
+ * Start the session before the content is sent to prevent the "headers already sent" warning
226
+ * @internal
227
+ */
228
+ function _action_fw_flash_message_frontend_prepare() {
229
+ if (
230
+ /**
231
+ * In ajax it's not possible to call flash message after headers were sent,
232
+ * so there will be no "headers already sent" warning.
233
+ * Also in the Backups extension, are made many internal ajax request,
234
+ * each creating a new independent request that don't remember/use session cookie from previous request,
235
+ * thus on server side are created many (not used) new sessions.
236
+ */
237
+ !(defined('DOING_AJAX') && DOING_AJAX)
238
+ &&
239
+ !session_id()
240
+ ) {
241
+ session_start();
242
+ }
243
+ }
244
+ add_action('send_headers', '_action_fw_flash_message_frontend_prepare', 9999);
245
+
246
+ /**
247
+ * Print flash messages in frontend if this has not been done from theme
248
+ */
249
+ function _action_fw_flash_message_frontend_print() {
250
+ if (FW_Flash_Messages::_frontend_printed()) {
251
+ return;
252
+ }
253
+
254
+ if (!FW_Flash_Messages::_print_frontend()) {
255
+ return;
256
+ }
257
+
258
+ ?>
259
+ <script type="text/javascript">
260
+ (function(){
261
+ if (typeof jQuery === "undefined") {
262
+ return;
263
+ }
264
+
265
+ jQuery(function($){
266
+ var $container;
267
+
268
+ // Try to find the content element
269
+ {
270
+ var selector, selectors = [
271
+ '#main #content',
272
+ '#content #main',
273
+ '#main',
274
+ '#content',
275
+ '#content-container',
276
+ '#container',
277
+ '.container:first'
278
+ ];
279
+
280
+ while (selector = selectors.shift()) {
281
+ $container = $(selector);
282
+
283
+ if ($container.length) {
284
+ break;
285
+ }
286
+ }
287
+ }
288
+
289
+ if (!$container.length) {
290
+ // Try to find main page H1 container
291
+ $container = $('h1:first').parent();
292
+ }
293
+
294
+ if (!$container.length) {
295
+ // If nothing found, just add to body
296
+ $container = $(document.body);
297
+ }
298
+
299
+ $(".fw-flash-messages").prependTo($container);
300
+ });
301
+ })();
302
+ </script>
303
+ <style type="text/css">
304
+ .fw-flash-messages .fw-flash-type-error { color: #f00; }
305
+ .fw-flash-messages .fw-flash-type-warning { color: #f70; }
306
+ .fw-flash-messages .fw-flash-type-success { color: #070; }
307
+ .fw-flash-messages .fw-flash-type-info { color: #07f; }
308
+ </style>
309
+ <?php
310
+ }
311
+ add_action('wp_footer', '_action_fw_flash_message_frontend_print', 9999);
312
+ }
framework/helpers/class-fw-form.php CHANGED
@@ -1,585 +1,585 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- /**
6
- * Dynamic forms
7
- */
8
- class FW_Form {
9
- /**
10
- * Store all form ids created with this class
11
- * @var FW_Form[] {'form_id' => instance}
12
- */
13
- protected static $forms = array();
14
-
15
- /**
16
- * The id of the submitted form id
17
- * @var string
18
- */
19
- protected static $submitted_id;
20
-
21
- /**
22
- * Hidden input name that stores the form id
23
- * @var string
24
- */
25
- protected static $id_input_name = 'fwf';
26
-
27
- /**
28
- * Form id
29
- * @var string
30
- */
31
- protected $id;
32
-
33
- /**
34
- * Html attributes for <form> tag
35
- * @var array
36
- */
37
- protected $attr;
38
-
39
- /**
40
- * Found validation errors
41
- * @var array
42
- */
43
- protected $errors;
44
-
45
- /**
46
- * If the get_errors() method was called at leas once
47
- * @var bool
48
- */
49
- protected $errors_accessed = false;
50
-
51
- /**
52
- * If current request is the submit of this form
53
- * @var bool
54
- */
55
- protected $is_submitted;
56
-
57
- /**
58
- * @var bool
59
- */
60
- protected $validate_and_save_called = false;
61
-
62
- protected $callbacks = array(
63
- 'render' => false,
64
- 'validate' => false,
65
- 'save' => false
66
- );
67
-
68
- /**
69
- * @param string $id Unique
70
- * @param array $data (optional)
71
- * array(
72
- * 'render' => callback // The callback that will render the form's html
73
- * 'validate' => callback // The callback that will validate user input
74
- * 'save' => callback // The callback that will save successfully validated user input
75
- * 'attr' => array() // Custom <form ...> attributes
76
- * )
77
- */
78
- public function __construct( $id, $data = array() ) {
79
- if ( isset( self::$forms[ $id ] ) ) {
80
- trigger_error( sprintf( __( 'Form with id "%s" was already defined', 'fw' ), $id ), E_USER_ERROR );
81
- }
82
-
83
- $this->id = $id;
84
-
85
- self::$forms[ $this->id ] =& $this;
86
-
87
- // prepare $this->attr
88
- {
89
- if ( ! isset( $data['attr'] ) || ! is_array( $data['attr'] ) ) {
90
- $data['attr'] = array();
91
- }
92
-
93
- $data['attr']['data-fw-form-id'] = $this->id;
94
-
95
- /** @deprecated */
96
- $data['attr']['class'] = 'fw_form_' . $this->id;
97
-
98
- if ( isset( $data['attr']['method'] ) ) {
99
- $data['attr']['method'] = strtolower( $data['attr']['method'] );
100
-
101
- $data['attr']['method'] = in_array( $data['attr']['method'], array( 'get', 'post' ) )
102
- ? $data['attr']['method']
103
- : 'post';
104
- } else {
105
- $data['attr']['method'] = 'post';
106
- }
107
-
108
- if ( ! isset( $data['attr']['action'] ) ) {
109
- $data['attr']['action'] = fw_current_url();
110
- }
111
-
112
- $this->attr = $data['attr'];
113
- }
114
-
115
- // prepare $this->callbacks
116
- {
117
- $this->callbacks = array(
118
- 'render' => empty( $data['render'] ) ? false : $data['render'],
119
- 'validate' => empty( $data['validate'] ) ? false : $data['validate'],
120
- 'save' => empty( $data['save'] ) ? false : $data['save'],
121
- );
122
- }
123
-
124
- if ( did_action( 'wp_loaded' ) ) {
125
- // in case if form instance was created after action
126
- $this->_validate_and_save();
127
- } else {
128
- // attach to an action before 'send_headers' action, to be able to do redirects
129
- add_action( 'wp_loaded', array( $this, '_validate_and_save' ), 101 );
130
- }
131
- }
132
-
133
- protected function validate() {
134
- if ( is_array( $this->errors ) ) {
135
- trigger_error( __METHOD__ . ' already called', E_USER_WARNING );
136
-
137
- return;
138
- }
139
-
140
- /**
141
- * Errors array {'input[name]' => 'Error message'}
142
- */
143
- $errors = array();
144
-
145
- /**
146
- * Call validate callback
147
- *
148
- * Callback must 'manually' extract input values from $_POST (or $_GET)
149
- */
150
- if ( $this->callbacks['validate'] ) {
151
- $errors = call_user_func_array( $this->callbacks['validate'], array( $errors ) );
152
-
153
- if ( ! is_array( $errors ) ) {
154
-
155
- $errors = array();
156
- }
157
- }
158
-
159
- /**
160
- * check nonce
161
- */
162
- if ( $this->attr['method'] == 'post' ) {
163
- $nonce_name = '_nonce_' . md5( $this->id );
164
-
165
- if ( ! isset( $_REQUEST[ $nonce_name ] ) || wp_verify_nonce( $_REQUEST[ $nonce_name ],
166
- 'submit_fwf' ) === false
167
- ) {
168
- $errors[ $nonce_name ] = __( 'Nonce verification failed', 'fw' );
169
- }
170
- }
171
-
172
- $this->errors = $errors;
173
- }
174
-
175
- protected function save() {
176
- $save_data = array(
177
- // you can set here a url for redirect after save
178
- 'redirect' => null
179
- );
180
-
181
- /**
182
- * Call save callback
183
- *
184
- * Callback must 'manually' extract input values from $_POST (or $_GET)
185
- */
186
- if ( $this->callbacks['save'] ) {
187
- $data = call_user_func_array( $this->callbacks['save'], array( $save_data ) );
188
-
189
- if ( ! is_array( $data ) ) {
190
- // fix if returned wrong data from callback
191
- $data = $save_data;
192
- }
193
-
194
- $save_data = $data;
195
-
196
- unset( $data );
197
- }
198
-
199
- if ( ! $this->is_ajax() ) {
200
- if ( isset( $save_data['redirect'] ) ) {
201
- wp_redirect( $save_data['redirect'] );
202
- exit;
203
- }
204
- }
205
-
206
- return $save_data;
207
- }
208
-
209
- protected function is_ajax() {
210
- return defined( 'DOING_AJAX' ) && DOING_AJAX;
211
- }
212
-
213
- /**
214
- * If current form was submitted, validate and save it
215
- *
216
- * Note: This callback can abort script execution if save does redirect
217
- *
218
- * @return bool|null
219
- * @internal
220
- */
221
- public function _validate_and_save() {
222
- if ( $this->validate_and_save_called ) {
223
- trigger_error( __METHOD__ . ' already called', E_USER_WARNING );
224
-
225
- return null;
226
- } else {
227
- $this->validate_and_save_called = true;
228
- }
229
-
230
- if ( ! $this->is_submitted() ) {
231
- return null;
232
- }
233
-
234
- $this->validate();
235
-
236
- if ( $this->is_ajax() ) {
237
- $json_data = array();
238
-
239
- if ( $this->is_valid() ) {
240
- $json_data['save_data'] = $this->save();
241
- } else {
242
- $json_data['errors'] = $this->get_errors();
243
- }
244
-
245
- /**
246
- * Transform flash messages structure from
247
- * array( 'type' => array( 'message_id' => array(...) ) )
248
- * to
249
- * array( 'type' => array( 'message_id' => 'Message' ) )
250
- */
251
- {
252
- $flash_messages = array();
253
-
254
- foreach (FW_Flash_Messages::_get_messages(true) as $type => $messages) {
255
- $flash_messages[$type] = array();
256
-
257
- foreach ($messages as $id => $message_data) {
258
- $flash_messages[$type][$id] = $message_data['message'];
259
- }
260
- }
261
-
262
- $json_data['flash_messages'] = $flash_messages;
263
- }
264
-
265
- /**
266
- * Important!
267
- * We can't send form html in response:
268
- *
269
- * ob_start();
270
- * $this->render();
271
- * $json_data['html'] = ob_get_clean();
272
- *
273
- * because the render() method is not called within this class
274
- * but by the code that created and owns the $form,
275
- * and it's usually called with some custom data $this->render(array(...))
276
- * that it's impossible to know here which data is that.
277
- * If we will call $this->render(); without data, this may throw errors because
278
- * the render callback may expect some custom data.
279
- * Also it may be called or not, depending on the owner code inner logic.
280
- *
281
- * The only way to get the latest form html on ajax submit
282
- * is to make a new ajax GET to current page and extract form html from the response.
283
- */
284
-
285
- if ( $this->is_valid() ) {
286
- wp_send_json_success($json_data);
287
- } else {
288
- wp_send_json_error($json_data);
289
- }
290
- } else {
291
- if ( ! $this->is_valid() ) {
292
- return false;
293
- }
294
-
295
- $this->save();
296
- }
297
-
298
- return true;
299
- }
300
-
301
- /**
302
- * @return string
303
- */
304
- public function get_id() {
305
- return $this->id;
306
- }
307
-
308
- /**
309
- * Get html attribute(s)
310
- *
311
- * @param null|string $name
312
- *
313
- * @return array|string
314
- */
315
- public function attr( $name = null ) {
316
- if ( $name ) {
317
- return isset( $this->attr[ $name ] ) ? $this->attr[ $name ] : null;
318
- } else {
319
- return $this->attr;
320
- }
321
- }
322
-
323
- /**
324
- * Render form's html
325
- *
326
- * @param array $data
327
- */
328
- public function render( $data = array() ) {
329
- $render_data = array(
330
- 'submit' => array(
331
- 'value' => __( 'Submit', 'fw' ),
332
- /**
333
- * you can set here custom submit button html
334
- * and the 'value' parameter will not be used
335
- */
336
- 'html' => null,
337
- ),
338
- 'data' => $data,
339
- 'attr' => $this->attr,
340
- );
341
-
342
- unset( $data );
343
-
344
- if ( $this->callbacks['render'] ) {
345
- ob_start();
346
-
347
- $data = call_user_func_array( $this->callbacks['render'], array( $render_data, $this ) );
348
-
349
- $html = ob_get_clean();
350
-
351
- if ( empty( $data ) ) {
352
- // fix if returned wrong data from callback
353
- $data = $render_data;
354
- }
355
-
356
- $render_data = $data;
357
-
358
- unset( $data );
359
- }
360
-
361
- // display form errors in frontend
362
- do {
363
- if (is_admin()) {
364
- // errors in admin side are displayed by a script at the end of this file
365
- break;
366
- }
367
-
368
- $submitted_form = FW_Form::get_submitted();
369
-
370
- if ( ! $submitted_form ) {
371
- break;
372
- }
373
-
374
- if ( $submitted_form->get_id() !== $this->get_id() ) {
375
- // the submitted form is not current form
376
- break;
377
- }
378
-
379
- unset($submitted_form); // not needed anymore, below will be used only with $this (because it's the same form)
380
-
381
- if ( $this->is_valid() ) {
382
- break;
383
- }
384
-
385
- /**
386
- * Use this action to customize errors display in your theme
387
- */
388
- do_action('fw_form_display_errors_frontend', $this);
389
-
390
- if ( $this->errors_accessed() ) {
391
- // already displayed, prevent/cancel default display
392
- break;
393
- }
394
-
395
- $errors = $this->get_errors();
396
-
397
- if (empty($errors)) {
398
- break;
399
- }
400
-
401
- echo '<ul class="fw-form-errors">';
402
-
403
- foreach ($errors as $input_name => $error_message) {
404
- echo fw_html_tag(
405
- 'li',
406
- array(
407
- 'data-input-name' => $input_name,
408
- ),
409
- $error_message
410
- );
411
- }
412
-
413
- echo '</ul>';
414
-
415
- unset($errors);
416
- } while(false);
417
-
418
- echo '<form '. fw_attr_to_html( $render_data['attr'] ) .' >';
419
-
420
- echo fw_html_tag('input', array(
421
- 'type' => 'hidden',
422
- 'name' => self::$id_input_name,
423
- 'value' => $this->id,
424
- ));
425
-
426
- if ( $render_data['attr']['method'] == 'post' ) {
427
- wp_nonce_field( 'submit_fwf', '_nonce_' . md5( $this->id ) );
428
- }
429
-
430
- if ( ! empty( $render_data['attr']['action'] ) && $render_data['attr']['method'] == 'get' ) {
431
- /**
432
- * Add query vars from the action attribute url to hidden inputs to not loose them
433
- */
434
-
435
- parse_str( parse_url( $render_data['attr']['action'], PHP_URL_QUERY ), $query_vars );
436
-
437
- if ( ! empty( $query_vars ) ) {
438
- foreach ( $query_vars as $var_name => $var_value ) {
439
- echo fw_html_tag('input', array(
440
- 'type' => 'hidden',
441
- 'name' => $var_name,
442
- 'value' => $var_value,
443
- ));
444
- }
445
- }
446
- }
447
-
448
- echo $html;
449
-
450
- // In filter can be defined custom html for submit button
451
- if ( isset( $render_data['submit']['html'] ) ) {
452
- echo $render_data['submit']['html'];
453
- } else {
454
- echo fw_html_tag('input', array(
455
- 'type' => 'submit',
456
- 'value' => $render_data['submit']['value']
457
- ));
458
- }
459
-
460
- echo '</form>';
461
- }
462
-
463
- /**
464
- * If now is a submit of this form
465
- * @return bool
466
- */
467
- public function is_submitted() {
468
- if ( is_null( $this->is_submitted ) ) {
469
- $method = strtoupper( $this->attr( 'method' ) );
470
-
471
- if ( $method === 'POST' ) {
472
- $this->is_submitted = (
473
- isset( $_POST[ self::$id_input_name ] )
474
- &&
475
- FW_Request::POST( self::$id_input_name ) === $this->id
476
- );
477
-
478
- } elseif ( $method === 'GET' ) {
479
- $this->is_submitted = (
480
- isset( $_GET[ self::$id_input_name ] )
481
- &&
482
- FW_Request::GET( self::$id_input_name ) === $this->id
483
- );
484
- } else {
485
- $this->is_submitted = false;
486
- }
487
- }
488
-
489
- return $this->is_submitted;
490
- }
491
-
492
- /**
493
- * @return bool
494
- */
495
- public function is_valid() {
496
- if ( ! $this->validate_and_save_called ) {
497
- trigger_error( __METHOD__ . ' called before validation', E_USER_WARNING );
498
-
499
- return null;
500
- }
501
-
502
- return empty( $this->errors );
503
- }
504
-
505
- /**
506
- * Get validation errors
507
- * @return array
508
- */
509
- public function get_errors() {
510
- if ( ! $this->validate_and_save_called ) {
511
- trigger_error( __METHOD__ . ' called before validation', E_USER_WARNING );
512
-
513
- return array( '~' => true );
514
- }
515
-
516
- $this->errors_accessed = true;
517
-
518
- return $this->errors;
519
- }
520
-
521
- public function errors_accessed()
522
- {
523
- return $this->errors_accessed;
524
- }
525
-
526
- /**
527
- * Get submitted form instance (or false if no form is currently submitted)
528
- * @return FW_Form|false
529
- */
530
- public static function get_submitted() {
531
- if ( is_null( self::$submitted_id ) ) {
532
- // method called first time, search for submitted form
533
- do {
534
- foreach ( self::$forms as $form ) {
535
- if ( $form->is_submitted() ) {
536
- self::$submitted_id = $form->get_id();
537
- break 2;
538
- }
539
- }
540
-
541
- self::$submitted_id = false;
542
- } while ( false );
543
- }
544
-
545
- if ( is_string( self::$submitted_id ) ) {
546
- return self::$forms[ self::$submitted_id ];
547
- } else {
548
- return false;
549
- }
550
- }
551
- }
552
-
553
- if ( is_admin() ) {
554
- /**
555
- * Display form errors in admin side
556
- * @internal
557
- */
558
- function _action_fw_form_show_errors_in_admin() {
559
- $form = FW_Form::get_submitted();
560
-
561
- if ( ! $form || $form->is_valid() ) {
562
- return;
563
- }
564
-
565
- foreach ( $form->get_errors() as $input_name => $error_message ) {
566
- FW_Flash_Messages::add( 'fw-form-admin-' . $input_name, $error_message, 'error' );
567
- }
568
- }
569
- add_action( 'wp_loaded', '_action_fw_form_show_errors_in_admin', 111 );
570
- } else {
571
- /**
572
- * to disable this use remove_action('wp_print_styles', '_action_fw_form_frontend_default_styles');
573
- * @internal
574
- */
575
- function _action_fw_form_frontend_default_styles() {
576
- $form = FW_Form::get_submitted();
577
-
578
- if ( ! $form || $form->is_valid() ) {
579
- return;
580
- }
581
-
582
- echo '<style type="text/css">.fw-form-errors { color: #bf0000; }</style>';
583
- }
584
- add_action( 'wp_print_styles', '_action_fw_form_frontend_default_styles' );
585
- }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ /**
6
+ * Dynamic forms
7
+ */
8
+ class FW_Form {
9
+ /**
10
+ * Store all form ids created with this class
11
+ * @var FW_Form[] {'form_id' => instance}
12
+ */
13
+ protected static $forms = array();
14
+
15
+ /**
16
+ * The id of the submitted form id
17
+ * @var string
18
+ */
19
+ protected static $submitted_id;
20
+
21
+ /**
22
+ * Hidden input name that stores the form id
23
+ * @var string
24
+ */
25
+ protected static $id_input_name = 'fwf';
26
+
27
+ /**
28
+ * Form id
29
+ * @var string
30
+ */
31
+ protected $id;
32
+
33
+ /**
34
+ * Html attributes for <form> tag
35
+ * @var array
36
+ */
37
+ protected $attr;
38
+
39
+ /**
40
+ * Found validation errors
41
+ * @var array
42
+ */
43
+ protected $errors;
44
+
45
+ /**
46
+ * If the get_errors() method was called at leas once
47
+ * @var bool
48
+ */
49
+ protected $errors_accessed = false;
50
+
51
+ /**
52
+ * If current request is the submit of this form
53
+ * @var bool
54
+ */
55
+ protected $is_submitted;
56
+
57
+ /**
58
+ * @var bool
59
+ */
60
+ protected $validate_and_save_called = false;
61
+
62
+ protected $callbacks = array(
63
+ 'render' => false,
64
+ 'validate' => false,
65
+ 'save' => false
66
+ );
67
+
68
+ /**
69
+ * @param string $id Unique
70
+ * @param array $data (optional)
71
+ * array(
72
+ * 'render' => callback // The callback that will render the form's html
73
+ * 'validate' => callback // The callback that will validate user input
74
+ * 'save' => callback // The callback that will save successfully validated user input
75
+ * 'attr' => array() // Custom <form ...> attributes
76
+ * )
77
+ */
78
+ public function __construct( $id, $data = array() ) {
79
+ if ( isset( self::$forms[ $id ] ) ) {
80
+ trigger_error( sprintf( __( 'Form with id "%s" was already defined', 'fw' ), $id ), E_USER_ERROR );
81
+ }
82
+
83
+ $this->id = $id;
84
+
85
+ self::$forms[ $this->id ] =& $this;
86
+
87
+ // prepare $this->attr
88
+ {
89
+ if ( ! isset( $data['attr'] ) || ! is_array( $data['attr'] ) ) {
90
+ $data['attr'] = array();
91
+ }
92
+
93
+ $data['attr']['data-fw-form-id'] = $this->id;
94
+
95
+ /** @deprecated */
96
+ $data['attr']['class'] = 'fw_form_' . $this->id;
97
+
98
+ if ( isset( $data['attr']['method'] ) ) {
99
+ $data['attr']['method'] = strtolower( $data['attr']['method'] );
100
+
101
+ $data['attr']['method'] = in_array( $data['attr']['method'], array( 'get', 'post' ) )
102
+ ? $data['attr']['method']
103
+ : 'post';
104
+ } else {
105
+ $data['attr']['method'] = 'post';
106
+ }
107
+
108
+ if ( ! isset( $data['attr']['action'] ) ) {
109
+ $data['attr']['action'] = fw_current_url();
110
+ }
111
+
112
+ $this->attr = $data['attr'];
113
+ }
114
+
115
+ // prepare $this->callbacks
116
+ {
117
+ $this->callbacks = array(
118
+ 'render' => empty( $data['render'] ) ? false : $data['render'],
119
+ 'validate' => empty( $data['validate'] ) ? false : $data['validate'],
120
+ 'save' => empty( $data['save'] ) ? false : $data['save'],
121
+ );
122
+ }
123
+
124
+ if ( did_action( 'wp_loaded' ) ) {
125
+ // in case if form instance was created after action
126
+ $this->_validate_and_save();
127
+ } else {
128
+ // attach to an action before 'send_headers' action, to be able to do redirects
129
+ add_action( 'wp_loaded', array( $this, '_validate_and_save' ), 101 );
130
+ }
131
+ }
132
+
133
+ protected function validate() {
134
+ if ( is_array( $this->errors ) ) {
135
+ trigger_error( __METHOD__ . ' already called', E_USER_WARNING );
136
+
137
+ return;
138
+ }
139
+
140
+ /**
141
+ * Errors array {'input[name]' => 'Error message'}
142
+ */
143
+ $errors = array();
144
+
145
+ /**
146
+ * Call validate callback
147
+ *
148
+ * Callback must 'manually' extract input values from $_POST (or $_GET)
149
+ */
150
+ if ( $this->callbacks['validate'] ) {
151
+ $errors = call_user_func_array( $this->callbacks['validate'], array( $errors ) );
152
+
153
+ if ( ! is_array( $errors ) ) {
154
+
155
+ $errors = array();
156
+ }
157
+ }
158
+
159
+ /**
160
+ * check nonce
161
+ */
162
+ if ( $this->attr['method'] == 'post' ) {
163
+ $nonce_name = '_nonce_' . md5( $this->id );
164
+
165
+ if ( ! isset( $_REQUEST[ $nonce_name ] ) || wp_verify_nonce( $_REQUEST[ $nonce_name ],
166
+ 'submit_fwf' ) === false
167
+ ) {
168
+ $errors[ $nonce_name ] = __( 'Nonce verification failed', 'fw' );
169
+ }
170
+ }
171
+
172
+ $this->errors = $errors;
173
+ }
174
+
175
+ protected function save() {
176
+ $save_data = array(
177
+ // you can set here a url for redirect after save
178
+ 'redirect' => null
179
+ );
180
+
181
+ /**
182
+ * Call save callback
183
+ *
184
+ * Callback must 'manually' extract input values from $_POST (or $_GET)
185
+ */
186
+ if ( $this->callbacks['save'] ) {
187
+ $data = call_user_func_array( $this->callbacks['save'], array( $save_data ) );
188
+
189
+ if ( ! is_array( $data ) ) {
190
+ // fix if returned wrong data from callback
191
+ $data = $save_data;
192
+ }
193
+
194
+ $save_data = $data;
195
+
196
+ unset( $data );
197
+ }
198
+
199
+ if ( ! $this->is_ajax() ) {
200
+ if ( isset( $save_data['redirect'] ) ) {
201
+ wp_redirect( $save_data['redirect'] );
202
+ exit;
203
+ }
204
+ }
205
+
206
+ return $save_data;
207
+ }
208
+
209
+ protected function is_ajax() {
210
+ return defined( 'DOING_AJAX' ) && DOING_AJAX;
211
+ }
212
+
213
+ /**
214
+ * If current form was submitted, validate and save it
215
+ *
216
+ * Note: This callback can abort script execution if save does redirect
217
+ *
218
+ * @return bool|null
219
+ * @internal
220
+ */
221
+ public function _validate_and_save() {
222
+ if ( $this->validate_and_save_called ) {
223
+ trigger_error( __METHOD__ . ' already called', E_USER_WARNING );
224
+
225
+ return null;
226
+ } else {
227
+ $this->validate_and_save_called = true;
228
+ }
229
+
230
+ if ( ! $this->is_submitted() ) {
231
+ return null;
232
+ }
233
+
234
+ $this->validate();
235
+
236
+ if ( $this->is_ajax() ) {
237
+ $json_data = array();
238
+
239
+ if ( $this->is_valid() ) {
240
+ $json_data['save_data'] = $this->save();
241
+ } else {
242
+ $json_data['errors'] = $this->get_errors();
243
+ }
244
+
245
+ /**
246
+ * Transform flash messages structure from
247
+ * array( 'type' => array( 'message_id' => array(...) ) )
248
+ * to
249
+ * array( 'type' => array( 'message_id' => 'Message' ) )
250
+ */
251
+ {
252
+ $flash_messages = array();
253
+
254
+ foreach (FW_Flash_Messages::_get_messages(true) as $type => $messages) {
255
+ $flash_messages[$type] = array();
256
+
257
+ foreach ($messages as $id => $message_data) {
258
+ $flash_messages[$type][$id] = $message_data['message'];
259
+ }
260
+ }
261
+
262
+ $json_data['flash_messages'] = $flash_messages;
263
+ }
264
+
265
+ /**
266
+ * Important!
267
+ * We can't send form html in response:
268
+ *
269
+ * ob_start();
270
+ * $this->render();
271
+ * $json_data['html'] = ob_get_clean();
272
+ *
273
+ * because the render() method is not called within this class
274
+ * but by the code that created and owns the $form,
275
+ * and it's usually called with some custom data $this->render(array(...))
276
+ * that it's impossible to know here which data is that.
277
+ * If we will call $this->render(); without data, this may throw errors because
278
+ * the render callback may expect some custom data.
279
+ * Also it may be called or not, depending on the owner code inner logic.
280
+ *
281
+ * The only way to get the latest form html on ajax submit
282
+ * is to make a new ajax GET to current page and extract form html from the response.
283
+ */
284
+
285
+ if ( $this->is_valid() ) {
286
+ wp_send_json_success($json_data);
287
+ } else {
288
+ wp_send_json_error($json_data);
289
+ }
290
+ } else {
291
+ if ( ! $this->is_valid() ) {
292
+ return false;
293
+ }
294
+
295
+ $this->save();
296
+ }
297
+
298
+ return true;
299
+ }
300
+
301
+ /**
302
+ * @return string
303
+ */
304
+ public function get_id() {
305
+ return $this->id;
306
+ }
307
+
308
+ /**
309
+ * Get html attribute(s)
310
+ *
311
+ * @param null|string $name
312
+ *
313
+ * @return array|string
314
+ */
315
+ public function attr( $name = null ) {
316
+ if ( $name ) {
317
+ return isset( $this->attr[ $name ] ) ? $this->attr[ $name ] : null;
318
+ } else {
319
+ return $this->attr;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Render form's html
325
+ *
326
+ * @param array $data
327
+ */
328
+ public function render( $data = array() ) {
329
+ $render_data = array(
330
+ 'submit' => array(
331
+ 'value' => __( 'Submit', 'fw' ),
332
+ /**
333
+ * you can set here custom submit button html
334
+ * and the 'value' parameter will not be used
335
+ */
336
+ 'html' => null,
337
+ ),
338
+ 'data' => $data,
339
+ 'attr' => $this->attr,
340
+ );
341
+
342
+ unset( $data );
343
+
344
+ if ( $this->callbacks['render'] ) {
345
+ ob_start();
346
+
347
+ $data = call_user_func_array( $this->callbacks['render'], array( $render_data, $this ) );
348
+
349
+ $html = ob_get_clean();
350
+
351
+ if ( empty( $data ) ) {
352
+ // fix if returned wrong data from callback
353
+ $data = $render_data;
354
+ }
355
+
356
+ $render_data = $data;
357
+
358
+ unset( $data );
359
+ }
360
+
361
+ // display form errors in frontend
362
+ do {
363
+ if (is_admin()) {
364
+ // errors in admin side are displayed by a script at the end of this file
365
+ break;
366
+ }
367
+
368
+ $submitted_form = FW_Form::get_submitted();
369
+
370
+ if ( ! $submitted_form ) {
371
+ break;
372
+ }
373
+
374
+ if ( $submitted_form->get_id() !== $this->get_id() ) {
375
+ // the submitted form is not current form
376
+ break;
377
+ }
378
+
379
+ unset($submitted_form); // not needed anymore, below will be used only with $this (because it's the same form)
380
+
381
+ if ( $this->is_valid() ) {
382
+ break;
383
+ }
384
+
385
+ /**
386
+ * Use this action to customize errors display in your theme
387
+ */
388
+ do_action('fw_form_display_errors_frontend', $this);
389
+
390
+ if ( $this->errors_accessed() ) {
391
+ // already displayed, prevent/cancel default display
392
+ break;
393
+ }
394
+
395
+ $errors = $this->get_errors();
396
+
397
+ if (empty($errors)) {
398
+ break;
399
+ }
400
+
401
+ echo '<ul class="fw-form-errors">';
402
+
403
+ foreach ($errors as $input_name => $error_message) {
404
+ echo fw_html_tag(
405
+ 'li',
406
+ array(
407
+ 'data-input-name' => $input_name,
408
+ ),
409
+ $error_message
410
+ );
411
+ }
412
+
413
+ echo '</ul>';
414
+
415
+ unset($errors);
416
+ } while(false);
417
+
418
+ echo '<form '. fw_attr_to_html( $render_data['attr'] ) .' >';
419
+
420
+ echo fw_html_tag('input', array(
421
+ 'type' => 'hidden',
422
+ 'name' => self::$id_input_name,
423
+ 'value' => $this->id,
424
+ ));
425
+
426
+ if ( $render_data['attr']['method'] == 'post' ) {
427
+ wp_nonce_field( 'submit_fwf', '_nonce_' . md5( $this->id ) );
428
+ }
429
+
430
+ if ( ! empty( $render_data['attr']['action'] ) && $render_data['attr']['method'] == 'get' ) {
431
+ /**
432
+ * Add query vars from the action attribute url to hidden inputs to not loose them
433
+ */
434
+
435
+ parse_str( parse_url( $render_data['attr']['action'], PHP_URL_QUERY ), $query_vars );
436
+
437
+ if ( ! empty( $query_vars ) ) {
438
+ foreach ( $query_vars as $var_name => $var_value ) {
439
+ echo fw_html_tag('input', array(
440
+ 'type' => 'hidden',
441
+ 'name' => $var_name,
442
+ 'value' => $var_value,
443
+ ));
444
+ }
445
+ }
446
+ }
447
+
448
+ echo $html;
449
+
450
+ // In filter can be defined custom html for submit button
451
+ if ( isset( $render_data['submit']['html'] ) ) {
452
+ echo $render_data['submit']['html'];
453
+ } else {
454
+ echo fw_html_tag('input', array(
455
+ 'type' => 'submit',
456
+ 'value' => $render_data['submit']['value']
457
+ ));
458
+ }
459
+
460
+ echo '</form>';
461
+ }
462
+
463
+ /**
464
+ * If now is a submit of this form
465
+ * @return bool
466
+ */
467
+ public function is_submitted() {
468
+ if ( is_null( $this->is_submitted ) ) {
469
+ $method = strtoupper( $this->attr( 'method' ) );
470
+
471
+ if ( $method === 'POST' ) {
472
+ $this->is_submitted = (
473
+ isset( $_POST[ self::$id_input_name ] )
474
+ &&
475
+ FW_Request::POST( self::$id_input_name ) === $this->id
476
+ );
477
+
478
+ } elseif ( $method === 'GET' ) {
479
+ $this->is_submitted = (
480
+ isset( $_GET[ self::$id_input_name ] )
481
+ &&
482
+ FW_Request::GET( self::$id_input_name ) === $this->id
483
+ );
484
+ } else {
485
+ $this->is_submitted = false;
486
+ }
487
+ }
488
+
489
+ return $this->is_submitted;
490
+ }
491
+
492
+ /**
493
+ * @return bool
494
+ */
495
+ public function is_valid() {
496
+ if ( ! $this->validate_and_save_called ) {
497
+ trigger_error( __METHOD__ . ' called before validation', E_USER_WARNING );
498
+
499
+ return null;
500
+ }
501
+
502
+ return empty( $this->errors );
503
+ }
504
+
505
+ /**
506
+ * Get validation errors
507
+ * @return array
508
+ */
509
+ public function get_errors() {
510
+ if ( ! $this->validate_and_save_called ) {
511
+ trigger_error( __METHOD__ . ' called before validation', E_USER_WARNING );
512
+
513
+ return array( '~' => true );
514
+ }
515
+
516
+ $this->errors_accessed = true;
517
+
518
+ return $this->errors;
519
+ }
520
+
521
+ public function errors_accessed()
522
+ {
523
+ return $this->errors_accessed;
524
+ }
525
+
526
+ /**
527
+ * Get submitted form instance (or false if no form is currently submitted)
528
+ * @return FW_Form|false
529
+ */
530
+ public static function get_submitted() {
531
+ if ( is_null( self::$submitted_id ) ) {
532
+ // method called first time, search for submitted form
533
+ do {
534
+ foreach ( self::$forms as $form ) {
535
+ if ( $form->is_submitted() ) {
536
+ self::$submitted_id = $form->get_id();
537
+ break 2;
538
+ }
539
+ }
540
+
541
+ self::$submitted_id = false;
542
+ } while ( false );
543
+ }
544
+
545
+ if ( is_string( self::$submitted_id ) ) {
546
+ return self::$forms[ self::$submitted_id ];
547
+ } else {
548
+ return false;
549
+ }
550
+ }
551
+ }
552
+
553
+ if ( is_admin() ) {
554
+ /**
555
+ * Display form errors in admin side
556
+ * @internal
557
+ */
558
+ function _action_fw_form_show_errors_in_admin() {
559
+ $form = FW_Form::get_submitted();
560
+
561
+ if ( ! $form || $form->is_valid() ) {
562
+ return;
563
+ }
564
+
565
+ foreach ( $form->get_errors() as $input_name => $error_message ) {
566
+ FW_Flash_Messages::add( 'fw-form-admin-' . $input_name, $error_message, 'error' );
567
+ }
568
+ }
569
+ add_action( 'wp_loaded', '_action_fw_form_show_errors_in_admin', 111 );
570
+ } else {
571
+ /**
572
+ * to disable this use remove_action('wp_print_styles', '_action_fw_form_frontend_default_styles');
573
+ * @internal
574
+ */
575
+ function _action_fw_form_frontend_default_styles() {
576
+ $form = FW_Form::get_submitted();
577
+
578
+ if ( ! $form || $form->is_valid() ) {
579
+ return;
580
+ }
581
+
582
+ echo '<style type="text/css">.fw-form-errors { color: #bf0000; }</style>';
583
+ }
584
+ add_action( 'wp_print_styles', '_action_fw_form_frontend_default_styles' );
585
+ }
framework/helpers/class-fw-request.php CHANGED
@@ -1,90 +1,90 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * WordPress automatically adds slashes to:
5
- * $_REQUEST
6
- * $_POST
7
- * $_GET
8
- * $_COOKIE
9
- *
10
- * For e.g.
11
- *
12
- * If value is simple, get value directly:
13
- * $foo = isset($_GET['bar']) && $_GET['bar'] == 'yes';
14
- *
15
- * If value can contain some user input and can have quotes or json from some option, then use this helper:
16
- * $foo = json_decode(FW_Request::POST('bar')); // json_decode($_POST('bar')) will not work if json will contain quotes
17
- *
18
- * You can test that problem.
19
- * Add somewhere this code:
20
- fw_print(array(
21
- $_GET['test'],
22
- json_decode($_GET['test']),
23
- FW_Request::GET('test'),
24
- json_decode(FW_Request::GET('test'))
25
- ));
26
- * and access: http://your-site.com/?test={'a':1}
27
- */
28
- class FW_Request
29
- {
30
- protected static function prepare_key($key)
31
- {
32
- return (get_magic_quotes_gpc() && is_string($key) ? addslashes($key) : $key);
33
- }
34
-
35
- protected static function get_set_key($multikey = null, $set_value = null, &$value)
36
- {
37
- $multikey = self::prepare_key($multikey);
38
-
39
- if ($set_value === null) { // get
40
- return fw_stripslashes_deep_keys($multikey === null ? $value : fw_akg($multikey, $value));
41
- } else { // set
42
- fw_aks($multikey, fw_addslashes_deep_keys($set_value), $value);
43
- }
44
-
45
- return '';
46
- }
47
-
48
- public static function GET($multikey = null, $default_value = null)
49
- {
50
- return fw_stripslashes_deep_keys(
51
- $multikey === null
52
- ? $_GET
53
- : fw_akg($multikey, $_GET, $default_value)
54
- );
55
- }
56
-
57
- public static function POST($multikey = null, $default_value = null)
58
- {
59
- return fw_stripslashes_deep_keys(
60
- $multikey === null
61
- ? $_POST
62
- : fw_akg($multikey, $_POST, $default_value)
63
- );
64
- }
65
-
66
- public static function COOKIE($multikey = null, $set_value = null, $expire = 0, $path = null)
67
- {
68
- if ($set_value !== null) {
69
-
70
- // transforms a string ( key1/key2/key3 => key1][key2][key3] )
71
- $multikey = str_replace('/', '][', $multikey) . ']';
72
-
73
- // removes the first closed square bracket ( key1][key2][key3] => key1[key2][key3] )
74
- $multikey = preg_replace('/\]/', '', $multikey, 1);
75
-
76
- return setcookie($multikey, $set_value, $expire, $path);
77
- } else {
78
- return self::get_set_key($multikey, $set_value, $_COOKIE);
79
- }
80
- }
81
-
82
- public static function REQUEST($multikey = null, $default_value = null)
83
- {
84
- return fw_stripslashes_deep_keys(
85
- $multikey === null
86
- ? $_REQUEST
87
- : fw_akg($multikey, $_REQUEST, $default_value)
88
- );
89
- }
90
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * WordPress automatically adds slashes to:
5
+ * $_REQUEST
6
+ * $_POST
7
+ * $_GET
8
+ * $_COOKIE
9
+ *
10
+ * For e.g.
11
+ *
12
+ * If value is simple, get value directly:
13
+ * $foo = isset($_GET['bar']) && $_GET['bar'] == 'yes';
14
+ *
15
+ * If value can contain some user input and can have quotes or json from some option, then use this helper:
16
+ * $foo = json_decode(FW_Request::POST('bar')); // json_decode($_POST('bar')) will not work if json will contain quotes
17
+ *
18
+ * You can test that problem.
19
+ * Add somewhere this code:
20
+ fw_print(array(
21
+ $_GET['test'],
22
+ json_decode($_GET['test']),
23
+ FW_Request::GET('test'),
24
+ json_decode(FW_Request::GET('test'))
25
+ ));
26
+ * and access: http://your-site.com/?test={'a':1}
27
+ */
28
+ class FW_Request
29
+ {
30
+ protected static function prepare_key($key)
31
+ {
32
+ return (get_magic_quotes_gpc() && is_string($key) ? addslashes($key) : $key);
33
+ }
34
+
35
+ protected static function get_set_key($multikey = null, $set_value = null, &$value)
36
+ {
37
+ $multikey = self::prepare_key($multikey);
38
+
39
+ if ($set_value === null) { // get
40
+ return fw_stripslashes_deep_keys($multikey === null ? $value : fw_akg($multikey, $value));
41
+ } else { // set
42
+ fw_aks($multikey, fw_addslashes_deep_keys($set_value), $value);
43
+ }
44
+
45
+ return '';
46
+ }
47
+
48
+ public static function GET($multikey = null, $default_value = null)
49
+ {
50
+ return fw_stripslashes_deep_keys(
51
+ $multikey === null
52
+ ? $_GET
53
+ : fw_akg($multikey, $_GET, $default_value)
54
+ );
55
+ }
56
+
57
+ public static function POST($multikey = null, $default_value = null)
58
+ {
59
+ return fw_stripslashes_deep_keys(
60
+ $multikey === null
61
+ ? $_POST
62
+ : fw_akg($multikey, $_POST, $default_value)
63
+ );
64
+ }
65
+
66
+ public static function COOKIE($multikey = null, $set_value = null, $expire = 0, $path = null)
67
+ {
68
+ if ($set_value !== null) {
69
+
70
+ // transforms a string ( key1/key2/key3 => key1][key2][key3] )
71
+ $multikey = str_replace('/', '][', $multikey) . ']';
72
+
73
+ // removes the first closed square bracket ( key1][key2][key3] => key1[key2][key3] )
74
+ $multikey = preg_replace('/\]/', '', $multikey, 1);
75
+
76
+ return setcookie($multikey, $set_value, $expire, $path);
77
+ } else {
78
+ return self::get_set_key($multikey, $set_value, $_COOKIE);
79
+ }
80
+ }
81
+
82
+ public static function REQUEST($multikey = null, $default_value = null)
83
+ {
84
+ return fw_stripslashes_deep_keys(
85
+ $multikey === null
86
+ ? $_REQUEST
87
+ : fw_akg($multikey, $_REQUEST, $default_value)
88
+ );
89
+ }
90
+ }
framework/helpers/class-fw-resize.php CHANGED
@@ -1,177 +1,177 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- if ( ! class_exists( 'FW_Resize' ) ) {
6
- class FW_Resize {
7
- /**
8
- * The singleton instance
9
- */
10
- static private $instance = null;
11
-
12
- /**
13
- * No initialization allowed
14
- */
15
- private function __construct() {
16
- }
17
-
18
- /**
19
- * No cloning allowed
20
- */
21
- private function __clone() {
22
- }
23
-
24
- static public function getInstance() {
25
- if ( self::$instance == null ) {
26
- self::$instance = new self;
27
- }
28
-
29
- return self::$instance;
30
- }
31
-
32
- private function get_attachment_info( $attachment ) {
33
-
34
- $row = $this->get_attachment( $attachment );
35
- $path = get_attached_file( $row['ID'] );
36
-
37
- return ( ! isset( $row ) || ! $path ) ? false : array(
38
- 'id' => intval( $row['ID'] ),
39
- 'path' => $path,
40
- 'url' => $row['guid']
41
- );
42
- }
43
-
44
- private function get_attachment( $attachment ) {
45
- /**
46
- * @var WPDB $wpdb
47
- */
48
- global $wpdb;
49
-
50
- if ( is_numeric( $attachment ) ) {
51
- return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID=%d LIMIT 1", $attachment ), ARRAY_A );
52
- } else {
53
-
54
- $attachment = str_replace( array( 'http:', 'https:' ), '', $attachment );
55
-
56
- return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE guid LIKE %s LIMIT 1", '%' . $wpdb->esc_like($attachment) ), ARRAY_A );
57
- }
58
- }
59
-
60
- public function process( $attachment, $width = false, $height = false, $crop = false ) {
61
-
62
- $attachment_info = $this->get_attachment_info( $attachment );
63
-
64
- if ( ! $attachment_info ) {
65
- return new WP_Error( 'invalid_attachment', 'Invalid Attachment', $attachment );
66
- }
67
-
68
- $file_path = $attachment_info['path'];
69
-
70
- $info = pathinfo( $file_path );
71
- $dir = $info['dirname'];
72
- $ext = ( isset( $info['extension'] ) ) ? $info['extension'] : 'jpg';
73
- $name = wp_basename( $file_path, ".$ext" );
74
- $name = preg_replace( '/(.+)(\-\d+x\d+)$/', '$1', $name );
75
-
76
- {
77
- if ( ! $width || ! $height ) {
78
- $editor = wp_get_image_editor( $file_path );
79
- $size = $editor->get_size();
80
- $orig_width = $size['width'];
81
- $orig_height = $size['height'];
82
- if ( ! $height && $width ) {
83
- $height = round( ( $orig_height * $width ) / $orig_width );
84
- } elseif ( ! $width && $height ) {
85
- $width = round( ( $orig_width * $height ) / $orig_height );
86
- } else {
87
- return $attachment;
88
- }
89
- }
90
- }
91
-
92
- // Suffix applied to filename
93
- $suffix = "{$width}x{$height}";
94
-
95
- // Get the destination file name
96
- $destination_file_name = "{$dir}/{$name}-{$suffix}.{$ext}";
97
- // No need to resize & create a new image if it already exists
98
- if ( ! file_exists( $destination_file_name ) ) {
99
- //Image Resize
100
- $editor = (isset($editor)) ? $editor : wp_get_image_editor( $file_path );
101
-
102
- if ( is_wp_error( $editor ) ) {
103
- return new WP_Error( 'wp_image_editor', 'WP Image editor can\'t resize this attachment', $attachment );
104
- }
105
-
106
- // Get the original image size
107
- $size = $editor->get_size();
108
- $orig_width = $size['width'];
109
- $orig_height = $size['height'];
110
-
111
- $src_x = $src_y = 0;
112
- $src_w = $orig_width;
113
- $src_h = $orig_height;
114
-
115
- if ( $crop ) {
116
-
117
- $cmp_x = $orig_width / $width;
118
- $cmp_y = $orig_height / $height;
119
-
120
- // Calculate x or y coordinate, and width or height of source
121
- if ( $cmp_x > $cmp_y ) {
122
- $src_w = round( $orig_width / $cmp_x * $cmp_y );
123
- $src_x = round( ( $orig_width - ( $orig_width / $cmp_x * $cmp_y ) ) / 2 );
124
- } else if ( $cmp_y > $cmp_x ) {
125
- $src_h = round( $orig_height / $cmp_y * $cmp_x );
126
- $src_y = round( ( $orig_height - ( $orig_height / $cmp_y * $cmp_x ) ) / 2 );
127
- }
128
-
129
- }
130
-
131
- $editor->crop( $src_x, $src_y, $src_w, $src_h, $width, $height );
132
-
133
- $saved = $editor->save( $destination_file_name );
134
-
135
- $images = wp_get_attachment_metadata( $attachment_info['id'] );
136
- if ( ! empty( $images['resizes'] ) && is_array( $images['resizes'] ) ) {
137
- foreach ( $images['resizes'] as $image_size => $image_path ) {
138
- $images['resizes'][ $image_size ] = addslashes( $image_path );
139
- }
140
- }
141
- $uploads_dir = wp_upload_dir();
142
- $images['resizes'][ $suffix ] = $uploads_dir['subdir'] . '/' . $saved['file'];
143
- wp_update_attachment_metadata( $attachment_info['id'], $images );
144
-
145
- }
146
-
147
- return array(
148
- 'id' => $attachment_info['id'],
149
- 'src' => str_replace( basename( $attachment_info['url'] ), basename( $destination_file_name ), $attachment_info['url'] )
150
- );
151
- }
152
- }
153
- }
154
-
155
- if ( ! function_exists( 'fw_resize' ) ) {
156
- function fw_resize( $url, $width = false, $height = false, $crop = false ) {
157
- $fw_resize = FW_Resize::getInstance();
158
- $response = $fw_resize->process( $url, $width, $height, $crop );
159
-
160
- return ( ! is_wp_error( $response ) && ! empty( $response['src'] ) ) ? $response['src'] : $url;
161
- }
162
- }
163
-
164
- if ( ! function_exists( 'fw_delete_resized_thumbnails' ) ) {
165
- function fw_delete_resized_thumbnails( $id ) {
166
- $images = wp_get_attachment_metadata( $id );
167
- if ( ! empty( $images['resizes'] ) ) {
168
- $uploads_dir = wp_upload_dir();
169
- foreach ( $images['resizes'] as $image ) {
170
- $file = $uploads_dir['basedir'] . '/' . $image;
171
- @unlink( $file );
172
- }
173
- }
174
- }
175
-
176
- add_action( 'delete_attachment', 'fw_delete_resized_thumbnails' );
177
- }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ if ( ! class_exists( 'FW_Resize' ) ) {
6
+ class FW_Resize {
7
+ /**
8
+ * The singleton instance
9
+ */
10
+ static private $instance = null;
11
+
12
+ /**
13
+ * No initialization allowed
14
+ */
15
+ private function __construct() {
16
+ }
17
+
18
+ /**
19
+ * No cloning allowed
20
+ */
21
+ private function __clone() {
22
+ }
23
+
24
+ static public function getInstance() {
25
+ if ( self::$instance == null ) {
26
+ self::$instance = new self;
27
+ }
28
+
29
+ return self::$instance;
30
+ }
31
+
32
+ private function get_attachment_info( $attachment ) {
33
+
34
+ $row = $this->get_attachment( $attachment );
35
+ $path = get_attached_file( $row['ID'] );
36
+
37
+ return ( ! isset( $row ) || ! $path ) ? false : array(
38
+ 'id' => intval( $row['ID'] ),
39
+ 'path' => $path,
40
+ 'url' => $row['guid']
41
+ );
42
+ }
43
+
44
+ private function get_attachment( $attachment ) {
45
+ /**
46
+ * @var WPDB $wpdb
47
+ */
48
+ global $wpdb;
49
+
50
+ if ( is_numeric( $attachment ) ) {
51
+ return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID=%d LIMIT 1", $attachment ), ARRAY_A );
52
+ } else {
53
+
54
+ $attachment = str_replace( array( 'http:', 'https:' ), '', $attachment );
55
+
56
+ return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE guid LIKE %s LIMIT 1", '%' . $wpdb->esc_like($attachment) ), ARRAY_A );
57
+ }
58
+ }
59
+
60
+ public function process( $attachment, $width = false, $height = false, $crop = false ) {
61
+
62
+ $attachment_info = $this->get_attachment_info( $attachment );
63
+
64
+ if ( ! $attachment_info ) {
65
+ return new WP_Error( 'invalid_attachment', 'Invalid Attachment', $attachment );
66
+ }
67
+
68
+ $file_path = $attachment_info['path'];
69
+
70
+ $info = pathinfo( $file_path );
71
+ $dir = $info['dirname'];
72
+ $ext = ( isset( $info['extension'] ) ) ? $info['extension'] : 'jpg';
73
+ $name = wp_basename( $file_path, ".$ext" );
74
+ $name = preg_replace( '/(.+)(\-\d+x\d+)$/', '$1', $name );
75
+
76
+ {
77
+ if ( ! $width || ! $height ) {
78
+ $editor = wp_get_image_editor( $file_path );
79
+ $size = $editor->get_size();
80
+ $orig_width = $size['width'];
81
+ $orig_height = $size['height'];
82
+ if ( ! $height && $width ) {
83
+ $height = round( ( $orig_height * $width ) / $orig_width );
84
+ } elseif ( ! $width && $height ) {
85
+ $width = round( ( $orig_width * $height ) / $orig_height );
86
+ } else {
87
+ return $attachment;
88
+ }
89
+ }
90
+ }
91
+
92
+ // Suffix applied to filename
93
+ $suffix = "{$width}x{$height}";
94
+
95
+ // Get the destination file name
96
+ $destination_file_name = "{$dir}/{$name}-{$suffix}.{$ext}";
97
+ // No need to resize & create a new image if it already exists
98
+ if ( ! file_exists( $destination_file_name ) ) {
99
+ //Image Resize
100
+ $editor = (isset($editor)) ? $editor : wp_get_image_editor( $file_path );
101
+
102
+ if ( is_wp_error( $editor ) ) {
103
+ return new WP_Error( 'wp_image_editor', 'WP Image editor can\'t resize this attachment', $attachment );
104
+ }
105
+
106
+ // Get the original image size
107
+ $size = $editor->get_size();
108
+ $orig_width = $size['width'];
109
+ $orig_height = $size['height'];
110
+
111
+ $src_x = $src_y = 0;
112
+ $src_w = $orig_width;
113
+ $src_h = $orig_height;
114
+
115
+ if ( $crop ) {
116
+
117
+ $cmp_x = $orig_width / $width;
118
+ $cmp_y = $orig_height / $height;
119
+
120
+ // Calculate x or y coordinate, and width or height of source
121
+ if ( $cmp_x > $cmp_y ) {
122
+ $src_w = round( $orig_width / $cmp_x * $cmp_y );
123
+ $src_x = round( ( $orig_width - ( $orig_width / $cmp_x * $cmp_y ) ) / 2 );
124
+ } else if ( $cmp_y > $cmp_x ) {
125
+ $src_h = round( $orig_height / $cmp_y * $cmp_x );
126
+ $src_y = round( ( $orig_height - ( $orig_height / $cmp_y * $cmp_x ) ) / 2 );
127
+ }
128
+
129
+ }
130
+
131
+ $editor->crop( $src_x, $src_y, $src_w, $src_h, $width, $height );
132
+
133
+ $saved = $editor->save( $destination_file_name );
134
+
135
+ $images = wp_get_attachment_metadata( $attachment_info['id'] );
136
+ if ( ! empty( $images['resizes'] ) && is_array( $images['resizes'] ) ) {
137
+ foreach ( $images['resizes'] as $image_size => $image_path ) {
138
+ $images['resizes'][ $image_size ] = addslashes( $image_path );
139
+ }
140
+ }
141
+ $uploads_dir = wp_upload_dir();
142
+ $images['resizes'][ $suffix ] = $uploads_dir['subdir'] . '/' . $saved['file'];
143
+ wp_update_attachment_metadata( $attachment_info['id'], $images );
144
+
145
+ }
146
+
147
+ return array(
148
+ 'id' => $attachment_info['id'],
149
+ 'src' => str_replace( basename( $attachment_info['url'] ), basename( $destination_file_name ), $attachment_info['url'] )
150
+ );
151
+ }
152
+ }
153
+ }
154
+
155
+ if ( ! function_exists( 'fw_resize' ) ) {
156
+ function fw_resize( $url, $width = false, $height = false, $crop = false ) {
157
+ $fw_resize = FW_Resize::getInstance();
158
+ $response = $fw_resize->process( $url, $width, $height, $crop );
159
+
160
+ return ( ! is_wp_error( $response ) && ! empty( $response['src'] ) ) ? $response['src'] : $url;
161
+ }
162
+ }
163
+
164
+ if ( ! function_exists( 'fw_delete_resized_thumbnails' ) ) {
165
+ function fw_delete_resized_thumbnails( $id ) {
166
+ $images = wp_get_attachment_metadata( $id );
167
+ if ( ! empty( $images['resizes'] ) ) {
168
+ $uploads_dir = wp_upload_dir();
169
+ foreach ( $images['resizes'] as $image ) {
170
+ $file = $uploads_dir['basedir'] . '/' . $image;
171
+ @unlink( $file );
172
+ }
173
+ }
174
+ }
175
+
176
+ add_action( 'delete_attachment', 'fw_delete_resized_thumbnails' );
177
+ }
framework/helpers/class-fw-session.php CHANGED
@@ -1,36 +1,36 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Work with $_SESSION
5
- *
6
- * Advantages: Do not session_start() on every refresh, but only when it is accessed
7
- */
8
- class FW_Session
9
- {
10
- private static function start_session()
11
- {
12
- if (!session_id()) {
13
- session_start();
14
- }
15
- }
16
-
17
- public static function get($key, $default_value = null)
18
- {
19
- self::start_session();
20
-
21
- return fw_akg($key, $_SESSION, $default_value);
22
- }
23
-
24
- public static function set($key, $value)
25
- {
26
- self::start_session();
27
-
28
- fw_aks($key, $value, $_SESSION);
29
- }
30
-
31
- public static function del( $key ) {
32
- self::start_session();
33
-
34
- fw_aku( $key, $_SESSION );
35
- }
36
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Work with $_SESSION
5
+ *
6
+ * Advantages: Do not session_start() on every refresh, but only when it is accessed
7
+ */
8
+ class FW_Session
9
+ {
10
+ private static function start_session()
11
+ {
12
+ if (!session_id()) {
13
+ session_start();
14
+ }
15
+ }
16
+
17
+ public static function get($key, $default_value = null)
18
+ {
19
+ self::start_session();
20
+
21
+ return fw_akg($key, $_SESSION, $default_value);
22
+ }
23
+
24
+ public static function set($key, $value)
25
+ {
26
+ self::start_session();
27
+
28
+ fw_aks($key, $value, $_SESSION);
29
+ }
30
+
31
+ public static function del( $key ) {
32
+ self::start_session();
33
+
34
+ fw_aku( $key, $_SESSION );
35
+ }
36
+ }
framework/helpers/class-fw-wp-filesystem.php CHANGED
@@ -1,312 +1,312 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- class FW_WP_Filesystem
4
- {
5
- /**
6
- * Request WP Filesystem access
7
- * @param string $context
8
- * @param string $url
9
- * @param array $extra_fields
10
- * @return null|bool // todo: Create a new method that will return WP_Error with message on failure
11
- * null - if has no access and the input credentials form was displayed
12
- * false - if user submitted wrong credentials
13
- * true - if we have filesystem access
14
- */
15
- final public static function request_access($context = null, $url = null, $extra_fields = array())
16
- {
17
- /** @var WP_Filesystem_Base $wp_filesystem */
18
- global $wp_filesystem;
19
-
20
- if ($wp_filesystem) {
21
- if (
22
- is_object($wp_filesystem)
23
- &&
24
- !(is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())
25
- ) {
26
- return true; // already initialized
27
- }
28
- }
29
-
30
- if ( empty( $url ) ) {
31
- $url = fw_current_url();
32
- }
33
-
34
- if ( get_filesystem_method() === 'direct' ) {
35
- // in case if direct access is available
36
-
37
- /* you can safely run request_filesystem_credentials() without any issues and don't need to worry about passing in a URL */
38
- $creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, null );
39
-
40
- /* initialize the API */
41
- if ( ! WP_Filesystem( $creds ) ) {
42
- /* any problems and we exit */
43
- trigger_error( __( 'Cannot connect to Filesystem directly', 'fw' ), E_USER_WARNING );
44
-
45
- return false;
46
- }
47
- } else {
48
- $creds = request_filesystem_credentials( $url, '', false, $context, $extra_fields );
49
-
50
- if ( ! $creds ) {
51
- // the form was printed to the user
52
- return null;
53
- }
54
-
55
- /* initialize the API */
56
- if ( ! WP_Filesystem( $creds ) ) {
57
- /* any problems and we exit */
58
- request_filesystem_credentials( $url, '', true, $context, $extra_fields ); // the third parameter is true to show error to the user
59
- return false;
60
- }
61
- }
62
-
63
- if (
64
- ! is_object($wp_filesystem)
65
- ||
66
- (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())
67
- ) {
68
- return false;
69
- }
70
-
71
- if (
72
- $wp_filesystem->abspath()
73
- &&
74
- $wp_filesystem->wp_content_dir()
75
- &&
76
- $wp_filesystem->wp_plugins_dir()
77
- &&
78
- $wp_filesystem->wp_themes_dir()
79
- &&
80
- $wp_filesystem->find_folder($context)
81
- ) {
82
- return true;
83
- } else {
84
- return false;
85
- }
86
- }
87
-
88
- /**
89
- * @return array {base_dir_real_path => base_dir_wp_filesystem_path}
90
- */
91
- public static function get_base_dirs_map()
92
- {
93
- /** @var WP_Filesystem_Base $wp_filesystem */
94
- global $wp_filesystem;
95
-
96
- if (!$wp_filesystem) {
97
- trigger_error('Filesystem is not available', E_USER_ERROR);
98
- } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
99
- trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
100
- }
101
-
102
- try {
103
- $cache_key = 'fw_wp_filesystem/base_dirs_map';
104
-
105
- return FW_Cache::get($cache_key);
106
- } catch (FW_Cache_Not_Found_Exception $e) {
107
- // code from $wp_filesystem->wp_themes_dir()
108
- {
109
- $themes_dir = get_theme_root();
110
-
111
- // Account for relative theme roots
112
- if ( '/themes' == $themes_dir || ! is_dir( $themes_dir ) ) {
113
- $themes_dir = WP_CONTENT_DIR . $themes_dir;
114
- }
115
- }
116
-
117
- $dirs = array(
118
- fw_fix_path(ABSPATH) => fw_fix_path($wp_filesystem->abspath()),
119
- fw_fix_path(WP_CONTENT_DIR) => fw_fix_path($wp_filesystem->wp_content_dir()),
120
- fw_fix_path(WP_PLUGIN_DIR) => fw_fix_path($wp_filesystem->wp_plugins_dir()),
121
- fw_fix_path($themes_dir) => fw_fix_path($wp_filesystem->wp_themes_dir()),
122
- );
123
-
124
- FW_Cache::set($cache_key, $dirs);
125
-
126
- return $dirs;
127
- }
128
- }
129
-
130
- /**
131
- * Convert real file path to WP Filesystem path
132
- * @param string $real_path
133
- * @return string|false
134
- */
135
- final public static function real_path_to_filesystem_path($real_path) {
136
- /** @var WP_Filesystem_Base $wp_filesystem */
137
- global $wp_filesystem;
138
-
139
- if (!$wp_filesystem) {
140
- trigger_error('Filesystem is not available', E_USER_ERROR);
141
- } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
142
- trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
143
- }
144
-
145
- $real_path = fw_fix_path($real_path);
146
-
147
- foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
148
- $prefix_regex = '/^'. preg_quote($base_real_path, '/') .'/';
149
-
150
- // check if path is inside base path
151
- if (!preg_match($prefix_regex, $real_path)) {
152
- continue;
153
- }
154
-
155
- if ($base_real_path === '/') {
156
- $relative_path = $real_path;
157
- } else {
158
- $relative_path = preg_replace($prefix_regex, '', $real_path);
159
- }
160
-
161
- return $base_wp_filesystem_path . $relative_path;
162
- }
163
-
164
- return false;
165
- }
166
-
167
- /**
168
- * Convert WP Filesystem path to real file path
169
- * @param string $wp_filesystem_path
170
- * @return string|false
171
- */
172
- final public static function filesystem_path_to_real_path($wp_filesystem_path) {
173
- /** @var WP_Filesystem_Base $wp_filesystem */
174
- global $wp_filesystem;
175
-
176
- if (!$wp_filesystem) {
177
- trigger_error('Filesystem is not available', E_USER_ERROR);
178
- } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
179
- trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
180
- }
181
-
182
- $wp_filesystem_path = fw_fix_path($wp_filesystem_path);
183
-
184
- foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
185
- $prefix_regex = '/^'. preg_quote($base_wp_filesystem_path, '/') .'/';
186
-
187
- // check if path is inside base path
188
- if (!preg_match($prefix_regex, $wp_filesystem_path)) {
189
- continue;
190
- }
191
-
192
- if ($base_wp_filesystem_path === '/') {
193
- $relative_path = $wp_filesystem_path;
194
- } else {
195
- $relative_path = preg_replace($prefix_regex, '', $wp_filesystem_path);
196
- }
197
-
198
- return $base_real_path . $relative_path;
199
- }
200
-
201
- return false;
202
- }
203
-
204
- /**
205
- * Check if there is direct filesystem access, so we can make changes without asking the credentials via form
206
- * @param string|null $context
207
- * @return bool
208
- */
209
- final public static function has_direct_access($context = null)
210
- {
211
- /** @var WP_Filesystem_Base $wp_filesystem */
212
- global $wp_filesystem;
213
-
214
- if ($wp_filesystem) {
215
- if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
216
- return false;
217
- } else {
218
- return $wp_filesystem->method === 'direct';
219
- }
220
- }
221
-
222
- if (get_filesystem_method(array(), $context) === 'direct') {
223
- ob_start();
224
- {
225
- $creds = request_filesystem_credentials(admin_url(), '', false, $context, null);
226
- }
227
- ob_end_clean();
228
-
229
- if ( WP_Filesystem($creds) ) {
230
- return true;
231
- }
232
- }
233
-
234
- return false;
235
- }
236
-
237
- /**
238
- * Create wp filesystem directory recursive
239
- * @param string $wp_filesystem_dir_path
240
- * @return bool
241
- */
242
- final public static function mkdir_recursive($wp_filesystem_dir_path) {
243
- /** @var WP_Filesystem_Base $wp_filesystem */
244
- global $wp_filesystem;
245
-
246
- if (!$wp_filesystem) {
247
- trigger_error('Filesystem is not available', E_USER_ERROR);
248
- } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
249
- trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
250
- }
251
-
252
- $wp_filesystem_dir_path = fw_fix_path($wp_filesystem_dir_path);
253
-
254
- $path = false;
255
-
256
- foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
257
- $prefix_regex = '/^'. preg_quote($base_wp_filesystem_path, '/') .'/';
258
-
259
- // check if path is inside base path
260
- if (!preg_match($prefix_regex, $wp_filesystem_dir_path)) {
261
- continue;
262
- }
263
-
264
- $path = $base_wp_filesystem_path;
265
- break;
266
- }
267
-
268
- if (!$path) {
269
- trigger_error(
270
- sprintf(
271
- __('Cannot create directory "%s". It must be inside "%s"', 'fw'),
272
- $wp_filesystem_dir_path,
273
- implode(__('" or "', 'fw'), self::get_base_dirs_map())
274
- ),
275
- E_USER_WARNING
276
- );
277
- return false;
278
- }
279
-
280
- if ($path === '/') {
281
- $rel_path = $wp_filesystem_dir_path;
282
- } else {
283
- $rel_path = preg_replace('/^'. preg_quote($path, '/') .'/', '', $wp_filesystem_dir_path);
284
- }
285
-
286
- // improvement: do not check directory for existence if it's known that sure it doesn't exist
287
- $check_if_exists = true;
288
-
289
- foreach (explode('/', ltrim($rel_path, '/')) as $dir_name) {
290
- $path .= '/' . $dir_name;
291
-
292
- // When WP FS abspath is '/', $path can be '//wp-content'. Fix it '/wp-content'
293
- $path = fw_fix_path($path);
294
-
295
- if ($check_if_exists) {
296
- if ($wp_filesystem->is_dir($path)) {
297
- // do nothing if exists
298
- continue;
299
- } else {
300
- // do not check anymore, next directories sure doesn't exist
301
- $check_if_exists = false;
302
- }
303
- }
304
-
305
- if (!$wp_filesystem->mkdir($path, FS_CHMOD_DIR)) {
306
- return false;
307
- }
308
- }
309
-
310
- return true;
311
- }
312
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ class FW_WP_Filesystem
4
+ {
5
+ /**
6
+ * Request WP Filesystem access
7
+ * @param string $context
8
+ * @param string $url
9
+ * @param array $extra_fields
10
+ * @return null|bool // todo: Create a new method that will return WP_Error with message on failure
11
+ * null - if has no access and the input credentials form was displayed
12
+ * false - if user submitted wrong credentials
13
+ * true - if we have filesystem access
14
+ */
15
+ final public static function request_access($context = null, $url = null, $extra_fields = array())
16
+ {
17
+ /** @var WP_Filesystem_Base $wp_filesystem */
18
+ global $wp_filesystem;
19
+
20
+ if ($wp_filesystem) {
21
+ if (
22
+ is_object($wp_filesystem)
23
+ &&
24
+ !(is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())
25
+ ) {
26
+ return true; // already initialized
27
+ }
28
+ }
29
+
30
+ if ( empty( $url ) ) {
31
+ $url = fw_current_url();
32
+ }
33
+
34
+ if ( get_filesystem_method() === 'direct' ) {
35
+ // in case if direct access is available
36
+
37
+ /* you can safely run request_filesystem_credentials() without any issues and don't need to worry about passing in a URL */
38
+ $creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, null );
39
+
40
+ /* initialize the API */
41
+ if ( ! WP_Filesystem( $creds ) ) {
42
+ /* any problems and we exit */
43
+ trigger_error( __( 'Cannot connect to Filesystem directly', 'fw' ), E_USER_WARNING );
44
+
45
+ return false;
46
+ }
47
+ } else {
48
+ $creds = request_filesystem_credentials( $url, '', false, $context, $extra_fields );
49
+
50
+ if ( ! $creds ) {
51
+ // the form was printed to the user
52
+ return null;
53
+ }
54
+
55
+ /* initialize the API */
56
+ if ( ! WP_Filesystem( $creds ) ) {
57
+ /* any problems and we exit */
58
+ request_filesystem_credentials( $url, '', true, $context, $extra_fields ); // the third parameter is true to show error to the user
59
+ return false;
60
+ }
61
+ }
62
+
63
+ if (
64
+ ! is_object($wp_filesystem)
65
+ ||
66
+ (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())
67
+ ) {
68
+ return false;
69
+ }
70
+
71
+ if (
72
+ $wp_filesystem->abspath()
73
+ &&
74
+ $wp_filesystem->wp_content_dir()
75
+ &&
76
+ $wp_filesystem->wp_plugins_dir()
77
+ &&
78
+ $wp_filesystem->wp_themes_dir()
79
+ &&
80
+ $wp_filesystem->find_folder($context)
81
+ ) {
82
+ return true;
83
+ } else {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @return array {base_dir_real_path => base_dir_wp_filesystem_path}
90
+ */
91
+ public static function get_base_dirs_map()
92
+ {
93
+ /** @var WP_Filesystem_Base $wp_filesystem */
94
+ global $wp_filesystem;
95
+
96
+ if (!$wp_filesystem) {
97
+ trigger_error('Filesystem is not available', E_USER_ERROR);
98
+ } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
99
+ trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
100
+ }
101
+
102
+ try {
103
+ $cache_key = 'fw_wp_filesystem/base_dirs_map';
104
+
105
+ return FW_Cache::get($cache_key);
106
+ } catch (FW_Cache_Not_Found_Exception $e) {
107
+ // code from $wp_filesystem->wp_themes_dir()
108
+ {
109
+ $themes_dir = get_theme_root();
110
+
111
+ // Account for relative theme roots
112
+ if ( '/themes' == $themes_dir || ! is_dir( $themes_dir ) ) {
113
+ $themes_dir = WP_CONTENT_DIR . $themes_dir;
114
+ }
115
+ }
116
+
117
+ $dirs = array(
118
+ fw_fix_path(ABSPATH) => fw_fix_path($wp_filesystem->abspath()),
119
+ fw_fix_path(WP_CONTENT_DIR) => fw_fix_path($wp_filesystem->wp_content_dir()),
120
+ fw_fix_path(WP_PLUGIN_DIR) => fw_fix_path($wp_filesystem->wp_plugins_dir()),
121
+ fw_fix_path($themes_dir) => fw_fix_path($wp_filesystem->wp_themes_dir()),
122
+ );
123
+
124
+ FW_Cache::set($cache_key, $dirs);
125
+
126
+ return $dirs;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Convert real file path to WP Filesystem path
132
+ * @param string $real_path
133
+ * @return string|false
134
+ */
135
+ final public static function real_path_to_filesystem_path($real_path) {
136
+ /** @var WP_Filesystem_Base $wp_filesystem */
137
+ global $wp_filesystem;
138
+
139
+ if (!$wp_filesystem) {
140
+ trigger_error('Filesystem is not available', E_USER_ERROR);
141
+ } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
142
+ trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
143
+ }
144
+
145
+ $real_path = fw_fix_path($real_path);
146
+
147
+ foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
148
+ $prefix_regex = '/^'. preg_quote($base_real_path, '/') .'/';
149
+
150
+ // check if path is inside base path
151
+ if (!preg_match($prefix_regex, $real_path)) {
152
+ continue;
153
+ }
154
+
155
+ if ($base_real_path === '/') {
156
+ $relative_path = $real_path;
157
+ } else {
158
+ $relative_path = preg_replace($prefix_regex, '', $real_path);
159
+ }
160
+
161
+ return $base_wp_filesystem_path . $relative_path;
162
+ }
163
+
164
+ return false;
165
+ }
166
+
167
+ /**
168
+ * Convert WP Filesystem path to real file path
169
+ * @param string $wp_filesystem_path
170
+ * @return string|false
171
+ */
172
+ final public static function filesystem_path_to_real_path($wp_filesystem_path) {
173
+ /** @var WP_Filesystem_Base $wp_filesystem */
174
+ global $wp_filesystem;
175
+
176
+ if (!$wp_filesystem) {
177
+ trigger_error('Filesystem is not available', E_USER_ERROR);
178
+ } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
179
+ trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
180
+ }
181
+
182
+ $wp_filesystem_path = fw_fix_path($wp_filesystem_path);
183
+
184
+ foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
185
+ $prefix_regex = '/^'. preg_quote($base_wp_filesystem_path, '/') .'/';
186
+
187
+ // check if path is inside base path
188
+ if (!preg_match($prefix_regex, $wp_filesystem_path)) {
189
+ continue;
190
+ }
191
+
192
+ if ($base_wp_filesystem_path === '/') {
193
+ $relative_path = $wp_filesystem_path;
194
+ } else {
195
+ $relative_path = preg_replace($prefix_regex, '', $wp_filesystem_path);
196
+ }
197
+
198
+ return $base_real_path . $relative_path;
199
+ }
200
+
201
+ return false;
202
+ }
203
+
204
+ /**
205
+ * Check if there is direct filesystem access, so we can make changes without asking the credentials via form
206
+ * @param string|null $context
207
+ * @return bool
208
+ */
209
+ final public static function has_direct_access($context = null)
210
+ {
211
+ /** @var WP_Filesystem_Base $wp_filesystem */
212
+ global $wp_filesystem;
213
+
214
+ if ($wp_filesystem) {
215
+ if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
216
+ return false;
217
+ } else {
218
+ return $wp_filesystem->method === 'direct';
219
+ }
220
+ }
221
+
222
+ if (get_filesystem_method(array(), $context) === 'direct') {
223
+ ob_start();
224
+ {
225
+ $creds = request_filesystem_credentials(admin_url(), '', false, $context, null);
226
+ }
227
+ ob_end_clean();
228
+
229
+ if ( WP_Filesystem($creds) ) {
230
+ return true;
231
+ }
232
+ }
233
+
234
+ return false;
235
+ }
236
+
237
+ /**
238
+ * Create wp filesystem directory recursive
239
+ * @param string $wp_filesystem_dir_path
240
+ * @return bool
241
+ */
242
+ final public static function mkdir_recursive($wp_filesystem_dir_path) {
243
+ /** @var WP_Filesystem_Base $wp_filesystem */
244
+ global $wp_filesystem;
245
+
246
+ if (!$wp_filesystem) {
247
+ trigger_error('Filesystem is not available', E_USER_ERROR);
248
+ } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
249
+ trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
250
+ }
251
+
252
+ $wp_filesystem_dir_path = fw_fix_path($wp_filesystem_dir_path);
253
+
254
+ $path = false;
255
+
256
+ foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
257
+ $prefix_regex = '/^'. preg_quote($base_wp_filesystem_path, '/') .'/';
258
+
259
+ // check if path is inside base path
260
+ if (!preg_match($prefix_regex, $wp_filesystem_dir_path)) {
261
+ continue;
262
+ }
263
+
264
+ $path = $base_wp_filesystem_path;
265
+ break;
266
+ }
267
+
268
+ if (!$path) {
269
+ trigger_error(
270
+ sprintf(
271
+ __('Cannot create directory "%s". It must be inside "%s"', 'fw'),
272
+ $wp_filesystem_dir_path,
273
+ implode(__('" or "', 'fw'), self::get_base_dirs_map())
274
+ ),
275
+ E_USER_WARNING
276
+ );
277
+ return false;
278
+ }
279
+
280
+ if ($path === '/') {
281
+ $rel_path = $wp_filesystem_dir_path;
282
+ } else {
283
+ $rel_path = preg_replace('/^'. preg_quote($path, '/') .'/', '', $wp_filesystem_dir_path);
284
+ }
285
+
286
+ // improvement: do not check directory for existence if it's known that sure it doesn't exist
287
+ $check_if_exists = true;
288
+
289
+ foreach (explode('/', ltrim($rel_path, '/')) as $dir_name) {
290
+ $path .= '/' . $dir_name;
291
+
292
+ // When WP FS abspath is '/', $path can be '//wp-content'. Fix it '/wp-content'
293
+ $path = fw_fix_path($path);
294
+
295
+ if ($check_if_exists) {
296
+ if ($wp_filesystem->is_dir($path)) {
297
+ // do nothing if exists
298
+ continue;
299
+ } else {
300
+ // do not check anymore, next directories sure doesn't exist
301
+ $check_if_exists = false;
302
+ }
303
+ }
304
+
305
+ if (!$wp_filesystem->mkdir($path, FS_CHMOD_DIR)) {
306
+ return false;
307
+ }
308
+ }
309
+
310
+ return true;
311
+ }
312
+ }
framework/helpers/class-fw-wp-list-table.php CHANGED
@@ -1,968 +1,968 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
- /**
5
- * Base class for displaying a list of items in an ajaxified HTML table.
6
- *
7
- * @package WordPress
8
- * @subpackage List_Table
9
- * @since 3.1.0
10
- * @access private
11
- */
12
- class FW_WP_List_Table {
13
-
14
- /**
15
- * The current list of items
16
- *
17
- * @since 3.1.0
18
- * @var array
19
- * @access protected
20
- */
21
- var $items;
22
-
23
- /**
24
- * Various information about the current table
25
- *
26
- * @since 3.1.0
27
- * @var array
28
- * @access private
29
- */
30
- var $_args;
31
-
32
- /**
33
- * Various information needed for displaying the pagination
34
- *
35
- * @since 3.1.0
36
- * @var array
37
- * @access private
38
- */
39
- var $_pagination_args = array();
40
-
41
- /**
42
- * The current screen
43
- *
44
- * @since 3.1.0
45
- * @var object
46
- * @access protected
47
- */
48
- var $screen;
49
-
50
- /**
51
- * Cached bulk actions
52
- *
53
- * @since 3.1.0
54
- * @var array
55
- * @access private
56
- */
57
- var $_actions;
58
-
59
- /**
60
- * Cached pagination output
61
- *
62
- * @since 3.1.0
63
- * @var string
64
- * @access private
65
- */
66
- var $_pagination;
67
-
68
- /**
69
- * Constructor. The child class should call this constructor from its own constructor
70
- *
71
- * @param array $args An associative array with information about the current table
72
- * @access protected
73
- */
74
- function __construct( $args = array() ) {
75
- $args = wp_parse_args( $args, array(
76
- 'plural' => '',
77
- 'singular' => '',
78
- 'ajax' => false,
79
- 'screen' => null,
80
- ) );
81
-
82
- $this->screen = convert_to_screen( $args['screen'] );
83
-
84
- add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
85
-
86
- if ( !$args['plural'] )
87
- $args['plural'] = $this->screen->base;
88
-
89
- $args['plural'] = sanitize_key( $args['plural'] );
90
- $args['singular'] = sanitize_key( $args['singular'] );
91
-
92
- $this->_args = $args;
93
-
94
- if ( $args['ajax'] ) {
95
- // wp_enqueue_script( 'list-table' );
96
- add_action( 'admin_footer', array( $this, '_js_vars' ) );
97
- }
98
- }
99
-
100
- /**
101
- * Checks the current user's permissions
102
- * @uses wp_die()
103
- *
104
- * @since 3.1.0
105
- * @access public
106
- * @abstract
107
- */
108
- function ajax_user_can() {
109
- die( 'function WP_List_Table::ajax_user_can() must be over-ridden in a sub-class.' );
110
- }
111
-
112
- /**
113
- * Prepares the list of items for displaying.
114
- * @uses WP_List_Table::set_pagination_args()
115
- *
116
- * @since 3.1.0
117
- * @access public
118
- * @abstract
119
- */
120
- function prepare_items() {
121
- die( 'function WP_List_Table::prepare_items() must be over-ridden in a sub-class.' );
122
- }
123
-
124
- /**
125
- * An internal method that sets all the necessary pagination arguments
126
- *
127
- * @param array $args An associative array with information about the pagination
128
- * @access protected
129
- */
130
- function set_pagination_args( $args ) {
131
- $args = wp_parse_args( $args, array(
132
- 'total_items' => 0,
133
- 'total_pages' => 0,
134
- 'per_page' => 0,
135
- ) );
136
-
137
- if ( !$args['total_pages'] && $args['per_page'] > 0 )
138
- $args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
139
-
140
- // redirect if page number is invalid and headers are not already sent
141
- if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
142
- wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
143
- exit;
144
- }
145
-
146
- $this->_pagination_args = $args;
147
- }
148
-
149
- /**
150
- * Access the pagination args
151
- *
152
- * @since 3.1.0
153
- * @access public
154
- *
155
- * @param string $key
156
- * @return array
157
- */
158
- function get_pagination_arg( $key ) {
159
- if ( 'page' == $key )
160
- return $this->get_pagenum();
161
-
162
- if ( isset( $this->_pagination_args[$key] ) )
163
- return $this->_pagination_args[$key];
164
- }
165
-
166
- /**
167
- * Whether the table has items to display or not
168
- *
169
- * @since 3.1.0
170
- * @access public
171
- *
172
- * @return bool
173
- */
174
- function has_items() {
175
- return !empty( $this->items );
176
- }
177
-
178
- /**
179
- * Message to be displayed when there are no items
180
- *
181
- * @since 3.1.0
182
- * @access public
183
- */
184
- function no_items() {
185
- _e( 'No items found.', 'fw' );
186
- }
187
-
188
- /**
189
- * Display the search box.
190
- *
191
- * @since 3.1.0
192
- * @access public
193
- *
194
- * @param string $text The search button text
195
- * @param string $input_id The search input id
196
- */
197
- function search_box( $text, $input_id ) {
198
- if ( empty( $_REQUEST['s'] ) && !$this->has_items() )
199
- return;
200
-
201
- $input_id = $input_id . '-search-input';
202
-
203
- if ( ! empty( $_REQUEST['orderby'] ) )
204
- echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
205
- if ( ! empty( $_REQUEST['order'] ) )
206
- echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
207
- if ( ! empty( $_REQUEST['post_mime_type'] ) )
208
- echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
209
- if ( ! empty( $_REQUEST['detached'] ) )
210
- echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
211
- ?>
212
- <p class="search-box">
213
- <label class="screen-reader-text" for="<?php echo esc_attr($input_id) ?>"><?php echo $text; ?>:</label>
214
- <input type="search" id="<?php echo esc_attr($input_id) ?>" name="s" value="<?php _admin_search_query(); ?>" />
215
- <?php submit_button( $text, 'button', false, false, array('id' => 'search-submit') ); ?>
216
- </p>
217
- <?php
218
- }
219
-
220
- /**
221
- * Get an associative array ( id => link ) with the list
222
- * of views available on this table.
223
- *
224
- * @since 3.1.0
225
- * @access protected
226
- *
227
- * @return array
228
- */
229
- function get_views() {
230
- return array();
231
- }
232
-
233
- /**
234
- * Display the list of views available on this table.
235
- *
236
- * @since 3.1.0
237
- * @access public
238
- */
239
- function views() {
240
- $views = $this->get_views();
241
- /**
242
- * Filter the list of available list table views.
243
- *
244
- * The dynamic portion of the hook name, $this->screen->id, refers
245
- * to the ID of the current screen, usually a string.
246
- *
247
- * @since 3.5.0
248
- *
249
- * @param array $views An array of available list table views.
250
- */
251
- $views = apply_filters( "views_{$this->screen->id}", $views );
252
-
253
- if ( empty( $views ) )
254
- return;
255
-
256
- echo "<ul class='subsubsub'>\n";
257
- foreach ( $views as $class => $view ) {
258
- $views[ $class ] = "\t<li class='$class'>$view";
259
- }
260
- echo implode( " |</li>\n", $views ) . "</li>\n";
261
- echo "</ul>";
262
- }
263
-
264
- /**
265
- * Get an associative array ( option_name => option_title ) with the list
266
- * of bulk actions available on this table.
267
- *
268
- * @since 3.1.0
269
- * @access protected
270
- *
271
- * @return array
272
- */
273
- function get_bulk_actions() {
274
- return array();
275
- }
276
-
277
- /**
278
- * Display the bulk actions dropdown.
279
- *
280
- * @since 3.1.0
281
- * @access public
282
- */
283
- function bulk_actions() {
284
- if ( is_null( $this->_actions ) ) {
285
- $no_new_actions = $this->_actions = $this->get_bulk_actions();
286
- /**
287
- * Filter the list table Bulk Actions drop-down.
288
- *
289
- * The dynamic portion of the hook name, $this->screen->id, refers
290
- * to the ID of the current screen, usually a string.
291
- *
292
- * This filter can currently only be used to remove bulk actions.
293
- *
294
- * @since 3.5.0
295
- *
296
- * @param array $actions An array of the available bulk actions.
297
- */
298
- $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
299
- $this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
300
- $two = '';
301
- } else {
302
- $two = '2';
303
- }
304
-
305
- if ( empty( $this->_actions ) )
306
- return;
307
-
308
- echo "<select name='action$two'>\n";
309
- echo "<option value='-1' selected='selected'>" . __( 'Bulk Actions', 'fw' ) . "</option>\n";
310
-
311
- foreach ( $this->_actions as $name => $title ) {
312
- $class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
313
-
314
- echo "\t<option value='$name'$class>$title</option>\n";
315
- }
316
-
317
- echo "</select>\n";
318
-
319
- submit_button( __( 'Apply', 'fw' ), 'action', false, false, array( 'id' => "doaction$two" ) );
320
- echo "\n";
321
- }
322
-
323
- /**
324
- * Get the current action selected from the bulk actions dropdown.
325
- *
326
- * @since 3.1.0
327
- * @access public
328
- *
329
- * @return string|bool The action name or False if no action was selected
330
- */
331
- function current_action() {
332
- if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] )
333
- return $_REQUEST['action'];
334
-
335
- if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] )
336
- return $_REQUEST['action2'];
337
-
338
- return false;
339
- }
340
-
341
- /**
342
- * Generate row actions div
343
- *
344
- * @since 3.1.0
345
- * @access protected
346
- *
347
- * @param array $actions The list of actions
348
- * @param bool $always_visible Whether the actions should be always visible
349
- * @return string
350
- */
351
- function row_actions( $actions, $always_visible = false ) {
352
- $action_count = count( $actions );
353
- $i = 0;
354
-
355
- if ( !$action_count )
356
- return '';
357
-
358
- $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
359
- foreach ( $actions as $action => $link ) {
360
- ++$i;
361
- ( $i == $action_count ) ? $sep = '' : $sep = ' | ';
362
- $out .= "<span class='$action'>$link$sep</span>";
363
- }
364
- $out .= '</div>';
365
-
366
- return $out;
367
- }
368
-
369
- /**
370
- * Display a monthly dropdown for filtering items
371
- *
372
- * @since 3.1.0
373
- * @access protected
374
- */
375
- function months_dropdown( $post_type ) {
376
- global $wpdb, $wp_locale;
377
-
378
- $months = $wpdb->get_results( $wpdb->prepare( "
379
- SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
380
- FROM $wpdb->posts
381
- WHERE post_type = %s
382
- ORDER BY post_date DESC
383
- ", $post_type ) );
384
-
385
- /**
386
- * Filter the 'Months' drop-down results.
387
- *
388
- * @since 3.7.0
389
- *
390
- * @param object $months The months drop-down query results.
391
- * @param string $post_type The post type.
392
- */
393
- $months = apply_filters( 'months_dropdown_results', $months, $post_type );
394
-
395
- $month_count = count( $months );
396
-
397
- if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) )
398
- return;
399
-
400
- $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
401
- ?>
402
- <select name='m'>
403
- <option<?php selected( $m, 0 ); ?> value='0'><?php _e( 'All dates', 'fw' ); ?></option>
404
- <?php
405
- foreach ( $months as $arc_row ) {
406
- if ( 0 == $arc_row->year )
407
- continue;
408
-
409
- $month = zeroise( $arc_row->month, 2 );
410
- $year = $arc_row->year;
411
-
412
- printf( "<option %s value='%s'>%s</option>\n",
413
- selected( $m, $year . $month, false ),
414
- esc_attr( $arc_row->year . $month ),
415
- /* translators: 1: month name, 2: 4-digit year */
416
- sprintf( __( '%1$s %2$d', 'fw' ), $wp_locale->get_month( $month ), $year )
417
- );
418
- }
419
- ?>
420
- </select>
421
- <?php
422
- }
423
-
424
- /**
425
- * Display a view switcher
426
- *
427
- * @since 3.1.0
428
- * @access protected
429
- */
430
- function view_switcher( $current_mode ) {
431
- $modes = array(
432
- 'list' => __( 'List View', 'fw' ),
433
- 'excerpt' => __( 'Excerpt View', 'fw' )
434
- );
435
-
436
- ?>
437
- <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
438
- <div class="view-switch">
439
- <?php
440
- foreach ( $modes as $mode => $title ) {
441
- $class = ( $current_mode == $mode ) ? 'class="current"' : '';
442
- echo "<a href='" . esc_url( add_query_arg( 'mode', $mode, $_SERVER['REQUEST_URI'] ) ) . "' $class><img id='view-switch-$mode' src='" . esc_url( includes_url( 'images/blank.gif' ) ) . "' width='20' height='20' title='$title' alt='$title' /></a>\n";
443
- }
444
- ?>
445
- </div>
446
- <?php
447
- }
448
-
449
- /**
450
- * Display a comment count bubble
451
- *
452
- * @since 3.1.0
453
- * @access protected
454
- *
455
- * @param int $post_id
456
- * @param int $pending_comments
457
- */
458
- function comments_bubble( $post_id, $pending_comments ) {
459
- $pending_phrase = sprintf( __( '%s pending', 'fw' ), number_format( $pending_comments ) );
460
-
461
- if ( $pending_comments )
462
- echo '<strong>';
463
-
464
- echo "<a href='" . esc_url( add_query_arg( 'p', $post_id, admin_url( 'edit-comments.php' ) ) ) . "' title='" . esc_attr( $pending_phrase ) . "' class='post-com-count'><span class='comment-count'>" . number_format_i18n( get_comments_number() ) . "</span></a>";
465
-
466
- if ( $pending_comments )
467
- echo '</strong>';
468
- }
469
-
470
- /**
471
- * Get the current page number
472
- *
473
- * @since 3.1.0
474
- * @access protected
475
- *
476
- * @return int
477
- */
478
- function get_pagenum() {
479
- $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
480
-
481
- if( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] )
482
- $pagenum = $this->_pagination_args['total_pages'];
483
-
484
- return max( 1, $pagenum );
485
- }
486
-
487
- /**
488
- * Get number of items to display on a single page
489
- *
490
- * @since 3.1.0
491
- * @access protected
492
- *
493
- * @return int
494
- */
495
- function get_items_per_page( $option, $default = 20 ) {
496
- $per_page = (int) get_user_option( $option );
497
- if ( empty( $per_page ) || $per_page < 1 )
498
- $per_page = $default;
499
-
500
- /**
501
- * Filter the number of items to be displayed on each page of the list table.
502
- *
503
- * The dynamic hook name, $option, refers to the per page option depending
504
- * on the type of list table in use. Possible values may include:
505
- * 'edit_comments_per_page', 'sites_network_per_page', 'site_themes_network_per_page',
506
- * 'themes_netework_per_page', 'users_network_per_page', 'edit_{$post_type}', etc.
507
- *
508
- * @since 2.9.0
509
- *
510
- * @param int $per_page Number of items to be displayed. Default 20.
511
- */
512
- return (int) apply_filters( $option, $per_page );
513
- }
514
-
515
- /**
516
- * Display the pagination.
517
- *
518
- * @since 3.1.0
519
- * @access protected
520
- */
521
- function pagination( $which ) {
522
- if ( empty( $this->_pagination_args ) )
523
- return;
524
-
525
- extract( $this->_pagination_args, EXTR_SKIP );
526
-
527
- $output = '<span class="displaying-num">' . sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
528
-
529
- $current = $this->get_pagenum();
530
-
531
- $current_url = fw_current_url();
532
-
533
- $current_url = remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
534
-
535
- $page_links = array();
536
-
537
- $disable_first = $disable_last = '';
538
- if ( $current == 1 )
539
- $disable_first = ' disabled';
540
- if ( $current == $total_pages )
541
- $disable_last = ' disabled';
542
-
543
- $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
544
- 'first-page' . $disable_first,
545
- esc_attr__( 'Go to the first page', 'fw' ),
546
- esc_url( remove_query_arg( 'paged', $current_url ) ),
547
- '&laquo;'
548
- );
549
-
550
- $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
551
- 'prev-page' . $disable_first,
552
- esc_attr__( 'Go to the previous page', 'fw' ),
553
- esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
554
- '&lsaquo;'
555
- );
556
-
557
- if ( 'bottom' == $which )
558
- $html_current_page = $current;
559
- else
560
- $html_current_page = sprintf( "<input class='current-page' title='%s' type='text' name='paged' value='%s' size='%d' />",
561
- esc_attr__( 'Current page', 'fw' ),
562
- $current,
563
- strlen( $total_pages )
564
- );
565
-
566
- $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
567
- $page_links[] = '<span class="paging-input">' . sprintf( _x( '%1$s of %2$s', 'paging', 'fw' ), $html_current_page, $html_total_pages ) . '</span>';
568
-
569
- $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
570
- 'next-page' . $disable_last,
571
- esc_attr__( 'Go to the next page', 'fw' ),
572
- esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
573
- '&rsaquo;'
574
- );
575
-
576
- $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
577
- 'last-page' . $disable_last,
578
- esc_attr__( 'Go to the last page', 'fw' ),
579
- esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
580
- '&raquo;'
581
- );
582
-
583
- $pagination_links_class = 'pagination-links';
584
- if ( ! empty( $infinite_scroll ) )
585
- $pagination_links_class = ' hide-if-js';
586
- $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
587
-
588
- if ( $total_pages )
589
- $page_class = $total_pages < 2 ? ' one-page' : '';
590
- else
591
- $page_class = ' no-pages';
592
-
593
- $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
594
-
595
- echo $this->_pagination;
596
- }
597
-
598
- /**
599
- * Get a list of columns. The format is:
600
- * 'internal-name' => 'Title'
601
- *
602
- * @since 3.1.0
603
- * @access protected
604
- * @abstract
605
- *
606
- * @return array
607
- */
608
- function get_columns() {
609
- die( 'function WP_List_Table::get_columns() must be over-ridden in a sub-class.' );
610
- }
611
-
612
- /**
613
- * Get a list of sortable columns. The format is:
614
- * 'internal-name' => 'orderby'
615
- * or
616
- * 'internal-name' => array( 'orderby', true )
617
- *
618
- * The second format will make the initial sorting order be descending
619
- *
620
- * @since 3.1.0
621
- * @access protected
622
- *
623
- * @return array
624
- */
625
- function get_sortable_columns() {
626
- return array();
627
- }
628
-
629
- /**
630
- * Get a list of all, hidden and sortable columns, with filter applied
631
- *
632
- * @since 3.1.0
633
- * @access protected
634
- *
635
- * @return array
636
- */
637
- function get_column_info() {
638
- if ( isset( $this->_column_headers ) )
639
- return $this->_column_headers;
640
-
641
- $columns = get_column_headers( $this->screen );
642
- $hidden = get_hidden_columns( $this->screen );
643
-
644
- $sortable_columns = $this->get_sortable_columns();
645
- /**
646
- * Filter the list table sortable columns for a specific screen.
647
- *
648
- * The dynamic portion of the hook name, $this->screen->id, refers
649
- * to the ID of the current screen, usually a string.
650
- *
651
- * @since 3.5.0
652
- *
653
- * @param array $sortable_columns An array of sortable columns.
654
- */
655
- $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
656
-
657
- $sortable = array();
658
- foreach ( $_sortable as $id => $data ) {
659
- if ( empty( $data ) )
660
- continue;
661
-
662
- $data = (array) $data;
663
- if ( !isset( $data[1] ) )
664
- $data[1] = false;
665
-
666
- $sortable[$id] = $data;
667
- }
668
-
669
- $this->_column_headers = array( $columns, $hidden, $sortable );
670
-
671
- return $this->_column_headers;
672
- }
673
-
674
- /**
675
- * Return number of visible columns
676
- *
677
- * @since 3.1.0
678
- * @access public
679
- *
680
- * @return int
681
- */
682
- function get_column_count() {
683
- list ( $columns, $hidden ) = $this->get_column_info();
684
- $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
685
- return count( $columns ) - count( $hidden );
686
- }
687
-
688
- /**
689
- * Print column headers, accounting for hidden and sortable columns.
690
- *
691
- * @since 3.1.0
692
- * @access protected
693
- *
694
- * @param bool $with_id Whether to set the id attribute or not
695
- */
696
- function print_column_headers( $with_id = true ) {
697
- list( $columns, $hidden, $sortable ) = $this->get_column_info();
698
-
699
- $current_url = fw_current_url();
700
- $current_url = remove_query_arg( 'paged', $current_url );
701
-
702
- if ( isset( $_GET['orderby'] ) )
703
- $current_orderby = $_GET['orderby'];
704
- else
705
- $current_orderby = '';
706
-
707
- if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] )
708
- $current_order = 'desc';
709
- else
710
- $current_order = 'asc';
711
-
712
- if ( ! empty( $columns['cb'] ) ) {
713
- static $cb_counter = 1;
714
- $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All', 'fw' ) . '</label>'
715
- . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
716
- $cb_counter++;
717
- }
718
-
719
- foreach ( $columns as $column_key => $column_display_name ) {
720
- $class = array( 'manage-column', "column-$column_key" );
721
-
722
- $style = '';
723
- if ( in_array( $column_key, $hidden ) )
724
- $style = 'display:none;';
725
-
726
- $style = ' style="' . $style . '"';
727
-
728
- if ( 'cb' == $column_key )
729
- $class[] = 'check-column';
730
- elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) )
731
- $class[] = 'num';
732
-
733
- if ( isset( $sortable[$column_key] ) ) {
734
- list( $orderby, $desc_first ) = $sortable[$column_key];
735
-
736
- if ( $current_orderby == $orderby ) {
737
- $order = 'asc' == $current_order ? 'desc' : 'asc';
738
- $class[] = 'sorted';
739
- $class[] = $current_order;
740
- } else {
741
- $order = $desc_first ? 'desc' : 'asc';
742
- $class[] = 'sortable';
743
- $class[] = $desc_first ? 'asc' : 'desc';
744
- }
745
-
746
- $column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
747
- }
748
-
749
- $id = $with_id ? "id='$column_key'" : '';
750
-
751
- if ( !empty( $class ) )
752
- $class = "class='" . join( ' ', $class ) . "'";
753
-
754
- echo "<th scope='col' $id $class $style>$column_display_name</th>";
755
- }
756
- }
757
-
758
- /**
759
- * Display the table
760
- *
761
- * @since 3.1.0
762
- * @access public
763
- */
764
- function display() {
765
- extract( $this->_args );
766
-
767
- $this->display_tablenav( 'top' );
768
-
769
- ?>
770
- <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
771
- <thead>
772
- <tr>
773
- <?php $this->print_column_headers(); ?>
774
- </tr>
775
- </thead>
776
-
777
- <tfoot>
778
- <tr>
779
- <?php $this->print_column_headers( false ); ?>
780
- </tr>
781
- </tfoot>
782
-
783
- <tbody id="the-list"<?php if ( $singular ) echo " data-wp-lists='list:$singular'"; ?>>
784
- <?php $this->display_rows_or_placeholder(); ?>
785
- </tbody>
786
- </table>
787
- <?php
788
- $this->display_tablenav( 'bottom' );
789
- }
790
-
791
- /**
792
- * Get a list of CSS classes for the <table> tag
793
- *
794
- * @since 3.1.0
795
- * @access protected
796
- *
797
- * @return array
798
- */
799
- function get_table_classes() {
800
- return array( 'widefat', 'fixed', $this->_args['plural'] );
801
- }
802
-
803
- /**
804
- * Generate the table navigation above or below the table
805
- *
806
- * @since 3.1.0
807
- * @access protected
808
- */
809
- function display_tablenav( $which ) {
810
- if ( 'top' == $which )
811
- wp_nonce_field( 'bulk-' . $this->_args['plural'] );
812
- ?>
813
- <div class="tablenav <?php echo esc_attr( $which ); ?>">
814
-
815
- <div class="alignleft actions bulkactions">
816
- <?php $this->bulk_actions(); ?>
817
- </div>
818
- <?php
819
- $this->extra_tablenav( $which );
820
- $this->pagination( $which );
821
- ?>
822
-
823
- <br class="clear" />
824
- </div>
825
- <?php
826
- }
827
-
828
- /**
829
- * Extra controls to be displayed between bulk actions and pagination
830
- *
831
- * @since 3.1.0
832
- * @access protected
833
- */
834
- function extra_tablenav( $which ) {}
835
-
836
- /**
837
- * Generate the <tbody> part of the table
838
- *
839
- * @since 3.1.0
840
- * @access protected
841
- */
842
- function display_rows_or_placeholder() {
843
- if ( $this->has_items() ) {
844
- $this->display_rows();
845
- } else {
846
- list( $columns, $hidden ) = $this->get_column_info();
847
- echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
848
- $this->no_items();
849
- echo '</td></tr>';
850
- }
851
- }
852
-
853
- /**
854
- * Generate the table rows
855
- *
856
- * @since 3.1.0
857
- * @access protected
858
- */
859
- function display_rows() {
860
- foreach ( $this->items as $item )
861
- $this->single_row( $item );
862
- }
863
-
864
- /**
865
- * Generates content for a single row of the table
866
- *
867
- * @since 3.1.0
868
- * @access protected
869
- *
870
- * @param object $item The current item
871
- */
872
- function single_row( $item ) {
873
- static $row_class = '';
874
- $row_class = ( $row_class == '' ? ' class="alternate"' : '' );
875
-
876
- echo '<tr' . $row_class . '>';
877
- $this->single_row_columns( $item );
878
- echo '</tr>';
879
- }
880
-
881
- /**
882
- * Generates the columns for a single row of the table
883
- *
884
- * @since 3.1.0
885
- * @access protected
886
- *
887
- * @param object $item The current item
888
- */
889
- function single_row_columns( $item ) {
890
- list( $columns, $hidden ) = $this->get_column_info();
891
-
892
- foreach ( $columns as $column_name => $column_display_name ) {
893
- $class = "class='$column_name column-$column_name'";
894
-
895
- $style = '';
896
- if ( in_array( $column_name, $hidden ) )
897
- $style = ' style="display:none;"';
898
-
899
- $attributes = "$class$style";
900
-
901
- if ( 'cb' == $column_name ) {
902
- echo '<th scope="row" class="check-column">';
903
- echo $this->column_cb( $item );
904
- echo '</th>';
905
- }
906
- elseif ( method_exists( $this, 'column_' . $column_name ) ) {
907
- echo "<td $attributes>";
908
- echo call_user_func( array( $this, 'column_' . $column_name ), $item );
909
- echo "</td>";
910
- }
911
- else {
912
- echo "<td $attributes>";
913
- echo $this->column_default( $item, $column_name );
914
- echo "</td>";
915
- }
916
- }
917
- }
918
-
919
- /**
920
- * Handle an incoming ajax request (called from admin-ajax.php)
921
- *
922
- * @since 3.1.0
923
- * @access public
924
- */
925
- function ajax_response() {
926
- $this->prepare_items();
927
-
928
- extract( $this->_args );
929
- extract( $this->_pagination_args, EXTR_SKIP );
930
-
931
- ob_start();
932
- if ( ! empty( $_REQUEST['no_placeholder'] ) )
933
- $this->display_rows();
934
- else
935
- $this->display_rows_or_placeholder();
936
-
937
- $rows = ob_get_clean();
938
-
939
- $response = array( 'rows' => $rows );
940
-
941
- if ( isset( $total_items ) )
942
- $response['total_items_i18n'] = sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) );
943
-
944
- if ( isset( $total_pages ) ) {
945
- $response['total_pages'] = $total_pages;
946
- $response['total_pages_i18n'] = number_format_i18n( $total_pages );
947
- }
948
-
949
- die( json_encode( $response ) );
950
- }
951
-
952
- /**
953
- * Send required variables to JavaScript land
954
- *
955
- * @access private
956
- */
957
- function _js_vars() {
958
- $args = array(
959
- 'class' => get_class( $this ),
960
- 'screen' => array(
961
- 'id' => $this->screen->id,
962
- 'base' => $this->screen->base,
963
- )
964
- );
965
-
966
- printf( "<script type='text/javascript'>list_args = %s;</script>\n", json_encode( $args ) );
967
- }
968
- }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+ /**
5
+ * Base class for displaying a list of items in an ajaxified HTML table.
6
+ *
7
+ * @package WordPress
8
+ * @subpackage List_Table
9
+ * @since 3.1.0
10
+ * @access private
11
+ */
12
+ class FW_WP_List_Table {
13
+
14
+ /**
15
+ * The current list of items
16
+ *
17
+ * @since 3.1.0
18
+ * @var array
19
+ * @access protected
20
+ */
21
+ var $items;
22
+
23
+ /**
24
+ * Various information about the current table
25
+ *
26
+ * @since 3.1.0
27
+ * @var array
28
+ * @access private
29
+ */
30
+ var $_args;
31
+
32
+ /**
33
+ * Various information needed for displaying the pagination
34
+ *
35
+ * @since 3.1.0
36
+ * @var array
37
+ * @access private
38
+ */
39
+ var $_pagination_args = array();
40
+
41
+ /**
42
+ * The current screen
43
+ *
44
+ * @since 3.1.0
45
+ * @var object
46
+ * @access protected
47
+ */
48
+ var $screen;
49
+
50
+ /**
51
+ * Cached bulk actions
52
+ *
53
+ * @since 3.1.0
54
+ * @var array
55
+ * @access private
56
+ */
57
+ var $_actions;
58
+
59
+ /**
60
+ * Cached pagination output
61
+ *
62
+ * @since 3.1.0
63
+ * @var string
64
+ * @access private
65
+ */
66
+ var $_pagination;
67
+
68
+ /**
69
+ * Constructor. The child class should call this constructor from its own constructor
70
+ *
71
+ * @param array $args An associative array with information about the current table
72
+ * @access protected
73
+ */
74
+ function __construct( $args = array() ) {
75
+ $args = wp_parse_args( $args, array(
76
+ 'plural' => '',
77
+ 'singular' => '',
78
+ 'ajax' => false,
79
+ 'screen' => null,
80
+ ) );
81
+
82
+ $this->screen = convert_to_screen( $args['screen'] );
83
+
84
+ add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
85
+
86
+ if ( !$args['plural'] )
87
+ $args['plural'] = $this->screen->base;
88
+
89
+ $args['plural'] = sanitize_key( $args['plural'] );
90
+ $args['singular'] = sanitize_key( $args['singular'] );
91
+
92
+ $this->_args = $args;
93
+
94
+ if ( $args['ajax'] ) {
95
+ // wp_enqueue_script( 'list-table' );
96
+ add_action( 'admin_footer', array( $this, '_js_vars' ) );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Checks the current user's permissions
102
+ * @uses wp_die()
103
+ *
104
+ * @since 3.1.0
105
+ * @access public
106
+ * @abstract
107
+ */
108
+ function ajax_user_can() {
109
+ die( 'function WP_List_Table::ajax_user_can() must be over-ridden in a sub-class.' );
110
+ }
111
+
112
+ /**
113
+ * Prepares the list of items for displaying.
114
+ * @uses WP_List_Table::set_pagination_args()
115
+ *
116
+ * @since 3.1.0
117
+ * @access public
118
+ * @abstract
119
+ */
120
+ function prepare_items() {
121
+ die( 'function WP_List_Table::prepare_items() must be over-ridden in a sub-class.' );
122
+ }
123
+
124
+ /**
125
+ * An internal method that sets all the necessary pagination arguments
126
+ *
127
+ * @param array $args An associative array with information about the pagination
128
+ * @access protected
129
+ */
130
+ function set_pagination_args( $args ) {
131
+ $args = wp_parse_args( $args, array(
132
+ 'total_items' => 0,
133
+ 'total_pages' => 0,
134
+ 'per_page' => 0,
135
+ ) );
136
+
137
+ if ( !$args['total_pages'] && $args['per_page'] > 0 )
138
+ $args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
139
+
140
+ // redirect if page number is invalid and headers are not already sent
141
+ if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
142
+ wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
143
+ exit;
144
+ }
145
+
146
+ $this->_pagination_args = $args;
147
+ }
148
+
149
+ /**
150
+ * Access the pagination args
151
+ *
152
+ * @since 3.1.0
153
+ * @access public
154
+ *
155
+ * @param string $key
156
+ * @return array
157
+ */
158
+ function get_pagination_arg( $key ) {
159
+ if ( 'page' == $key )
160
+ return $this->get_pagenum();
161
+
162
+ if ( isset( $this->_pagination_args[$key] ) )
163
+ return $this->_pagination_args[$key];
164
+ }
165
+
166
+ /**
167
+ * Whether the table has items to display or not
168
+ *
169
+ * @since 3.1.0
170
+ * @access public
171
+ *
172
+ * @return bool
173
+ */
174
+ function has_items() {
175
+ return !empty( $this->items );
176
+ }
177
+
178
+ /**
179
+ * Message to be displayed when there are no items
180
+ *
181
+ * @since 3.1.0
182
+ * @access public
183
+ */
184
+ function no_items() {
185
+ _e( 'No items found.', 'fw' );
186
+ }
187
+
188
+ /**
189
+ * Display the search box.
190
+ *
191
+ * @since 3.1.0
192
+ * @access public
193
+ *
194
+ * @param string $text The search button text
195
+ * @param string $input_id The search input id
196
+ */
197
+ function search_box( $text, $input_id ) {
198
+ if ( empty( $_REQUEST['s'] ) && !$this->has_items() )
199
+ return;
200
+
201
+ $input_id = $input_id . '-search-input';
202
+
203
+ if ( ! empty( $_REQUEST['orderby'] ) )
204
+ echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
205
+ if ( ! empty( $_REQUEST['order'] ) )
206
+ echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
207
+ if ( ! empty( $_REQUEST['post_mime_type'] ) )
208
+ echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
209
+ if ( ! empty( $_REQUEST['detached'] ) )
210
+ echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
211
+ ?>
212
+ <p class="search-box">
213
+ <label class="screen-reader-text" for="<?php echo esc_attr($input_id) ?>"><?php echo $text; ?>:</label>
214
+ <input type="search" id="<?php echo esc_attr($input_id) ?>" name="s" value="<?php _admin_search_query(); ?>" />
215
+ <?php submit_button( $text, 'button', false, false, array('id' => 'search-submit') ); ?>
216
+ </p>
217
+ <?php
218
+ }
219
+
220
+ /**
221
+ * Get an associative array ( id => link ) with the list
222
+ * of views available on this table.
223
+ *
224
+ * @since 3.1.0
225
+ * @access protected
226
+ *
227
+ * @return array
228
+ */
229
+ function get_views() {
230
+ return array();
231
+ }
232
+
233
+ /**
234
+ * Display the list of views available on this table.
235
+ *
236
+ * @since 3.1.0
237
+ * @access public
238
+ */
239
+ function views() {
240
+ $views = $this->get_views();
241
+ /**
242
+ * Filter the list of available list table views.
243
+ *
244
+ * The dynamic portion of the hook name, $this->screen->id, refers
245
+ * to the ID of the current screen, usually a string.
246
+ *
247
+ * @since 3.5.0
248
+ *
249
+ * @param array $views An array of available list table views.
250
+ */
251
+ $views = apply_filters( "views_{$this->screen->id}", $views );
252
+
253
+ if ( empty( $views ) )
254
+ return;
255
+
256
+ echo "<ul class='subsubsub'>\n";
257
+ foreach ( $views as $class => $view ) {
258
+ $views[ $class ] = "\t<li class='$class'>$view";
259
+ }
260
+ echo implode( " |</li>\n", $views ) . "</li>\n";
261
+ echo "</ul>";
262
+ }
263
+
264
+ /**
265
+ * Get an associative array ( option_name => option_title ) with the list
266
+ * of bulk actions available on this table.
267
+ *
268
+ * @since 3.1.0
269
+ * @access protected
270
+ *
271
+ * @return array
272
+ */
273
+ function get_bulk_actions() {
274
+ return array();
275
+ }
276
+
277
+ /**
278
+ * Display the bulk actions dropdown.
279
+ *
280
+ * @since 3.1.0
281
+ * @access public
282
+ */
283
+ function bulk_actions() {
284
+ if ( is_null( $this->_actions ) ) {
285
+ $no_new_actions = $this->_actions = $this->get_bulk_actions();
286
+ /**
287
+ * Filter the list table Bulk Actions drop-down.
288
+ *
289
+ * The dynamic portion of the hook name, $this->screen->id, refers
290
+ * to the ID of the current screen, usually a string.
291
+ *
292
+ * This filter can currently only be used to remove bulk actions.
293
+ *
294
+ * @since 3.5.0
295
+ *
296
+ * @param array $actions An array of the available bulk actions.
297
+ */
298
+ $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
299
+ $this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
300
+ $two = '';
301
+ } else {
302
+ $two = '2';
303
+ }
304
+
305
+ if ( empty( $this->_actions ) )
306
+ return;
307
+
308
+ echo "<select name='action$two'>\n";
309
+ echo "<option value='-1' selected='selected'>" . __( 'Bulk Actions', 'fw' ) . "</option>\n";
310
+
311
+ foreach ( $this->_actions as $name => $title ) {
312
+ $class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
313
+
314
+ echo "\t<option value='$name'$class>$title</option>\n";
315
+ }
316
+
317
+ echo "</select>\n";
318
+
319
+ submit_button( __( 'Apply', 'fw' ), 'action', false, false, array( 'id' => "doaction$two" ) );
320
+ echo "\n";
321
+ }
322
+
323
+ /**
324
+ * Get the current action selected from the bulk actions dropdown.
325
+ *
326
+ * @since 3.1.0
327
+ * @access public
328
+ *
329
+ * @return string|bool The action name or False if no action was selected
330
+ */
331
+ function current_action() {
332
+ if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] )
333
+ return $_REQUEST['action'];
334
+
335
+ if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] )
336
+ return $_REQUEST['action2'];
337
+
338
+ return false;
339
+ }
340
+
341
+ /**
342
+ * Generate row actions div
343
+ *
344
+ * @since 3.1.0
345
+ * @access protected
346
+ *
347
+ * @param array $actions The list of actions
348
+ * @param bool $always_visible Whether the actions should be always visible
349
+ * @return string
350
+ */
351
+ function row_actions( $actions, $always_visible = false ) {
352
+ $action_count = count( $actions );
353
+ $i = 0;
354
+
355
+ if ( !$action_count )
356
+ return '';
357
+
358
+ $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
359
+ foreach ( $actions as $action => $link ) {
360
+ ++$i;
361
+ ( $i == $action_count ) ? $sep = '' : $sep = ' | ';
362
+ $out .= "<span class='$action'>$link$sep</span>";
363
+ }
364
+ $out .= '</div>';
365
+
366
+ return $out;
367
+ }
368
+
369
+ /**
370
+ * Display a monthly dropdown for filtering items
371
+ *
372
+ * @since 3.1.0
373
+ * @access protected
374
+ */
375
+ function months_dropdown( $post_type ) {
376
+ global $wpdb, $wp_locale;
377
+
378
+ $months = $wpdb->get_results( $wpdb->prepare( "
379
+ SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
380
+ FROM $wpdb->posts
381
+ WHERE post_type = %s
382
+ ORDER BY post_date DESC
383
+ ", $post_type ) );
384
+
385
+ /**
386
+ * Filter the 'Months' drop-down results.
387
+ *
388
+ * @since 3.7.0
389
+ *
390
+ * @param object $months The months drop-down query results.
391
+ * @param string $post_type The post type.
392
+ */
393
+ $months = apply_filters( 'months_dropdown_results', $months, $post_type );
394
+
395
+ $month_count = count( $months );
396
+
397
+ if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) )
398
+ return;
399
+
400
+ $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
401
+ ?>
402
+ <select name='m'>
403
+ <option<?php selected( $m, 0 ); ?> value='0'><?php _e( 'All dates', 'fw' ); ?></option>
404
+ <?php
405
+ foreach ( $months as $arc_row ) {
406
+ if ( 0 == $arc_row->year )
407
+ continue;
408
+
409
+ $month = zeroise( $arc_row->month, 2 );
410
+ $year = $arc_row->year;
411
+
412
+ printf( "<option %s value='%s'>%s</option>\n",
413
+ selected( $m, $year . $month, false ),
414
+ esc_attr( $arc_row->year . $month ),
415
+ /* translators: 1: month name, 2: 4-digit year */
416
+ sprintf( __( '%1$s %2$d', 'fw' ), $wp_locale->get_month( $month ), $year )
417
+ );
418
+ }
419
+ ?>
420
+ </select>
421
+ <?php
422
+ }
423
+
424
+ /**
425
+ * Display a view switcher
426
+ *
427
+ * @since 3.1.0
428
+ * @access protected
429
+ */
430
+ function view_switcher( $current_mode ) {
431
+ $modes = array(
432
+ 'list' => __( 'List View', 'fw' ),
433
+ 'excerpt' => __( 'Excerpt View', 'fw' )
434
+ );
435
+
436
+ ?>
437
+ <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
438
+ <div class="view-switch">
439
+ <?php
440
+ foreach ( $modes as $mode => $title ) {
441
+ $class = ( $current_mode == $mode ) ? 'class="current"' : '';
442
+ echo "<a href='" . esc_url( add_query_arg( 'mode', $mode, $_SERVER['REQUEST_URI'] ) ) . "' $class><img id='view-switch-$mode' src='" . esc_url( includes_url( 'images/blank.gif' ) ) . "' width='20' height='20' title='$title' alt='$title' /></a>\n";
443
+ }
444
+ ?>
445
+ </div>
446
+ <?php
447
+ }
448
+
449
+ /**
450
+ * Display a comment count bubble
451
+ *
452
+ * @since 3.1.0
453
+ * @access protected
454
+ *
455
+ * @param int $post_id
456
+ * @param int $pending_comments
457
+ */
458
+ function comments_bubble( $post_id, $pending_comments ) {
459
+ $pending_phrase = sprintf( __( '%s pending', 'fw' ), number_format( $pending_comments ) );
460
+
461
+ if ( $pending_comments )
462
+ echo '<strong>';
463
+
464
+ echo "<a href='" . esc_url( add_query_arg( 'p', $post_id, admin_url( 'edit-comments.php' ) ) ) . "' title='" . esc_attr( $pending_phrase ) . "' class='post-com-count'><span class='comment-count'>" . number_format_i18n( get_comments_number() ) . "</span></a>";
465
+
466
+ if ( $pending_comments )
467
+ echo '</strong>';
468
+ }
469
+
470
+ /**
471
+ * Get the current page number
472
+ *
473
+ * @since 3.1.0
474
+ * @access protected
475
+ *
476
+ * @return int
477
+ */
478
+ function get_pagenum() {
479
+ $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
480
+
481
+ if( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] )
482
+ $pagenum = $this->_pagination_args['total_pages'];
483
+
484
+ return max( 1, $pagenum );
485
+ }
486
+
487
+ /**
488
+ * Get number of items to display on a single page
489
+ *
490
+ * @since 3.1.0
491
+ * @access protected
492
+ *
493
+ * @return int
494
+ */
495
+ function get_items_per_page( $option, $default = 20 ) {
496
+ $per_page = (int) get_user_option( $option );
497
+ if ( empty( $per_page ) || $per_page < 1 )
498
+ $per_page = $default;
499
+
500
+ /**
501
+ * Filter the number of items to be displayed on each page of the list table.
502
+ *
503
+ * The dynamic hook name, $option, refers to the per page option depending
504
+ * on the type of list table in use. Possible values may include:
505
+ * 'edit_comments_per_page', 'sites_network_per_page', 'site_themes_network_per_page',
506
+ * 'themes_netework_per_page', 'users_network_per_page', 'edit_{$post_type}', etc.
507
+ *
508
+ * @since 2.9.0
509
+ *
510
+ * @param int $per_page Number of items to be displayed. Default 20.
511
+ */
512
+ return (int) apply_filters( $option, $per_page );
513
+ }
514
+
515
+ /**
516
+ * Display the pagination.
517
+ *
518
+ * @since 3.1.0
519
+ * @access protected
520
+ */
521
+ function pagination( $which ) {
522
+ if ( empty( $this->_pagination_args ) )
523
+ return;
524
+
525
+ extract( $this->_pagination_args, EXTR_SKIP );
526
+
527
+ $output = '<span class="displaying-num">' . sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
528
+
529
+ $current = $this->get_pagenum();
530
+
531
+ $current_url = fw_current_url();
532
+
533
+ $current_url = remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
534
+
535
+ $page_links = array();
536
+
537
+ $disable_first = $disable_last = '';
538
+ if ( $current == 1 )
539
+ $disable_first = ' disabled';
540
+ if ( $current == $total_pages )
541
+ $disable_last = ' disabled';
542
+
543
+ $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
544
+ 'first-page' . $disable_first,
545
+ esc_attr__( 'Go to the first page', 'fw' ),
546
+ esc_url( remove_query_arg( 'paged', $current_url ) ),
547
+ '&laquo;'
548
+ );
549
+
550
+ $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
551
+ 'prev-page' . $disable_first,
552
+ esc_attr__( 'Go to the previous page', 'fw' ),
553
+ esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
554
+ '&lsaquo;'
555
+ );
556
+
557
+ if ( 'bottom' == $which )
558
+ $html_current_page = $current;
559
+ else
560
+ $html_current_page = sprintf( "<input class='current-page' title='%s' type='text' name='paged' value='%s' size='%d' />",
561
+ esc_attr__( 'Current page', 'fw' ),
562
+ $current,
563
+ strlen( $total_pages )
564
+ );
565
+
566
+ $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
567
+ $page_links[] = '<span class="paging-input">' . sprintf( _x( '%1$s of %2$s', 'paging', 'fw' ), $html_current_page, $html_total_pages ) . '</span>';
568
+
569
+ $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
570
+ 'next-page' . $disable_last,
571
+ esc_attr__( 'Go to the next page', 'fw' ),
572
+ esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
573
+ '&rsaquo;'
574
+ );
575
+
576
+ $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
577
+ 'last-page' . $disable_last,
578
+ esc_attr__( 'Go to the last page', 'fw' ),
579
+ esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
580
+ '&raquo;'
581
+ );
582
+
583
+ $pagination_links_class = 'pagination-links';
584
+ if ( ! empty( $infinite_scroll ) )
585
+ $pagination_links_class = ' hide-if-js';
586
+ $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
587
+
588
+ if ( $total_pages )
589
+ $page_class = $total_pages < 2 ? ' one-page' : '';
590
+ else
591
+ $page_class = ' no-pages';
592
+
593
+ $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
594
+
595
+ echo $this->_pagination;
596
+ }
597
+
598
+ /**
599
+ * Get a list of columns. The format is:
600
+ * 'internal-name' => 'Title'
601
+ *
602
+ * @since 3.1.0
603
+ * @access protected
604
+ * @abstract
605
+ *
606
+ * @return array
607
+ */
608
+ function get_columns() {
609
+ die( 'function WP_List_Table::get_columns() must be over-ridden in a sub-class.' );
610
+ }
611
+
612
+ /**
613
+ * Get a list of sortable columns. The format is:
614
+ * 'internal-name' => 'orderby'
615
+ * or
616
+ * 'internal-name' => array( 'orderby', true )
617
+ *
618
+ * The second format will make the initial sorting order be descending
619
+ *
620
+ * @since 3.1.0
621
+ * @access protected
622
+ *
623
+ * @return array
624
+ */
625
+ function get_sortable_columns() {
626
+ return array();
627
+ }
628
+
629
+ /**
630
+ * Get a list of all, hidden and sortable columns, with filter applied
631
+ *
632
+ * @since 3.1.0
633
+ * @access protected
634
+ *
635
+ * @return array
636
+ */
637
+ function get_column_info() {
638
+ if ( isset( $this->_column_headers ) )
639
+ return $this->_column_headers;
640
+
641
+ $columns = get_column_headers( $this->screen );
642
+ $hidden = get_hidden_columns( $this->screen );
643
+
644
+ $sortable_columns = $this->get_sortable_columns();
645
+ /**
646
+ * Filter the list table sortable columns for a specific screen.
647
+ *
648
+ * The dynamic portion of the hook name, $this->screen->id, refers
649
+ * to the ID of the current screen, usually a string.
650
+ *
651
+ * @since 3.5.0
652
+ *
653
+ * @param array $sortable_columns An array of sortable columns.
654
+ */
655
+ $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
656
+
657
+ $sortable = array();
658
+ foreach ( $_sortable as $id => $data ) {
659
+ if ( empty( $data ) )
660
+ continue;
661
+
662
+ $data = (array) $data;
663
+ if ( !isset( $data[1] ) )
664
+ $data[1] = false;
665
+
666
+ $sortable[$id] = $data;
667
+ }
668
+
669
+ $this->_column_headers = array( $columns, $hidden, $sortable );
670
+
671
+ return $this->_column_headers;
672
+ }
673
+
674
+ /**
675
+ * Return number of visible columns
676
+ *
677
+ * @since 3.1.0
678
+ * @access public
679
+ *
680
+ * @return int
681
+ */
682
+ function get_column_count() {
683
+ list ( $columns, $hidden ) = $this->get_column_info();
684
+ $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
685
+ return count( $columns ) - count( $hidden );
686
+ }
687
+
688
+ /**
689
+ * Print column headers, accounting for hidden and sortable columns.
690
+ *
691
+ * @since 3.1.0
692
+ * @access protected
693
+ *
694
+ * @param bool $with_id Whether to set the id attribute or not
695
+ */
696
+ function print_column_headers( $with_id = true ) {
697
+ list( $columns, $hidden, $sortable ) = $this->get_column_info();
698
+
699
+ $current_url = fw_current_url();
700
+ $current_url = remove_query_arg( 'paged', $current_url );
701
+
702
+ if ( isset( $_GET['orderby'] ) )
703
+ $current_orderby = $_GET['orderby'];
704
+ else
705
+ $current_orderby = '';
706
+
707
+ if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] )
708
+ $current_order = 'desc';
709
+ else
710
+ $current_order = 'asc';
711
+
712
+ if ( ! empty( $columns['cb'] ) ) {
713
+ static $cb_counter = 1;
714
+ $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All', 'fw' ) . '</label>'
715
+ . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
716
+ $cb_counter++;
717
+ }
718
+
719
+ foreach ( $columns as $column_key => $column_display_name ) {
720
+ $class = array( 'manage-column', "column-$column_key" );
721
+
722
+ $style = '';
723
+ if ( in_array( $column_key, $hidden ) )
724
+ $style = 'display:none;';
725
+
726
+ $style = ' style="' . $style . '"';
727
+
728
+ if ( 'cb' == $column_key )
729
+ $class[] = 'check-column';
730
+ elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) )
731
+ $class[] = 'num';
732
+
733
+ if ( isset( $sortable[$column_key] ) ) {
734
+ list( $orderby, $desc_first ) = $sortable[$column_key];
735
+
736
+ if ( $current_orderby == $orderby ) {
737
+ $order = 'asc' == $current_order ? 'desc' : 'asc';
738
+ $class[] = 'sorted';
739
+ $class[] = $current_order;
740
+ } else {
741
+ $order = $desc_first ? 'desc' : 'asc';
742
+ $class[] = 'sortable';
743
+ $class[] = $desc_first ? 'asc' : 'desc';
744
+ }
745
+
746
+ $column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
747
+ }
748
+
749
+ $id = $with_id ? "id='$column_key'" : '';
750
+
751
+ if ( !empty( $class ) )
752
+ $class = "class='" . join( ' ', $class ) . "'";
753
+
754
+ echo "<th scope='col' $id $class $style>$column_display_name</th>";
755
+ }
756
+ }
757
+
758
+ /**
759
+ * Display the table
760
+ *
761
+ * @since 3.1.0
762
+ * @access public
763
+ */
764
+ function display() {
765
+ extract( $this->_args );
766
+
767
+ $this->display_tablenav( 'top' );
768
+
769
+ ?>
770
+ <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
771
+ <thead>
772
+ <tr>
773
+ <?php $this->print_column_headers(); ?>
774
+ </tr>
775
+ </thead>
776
+
777
+ <tfoot>
778
+ <tr>
779
+ <?php $this->print_column_headers( false ); ?>
780
+ </tr>
781
+ </tfoot>
782
+
783
+ <tbody id="the-list"<?php if ( $singular ) echo " data-wp-lists='list:$singular'"; ?>>
784
+ <?php $this->display_rows_or_placeholder(); ?>
785
+ </tbody>
786
+ </table>
787
+ <?php
788
+ $this->display_tablenav( 'bottom' );
789
+ }
790
+
791
+ /**
792
+ * Get a list of CSS classes for the <table> tag
793
+ *
794
+ * @since 3.1.0
795
+ * @access protected
796
+ *
797
+ * @return array
798
+ */
799
+ function get_table_classes() {
800
+ return array( 'widefat', 'fixed', $this->_args['plural'] );
801
+ }
802
+
803
+ /**
804
+ * Generate the table navigation above or below the table
805
+ *
806
+ * @since 3.1.0
807
+ * @access protected
808
+ */
809
+ function display_tablenav( $which ) {
810
+ if ( 'top' == $which )
811
+ wp_nonce_field( 'bulk-' . $this->_args['plural'] );
812
+ ?>
813
+ <div class="tablenav <?php echo esc_attr( $which ); ?>">
814
+
815
+ <div class="alignleft actions bulkactions">
816
+ <?php $this->bulk_actions(); ?>
817
+ </div>
818
+ <?php
819
+ $this->extra_tablenav( $which );
820
+ $this->pagination( $which );
821
+ ?>
822
+
823
+ <br class="clear" />
824
+ </div>
825
+ <?php
826
+ }
827
+
828
+ /**
829
+ * Extra controls to be displayed between bulk actions and pagination
830
+ *
831
+ * @since 3.1.0
832
+ * @access protected
833
+ */
834
+ function extra_tablenav( $which ) {}
835
+
836
+ /**
837
+ * Generate the <tbody> part of the table
838
+ *
839
+ * @since 3.1.0
840
+ * @access protected
841
+ */
842
+ function display_rows_or_placeholder() {
843
+ if ( $this->has_items() ) {
844
+ $this->display_rows();
845
+ } else {
846
+ list( $columns, $hidden ) = $this->get_column_info();
847
+ echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
848
+ $this->no_items();
849
+ echo '</td></tr>';
850
+ }
851
+ }
852
+
853
+ /**
854
+ * Generate the table rows
855
+ *
856
+ * @since 3.1.0
857
+ * @access protected
858
+ */
859
+ function display_rows() {
860
+ foreach ( $this->items as $item )
861
+ $this->single_row( $item );
862
+ }
863
+
864
+ /**
865
+ * Generates content for a single row of the table
866
+ *
867
+ * @since 3.1.0
868
+ * @access protected
869
+ *
870
+ * @param object $item The current item
871
+ */
872
+ function single_row( $item ) {
873
+ static $row_class = '';
874
+ $row_class = ( $row_class == '' ? ' class="alternate"' : '' );
875
+
876
+ echo '<tr' . $row_class . '>';
877
+ $this->single_row_columns( $item );
878
+ echo '</tr>';
879
+ }
880
+
881
+ /**
882
+ * Generates the columns for a single row of the table
883
+ *
884
+ * @since 3.1.0
885
+ * @access protected
886
+ *
887
+ * @param object $item The current item
888
+ */
889
+ function single_row_columns( $item ) {
890
+ list( $columns, $hidden ) = $this->get_column_info();
891
+
892
+ foreach ( $columns as $column_name => $column_display_name ) {
893
+ $class = "class='$column_name column-$column_name'";
894
+
895
+ $style = '';
896
+ if ( in_array( $column_name, $hidden ) )
897
+ $style = ' style="display:none;"';
898
+
899
+ $attributes = "$class$style";
900
+
901
+ if ( 'cb' == $column_name ) {
902
+ echo '<th scope="row" class="check-column">';
903
+ echo $this->column_cb( $item );
904
+ echo '</th>';
905
+ }
906
+ elseif ( method_exists( $this, 'column_' . $column_name ) ) {
907
+ echo "<td $attributes>";
908
+ echo call_user_func( array( $this, 'column_' . $column_name ), $item );
909
+ echo "</td>";
910
+ }
911
+ else {
912
+ echo "<td $attributes>";
913
+ echo $this->column_default( $item, $column_name );
914
+ echo "</td>";
915
+ }
916
+ }
917
+ }
918
+
919
+ /**
920
+ * Handle an incoming ajax request (called from admin-ajax.php)
921
+ *
922
+ * @since 3.1.0
923
+ * @access public
924
+ */
925
+ function ajax_response() {
926
+ $this->prepare_items();
927
+
928
+ extract( $this->_args );
929
+ extract( $this->_pagination_args, EXTR_SKIP );
930
+
931
+ ob_start();
932
+ if ( ! empty( $_REQUEST['no_placeholder'] ) )
933
+ $this->display_rows();
934
+ else
935
+ $this->display_rows_or_placeholder();
936
+
937
+ $rows = ob_get_clean();
938
+
939
+ $response = array( 'rows' => $rows );
940
+
941
+ if ( isset( $total_items ) )
942
+ $response['total_items_i18n'] = sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) );
943
+
944
+ if ( isset( $total_pages ) ) {
945
+ $response['total_pages'] = $total_pages;
946
+ $response['total_pages_i18n'] = number_format_i18n( $total_pages );
947
+ }
948
+
949
+ die( json_encode( $response ) );
950
+ }
951
+
952
+ /**
953
+ * Send required variables to JavaScript land
954
+ *
955
+ * @access private
956
+ */
957
+ function _js_vars() {
958
+ $args = array(
959
+ 'class' => get_class( $this ),
960
+ 'screen' => array(
961
+ 'id' => $this->screen->id,
962
+ 'base' => $this->screen->base,
963
+ )
964
+ );
965
+
966
+ printf( "<script type='text/javascript'>list_args = %s;</script>\n", json_encode( $args ) );
967
+ }
968
+ }
framework/helpers/class-fw-wp-meta.php CHANGED
@@ -1,113 +1,113 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- /**
6
- *
7
- * Features:
8
- * - Works with "multi keys"
9
- * - The value is stored in two formats: original and prepared.
10
- * Prepared is used for frontend because it is translated (+ maybe other preparations in the future)
11
- */
12
- class FW_WP_Meta {
13
- /**
14
- * Store all this class data in cache within this key
15
- * @var string
16
- */
17
- private static $cache_key = 'wp_meta';
18
-
19
- /**
20
- * @param string $meta_type
21
- * @param int $object_id
22
- * @param string $multi_key 'abc' or 'ab/c/def'
23
- * @param array|string|int|bool $set_value
24
- */
25
- public static function set( $meta_type, $object_id, $multi_key, $set_value ) {
26
- if ( empty( $multi_key ) ) {
27
- trigger_error( 'Key not specified', E_USER_WARNING );
28
-
29
- return;
30
- }
31
-
32
- $multi_key = explode( '/', $multi_key );
33
- $key = array_shift( $multi_key );
34
- $multi_key = implode( '/', $multi_key );
35
-
36
- /*
37
- // Make sure meta is added to the post, not a revision.
38
- // fixme: why make sure? but I want to set post meta for a revision, how to do that?
39
- if ( $meta_type === 'post' && $the_post = wp_is_post_revision( $object_id ) ) {
40
- $object_id = $the_post;
41
- }
42
- */
43
-
44
- $cache_key = self::$cache_key . '/' . $meta_type . '/' . $object_id . '/' . $key;
45
-
46
- if ( empty( $multi_key ) && $multi_key !== '0' ) {
47
- /** Replace entire meta */
48
-
49
- fw_update_metadata( $meta_type, $object_id, $key, $set_value );
50
-
51
- FW_Cache::del( $cache_key );
52
-
53
- } else {
54
- /** Change only specified key */
55
-
56
- $values = array();
57
-
58
- $values['original'] = self::get( $meta_type, $object_id, $key, true );
59
- $values['prepared'] = self::get( $meta_type, $object_id, $key, false );
60
-
61
- fw_aks( $multi_key, $set_value, $values['original'] );
62
- fw_aks( $multi_key, fw_prepare_option_value( $set_value ), $values['prepared'] );
63
-
64
- FW_Cache::set( $cache_key, $values );
65
-
66
- fw_update_metadata( $meta_type, $object_id, $key, $values['original'] );
67
- }
68
- }
69
-
70
- /**
71
- * @param string $meta_type
72
- * @param int $object_id
73
- * @param string $multi_key 'abc' or 'ab/c/def'
74
- * @param null|mixed $default_value If no option found in the database, this value will be returned
75
- * @param bool|null $get_original_value Original value from db, no changes and translations
76
- *
77
- * @return mixed|null
78
- */
79
- public static function get( $meta_type, $object_id, $multi_key, $default_value = null, $get_original_value = null ) {
80
- if ( $get_original_value === null ) {
81
- $get_original_value = is_admin();
82
- }
83
-
84
- if ( empty( $multi_key ) ) {
85
- trigger_error( 'Key not specified', E_USER_WARNING );
86
-
87
- return null;
88
- }
89
-
90
- $multi_key = explode( '/', $multi_key );
91
- $key = array_shift( $multi_key );
92
- $multi_key = implode( '/', $multi_key );
93
-
94
- $cache_key = self::$cache_key . '/' . $meta_type . '/' . $object_id . '/' . $key;
95
-
96
- try {
97
- $values = FW_Cache::get( $cache_key );
98
- } catch ( FW_Cache_Not_Found_Exception $e ) {
99
- $values = array();
100
-
101
- $values['original'] = get_metadata( $meta_type, $object_id, $key, true );
102
- $values['prepared'] = fw_prepare_option_value( $values['original'] );
103
-
104
- FW_Cache::set( $cache_key, $values );
105
- }
106
-
107
- return fw_akg(
108
- ($get_original_value ? 'original' : 'prepared') . (empty($multi_key) ? '' : '/'. $multi_key),
109
- $values,
110
- $default_value
111
- );
112
- }
113
- }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ /**
6
+ *
7
+ * Features:
8
+ * - Works with "multi keys"
9
+ * - The value is stored in two formats: original and prepared.
10
+ * Prepared is used for frontend because it is translated (+ maybe other preparations in the future)
11
+ */
12
+ class FW_WP_Meta {
13
+ /**
14
+ * Store all this class data in cache within this key
15
+ * @var string
16
+ */
17
+ private static $cache_key = 'wp_meta';
18
+
19
+ /**
20
+ * @param string $meta_type
21
+ * @param int $object_id
22
+ * @param string $multi_key 'abc' or 'ab/c/def'
23
+ * @param array|string|int|bool $set_value
24
+ */
25
+ public static function set( $meta_type, $object_id, $multi_key, $set_value ) {
26
+ if ( empty( $multi_key ) ) {
27
+ trigger_error( 'Key not specified', E_USER_WARNING );
28
+
29
+ return;
30
+ }
31
+
32
+ $multi_key = explode( '/', $multi_key );
33
+ $key = array_shift( $multi_key );
34
+ $multi_key = implode( '/', $multi_key );
35
+
36
+ /*
37
+ // Make sure meta is added to the post, not a revision.
38
+ // fixme: why make sure? but I want to set post meta for a revision, how to do that?
39
+ if ( $meta_type === 'post' && $the_post = wp_is_post_revision( $object_id ) ) {
40
+ $object_id = $the_post;
41
+ }
42
+ */
43
+
44
+ $cache_key = self::$cache_key . '/' . $meta_type . '/' . $object_id . '/' . $key;
45
+
46
+ if ( empty( $multi_key ) && $multi_key !== '0' ) {
47
+ /** Replace entire meta */
48
+
49
+ fw_update_metadata( $meta_type, $object_id, $key, $set_value );
50
+
51
+ FW_Cache::del( $cache_key );
52
+
53
+ } else {
54
+ /** Change only specified key */
55
+
56
+ $values = array();
57
+
58
+ $values['original'] = self::get( $meta_type, $object_id, $key, true );
59
+ $values['prepared'] = self::get( $meta_type, $object_id, $key, false );
60
+
61
+ fw_aks( $multi_key, $set_value, $values['original'] );
62
+ fw_aks( $multi_key, fw_prepare_option_value( $set_value ), $values['prepared'] );
63
+
64
+ FW_Cache::set( $cache_key, $values );
65
+
66
+ fw_update_metadata( $meta_type, $object_id, $key, $values['original'] );
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @param string $meta_type
72
+ * @param int $object_id
73
+ * @param string $multi_key 'abc' or 'ab/c/def'
74
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
75
+ * @param bool|null $get_original_value Original value from db, no changes and translations
76
+ *
77
+ * @return mixed|null
78
+ */
79
+ public static function get( $meta_type, $object_id, $multi_key, $default_value = null, $get_original_value = null ) {
80
+ if ( $get_original_value === null ) {
81
+ $get_original_value = is_admin();
82
+ }
83
+
84
+ if ( empty( $multi_key ) ) {
85
+ trigger_error( 'Key not specified', E_USER_WARNING );
86
+
87
+ return null;
88
+ }
89
+
90
+ $multi_key = explode( '/', $multi_key );
91
+ $key = array_shift( $multi_key );
92
+ $multi_key = implode( '/', $multi_key );
93
+
94
+ $cache_key = self::$cache_key . '/' . $meta_type . '/' . $object_id . '/' . $key;
95
+
96
+ try {
97
+ $values = FW_Cache::get( $cache_key );
98
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
99
+ $values = array();
100
+
101
+ $values['original'] = get_metadata( $meta_type, $object_id, $key, true );
102
+ $values['prepared'] = fw_prepare_option_value( $values['original'] );
103
+
104
+ FW_Cache::set( $cache_key, $values );
105
+ }
106
+
107
+ return fw_akg(
108
+ ($get_original_value ? 'original' : 'prepared') . (empty($multi_key) ? '' : '/'. $multi_key),
109
+ $values,
110
+ $default_value
111
+ );
112
+ }
113
+ }
framework/helpers/class-fw-wp-option.php CHANGED
@@ -1,84 +1,84 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Alternative to WordPress get_option() and update_option() functions
5
- *
6
- * Features:
7
- * - Works with "multi keys"
8
- * - The value is stored in two formats: original and prepared.
9
- * Prepared is used for frontend because it is translated (+ maybe other preparations in the future)
10
- */
11
- class FW_WP_Option
12
- {
13
- /**
14
- * Store all this class data in cache within this key
15
- * @var string
16
- */
17
- private static $cache_key = 'wp_option';
18
-
19
- /**
20
- * @param string $option_name
21
- * @param string|null $specific_multi_key 'ab/c/def'
22
- * @param null|mixed $default_value If no option found in the database, this value will be returned
23
- * @param bool|null $get_original_value Original value from db, no changes and translations
24
- * @return mixed|null
25
- */
26
- public static function get($option_name, $specific_multi_key = null, $default_value = null, $get_original_value = null)
27
- {
28
- if ($get_original_value === null) {
29
- $get_original_value = is_admin();
30
- }
31
-
32
- $cache_key = self::$cache_key .'/'. $option_name;
33
-
34
- try {
35
- $values = FW_Cache::get($cache_key);
36
- } catch (FW_Cache_Not_Found_Exception $e) {
37
- $values = array();
38
-
39
- $values['original'] = get_option($option_name, null);
40
- $values['prepared'] = fw_prepare_option_value($values['original']);
41
-
42
- FW_Cache::set($cache_key, $values);
43
- }
44
-
45
- return fw_akg(
46
- ($get_original_value ? 'original' : 'prepared') . (empty($specific_multi_key) ? '' : '/'. $specific_multi_key),
47
- $values,
48
- $default_value
49
- );
50
- }
51
-
52
- /**
53
- * Alternative for update_option()
54
- * @param string $option_name
55
- * @param string|null $specific_multi_key
56
- * @param array|string|int|bool $set_value
57
- */
58
- public static function set($option_name, $specific_multi_key = null, $set_value)
59
- {
60
- $cache_key = self::$cache_key .'/'. $option_name;
61
-
62
- if ($specific_multi_key === null) {
63
- /** Replace entire option */
64
-
65
- update_option($option_name, $set_value, false);
66
-
67
- FW_Cache::del($cache_key);
68
- } else {
69
- /** Change only specified key */
70
-
71
- $values = array();
72
-
73
- $values['original'] = self::get($option_name, null, true);
74
- $values['prepared'] = self::get($option_name, null, false);
75
-
76
- fw_aks($specific_multi_key, $set_value, $values['original']);
77
- fw_aks($specific_multi_key, fw_prepare_option_value($set_value), $values['prepared']);
78
-
79
- FW_Cache::set($cache_key, $values);
80
-
81
- update_option($option_name, $values['original'], false);
82
- }
83
- }
84
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Alternative to WordPress get_option() and update_option() functions
5
+ *
6
+ * Features:
7
+ * - Works with "multi keys"
8
+ * - The value is stored in two formats: original and prepared.
9
+ * Prepared is used for frontend because it is translated (+ maybe other preparations in the future)
10
+ */
11
+ class FW_WP_Option
12
+ {
13
+ /**
14
+ * Store all this class data in cache within this key
15
+ * @var string
16
+ */
17
+ private static $cache_key = 'wp_option';
18
+
19
+ /**
20
+ * @param string $option_name
21
+ * @param string|null $specific_multi_key 'ab/c/def'
22
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
23
+ * @param bool|null $get_original_value Original value from db, no changes and translations
24
+ * @return mixed|null
25
+ */
26
+ public static function get($option_name, $specific_multi_key = null, $default_value = null, $get_original_value = null)
27
+ {
28
+ if ($get_original_value === null) {
29
+ $get_original_value = is_admin();
30
+ }
31
+
32
+ $cache_key = self::$cache_key .'/'. $option_name;
33
+
34
+ try {
35
+ $values = FW_Cache::get($cache_key);
36
+ } catch (FW_Cache_Not_Found_Exception $e) {
37
+ $values = array();
38
+
39
+ $values['original'] = get_option($option_name, null);
40
+ $values['prepared'] = fw_prepare_option_value($values['original']);
41
+
42
+ FW_Cache::set($cache_key, $values);
43
+ }
44
+
45
+ return fw_akg(
46
+ ($get_original_value ? 'original' : 'prepared') . (empty($specific_multi_key) ? '' : '/'. $specific_multi_key),
47
+ $values,
48
+ $default_value
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Alternative for update_option()
54
+ * @param string $option_name
55
+ * @param string|null $specific_multi_key
56
+ * @param array|string|int|bool $set_value
57
+ */
58
+ public static function set($option_name, $specific_multi_key = null, $set_value)
59
+ {
60
+ $cache_key = self::$cache_key .'/'. $option_name;
61
+
62
+ if ($specific_multi_key === null) {
63
+ /** Replace entire option */
64
+
65
+ update_option($option_name, $set_value, false);
66
+
67
+ FW_Cache::del($cache_key);
68
+ } else {
69
+ /** Change only specified key */
70
+
71
+ $values = array();
72
+
73
+ $values['original'] = self::get($option_name, null, true);
74
+ $values['prepared'] = self::get($option_name, null, false);
75
+
76
+ fw_aks($specific_multi_key, $set_value, $values['original']);
77
+ fw_aks($specific_multi_key, fw_prepare_option_value($set_value), $values['prepared']);
78
+
79
+ FW_Cache::set($cache_key, $values);
80
+
81
+ update_option($option_name, $values['original'], false);
82
+ }
83
+ }
84
+ }
framework/helpers/database.php CHANGED
@@ -1,751 +1,846 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- /** Theme Settings Options */
6
- {
7
- /**
8
- * Get a theme settings option value from the database
9
- *
10
- * @param string|null $option_id Specific option id (accepts multikey). null - all options
11
- * @param null|mixed $default_value If no option found in the database, this value will be returned
12
- * @param null|bool $get_original_value Original value is that with no translations and other changes
13
- *
14
- * @return mixed|null
15
- */
16
- function fw_get_db_settings_option( $option_id = null, $default_value = null, $get_original_value = null ) {
17
- $value = FW_WP_Option::get(
18
- 'fw_theme_settings_options:' . fw()->theme->manifest->get_id(),
19
- $option_id, $default_value, $get_original_value
20
- );
21
-
22
- if (
23
- (!is_null($option_id) && is_null($value)) // a specific option_id was requested
24
- ||
25
- (is_null($option_id) && empty($value)) // all options were requested but the db value is empty (this can happen after Reset)
26
- ) {
27
- /**
28
- * Maybe the options was never saved or the given option id does not exist
29
- * Extract the default values from the options array and try to find there the option id
30
- */
31
-
32
- $cache_key = 'fw_default_options_values/settings';
33
-
34
- try {
35
- $all_options_values = FW_Cache::get( $cache_key );
36
- } catch ( FW_Cache_Not_Found_Exception $e ) {
37
- // extract the default values from options array
38
- $all_options_values = fw_get_options_values_from_input(
39
- fw()->theme->get_settings_options(),
40
- array()
41
- );
42
-
43
- FW_Cache::set( $cache_key, $all_options_values );
44
- }
45
-
46
- if ( empty( $option_id ) ) {
47
- // option id not specified, return all options values
48
- return $all_options_values;
49
- } else {
50
- return fw_akg( $option_id, $all_options_values, $default_value );
51
- }
52
- } else {
53
- return $value;
54
- }
55
- }
56
-
57
- /**
58
- * Set a theme settings option value in database
59
- *
60
- * @param null $option_id Specific option id (accepts multikey). null - all options
61
- * @param mixed $value
62
- */
63
- function fw_set_db_settings_option( $option_id = null, $value ) {
64
- FW_WP_Option::set(
65
- 'fw_theme_settings_options:' . fw()->theme->manifest->get_id(),
66
- $option_id, $value
67
- );
68
- }
69
- }
70
-
71
- /** Post Options */
72
- {
73
- /**
74
- * Get post option value from the database
75
- *
76
- * @param null|int $post_id
77
- * @param string|null $option_id Specific option id (accepts multikey). null - all options
78
- * @param null|mixed $default_value If no option found in the database, this value will be returned
79
- * @param null|bool $get_original_value Original value is that with no translations and other changes
80
- *
81
- * @return mixed|null
82
- */
83
- function fw_get_db_post_option( $post_id = null, $option_id = null, $default_value = null, $get_original_value = null ) {
84
- if ( ! $post_id ) {
85
- /** @var WP_Post $post */
86
- global $post;
87
-
88
- if ( ! $post ) {
89
- return $default_value;
90
- } else {
91
- $post_id = $post->ID;
92
- }
93
-
94
- /**
95
- * Check if is Preview and use the preview post_id instead of real/current post id
96
- *
97
- * Note: WordPress changes the global $post content on preview:
98
- * 1. https://github.com/WordPress/WordPress/blob/2096b451c704715db3c4faf699a1184260deade9/wp-includes/query.php#L3573-L3583
99
- * 2. https://github.com/WordPress/WordPress/blob/4a31dd6fe8b774d56f901a29e72dcf9523e9ce85/wp-includes/revision.php#L485-L528
100
- */
101
- if ( is_preview() && is_object($preview = wp_get_post_autosave($post->ID)) ) {
102
- $post_id = $preview->ID;
103
- }
104
- }
105
-
106
- $post_type = get_post_type(
107
- ($post_revision_id = wp_is_post_revision($post_id)) ? $post_revision_id : $post_id
108
- );
109
-
110
- /**
111
- * Before fw_db_option_storage_load() feature
112
- * there was possible to call fw_get_db_post_option() and it worked fine
113
- * but after v2.5.0 it's not possible anymore (it creates an infinite recursion)
114
- * but the Slider extension does that and maybe other extensions,
115
- * so the solution is to check if it is recursion, to not load the options array (disable the storage feature)
116
- */
117
- static $recursion = array();
118
-
119
- if (!isset($recursion[$post_type])) {
120
- $recursion[$post_type] = false;
121
- }
122
-
123
- if ($recursion[$post_type]) {
124
- /**
125
- * Allow known post types that sure don't have options with 'fw-storage' parameter
126
- */
127
- if (!in_array($post_type, array('fw-slider'))) {
128
- trigger_error(
129
- 'Infinite recursion detected in post type "'. $post_type .'" options caused by '. __FUNCTION__ .'()',
130
- E_USER_WARNING
131
- );
132
- }
133
-
134
- $options = array();
135
- } else {
136
- $recursion[$post_type] = true;
137
-
138
- $options = fw_extract_only_options( // todo: cache this (by post type)
139
- fw()->theme->get_post_options( $post_type )
140
- );
141
-
142
- $recursion[$post_type] = false;
143
- }
144
-
145
- if ($option_id) {
146
- $option_id = explode('/', $option_id); // 'option_id/sub/keys'
147
- $_option_id = array_shift($option_id); // 'option_id'
148
- $sub_keys = implode('/', $option_id); // 'sub/keys'
149
- $option_id = $_option_id;
150
- unset($_option_id);
151
-
152
- $value = FW_WP_Meta::get(
153
- 'post',
154
- $post_id,
155
- 'fw_options/' . $option_id,
156
- null,
157
- $get_original_value
158
- );
159
-
160
- if (isset($options[$option_id])) {
161
- $value = fw()->backend->option_type($options[$option_id]['type'])->storage_load(
162
- $option_id,
163
- $options[$option_id],
164
- $value,
165
- array( 'post-id' => $post_id, )
166
- );
167
- }
168
-
169
- if ($sub_keys) {
170
- return fw_akg($sub_keys, $value, $default_value);
171
- } else {
172
- return is_null($value) ? $default_value : $value;
173
- }
174
- } else {
175
- $value = FW_WP_Meta::get(
176
- 'post',
177
- $post_id,
178
- 'fw_options',
179
- $default_value,
180
- $get_original_value
181
- );
182
-
183
- if (!is_array($value)) {
184
- $value = array();
185
- }
186
-
187
- foreach ($options as $_option_id => $_option) {
188
- $value[$_option_id] = fw()->backend->option_type($_option['type'])->storage_load(
189
- $_option_id,
190
- $_option,
191
- isset($value[$_option_id]) ? $value[$_option_id] : null,
192
- array( 'post-id' => $post_id, )
193
- );
194
- }
195
-
196
- return $value;
197
- }
198
- }
199
-
200
- /**
201
- * Set post option value in database
202
- *
203
- * @param null|int $post_id
204
- * @param string|null $option_id Specific option id (accepts multikey). null - all options
205
- * @param $value
206
- */
207
- function fw_set_db_post_option( $post_id = null, $option_id = null, $value ) {
208
- $post_id = intval($post_id);
209
-
210
- if ( ! $post_id ) {
211
- /** @var WP_Post $post */
212
- global $post;
213
-
214
- if ( ! $post ) {
215
- return;
216
- } else {
217
- $post_id = $post->ID;
218
- }
219
- }
220
-
221
- $options = fw_extract_only_options( // todo: cache this (by post type)
222
- fw()->theme->get_post_options(
223
- get_post_type(
224
- ($post_revision_id = wp_is_post_revision($post_id)) ? $post_revision_id : $post_id
225
- )
226
- )
227
- );
228
-
229
- $sub_keys = null;
230
-
231
- if ($option_id) {
232
- $option_id = explode('/', $option_id); // 'option_id/sub/keys'
233
- $_option_id = array_shift($option_id); // 'option_id'
234
- $sub_keys = implode('/', $option_id); // 'sub/keys'
235
- $option_id = $_option_id;
236
- unset($_option_id);
237
-
238
- $old_value = fw_get_db_post_option($post_id, $option_id);
239
-
240
- if ($sub_keys) { // update sub_key in old_value and use the entire value
241
- $new_value = $old_value;
242
- fw_aks($sub_keys, $value, $new_value);
243
- $value = $new_value;
244
- unset($new_value);
245
-
246
- $old_value = fw_akg($sub_keys, $old_value);
247
- }
248
-
249
- if (isset($options[$option_id])) {
250
- $value = fw()->backend->option_type($options[$option_id]['type'])->storage_save(
251
- $option_id,
252
- $options[$option_id],
253
- $value,
254
- array( 'post-id' => $post_id, )
255
- );
256
- }
257
-
258
- FW_WP_Meta::set( 'post', $post_id, 'fw_options/'. $option_id, $value );
259
- } else {
260
- $old_value = fw_get_db_post_option($post_id);
261
-
262
- if (!is_array($value)) {
263
- $value = array();
264
- }
265
-
266
- foreach ($value as $_option_id => $_option_value) {
267
- if (isset($options[$_option_id])) {
268
- $value[$_option_id] = fw()->backend->option_type($options[$_option_id]['type'])->storage_save(
269
- $_option_id,
270
- $options[$_option_id],
271
- $_option_value,
272
- array( 'post-id' => $post_id, )
273
- );
274
- }
275
- }
276
-
277
- FW_WP_Meta::set( 'post', $post_id, 'fw_options', $value );
278
- }
279
-
280
- /**
281
- * @deprecated
282
- */
283
- fw()->backend->_sync_post_separate_meta($post_id);
284
-
285
- /**
286
- * @since 2.2.8
287
- */
288
- do_action('fw_post_options_update',
289
- $post_id,
290
- /**
291
- * Option id
292
- * First level multi-key
293
- *
294
- * For e.g.
295
- *
296
- * if $option_id is 'hello/world/7'
297
- * this will be 'hello'
298
- */
299
- $option_id,
300
- /**
301
- * The remaining sub-keys
302
- *
303
- * For e.g.
304
- *
305
- * if $option_id is 'hello/world/7'
306
- * $option_id_keys will be array('world', '7')
307
- *
308
- * if $option_id is 'hello'
309
- * $option_id_keys will be array()
310
- */
311
- explode('/', $sub_keys),
312
- /**
313
- * Old post option(s) value
314
- * @since 2.3.3
315
- */
316
- $old_value
317
- );
318
- }
319
- }
320
-
321
- /** Terms Options */
322
- {
323
- /**
324
- * Get term option value from the database
325
- *
326
- * @param int $term_id
327
- * @param string $taxonomy
328
- * @param string|null $option_id Specific option id (accepts multikey). null - all options
329
- * @param null|mixed $default_value If no option found in the database, this value will be returned
330
- * @param null|bool $get_original_value Original value is that with no translations and other changes
331
- *
332
- * @return mixed|null
333
- */
334
- function fw_get_db_term_option( $term_id, $taxonomy, $option_id = null, $default_value = null, $get_original_value = null ) {
335
- if ( ! taxonomy_exists( $taxonomy ) ) {
336
- return null;
337
- }
338
-
339
- $option_id = 'fw_options' . ( $option_id !== null ? '/' . $option_id : '' );
340
-
341
- return FW_WP_Meta::get( 'fw_term', $term_id, $option_id, $default_value, $get_original_value );
342
- }
343
-
344
- /**
345
- * Set term option value in database
346
- *
347
- * @param int $term_id
348
- * @param string $taxonomy
349
- * @param string|null $option_id Specific option id (accepts multikey). null - all options
350
- * @param mixed $value
351
- *
352
- * @return null
353
- */
354
- function fw_set_db_term_option( $term_id, $taxonomy, $option_id = null, $value ) {
355
- if ( ! taxonomy_exists( $taxonomy ) ) {
356
- return null;
357
- }
358
- $option_id = 'fw_options' . ( $option_id !== null ? '/' . $option_id : '' );
359
-
360
- FW_WP_Meta::set( 'fw_term', $term_id, $option_id, $value );
361
- }
362
- }
363
-
364
- /**
365
- * Extensions Data
366
- *
367
- * Used by extensions to store custom data in database.
368
- * By using these functions, extension prevent database spam with wp options for each extension,
369
- * because these functions store all data in one wp option.
370
- */
371
- {
372
- /**
373
- * Get extension's data from the database
374
- *
375
- * @param string $extension_name
376
- * @param string|null $multi_key The key of the data you want to get. null - all data
377
- * @param null|mixed $default_value If no option found in the database, this value will be returned
378
- * @param null|bool $get_original_value Original value is that with no translations and other changes
379
- *
380
- * @return mixed|null
381
- */
382
- function fw_get_db_extension_data( $extension_name, $multi_key = null, $default_value = null, $get_original_value = null ) {
383
- if ( ! fw()->extensions->get( $extension_name ) ) {
384
- trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
385
-
386
- return null;
387
- }
388
-
389
- if ( $multi_key ) {
390
- $multi_key = $extension_name . '/' . $multi_key;
391
- } else {
392
- $multi_key = $extension_name;
393
- }
394
-
395
- return FW_WP_Option::get( 'fw_extensions', $multi_key, $default_value, $get_original_value );
396
- }
397
-
398
- /**
399
- * Set some extension's data in database
400
- *
401
- * @param string $extension_name
402
- * @param string|null $multi_key The key of the data you want to set. null - all data
403
- * @param mixed $value
404
- */
405
- function fw_set_db_extension_data( $extension_name, $multi_key = null, $value ) {
406
- if ( ! fw()->extensions->get( $extension_name ) ) {
407
- trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
408
-
409
- return;
410
- }
411
-
412
- if ( $multi_key ) {
413
- $multi_key = $extension_name . '/' . $multi_key;
414
- } else {
415
- $multi_key = $extension_name;
416
- }
417
-
418
- FW_WP_Option::set( 'fw_extensions', $multi_key, $value );
419
- }
420
- }
421
-
422
- /**
423
- * Extensions Settings Options
424
- */
425
- {
426
- /**
427
- * Get extension's settings option value from the database
428
- *
429
- * @param string $extension_name
430
- * @param string|null $option_id
431
- * @param null|mixed $default_value If no option found in the database, this value will be returned
432
- * @param null|bool $get_original_value Original value is that with no translations and other changes
433
- *
434
- * @return mixed|null
435
- */
436
- function fw_get_db_ext_settings_option( $extension_name, $option_id = null, $default_value = null, $get_original_value = null ) {
437
- if ( ! ( $extension = fw()->extensions->get( $extension_name ) ) ) {
438
- trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
439
-
440
- return null;
441
- }
442
-
443
- $value = FW_WP_Option::get( 'fw_ext_settings_options:' . $extension_name, $option_id, $default_value, $get_original_value );
444
-
445
- if ( is_null( $value ) ) {
446
- /**
447
- * Maybe the options was never saved or the given option id does not exists
448
- * Extract the default values from the options array and try to find there the option id
449
- */
450
-
451
- $cache_key = 'fw_default_options_values/ext_settings:' . $extension_name;
452
-
453
- try {
454
- $all_options_values = FW_Cache::get( $cache_key );
455
- } catch ( FW_Cache_Not_Found_Exception $e ) {
456
- // extract the default values from options array
457
- $all_options_values = fw_get_options_values_from_input(
458
- $extension->get_settings_options(),
459
- array()
460
- );
461
-
462
- FW_Cache::set( $cache_key, $all_options_values );
463
- }
464
-
465
- if ( empty( $option_id ) ) {
466
- // option id not specified, return all options values
467
- return $all_options_values;
468
- } else {
469
- return fw_akg( $option_id, $all_options_values, $default_value );
470
- }
471
- } else {
472
- return $value;
473
- }
474
- }
475
-
476
- /**
477
- * Set extension's setting option value in database
478
- *
479
- * @param string $extension_name
480
- * @param string|null $option_id
481
- * @param mixed $value
482
- */
483
- function fw_set_db_ext_settings_option( $extension_name, $option_id = null, $value ) {
484
- if ( ! fw()->extensions->get( $extension_name ) ) {
485
- trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
486
-
487
- return;
488
- }
489
-
490
- FW_WP_Option::set( 'fw_ext_settings_options:' . $extension_name, $option_id, $value );
491
- }
492
- }
493
-
494
- {
495
- /**
496
- * Get user meta set by specific extension
497
- *
498
- * @param int $user_id
499
- * @param string $extension_name
500
- * @param string $keys
501
- *
502
- * If the extension doesn't exist or is disabled, or meta key doesn't exist, returns null,
503
- * else returns the meta key value
504
- *
505
- * @return mixed|null
506
- */
507
- function fw_get_db_extension_user_data( $user_id, $extension_name, $keys = null ) {
508
- if ( ! fw()->extensions->get( $extension_name ) ) {
509
- trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
510
-
511
- return null;
512
- }
513
- $data = get_user_meta( $user_id, 'fw_data', true );
514
-
515
- if ( is_null( $keys ) ) {
516
- return fw_akg( $extension_name, $data );
517
- }
518
-
519
- return fw_akg( $extension_name . '/' . $keys, $data );
520
- }
521
-
522
- /**
523
- * @param int $user_id
524
- * @param string $extension_name
525
- * @param mixed $value
526
- * @param string $keys
527
- *
528
- * In case the extension doesn't exist or is disabled, or the value is equal to previous, returns false
529
- *
530
- * @return bool|int
531
- */
532
- function fw_set_db_extension_user_data( $user_id, $extension_name, $value, $keys = null ) {
533
- if ( ! fw()->extensions->get( $extension_name ) ) {
534
- trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
535
-
536
- return false;
537
- }
538
- $data = get_user_meta( $user_id, 'fw_data', true );
539
-
540
- if ( $keys == null ) {
541
- fw_aks( $extension_name, $value, $data );
542
- } else {
543
- fw_aks( $extension_name . '/' . $keys, $value, $data );
544
- }
545
-
546
- return fw_update_user_meta( $user_id, 'fw_data', $data );
547
- }
548
- }
549
-
550
- /** Customizer Framework Options */
551
- {
552
- /**
553
- * Get a customizer framework option value from the database
554
- *
555
- * @param string|null $option_id Specific option id (accepts multikey). null - all options
556
- * @param null|mixed $default_value If no option found in the database, this value will be returned
557
- * // fixme: Maybe add this parameter? @ param null|bool $get_original_value Original value is that with no translations and other changes
558
- *
559
- * @return mixed|null
560
- */
561
- function fw_get_db_customizer_option( $option_id = null, $default_value = null ) {
562
- // note: this contains only changed controls/options
563
- $db_values = get_theme_mod(FW_Option_Type::get_default_name_prefix(), null);
564
-
565
- if (
566
- !is_null($default_value)
567
- &&
568
- is_null($option_id ? fw_akg($option_id, $db_values) : $db_values)
569
- ) {
570
- /**
571
- * Default value was provided in case db value is empty.
572
- *
573
- * Do not extract default values from options files (below)
574
- * maybe this function was called from options files and it will cause infinite loop,
575
- * that's why the developer provided a default value to prevent that.
576
- */
577
- return $default_value;
578
- }
579
-
580
- if (is_null($db_values)) {
581
- $db_values = array();
582
- }
583
-
584
- if (
585
- is_null($option_id)
586
- ||
587
- (
588
- ($base_key = explode('/', $option_id)) // note: option_id can be a multi-key 'a/b/c'
589
- &&
590
- ($base_key = array_shift($base_key))
591
- &&
592
- !array_key_exists($base_key, $db_values)
593
- )
594
- ) {
595
- // extract options default values
596
- {
597
- $cache_key = 'fw_default_options_values/customizer';
598
-
599
- try {
600
- $default_values = FW_Cache::get( $cache_key );
601
- } catch ( FW_Cache_Not_Found_Exception $e ) {
602
- // extract the default values from options array
603
- $default_values = fw_get_options_values_from_input(
604
- fw()->theme->get_customizer_options(),
605
- array()
606
- );
607
-
608
- FW_Cache::set( $cache_key, $default_values );
609
- }
610
- }
611
-
612
- $db_values = array_merge($default_values, $db_values);
613
- }
614
-
615
- return is_null($option_id)
616
- ? $db_values
617
- : fw_akg($option_id, $db_values, $default_value);
618
- }
619
-
620
- /**
621
- * Set a theme customizer option value in database
622
- *
623
- * @param null $option_id Specific option id (accepts multikey). null - all options
624
- * @param mixed $value
625
- */
626
- function fw_set_db_customizer_option( $option_id = null, $value ) {
627
- $db_value = get_theme_mod(FW_Option_Type::get_default_name_prefix(), array());
628
-
629
- if (is_null($option_id)) {
630
- $db_value = $value;
631
- } else {
632
- fw_aks($option_id, $value, $db_value);
633
- }
634
-
635
- set_theme_mod(
636
- FW_Option_Type::get_default_name_prefix(),
637
- $db_value
638
- );
639
- }
640
- }
641
-
642
- {
643
- /**
644
- * @param string $id
645
- * @param array $option
646
- * @param mixed $value
647
- * @param array $params
648
- *
649
- * @return mixed
650
- *
651
- * @since 2.5.0
652
- */
653
- function fw_db_option_storage_save($id, array $option, $value, array $params = array()) {
654
- if (
655
- !empty($option['fw-storage'])
656
- &&
657
- ($storage = is_array($option['fw-storage'])
658
- ? $option['fw-storage']
659
- : array('type' => $option['fw-storage'])
660
- )
661
- &&
662
- !empty($storage['type'])
663
- &&
664
- ($storage_type = fw_db_option_storage_type($storage['type']))
665
- ) {
666
- $option['fw-storage'] = $storage;
667
- } else {
668
- return $value;
669
- }
670
-
671
- /** @var FW_Option_Storage_Type $storage_type */
672
-
673
- return $storage_type->save($id, $option, $value, $params);
674
- }
675
-
676
- /**
677
- * @param string $id
678
- * @param array $option
679
- * @param mixed $value
680
- * @param array $params
681
- *
682
- * @return mixed
683
- *
684
- * @since 2.5.0
685
- */
686
- function fw_db_option_storage_load($id, array $option, $value, array $params = array()) {
687
- if (
688
- !empty($option['fw-storage'])
689
- &&
690
- ($storage = is_array($option['fw-storage'])
691
- ? $option['fw-storage']
692
- : array('type' => $option['fw-storage'])
693
- )
694
- &&
695
- !empty($storage['type'])
696
- &&
697
- ($storage_type = fw_db_option_storage_type($storage['type']))
698
- ) {
699
- $option['fw-storage'] = $storage;
700
- } else {
701
- return $value;
702
- }
703
-
704
- /** @var FW_Option_Storage_Type $storage_type */
705
-
706
- return $storage_type->load($id, $option, $value, $params);
707
- }
708
-
709
- /**
710
- * @param null|string $type
711
- * @return FW_Option_Storage_Type|FW_Option_Storage_Type[]|null
712
- * @since 2.5.0
713
- */
714
- function fw_db_option_storage_type($type = null) {
715
- static $types = null;
716
-
717
- if (is_null($types)) {
718
- $dir = fw_get_framework_directory('/includes/option-storage');
719
-
720
- if (!class_exists('FW_Option_Storage_Type')) {
721
- require_once $dir .'/class-fw-option-storage-type.php';
722
- }
723
- if (!class_exists('_FW_Option_Storage_Type_Register')) {
724
- require_once $dir .'/class--fw-option-storage-type-register.php';
725
- }
726
-
727
- $access_key = new FW_Access_Key('fw:option-storage-register');
728
- $register = new _FW_Option_Storage_Type_Register($access_key->get_key());
729
-
730
- {
731
- require_once $dir .'/type/class-fw-option-storage-type-post-meta.php';
732
- $register->register(new FW_Option_Storage_Type_Post_Meta());
733
-
734
- require_once $dir .'/type/class-fw-option-storage-type-wp-option.php';
735
- $register->register(new FW_Option_Storage_Type_WP_Option());
736
- }
737
-
738
- do_action('fw:option-storage-types:register', $register);
739
-
740
- $types = $register->_get_types($access_key);
741
- }
742
-
743
- if (empty($type)) {
744
- return $types;
745
- } elseif (isset($types[$type])) {
746
- return $types[$type];
747
- } else {
748
- return null;
749
- }
750
- }
751
- }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ /** Theme Settings Options */
6
+ {
7
+ /**
8
+ * Get a theme settings option value from the database
9
+ *
10
+ * @param string|null $option_id Specific option id (accepts multikey). null - all options
11
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
12
+ * @param null|bool $get_original_value Original value is that with no translations and other changes
13
+ *
14
+ * @return mixed|null
15
+ */
16
+ function fw_get_db_settings_option( $option_id = null, $default_value = null, $get_original_value = null ) {
17
+ static $merge_values_with_defaults = false;
18
+
19
+ if (empty($option_id)) {
20
+ $sub_keys = null;
21
+ } else {
22
+ $option_id = explode('/', $option_id); // 'option_id/sub/keys'
23
+ $_option_id = array_shift($option_id); // 'option_id'
24
+ $sub_keys = empty($option_id) ? null : implode('/', $option_id); // 'sub/keys'
25
+ $option_id = $_option_id;
26
+ unset($_option_id);
27
+ }
28
+
29
+ try {
30
+ $values = FW_Cache::get($cache_key = 'fw_settings_options/values');
31
+ } catch (FW_Cache_Not_Found_Exception $e) {
32
+ FW_Cache::set(
33
+ $cache_key,
34
+ $values = (array)FW_WP_Option::get(
35
+ 'fw_theme_settings_options:'. fw()->theme->manifest->get_id(), null, array(), $get_original_value
36
+ )
37
+ );
38
+
39
+ $merge_values_with_defaults = true;
40
+ }
41
+
42
+ /**
43
+ * If db value is not found and default value is provided
44
+ * return default value before loading options file
45
+ * to prevent infinite recursion in case if this function is called in options file
46
+ */
47
+ if ( ! is_null($default_value) ) {
48
+ if ( empty( $option_id ) ) {
49
+ if ( empty( $values ) && is_array( $default_value ) ) {
50
+ return $default_value;
51
+ }
52
+ } else {
53
+ if ( is_null( $sub_keys ) ) {
54
+ if ( ! isset( $values[ $option_id ] ) ) {
55
+ return $default_value;
56
+ }
57
+ } else {
58
+ if ( is_null( fw_akg( $sub_keys, $values[ $option_id ] ) ) ) {
59
+ return $default_value;
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ try {
66
+ $options = FW_Cache::get( $cache_key = 'fw_only_options/settings' );
67
+ } catch (FW_Cache_Not_Found_Exception $e) {
68
+ FW_Cache::set($cache_key, array()); // prevent infinite recursion
69
+ FW_Cache::set(
70
+ $cache_key,
71
+ $options = fw_extract_only_options(fw()->theme->get_settings_options())
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Complete missing db values with default values from options array
77
+ */
78
+ if ($merge_values_with_defaults) {
79
+ $merge_values_with_defaults = false;
80
+ FW_Cache::set(
81
+ 'fw_settings_options/values',
82
+ $values = array_merge(fw_get_options_values_from_input($options, array()), $values)
83
+ );
84
+ }
85
+
86
+ if (empty($option_id)) {
87
+ foreach ($options as $id => $option) {
88
+ $values[$id] = fw()->backend->option_type($options[$id]['type'])->storage_load(
89
+ $id, $options[$id], isset($values[$id]) ? $values[$id] : null, array()
90
+ );
91
+ }
92
+ } else {
93
+ if (isset($options[$option_id])) {
94
+ $values[ $option_id ] = fw()->backend->option_type( $options[ $option_id ]['type'] )->storage_load(
95
+ $option_id,
96
+ $options[ $option_id ],
97
+ isset($values[ $option_id ]) ? $values[ $option_id ] : null,
98
+ array()
99
+ );
100
+ }
101
+ }
102
+
103
+ if (empty($option_id)) {
104
+ return (empty($values) && is_array($default_value)) ? $default_value : $values;
105
+ } else {
106
+ if (is_null($sub_keys)) {
107
+ return isset($values[$option_id]) ? $values[$option_id] : $default_value;
108
+ } else {
109
+ return fw_akg($sub_keys, $values[$option_id], $default_value);
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Set a theme settings option value in database
116
+ *
117
+ * @param null $option_id Specific option id (accepts multikey). null - all options
118
+ * @param mixed $value
119
+ */
120
+ function fw_set_db_settings_option( $option_id = null, $value ) {
121
+ FW_Cache::del('fw_settings_options/values');
122
+
123
+ try {
124
+ $options = FW_Cache::get( $cache_key = 'fw_only_options/settings' );
125
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
126
+ FW_Cache::set(
127
+ $cache_key,
128
+ $options = fw_extract_only_options(fw()->theme->get_settings_options())
129
+ );
130
+ }
131
+
132
+ if (empty($option_id)) {
133
+ foreach ($options as $id => $option) {
134
+ if (isset($value[$id])) {
135
+ $value[ $id ] = fw()->backend->option_type( $options[ $id ]['type'] )->storage_save(
136
+ $id, $options[ $id ], $value[$id], array()
137
+ );
138
+ }
139
+ }
140
+ } else {
141
+ if (isset($options[$option_id]) && isset($value[ $option_id ])) {
142
+ $value[ $option_id ] = fw()->backend->option_type( $options[ $option_id ]['type'] )->storage_save(
143
+ $option_id,
144
+ $options[ $option_id ],
145
+ $value[ $option_id ],
146
+ array()
147
+ );
148
+ }
149
+ }
150
+
151
+ FW_WP_Option::set(
152
+ 'fw_theme_settings_options:' . fw()->theme->manifest->get_id(),
153
+ $option_id, $value
154
+ );
155
+ }
156
+ }
157
+
158
+ /** Post Options */
159
+ {
160
+ /**
161
+ * Get post option value from the database
162
+ *
163
+ * @param null|int $post_id
164
+ * @param string|null $option_id Specific option id (accepts multikey). null - all options
165
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
166
+ * @param null|bool $get_original_value Original value is that with no translations and other changes
167
+ *
168
+ * @return mixed|null
169
+ */
170
+ function fw_get_db_post_option(
171
+ $post_id = null,
172
+ $option_id = null,
173
+ $default_value = null,
174
+ $get_original_value = null
175
+ ) {
176
+ $meta_key = 'fw_options';
177
+
178
+ if ( ! $post_id ) {
179
+ /** @var WP_Post $post */
180
+ global $post;
181
+
182
+ if ( ! $post ) {
183
+ return $default_value;
184
+ } else {
185
+ $post_id = $post->ID;
186
+ }
187
+
188
+ /**
189
+ * Check if is Preview and use the preview post_id instead of real/current post id
190
+ *
191
+ * Note: WordPress changes the global $post content on preview:
192
+ * 1. https://github.com/WordPress/WordPress/blob/2096b451c704715db3c4faf699a1184260deade9/wp-includes/query.php#L3573-L3583
193
+ * 2. https://github.com/WordPress/WordPress/blob/4a31dd6fe8b774d56f901a29e72dcf9523e9ce85/wp-includes/revision.php#L485-L528
194
+ */
195
+ if ( is_preview() && is_object($preview = wp_get_post_autosave($post->ID)) ) {
196
+ $post_id = $preview->ID;
197
+ }
198
+ }
199
+
200
+ $post_type = get_post_type(
201
+ ($post_revision_id = wp_is_post_revision($post_id)) ? $post_revision_id : $post_id
202
+ );
203
+
204
+ /**
205
+ * Before fw_db_option_storage_load() feature
206
+ * there was possible to call fw_get_db_post_option() and it worked fine
207
+ * but after v2.5.0 it's not possible anymore (it creates an infinite recursion)
208
+ * but the Slider extension does that and maybe other extensions,
209
+ * so the solution is to check if it is recursion, to not load the options array (disable the storage feature)
210
+ */
211
+ static $recursion = array();
212
+
213
+ if (!isset($recursion[$post_type])) {
214
+ $recursion[$post_type] = false;
215
+ }
216
+
217
+ if ($recursion[$post_type]) {
218
+ /**
219
+ * Allow known post types that sure don't have options with 'fw-storage' parameter
220
+ */
221
+ if (!in_array($post_type, array('fw-slider'))) {
222
+ trigger_error(
223
+ 'Infinite recursion detected in post type "'. $post_type .'" options caused by '. __FUNCTION__ .'()',
224
+ E_USER_WARNING
225
+ );
226
+ }
227
+
228
+ $options = array();
229
+ } else {
230
+ $recursion[$post_type] = true;
231
+
232
+ $options = fw_extract_only_options( // todo: cache this (by post type)
233
+ fw()->theme->get_post_options( $post_type )
234
+ );
235
+
236
+ $recursion[$post_type] = false;
237
+ }
238
+
239
+ if ($option_id) {
240
+ $option_id = explode('/', $option_id); // 'option_id/sub/keys'
241
+ $_option_id = array_shift($option_id); // 'option_id'
242
+ $sub_keys = empty($option_id) ? null : implode('/', $option_id); // 'sub/keys'
243
+ $option_id = $_option_id;
244
+ unset($_option_id);
245
+
246
+ $value = FW_WP_Meta::get(
247
+ 'post',
248
+ $post_id,
249
+ $meta_key .'/'. $option_id,
250
+ null,
251
+ $get_original_value
252
+ );
253
+
254
+ if (isset($options[$option_id])) {
255
+ $value = fw()->backend->option_type($options[$option_id]['type'])->storage_load(
256
+ $option_id,
257
+ $options[$option_id],
258
+ $value,
259
+ array( 'post-id' => $post_id, )
260
+ );
261
+ }
262
+
263
+ if ($sub_keys) {
264
+ return fw_akg($sub_keys, $value, $default_value);
265
+ } else {
266
+ return is_null($value) ? $default_value : $value;
267
+ }
268
+ } else {
269
+ $value = FW_WP_Meta::get(
270
+ 'post',
271
+ $post_id,
272
+ $meta_key,
273
+ $default_value,
274
+ $get_original_value
275
+ );
276
+
277
+ if (!is_array($value)) {
278
+ $value = array();
279
+ }
280
+
281
+ foreach ($options as $_option_id => $_option) {
282
+ $value[$_option_id] = fw()->backend->option_type($_option['type'])->storage_load(
283
+ $_option_id,
284
+ $_option,
285
+ isset($value[$_option_id]) ? $value[$_option_id] : null,
286
+ array( 'post-id' => $post_id, )
287
+ );
288
+ }
289
+
290
+ return $value;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Set post option value in database
296
+ *
297
+ * @param null|int $post_id
298
+ * @param string|null $option_id Specific option id (accepts multikey). null - all options
299
+ * @param $value
300
+ */
301
+ function fw_set_db_post_option( $post_id = null, $option_id = null, $value ) {
302
+ $meta_key = 'fw_options';
303
+ $post_id = intval($post_id);
304
+
305
+ if ( ! $post_id ) {
306
+ /** @var WP_Post $post */
307
+ global $post;
308
+
309
+ if ( ! $post ) {
310
+ return;
311
+ } else {
312
+ $post_id = $post->ID;
313
+ }
314
+ }
315
+
316
+ $options = fw_extract_only_options( // todo: cache this (by post type)
317
+ fw()->theme->get_post_options(
318
+ get_post_type(
319
+ ($post_revision_id = wp_is_post_revision($post_id)) ? $post_revision_id : $post_id
320
+ )
321
+ )
322
+ );
323
+
324
+ $sub_keys = null;
325
+
326
+ if ($option_id) {
327
+ $option_id = explode('/', $option_id); // 'option_id/sub/keys'
328
+ $_option_id = array_shift($option_id); // 'option_id'
329
+ $sub_keys = empty($option_id) ? null : implode('/', $option_id); // 'sub/keys'
330
+ $option_id = $_option_id;
331
+ unset($_option_id);
332
+
333
+ $old_value = fw_get_db_post_option($post_id, $option_id);
334
+
335
+ if ($sub_keys) { // update sub_key in old_value and use the entire value
336
+ $new_value = $old_value;
337
+ fw_aks($sub_keys, $value, $new_value);
338
+ $value = $new_value;
339
+ unset($new_value);
340
+
341
+ $old_value = fw_akg($sub_keys, $old_value);
342
+ }
343
+
344
+ if (isset($options[$option_id])) {
345
+ $value = fw()->backend->option_type($options[$option_id]['type'])->storage_save(
346
+ $option_id,
347
+ $options[$option_id],
348
+ $value,
349
+ array( 'post-id' => $post_id, )
350
+ );
351
+ }
352
+
353
+ FW_WP_Meta::set( 'post', $post_id, $meta_key .'/'. $option_id, $value );
354
+ } else {
355
+ $old_value = fw_get_db_post_option($post_id);
356
+
357
+ if (!is_array($value)) {
358
+ $value = array();
359
+ }
360
+
361
+ foreach ($value as $_option_id => $_option_value) {
362
+ if (isset($options[$_option_id])) {
363
+ $value[$_option_id] = fw()->backend->option_type($options[$_option_id]['type'])->storage_save(
364
+ $_option_id,
365
+ $options[$_option_id],
366
+ $_option_value,
367
+ array( 'post-id' => $post_id, )
368
+ );
369
+ }
370
+ }
371
+
372
+ FW_WP_Meta::set( 'post', $post_id, $meta_key, $value );
373
+ }
374
+
375
+ /**
376
+ * @deprecated
377
+ */
378
+ fw()->backend->_sync_post_separate_meta($post_id);
379
+
380
+ /**
381
+ * @since 2.2.8
382
+ */
383
+ do_action('fw_post_options_update',
384
+ $post_id,
385
+ /**
386
+ * Option id
387
+ * First level multi-key
388
+ *
389
+ * For e.g.
390
+ *
391
+ * if $option_id is 'hello/world/7'
392
+ * this will be 'hello'
393
+ */
394
+ $option_id,
395
+ /**
396
+ * The remaining sub-keys
397
+ *
398
+ * For e.g.
399
+ *
400
+ * if $option_id is 'hello/world/7'
401
+ * $option_id_keys will be array('world', '7')
402
+ *
403
+ * if $option_id is 'hello'
404
+ * $option_id_keys will be array()
405
+ */
406
+ explode('/', $sub_keys),
407
+ /**
408
+ * Old post option(s) value
409
+ * @since 2.3.3
410
+ */
411
+ $old_value
412
+ );
413
+ }
414
+ }
415
+
416
+ /** Terms Options */
417
+ {
418
+ /**
419
+ * Get term option value from the database
420
+ *
421
+ * @param int $term_id
422
+ * @param string $taxonomy
423
+ * @param string|null $option_id Specific option id (accepts multikey). null - all options
424
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
425
+ * @param null|bool $get_original_value Original value is that with no translations and other changes
426
+ *
427
+ * @return mixed|null
428
+ */
429
+ function fw_get_db_term_option( $term_id, $taxonomy, $option_id = null, $default_value = null, $get_original_value = null ) {
430
+ if ( ! taxonomy_exists( $taxonomy ) ) {
431
+ return null;
432
+ }
433
+
434
+ $option_id = 'fw_options' . ( $option_id !== null ? '/' . $option_id : '' );
435
+
436
+ return FW_WP_Meta::get( 'fw_term', $term_id, $option_id, $default_value, $get_original_value );
437
+ }
438
+
439
+ /**
440
+ * Set term option value in database
441
+ *
442
+ * @param int $term_id
443
+ * @param string $taxonomy
444
+ * @param string|null $option_id Specific option id (accepts multikey). null - all options
445
+ * @param mixed $value
446
+ *
447
+ * @return null
448
+ */
449
+ function fw_set_db_term_option( $term_id, $taxonomy, $option_id = null, $value ) {
450
+ if ( ! taxonomy_exists( $taxonomy ) ) {
451
+ return null;
452
+ }
453
+ $option_id = 'fw_options' . ( $option_id !== null ? '/' . $option_id : '' );
454
+
455
+ FW_WP_Meta::set( 'fw_term', $term_id, $option_id, $value );
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Extensions Data
461
+ *
462
+ * Used by extensions to store custom data in database.
463
+ * By using these functions, extension prevent database spam with wp options for each extension,
464
+ * because these functions store all data in one wp option.
465
+ */
466
+ {
467
+ /**
468
+ * Get extension's data from the database
469
+ *
470
+ * @param string $extension_name
471
+ * @param string|null $multi_key The key of the data you want to get. null - all data
472
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
473
+ * @param null|bool $get_original_value Original value is that with no translations and other changes
474
+ *
475
+ * @return mixed|null
476
+ */
477
+ function fw_get_db_extension_data( $extension_name, $multi_key = null, $default_value = null, $get_original_value = null ) {
478
+ if ( ! fw()->extensions->get( $extension_name ) ) {
479
+ trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
480
+
481
+ return null;
482
+ }
483
+
484
+ if ( $multi_key ) {
485
+ $multi_key = $extension_name . '/' . $multi_key;
486
+ } else {
487
+ $multi_key = $extension_name;
488
+ }
489
+
490
+ return FW_WP_Option::get( 'fw_extensions', $multi_key, $default_value, $get_original_value );
491
+ }
492
+
493
+ /**
494
+ * Set some extension's data in database
495
+ *
496
+ * @param string $extension_name
497
+ * @param string|null $multi_key The key of the data you want to set. null - all data
498
+ * @param mixed $value
499
+ */
500
+ function fw_set_db_extension_data( $extension_name, $multi_key = null, $value ) {
501
+ if ( ! fw()->extensions->get( $extension_name ) ) {
502
+ trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
503
+
504
+ return;
505
+ }
506
+
507
+ if ( $multi_key ) {
508
+ $multi_key = $extension_name . '/' . $multi_key;
509
+ } else {
510
+ $multi_key = $extension_name;
511
+ }
512
+
513
+ FW_WP_Option::set( 'fw_extensions', $multi_key, $value );
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Extensions Settings Options
519
+ */
520
+ {
521
+ /**
522
+ * Get extension's settings option value from the database
523
+ *
524
+ * @param string $extension_name
525
+ * @param string|null $option_id
526
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
527
+ * @param null|bool $get_original_value Original value is that with no translations and other changes
528
+ *
529
+ * @return mixed|null
530
+ */
531
+ function fw_get_db_ext_settings_option( $extension_name, $option_id = null, $default_value = null, $get_original_value = null ) {
532
+ if ( ! ( $extension = fw()->extensions->get( $extension_name ) ) ) {
533
+ trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
534
+
535
+ return null;
536
+ }
537
+
538
+ $value = FW_WP_Option::get( 'fw_ext_settings_options:' . $extension_name, $option_id, $default_value, $get_original_value );
539
+
540
+ if ( is_null( $value ) ) {
541
+ /**
542
+ * Maybe the options was never saved or the given option id does not exists
543
+ * Extract the default values from the options array and try to find there the option id
544
+ */
545
+
546
+ $cache_key = 'fw_default_options_values/ext_settings:' . $extension_name;
547
+
548
+ try {
549
+ $all_options_values = FW_Cache::get( $cache_key );
550
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
551
+ // extract the default values from options array
552
+ $all_options_values = fw_get_options_values_from_input(
553
+ $extension->get_settings_options(),
554
+ array()
555
+ );
556
+
557
+ FW_Cache::set( $cache_key, $all_options_values );
558
+ }
559
+
560
+ if ( empty( $option_id ) ) {
561
+ // option id not specified, return all options values
562
+ return $all_options_values;
563
+ } else {
564
+ return fw_akg( $option_id, $all_options_values, $default_value );
565
+ }
566
+ } else {
567
+ return $value;
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Set extension's setting option value in database
573
+ *
574
+ * @param string $extension_name
575
+ * @param string|null $option_id
576
+ * @param mixed $value
577
+ */
578
+ function fw_set_db_ext_settings_option( $extension_name, $option_id = null, $value ) {
579
+ if ( ! fw()->extensions->get( $extension_name ) ) {
580
+ trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
581
+
582
+ return;
583
+ }
584
+
585
+ FW_WP_Option::set( 'fw_ext_settings_options:' . $extension_name, $option_id, $value );
586
+ }
587
+ }
588
+
589
+ {
590
+ /**
591
+ * Get user meta set by specific extension
592
+ *
593
+ * @param int $user_id
594
+ * @param string $extension_name
595
+ * @param string $keys
596
+ *
597
+