Unyson - Version 2.7.11

Version Description

  • Fixed #3052,#3050,#3044,#3043,#3014 ,#3008,#3006,#3071,#3061
Download this release

Release Info

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

Code changes from version 2.7.10 to 2.7.11

Files changed (65) hide show
  1. framework/LICENSE +674 -674
  2. framework/autoload.php +306 -303
  3. framework/bin/load-latest-fonts.php +172 -172
  4. framework/bootstrap.php +79 -79
  5. framework/core/Fw.php +79 -79
  6. framework/core/class-fw-manifest.php +571 -564
  7. framework/core/components/backend.php +2062 -2054
  8. framework/core/components/backend/class-fw-settings-form-theme.php +217 -217
  9. framework/core/components/extensions.php +688 -685
  10. framework/core/components/extensions/class-fw-extension-default.php +11 -11
  11. framework/core/components/extensions/manager/available-extensions.php +336 -334
  12. framework/core/components/extensions/manager/class--fw-extensions-manager.php +3670 -3671
  13. framework/core/components/extensions/manager/includes/available-ext/class--fw-available-extensions-register.php +7 -7
  14. framework/core/components/extensions/manager/includes/available-ext/class-fw-available-extension.php +131 -131
  15. framework/core/components/extensions/manager/includes/class--fw-extensions-delete-upgrader-skin.php +28 -28
  16. framework/core/components/extensions/manager/includes/class--fw-extensions-install-upgrader-skin.php +28 -28
  17. framework/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source-register.php +9 -9
  18. framework/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source.php +21 -20
  19. framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-custom.php +209 -0
  20. framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-github.php +188 -187
  21. framework/core/components/extensions/manager/includes/download-source/types/init.php +10 -12
  22. framework/core/components/extensions/manager/includes/parsedown/LICENSE.txt +19 -19
  23. framework/core/components/extensions/manager/includes/parsedown/Parsedown.php +1527 -1527
  24. framework/core/components/extensions/manager/static/extension-page.css +22 -22
  25. framework/core/components/extensions/manager/static/extension-page.js +2 -2
  26. framework/core/components/extensions/manager/static/extensions-page.css +260 -260
  27. framework/core/components/extensions/manager/static/extensions-page.js +107 -107
  28. framework/core/components/extensions/manager/static/unyson-font-icon/fonts/icomoon.svg +10 -10
  29. framework/core/components/extensions/manager/static/unyson-font-icon/style.css +28 -28
  30. framework/core/components/extensions/manager/views/delete-form.php +54 -54
  31. framework/core/components/extensions/manager/views/extension-page-header.php +50 -50
  32. framework/core/components/extensions/manager/views/extension.php +246 -229
  33. framework/core/components/extensions/manager/views/extensions-page.php +267 -242
  34. framework/core/components/extensions/manager/views/install-form.php +51 -51
  35. framework/core/components/theme.php +203 -203
  36. framework/core/exceptions/class-fw-option-type-exception.php +39 -39
  37. framework/core/extends/class-fw-container-type.php +233 -233
  38. framework/core/extends/class-fw-extension.php +514 -514
  39. framework/core/extends/class-fw-option-type.php +465 -453
  40. framework/core/extends/interface-fw-option-handler.php +13 -13
  41. framework/extensions/blog/class-fw-extension-blog.php +89 -89
  42. framework/extensions/blog/manifest.php +13 -13
  43. framework/extensions/update/class-fw-extension-update.php +1005 -963
  44. framework/extensions/update/config.php +9 -9
  45. framework/extensions/update/extensions/custom-update/class-fw-extension-custom-update.php +254 -0
  46. framework/extensions/update/extensions/custom-update/manifest.php +5 -0
  47. framework/extensions/update/extensions/github-update/class-fw-extension-github-update.php +433 -443
  48. framework/extensions/update/extensions/github-update/manifest.php +5 -5
  49. framework/extensions/update/includes/classes/class--fw-ext-update-extensions-list-table.php +128 -128
  50. framework/extensions/update/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php +36 -36
  51. framework/extensions/update/includes/classes/class--fw-ext-update-framework-upgrader-skin.php +33 -33
  52. framework/extensions/update/includes/classes/class--fw-ext-update-theme-upgrader-skin.php +33 -33
  53. framework/extensions/update/includes/extends/class-fw-ext-update-service.php +105 -105
  54. framework/extensions/update/manifest.php +10 -10
  55. framework/extensions/update/static.php +14 -14
  56. framework/extensions/update/static/css/admin-update-page.css +2 -2
  57. framework/extensions/update/views/updates-list.php +118 -113
  58. framework/helpers/class-fw-access-key.php +43 -43
  59. framework/helpers/class-fw-cache.php +308 -308
  60. framework/helpers/class-fw-callback.php +131 -131
  61. framework/helpers/class-fw-db-options-model.php +330 -319
  62. framework/helpers/class-fw-dumper.php +123 -123
  63. framework/helpers/class-fw-flash-messages.php +218 -218
  64. framework/helpers/class-fw-form.php +650 -650
  65. framework/helpers/class-fw-request.php +15 -90
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/autoload.php CHANGED
@@ -1,304 +1,307 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- spl_autoload_register( '_fw_core_autoload' );
6
- function _fw_core_autoload( $class ) {
7
- switch ( $class ) {
8
- case 'FW_Manifest' :
9
- case 'FW_Framework_Manifest' :
10
- case 'FW_Theme_Manifest' :
11
- case 'FW_Extension_Manifest' :
12
- require_once dirname( __FILE__ ) . '/core/class-fw-manifest.php';
13
- break;
14
- }
15
- }
16
-
17
- spl_autoload_register( '_fw_core_components_autoload' );
18
- function _fw_core_components_autoload( $class ) {
19
- switch ( $class ) {
20
- case '_FW_Component_Backend' :
21
- require_once dirname( __FILE__ ) . '/core/components/backend.php';
22
- break;
23
- case '_FW_Component_Extensions' :
24
- require_once dirname( __FILE__ ) . '/core/components/extensions.php';
25
- break;
26
- case '_FW_Component_Theme' :
27
- require_once dirname( __FILE__ ) . '/core/components/theme.php';
28
- break;
29
- case 'FW_Settings_Form_Theme' :
30
- require_once dirname( __FILE__ ) . '/core/components/backend/class-fw-settings-form-theme.php';
31
- break;
32
- }
33
- }
34
-
35
- spl_autoload_register( '_fw_core_components_extensions_autoload' );
36
- function _fw_core_components_extensions_autoload( $class ) {
37
- switch ( $class ) {
38
- case 'FW_Extension_Default' :
39
- require_once dirname( __FILE__ ) . '/core/components/extensions/class-fw-extension-default.php';
40
- break;
41
- case '_FW_Extensions_Manager' :
42
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/class--fw-extensions-manager.php';
43
- break;
44
- case '_FW_Extensions_Delete_Upgrader_Skin' :
45
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/class--fw-extensions-delete-upgrader-skin.php';
46
- break;
47
- case '_FW_Extensions_Install_Upgrader_Skin' :
48
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/class--fw-extensions-install-upgrader-skin.php';
49
- break;
50
- case 'Parsedown' :
51
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/parsedown/Parsedown.php';
52
- break;
53
- case 'FW_Ext_Download_Source' :
54
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source.php';
55
- break;
56
- case '_FW_Ext_Download_Source_Register' :
57
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source-register.php';
58
- break;
59
- case 'FW_Ext_Download_Source_Github' :
60
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-github.php';
61
- break;
62
- case '_FW_Available_Extensions_Register' :
63
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/available-ext/class--fw-available-extensions-register.php';
64
- break;
65
- case 'FW_Available_Extension' :
66
- require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/available-ext/class-fw-available-extension.php';
67
- break;
68
- }
69
- }
70
-
71
- spl_autoload_register( '_fw_core_extends_autoload' );
72
- function _fw_core_extends_autoload( $class ) {
73
- switch ( $class ) {
74
- case 'FW_Container_Type' :
75
- require_once dirname( __FILE__ ) . '/core/extends/class-fw-container-type.php';
76
- break;
77
- case 'FW_Option_Type' :
78
- require_once dirname( __FILE__ ) . '/core/extends/class-fw-option-type.php';
79
- break;
80
- case 'FW_Extension' :
81
- require_once dirname( __FILE__ ) . '/core/extends/class-fw-extension.php';
82
- break;
83
- case 'FW_Option_Handler' :
84
- require_once dirname( __FILE__ ) . '/core/extends/interface-fw-option-handler.php';
85
- break;
86
- }
87
- }
88
-
89
- spl_autoload_register( '_fw_code_exceptions_autoload' );
90
- function _fw_code_exceptions_autoload( $class ) {
91
- switch ( $class ) {
92
- case 'FW_Option_Type_Exception' :
93
- case 'FW_Option_Type_Exception_Not_Found' :
94
- case 'FW_Option_Type_Exception_Invalid_Class' :
95
- case 'FW_Option_Type_Exception_Already_Registered' :
96
- require_once dirname( __FILE__ ) . '/core/exceptions/class-fw-option-type-exception.php';
97
- break;
98
- }
99
- }
100
-
101
- // Autoload helper classes
102
- function _fw_autoload_helper_classes($class) {
103
- static $class_to_file = array(
104
- 'FW_Dumper' => 'class-fw-dumper',
105
- 'FW_Cache' => 'class-fw-cache',
106
- 'FW_Callback' => 'class-fw-callback',
107
- 'FW_Access_Key' => 'class-fw-access-key',
108
- 'FW_WP_Filesystem' => 'class-fw-wp-filesystem',
109
- 'FW_Form' => 'class-fw-form',
110
- 'FW_Form_Not_Found_Exception' => 'exceptions/class-fw-form-not-found-exception',
111
- 'FW_Form_Invalid_Submission_Exception' => 'exceptions/class-fw-form-invalid-submission-exception',
112
- 'FW_Settings_Form' => 'class-fw-settings-form',
113
- 'FW_Request' => 'class-fw-request',
114
- 'FW_Session' => 'class-fw-session',
115
- 'FW_WP_Option' => 'class-fw-wp-option',
116
- 'FW_WP_Meta' => 'class-fw-wp-meta',
117
- 'FW_Db_Options_Model' => 'class-fw-db-options-model',
118
- 'FW_Flash_Messages' => 'class-fw-flash-messages',
119
- 'FW_Resize' => 'class-fw-resize',
120
- 'FW_WP_List_Table' => 'class-fw-wp-list-table',
121
- 'FW_Type' => 'type/class-fw-type',
122
- 'FW_Type_Register' => 'type/class-fw-type-register',
123
- );
124
-
125
- if (isset($class_to_file[$class])) {
126
- require dirname(__FILE__) .'/helpers/'. $class_to_file[$class] .'.php';
127
- }
128
- }
129
- spl_autoload_register('_fw_autoload_helper_classes');
130
-
131
- spl_autoload_register( '_fw_includes_container_types_autoload' );
132
- function _fw_includes_container_types_autoload( $class ) {
133
- switch ( $class ) {
134
- case 'FW_Container_Type_Undefined' :
135
- require_once dirname( __FILE__ ) . '/includes/container-types/class-fw-container-type-undefined.php';
136
- break;
137
- case 'FW_Container_Type_Group' :
138
- require_once dirname( __FILE__ ) . '/includes/container-types/simple.php';
139
- break;
140
- case 'FW_Container_Type_Box' :
141
- require_once dirname( __FILE__ ) . '/includes/container-types/box/class-fw-container-type-box.php';
142
- break;
143
- case 'FW_Container_Type_Popup' :
144
- require_once dirname( __FILE__ ) . '/includes/container-types/popup/class-fw-container-type-popup.php';
145
- break;
146
- case 'FW_Container_Type_Tab' :
147
- require_once dirname( __FILE__ ) . '/includes/container-types/tab/class-fw-container-type-tab.php';
148
- break;
149
- }
150
- }
151
-
152
- spl_autoload_register( '_fw_includes_customizer_autoload' );
153
- function _fw_includes_customizer_autoload( $class ) {
154
- switch ( $class ) {
155
- case '_FW_Customizer_Control_Option_Wrapper' :
156
- require_once dirname( __FILE__ ) . '/includes/customizer/class--fw-customizer-control-option-wrapper.php';
157
- break;
158
- case '_FW_Customizer_Setting_Option' :
159
- require_once dirname( __FILE__ ) . '/includes/customizer/class--fw-customizer-setting-option.php';
160
- break;
161
- }
162
- }
163
-
164
- spl_autoload_register( '_fw_includes_option_storage_autoload' );
165
- function _fw_includes_option_storage_autoload( $class ) {
166
- switch ( $class ) {
167
- case '_FW_Option_Storage_Type_Register' :
168
- require_once dirname( __FILE__ ) . '/includes/option-storage/class--fw-option-storage-type-register.php';
169
- break;
170
- case 'FW_Option_Storage_Type' :
171
- require_once dirname( __FILE__ ) . '/includes/option-storage/class-fw-option-storage-type.php';
172
- break;
173
- case 'FW_Option_Storage_Type_Post_Meta' :
174
- require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-post-meta.php';
175
- break;
176
- case 'FW_Option_Storage_Type_Term_Meta' :
177
- require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-term-meta.php';
178
- break;
179
- case 'FW_Option_Storage_Type_WP_Option' :
180
- require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-wp-option.php';
181
- break;
182
- }
183
- }
184
-
185
- spl_autoload_register( '_fw_includes_option_types_autoload' );
186
- function _fw_includes_option_types_autoload( $class ) {
187
- switch ( $class ) {
188
- case 'FW_Option_Type_Undefined' :
189
- require_once dirname( __FILE__ ) . '/includes/option-types/class-fw-option-type-undefined.php';
190
- break;
191
- case 'FW_Option_Type_Hidden' :
192
- case 'FW_Option_Type_Text' :
193
- case 'FW_Option_Type_Short_Text' :
194
- case 'FW_Option_Type_Password' :
195
- case 'FW_Option_Type_Textarea' :
196
- case 'FW_Option_Type_Html' :
197
- case 'FW_Option_Type_Html_Fixed' :
198
- case 'FW_Option_Type_Html_Full' :
199
- case 'FW_Option_Type_Checkbox' :
200
- case 'FW_Option_Type_Checkboxes' :
201
- case 'FW_Option_Type_Radio' :
202
- case 'FW_Option_Type_Select' :
203
- case 'FW_Option_Type_Short_Select' :
204
- case 'FW_Option_Type_Select_Multiple' :
205
- case 'FW_Option_Type_Unique' :
206
- case 'FW_Option_Type_GMap_Key' :
207
- require_once dirname( __FILE__ ) . '/includes/option-types/simple.php';
208
- break;
209
- case 'FW_Option_Type_Addable_Box' :
210
- require_once dirname( __FILE__ ) . '/includes/option-types/addable-box/class-fw-option-type-addable-box.php';
211
- break;
212
- case 'FW_Option_Type_Addable_Popup' :
213
- case 'FW_Option_Type_Addable_Popup_Full' :
214
- require_once dirname( __FILE__ ) . '/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php';
215
- break;
216
- case 'FW_Option_Type_Addable_Option' :
217
- require_once dirname( __FILE__ ) . '/includes/option-types/addable-option/class-fw-option-type-addable-option.php';
218
- break;
219
- case 'FW_Option_Type_Background_Image' :
220
- require_once dirname( __FILE__ ) . '/includes/option-types/background-image/class-fw-option-type-background-image.php';
221
- break;
222
- case 'FW_Option_Type_Color_Picker' :
223
- require_once dirname( __FILE__ ) . '/includes/option-types/color-picker/class-fw-option-type-color-picker.php';
224
- break;
225
- case 'FW_Option_Type_Date_Picker' :
226
- require_once dirname( __FILE__ ) . '/includes/option-types/date-picker/class-fw-option-type-wp-date-picker.php';
227
- break;
228
- case 'FW_Option_Type_Datetime_Picker' :
229
- require_once dirname( __FILE__ ) . '/includes/option-types/datetime-picker/class-fw-option-type-datetime-picker.php';
230
- break;
231
- case 'FW_Option_Type_Datetime_Range' :
232
- require_once dirname( __FILE__ ) . '/includes/option-types/datetime-range/class-fw-option-type-datetime-range.php';
233
- break;
234
- case 'FW_Option_Type_Gradient' :
235
- require_once dirname( __FILE__ ) . '/includes/option-types/gradient/class-fw-option-type-gradient.php';
236
- break;
237
- case 'FW_Option_Type_Icon' :
238
- require_once dirname( __FILE__ ) . '/includes/option-types/icon/class-fw-option-type-icon.php';
239
- break;
240
- case 'FW_Option_Type_Icon_v2' :
241
- require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/class-fw-option-type-icon-v2.php';
242
- break;
243
- case 'FW_Option_Type_Image_Picker' :
244
- require_once dirname( __FILE__ ) . '/includes/option-types/image-picker/class-fw-option-type-image-picker.php';
245
- break;
246
- case 'FW_Option_Type_Map' :
247
- require_once dirname( __FILE__ ) . '/includes/option-types/map/class-fw-option-type-map.php';
248
- break;
249
- case 'FW_Option_Type_Multi' :
250
- require_once dirname( __FILE__ ) . '/includes/option-types/multi/class-fw-option-type-multi.php';
251
- break;
252
- case 'FW_Option_Type_Multi_Picker' :
253
- require_once dirname( __FILE__ ) . '/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php';
254
- break;
255
- case 'FW_Option_Type_Multi_Select' :
256
- require_once dirname( __FILE__ ) . '/includes/option-types/multi-select/class-fw-option-type-multi-select.php';
257
- break;
258
- case 'FW_Option_Type_Multi_Upload' :
259
- require_once dirname( __FILE__ ) . '/includes/option-types/multi-upload/class-fw-option-type-multi-upload.php';
260
- break;
261
- case 'FW_Option_Type_Oembed' :
262
- require_once dirname( __FILE__ ) . '/includes/option-types/oembed/class-fw-option-type-oembed.php';
263
- break;
264
- case 'FW_Option_Type_Popup' :
265
- require_once dirname( __FILE__ ) . '/includes/option-types/popup/class-fw-option-type-popup.php';
266
- break;
267
- case 'FW_Option_Type_Radio_Text' :
268
- require_once dirname( __FILE__ ) . '/includes/option-types/radio-text/class-fw-option-type-radio-text.php';
269
- break;
270
- case 'FW_Option_Type_Range_Slider' :
271
- require_once dirname( __FILE__ ) . '/includes/option-types/range-slider/class-fw-option-type-range-slider.php';
272
- break;
273
- case 'FW_Option_Type_Rgba_Color_Picker' :
274
- require_once dirname( __FILE__ ) . '/includes/option-types/rgba-color-picker/class-fw-option-type-rgba-color-picker.php';
275
- break;
276
- case 'FW_Option_Type_Slider' :
277
- require_once dirname( __FILE__ ) . '/includes/option-types/slider/class-fw-option-type-slider.php';
278
- break;
279
- case 'FW_Option_Type_Slider_Short' :
280
- require_once dirname( __FILE__ ) . '/includes/option-types/slider/class-fw-option-type-short-slider.php';
281
- break;
282
- case 'FW_Option_Type_Switch' :
283
- require_once dirname( __FILE__ ) . '/includes/option-types/switch/class-fw-option-type-switch.php';
284
- break;
285
- case 'FW_Option_Type_Typography' :
286
- require_once dirname( __FILE__ ) . '/includes/option-types/typography/class-fw-option-type-typography.php';
287
- break;
288
- case 'FW_Option_Type_Typography_v2' :
289
- require_once dirname( __FILE__ ) . '/includes/option-types/typography-v2/class-fw-option-type-typography-v2.php';
290
- break;
291
- case 'FW_Option_Type_Upload' :
292
- require_once dirname( __FILE__ ) . '/includes/option-types/upload/class-fw-option-type-upload.php';
293
- break;
294
- case 'FW_Option_Type_Wp_Editor' :
295
- require_once dirname( __FILE__ ) . '/includes/option-types/wp-editor/class-fw-option-type-wp-editor.php';
296
- break;
297
- case 'FW_Icon_V2_Favorites_Manager' :
298
- require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/includes/class-fw-icon-v2-favorites.php';
299
- break;
300
- case 'FW_Icon_V2_Packs_Loader' :
301
- require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/includes/class-fw-icon-v2-packs-loader.php';
302
- break;
303
- }
 
 
 
304
  }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ spl_autoload_register( '_fw_core_autoload' );
6
+ function _fw_core_autoload( $class ) {
7
+ switch ( $class ) {
8
+ case 'FW_Manifest' :
9
+ case 'FW_Framework_Manifest' :
10
+ case 'FW_Theme_Manifest' :
11
+ case 'FW_Extension_Manifest' :
12
+ require_once dirname( __FILE__ ) . '/core/class-fw-manifest.php';
13
+ break;
14
+ }
15
+ }
16
+
17
+ spl_autoload_register( '_fw_core_components_autoload' );
18
+ function _fw_core_components_autoload( $class ) {
19
+ switch ( $class ) {
20
+ case '_FW_Component_Backend' :
21
+ require_once dirname( __FILE__ ) . '/core/components/backend.php';
22
+ break;
23
+ case '_FW_Component_Extensions' :
24
+ require_once dirname( __FILE__ ) . '/core/components/extensions.php';
25
+ break;
26
+ case '_FW_Component_Theme' :
27
+ require_once dirname( __FILE__ ) . '/core/components/theme.php';
28
+ break;
29
+ case 'FW_Settings_Form_Theme' :
30
+ require_once dirname( __FILE__ ) . '/core/components/backend/class-fw-settings-form-theme.php';
31
+ break;
32
+ }
33
+ }
34
+
35
+ spl_autoload_register( '_fw_core_components_extensions_autoload' );
36
+ function _fw_core_components_extensions_autoload( $class ) {
37
+ switch ( $class ) {
38
+ case 'FW_Extension_Default' :
39
+ require_once dirname( __FILE__ ) . '/core/components/extensions/class-fw-extension-default.php';
40
+ break;
41
+ case '_FW_Extensions_Manager' :
42
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/class--fw-extensions-manager.php';
43
+ break;
44
+ case '_FW_Extensions_Delete_Upgrader_Skin' :
45
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/class--fw-extensions-delete-upgrader-skin.php';
46
+ break;
47
+ case '_FW_Extensions_Install_Upgrader_Skin' :
48
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/class--fw-extensions-install-upgrader-skin.php';
49
+ break;
50
+ case 'Parsedown' :
51
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/parsedown/Parsedown.php';
52
+ break;
53
+ case 'FW_Ext_Download_Source' :
54
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source.php';
55
+ break;
56
+ case '_FW_Ext_Download_Source_Register' :
57
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source-register.php';
58
+ break;
59
+ case 'FW_Ext_Download_Source_Github' :
60
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-github.php';
61
+ break;
62
+ case 'FW_Ext_Download_Source_Custom' :
63
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-custom.php';
64
+ break;
65
+ case '_FW_Available_Extensions_Register' :
66
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/available-ext/class--fw-available-extensions-register.php';
67
+ break;
68
+ case 'FW_Available_Extension' :
69
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/available-ext/class-fw-available-extension.php';
70
+ break;
71
+ }
72
+ }
73
+
74
+ spl_autoload_register( '_fw_core_extends_autoload' );
75
+ function _fw_core_extends_autoload( $class ) {
76
+ switch ( $class ) {
77
+ case 'FW_Container_Type' :
78
+ require_once dirname( __FILE__ ) . '/core/extends/class-fw-container-type.php';
79
+ break;
80
+ case 'FW_Option_Type' :
81
+ require_once dirname( __FILE__ ) . '/core/extends/class-fw-option-type.php';
82
+ break;
83
+ case 'FW_Extension' :
84
+ require_once dirname( __FILE__ ) . '/core/extends/class-fw-extension.php';
85
+ break;
86
+ case 'FW_Option_Handler' :
87
+ require_once dirname( __FILE__ ) . '/core/extends/interface-fw-option-handler.php';
88
+ break;
89
+ }
90
+ }
91
+
92
+ spl_autoload_register( '_fw_code_exceptions_autoload' );
93
+ function _fw_code_exceptions_autoload( $class ) {
94
+ switch ( $class ) {
95
+ case 'FW_Option_Type_Exception' :
96
+ case 'FW_Option_Type_Exception_Not_Found' :
97
+ case 'FW_Option_Type_Exception_Invalid_Class' :
98
+ case 'FW_Option_Type_Exception_Already_Registered' :
99
+ require_once dirname( __FILE__ ) . '/core/exceptions/class-fw-option-type-exception.php';
100
+ break;
101
+ }
102
+ }
103
+
104
+ // Autoload helper classes
105
+ function _fw_autoload_helper_classes($class) {
106
+ static $class_to_file = array(
107
+ 'FW_Dumper' => 'class-fw-dumper',
108
+ 'FW_Cache' => 'class-fw-cache',
109
+ 'FW_Callback' => 'class-fw-callback',
110
+ 'FW_Access_Key' => 'class-fw-access-key',
111
+ 'FW_WP_Filesystem' => 'class-fw-wp-filesystem',
112
+ 'FW_Form' => 'class-fw-form',
113
+ 'FW_Form_Not_Found_Exception' => 'exceptions/class-fw-form-not-found-exception',
114
+ 'FW_Form_Invalid_Submission_Exception' => 'exceptions/class-fw-form-invalid-submission-exception',
115
+ 'FW_Settings_Form' => 'class-fw-settings-form',
116
+ 'FW_Request' => 'class-fw-request',
117
+ 'FW_Session' => 'class-fw-session',
118
+ 'FW_WP_Option' => 'class-fw-wp-option',
119
+ 'FW_WP_Meta' => 'class-fw-wp-meta',
120
+ 'FW_Db_Options_Model' => 'class-fw-db-options-model',
121
+ 'FW_Flash_Messages' => 'class-fw-flash-messages',
122
+ 'FW_Resize' => 'class-fw-resize',
123
+ 'FW_WP_List_Table' => 'class-fw-wp-list-table',
124
+ 'FW_Type' => 'type/class-fw-type',
125
+ 'FW_Type_Register' => 'type/class-fw-type-register',
126
+ );
127
+
128
+ if (isset($class_to_file[$class])) {
129
+ require dirname(__FILE__) .'/helpers/'. $class_to_file[$class] .'.php';
130
+ }
131
+ }
132
+ spl_autoload_register('_fw_autoload_helper_classes');
133
+
134
+ spl_autoload_register( '_fw_includes_container_types_autoload' );
135
+ function _fw_includes_container_types_autoload( $class ) {
136
+ switch ( $class ) {
137
+ case 'FW_Container_Type_Undefined' :
138
+ require_once dirname( __FILE__ ) . '/includes/container-types/class-fw-container-type-undefined.php';
139
+ break;
140
+ case 'FW_Container_Type_Group' :
141
+ require_once dirname( __FILE__ ) . '/includes/container-types/simple.php';
142
+ break;
143
+ case 'FW_Container_Type_Box' :
144
+ require_once dirname( __FILE__ ) . '/includes/container-types/box/class-fw-container-type-box.php';
145
+ break;
146
+ case 'FW_Container_Type_Popup' :
147
+ require_once dirname( __FILE__ ) . '/includes/container-types/popup/class-fw-container-type-popup.php';
148
+ break;
149
+ case 'FW_Container_Type_Tab' :
150
+ require_once dirname( __FILE__ ) . '/includes/container-types/tab/class-fw-container-type-tab.php';
151
+ break;
152
+ }
153
+ }
154
+
155
+ spl_autoload_register( '_fw_includes_customizer_autoload' );
156
+ function _fw_includes_customizer_autoload( $class ) {
157
+ switch ( $class ) {
158
+ case '_FW_Customizer_Control_Option_Wrapper' :
159
+ require_once dirname( __FILE__ ) . '/includes/customizer/class--fw-customizer-control-option-wrapper.php';
160
+ break;
161
+ case '_FW_Customizer_Setting_Option' :
162
+ require_once dirname( __FILE__ ) . '/includes/customizer/class--fw-customizer-setting-option.php';
163
+ break;
164
+ }
165
+ }
166
+
167
+ spl_autoload_register( '_fw_includes_option_storage_autoload' );
168
+ function _fw_includes_option_storage_autoload( $class ) {
169
+ switch ( $class ) {
170
+ case '_FW_Option_Storage_Type_Register' :
171
+ require_once dirname( __FILE__ ) . '/includes/option-storage/class--fw-option-storage-type-register.php';
172
+ break;
173
+ case 'FW_Option_Storage_Type' :
174
+ require_once dirname( __FILE__ ) . '/includes/option-storage/class-fw-option-storage-type.php';
175
+ break;
176
+ case 'FW_Option_Storage_Type_Post_Meta' :
177
+ require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-post-meta.php';
178
+ break;
179
+ case 'FW_Option_Storage_Type_Term_Meta' :
180
+ require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-term-meta.php';
181
+ break;
182
+ case 'FW_Option_Storage_Type_WP_Option' :
183
+ require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-wp-option.php';
184
+ break;
185
+ }
186
+ }
187
+
188
+ spl_autoload_register( '_fw_includes_option_types_autoload' );
189
+ function _fw_includes_option_types_autoload( $class ) {
190
+ switch ( $class ) {
191
+ case 'FW_Option_Type_Undefined' :
192
+ require_once dirname( __FILE__ ) . '/includes/option-types/class-fw-option-type-undefined.php';
193
+ break;
194
+ case 'FW_Option_Type_Hidden' :
195
+ case 'FW_Option_Type_Text' :
196
+ case 'FW_Option_Type_Short_Text' :
197
+ case 'FW_Option_Type_Password' :
198
+ case 'FW_Option_Type_Textarea' :
199
+ case 'FW_Option_Type_Html' :
200
+ case 'FW_Option_Type_Html_Fixed' :
201
+ case 'FW_Option_Type_Html_Full' :
202
+ case 'FW_Option_Type_Checkbox' :
203
+ case 'FW_Option_Type_Checkboxes' :
204
+ case 'FW_Option_Type_Radio' :
205
+ case 'FW_Option_Type_Select' :
206
+ case 'FW_Option_Type_Short_Select' :
207
+ case 'FW_Option_Type_Select_Multiple' :
208
+ case 'FW_Option_Type_Unique' :
209
+ case 'FW_Option_Type_GMap_Key' :
210
+ require_once dirname( __FILE__ ) . '/includes/option-types/simple.php';
211
+ break;
212
+ case 'FW_Option_Type_Addable_Box' :
213
+ require_once dirname( __FILE__ ) . '/includes/option-types/addable-box/class-fw-option-type-addable-box.php';
214
+ break;
215
+ case 'FW_Option_Type_Addable_Popup' :
216
+ case 'FW_Option_Type_Addable_Popup_Full' :
217
+ require_once dirname( __FILE__ ) . '/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php';
218
+ break;
219
+ case 'FW_Option_Type_Addable_Option' :
220
+ require_once dirname( __FILE__ ) . '/includes/option-types/addable-option/class-fw-option-type-addable-option.php';
221
+ break;
222
+ case 'FW_Option_Type_Background_Image' :
223
+ require_once dirname( __FILE__ ) . '/includes/option-types/background-image/class-fw-option-type-background-image.php';
224
+ break;
225
+ case 'FW_Option_Type_Color_Picker' :
226
+ require_once dirname( __FILE__ ) . '/includes/option-types/color-picker/class-fw-option-type-color-picker.php';
227
+ break;
228
+ case 'FW_Option_Type_Date_Picker' :
229
+ require_once dirname( __FILE__ ) . '/includes/option-types/date-picker/class-fw-option-type-wp-date-picker.php';
230
+ break;
231
+ case 'FW_Option_Type_Datetime_Picker' :
232
+ require_once dirname( __FILE__ ) . '/includes/option-types/datetime-picker/class-fw-option-type-datetime-picker.php';
233
+ break;
234
+ case 'FW_Option_Type_Datetime_Range' :
235
+ require_once dirname( __FILE__ ) . '/includes/option-types/datetime-range/class-fw-option-type-datetime-range.php';
236
+ break;
237
+ case 'FW_Option_Type_Gradient' :
238
+ require_once dirname( __FILE__ ) . '/includes/option-types/gradient/class-fw-option-type-gradient.php';
239
+ break;
240
+ case 'FW_Option_Type_Icon' :
241
+ require_once dirname( __FILE__ ) . '/includes/option-types/icon/class-fw-option-type-icon.php';
242
+ break;
243
+ case 'FW_Option_Type_Icon_v2' :
244
+ require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/class-fw-option-type-icon-v2.php';
245
+ break;
246
+ case 'FW_Option_Type_Image_Picker' :
247
+ require_once dirname( __FILE__ ) . '/includes/option-types/image-picker/class-fw-option-type-image-picker.php';
248
+ break;
249
+ case 'FW_Option_Type_Map' :
250
+ require_once dirname( __FILE__ ) . '/includes/option-types/map/class-fw-option-type-map.php';
251
+ break;
252
+ case 'FW_Option_Type_Multi' :
253
+ require_once dirname( __FILE__ ) . '/includes/option-types/multi/class-fw-option-type-multi.php';
254
+ break;
255
+ case 'FW_Option_Type_Multi_Picker' :
256
+ require_once dirname( __FILE__ ) . '/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php';
257
+ break;
258
+ case 'FW_Option_Type_Multi_Select' :
259
+ require_once dirname( __FILE__ ) . '/includes/option-types/multi-select/class-fw-option-type-multi-select.php';
260
+ break;
261
+ case 'FW_Option_Type_Multi_Upload' :
262
+ require_once dirname( __FILE__ ) . '/includes/option-types/multi-upload/class-fw-option-type-multi-upload.php';
263
+ break;
264
+ case 'FW_Option_Type_Oembed' :
265
+ require_once dirname( __FILE__ ) . '/includes/option-types/oembed/class-fw-option-type-oembed.php';
266
+ break;
267
+ case 'FW_Option_Type_Popup' :
268
+ require_once dirname( __FILE__ ) . '/includes/option-types/popup/class-fw-option-type-popup.php';
269
+ break;
270
+ case 'FW_Option_Type_Radio_Text' :
271
+ require_once dirname( __FILE__ ) . '/includes/option-types/radio-text/class-fw-option-type-radio-text.php';
272
+ break;
273
+ case 'FW_Option_Type_Range_Slider' :
274
+ require_once dirname( __FILE__ ) . '/includes/option-types/range-slider/class-fw-option-type-range-slider.php';
275
+ break;
276
+ case 'FW_Option_Type_Rgba_Color_Picker' :
277
+ require_once dirname( __FILE__ ) . '/includes/option-types/rgba-color-picker/class-fw-option-type-rgba-color-picker.php';
278
+ break;
279
+ case 'FW_Option_Type_Slider' :
280
+ require_once dirname( __FILE__ ) . '/includes/option-types/slider/class-fw-option-type-slider.php';
281
+ break;
282
+ case 'FW_Option_Type_Slider_Short' :
283
+ require_once dirname( __FILE__ ) . '/includes/option-types/slider/class-fw-option-type-short-slider.php';
284
+ break;
285
+ case 'FW_Option_Type_Switch' :
286
+ require_once dirname( __FILE__ ) . '/includes/option-types/switch/class-fw-option-type-switch.php';
287
+ break;
288
+ case 'FW_Option_Type_Typography' :
289
+ require_once dirname( __FILE__ ) . '/includes/option-types/typography/class-fw-option-type-typography.php';
290
+ break;
291
+ case 'FW_Option_Type_Typography_v2' :
292
+ require_once dirname( __FILE__ ) . '/includes/option-types/typography-v2/class-fw-option-type-typography-v2.php';
293
+ break;
294
+ case 'FW_Option_Type_Upload' :
295
+ require_once dirname( __FILE__ ) . '/includes/option-types/upload/class-fw-option-type-upload.php';
296
+ break;
297
+ case 'FW_Option_Type_Wp_Editor' :
298
+ require_once dirname( __FILE__ ) . '/includes/option-types/wp-editor/class-fw-option-type-wp-editor.php';
299
+ break;
300
+ case 'FW_Icon_V2_Favorites_Manager' :
301
+ require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/includes/class-fw-icon-v2-favorites.php';
302
+ break;
303
+ case 'FW_Icon_V2_Packs_Loader' :
304
+ require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/includes/class-fw-icon-v2-packs-loader.php';
305
+ break;
306
+ }
307
  }
framework/bin/load-latest-fonts.php CHANGED
@@ -1,172 +1,172 @@
1
- #!/usr/bin/env php
2
-
3
- <?php
4
-
5
- /**
6
- * Download latest font packs from their sources.
7
- */
8
-
9
- function get_libs_dir() {
10
- return dirname(__FILE__) . '/../static/libs/';
11
- }
12
-
13
- $packs = array(
14
- array(
15
- 'name' => 'entypo',
16
- 'github-repo' => 'danielbruce/entypo',
17
- 'css-file' => 'font/entypo.css',
18
-
19
- 'fonts' => array(
20
- 'font/entypo.eot',
21
- 'font/entypo.svg',
22
- 'font/entypo.ttf',
23
- 'font/entypo.woff'
24
- ),
25
-
26
- 'replace' => array(
27
- array(
28
- 'from' => "icon-",
29
- 'to' => "entypo-"
30
- ),
31
-
32
- array(
33
- 'from' => "url('entypo",
34
- 'to' => "url('../fonts/entypo"
35
- ),
36
- )
37
- ),
38
-
39
- 'linearicons' => array(
40
- 'name' => 'lnr',
41
- 'css-file' => 'https://cdn.linearicons.com/free/1.0.0/icon-font.min.css',
42
- ),
43
-
44
- 'typicons' => array(
45
- 'name' => 'typcn',
46
- 'github-repo' => 'stephenhutchings/typicons.font',
47
-
48
- 'css-file' => 'src/font/typicons.css',
49
-
50
- 'fonts' => array(
51
- 'src/font/typicons.eot',
52
- 'src/font/typicons.svg',
53
- 'src/font/typicons.ttf',
54
- 'src/font/typicons.woff'
55
- ),
56
-
57
- 'replace' => array(
58
- array(
59
- 'from' => "url('typicons",
60
- 'to' => "url('../fonts/typicons"
61
- )
62
- )
63
- ),
64
-
65
- 'font-awesome' => array(
66
- 'name' => 'font-awesome',
67
- 'github-repo' => 'FortAwesome/Font-Awesome',
68
- 'css-file' => 'css/font-awesome.min.css',
69
- 'css-file-output' => 'font-awesome.min.css',
70
- 'fonts' => array(
71
- 'fonts/FontAwesome.otf',
72
- 'fonts/fontawesome-webfont.eot',
73
- 'fonts/fontawesome-webfont.svg',
74
- 'fonts/fontawesome-webfont.ttf',
75
- 'fonts/fontawesome-webfont.woff',
76
- 'fonts/fontawesome-webfont.woff2'
77
- )
78
- )
79
- );
80
-
81
- foreach ($packs as $pack) {
82
- create_pack_directory($pack);
83
-
84
- download_css($pack);
85
- download_fonts($pack);
86
- perform_replacements($pack);
87
- }
88
-
89
- function create_pack_directory($pack) {
90
- if (file_exists(get_libs_dir() . $pack['name'])) return;
91
-
92
- mkdir(get_libs_dir() . $pack['name']);
93
- mkdir(get_libs_dir() . $pack['name'] . '/css');
94
- mkdir(get_libs_dir() . $pack['name'] . '/fonts');
95
-
96
- echo 'install ' . $pack['name'] . "\n";
97
- }
98
-
99
- function download_css($pack) {
100
- if (! isset($pack['css-file'])) { return; }
101
-
102
- $url = github_or_network_for($pack['css-file'], $pack);
103
-
104
- $file_name = $pack['name'] . '.css';
105
-
106
- if (isset($pack['css-file-output'])) {
107
- $file_name = $pack['css-file-output'];
108
- }
109
-
110
- download_file(
111
- $url,
112
- // __DIR__ . '/static/css/' . $pack['name'] . '.css'
113
- get_libs_dir() . $pack['name'] . '/css/' . $file_name
114
- );
115
- }
116
-
117
- function download_fonts($pack) {
118
- if (! isset($pack['fonts'])) { return; }
119
-
120
- foreach ($pack['fonts'] as $font) {
121
- download_file(
122
- github_or_network_for($font, $pack),
123
- // __DIR__ . '/static/fonts/' . basename( $font )
124
- get_libs_dir() . $pack['name'] . '/fonts/' . basename( $font )
125
- );
126
- }
127
- }
128
-
129
- function perform_replacements($pack) {
130
- if (! isset($pack['replace'])) { return; }
131
-
132
- // $file_path = __DIR__ . '/static/css/' . $pack['name'] . '.css';
133
- $file_path = get_libs_dir() . $pack['name'] . '/css/' . $pack['name'] . '.css';
134
-
135
- $data = file_get_contents($file_path);
136
-
137
- foreach ($pack['replace'] as $recipe) {
138
- $data = str_replace(
139
- $recipe['from'],
140
- $recipe['to'],
141
- $data
142
- );
143
- }
144
-
145
- file_put_contents($file_path, $data);
146
- }
147
-
148
- function github_or_network_for($base_url, $pack) {
149
- if (! isset($pack['github-repo'])) { return $base_url; }
150
-
151
- return 'https://raw.githubusercontent.com/' .
152
- $pack['github-repo'] .
153
- '/master/' . $base_url;
154
- }
155
-
156
- function download_file($url, $destination) {
157
- echo 'downloading ' . $destination . "\n";
158
- $ch = curl_init();
159
- curl_setopt($ch, CURLOPT_URL, $url);
160
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
161
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
162
- curl_setopt( $ch, CURLOPT_ENCODING, "UTF-8" );
163
-
164
- $data = curl_exec ($ch);
165
- $error = curl_error($ch);
166
-
167
- curl_close ($ch);
168
-
169
- $file = fopen($destination, "w+");
170
- fputs($file, $data);
171
- fclose($file);
172
- }
1
+ #!/usr/bin/env php
2
+
3
+ <?php
4
+
5
+ /**
6
+ * Download latest font packs from their sources.
7
+ */
8
+
9
+ function get_libs_dir() {
10
+ return dirname(__FILE__) . '/../static/libs/';
11
+ }
12
+
13
+ $packs = array(
14
+ array(
15
+ 'name' => 'entypo',
16
+ 'github-repo' => 'danielbruce/entypo',
17
+ 'css-file' => 'font/entypo.css',
18
+
19
+ 'fonts' => array(
20
+ 'font/entypo.eot',
21
+ 'font/entypo.svg',
22
+ 'font/entypo.ttf',
23
+ 'font/entypo.woff'
24
+ ),
25
+
26
+ 'replace' => array(
27
+ array(
28
+ 'from' => "icon-",
29
+ 'to' => "entypo-"
30
+ ),
31
+
32
+ array(
33
+ 'from' => "url('entypo",
34
+ 'to' => "url('../fonts/entypo"
35
+ ),
36
+ )
37
+ ),
38
+
39
+ 'linearicons' => array(
40
+ 'name' => 'lnr',
41
+ 'css-file' => 'https://cdn.linearicons.com/free/1.0.0/icon-font.min.css',
42
+ ),
43
+
44
+ 'typicons' => array(
45
+ 'name' => 'typcn',
46
+ 'github-repo' => 'stephenhutchings/typicons.font',
47
+
48
+ 'css-file' => 'src/font/typicons.css',
49
+
50
+ 'fonts' => array(
51
+ 'src/font/typicons.eot',
52
+ 'src/font/typicons.svg',
53
+ 'src/font/typicons.ttf',
54
+ 'src/font/typicons.woff'
55
+ ),
56
+
57
+ 'replace' => array(
58
+ array(
59
+ 'from' => "url('typicons",
60
+ 'to' => "url('../fonts/typicons"
61
+ )
62
+ )
63
+ ),
64
+
65
+ 'font-awesome' => array(
66
+ 'name' => 'font-awesome',
67
+ 'github-repo' => 'FortAwesome/Font-Awesome',
68
+ 'css-file' => 'css/font-awesome.min.css',
69
+ 'css-file-output' => 'font-awesome.min.css',
70
+ 'fonts' => array(
71
+ 'fonts/FontAwesome.otf',
72
+ 'fonts/fontawesome-webfont.eot',
73
+ 'fonts/fontawesome-webfont.svg',
74
+ 'fonts/fontawesome-webfont.ttf',
75
+ 'fonts/fontawesome-webfont.woff',
76
+ 'fonts/fontawesome-webfont.woff2'
77
+ )
78
+ )
79
+ );
80
+
81
+ foreach ($packs as $pack) {
82
+ create_pack_directory($pack);
83
+
84
+ download_css($pack);
85
+ download_fonts($pack);
86
+ perform_replacements($pack);
87
+ }
88
+
89
+ function create_pack_directory($pack) {
90
+ if (file_exists(get_libs_dir() . $pack['name'])) return;
91
+
92
+ mkdir(get_libs_dir() . $pack['name']);
93
+ mkdir(get_libs_dir() . $pack['name'] . '/css');
94
+ mkdir(get_libs_dir() . $pack['name'] . '/fonts');
95
+
96
+ echo 'install ' . $pack['name'] . "\n";
97
+ }
98
+
99
+ function download_css($pack) {
100
+ if (! isset($pack['css-file'])) { return; }
101
+
102
+ $url = github_or_network_for($pack['css-file'], $pack);
103
+
104
+ $file_name = $pack['name'] . '.css';
105
+
106
+ if (isset($pack['css-file-output'])) {
107
+ $file_name = $pack['css-file-output'];
108
+ }
109
+
110
+ download_file(
111
+ $url,
112
+ // __DIR__ . '/static/css/' . $pack['name'] . '.css'
113
+ get_libs_dir() . $pack['name'] . '/css/' . $file_name
114
+ );
115
+ }
116
+
117
+ function download_fonts($pack) {
118
+ if (! isset($pack['fonts'])) { return; }
119
+
120
+ foreach ($pack['fonts'] as $font) {
121
+ download_file(
122
+ github_or_network_for($font, $pack),
123
+ // __DIR__ . '/static/fonts/' . basename( $font )
124
+ get_libs_dir() . $pack['name'] . '/fonts/' . basename( $font )
125
+ );
126
+ }
127
+ }
128
+
129
+ function perform_replacements($pack) {
130
+ if (! isset($pack['replace'])) { return; }
131
+
132
+ // $file_path = __DIR__ . '/static/css/' . $pack['name'] . '.css';
133
+ $file_path = get_libs_dir() . $pack['name'] . '/css/' . $pack['name'] . '.css';
134
+
135
+ $data = file_get_contents($file_path);
136
+
137
+ foreach ($pack['replace'] as $recipe) {
138
+ $data = str_replace(
139
+ $recipe['from'],
140
+ $recipe['to'],
141
+ $data
142
+ );
143
+ }
144
+
145
+ file_put_contents($file_path, $data);
146
+ }
147
+
148
+ function github_or_network_for($base_url, $pack) {
149
+ if (! isset($pack['github-repo'])) { return $base_url; }
150
+
151
+ return 'https://raw.githubusercontent.com/' .
152
+ $pack['github-repo'] .
153
+ '/master/' . $base_url;
154
+ }
155
+
156
+ function download_file($url, $destination) {
157
+ echo 'downloading ' . $destination . "\n";
158
+ $ch = curl_init();
159
+ curl_setopt($ch, CURLOPT_URL, $url);
160
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
161
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
162
+ curl_setopt( $ch, CURLOPT_ENCODING, "UTF-8" );
163
+
164
+ $data = curl_exec ($ch);
165
+ $error = curl_error($ch);
166
+
167
+ curl_close ($ch);
168
+
169
+ $file = fopen($destination, "w+");
170
+ fputs($file, $data);
171
+ fclose($file);
172
+ }
framework/bootstrap.php CHANGED
@@ -1,79 +1,79 @@
1
- <?php if (!defined('ABSPATH')) die('Forbidden');
2
-
3
- if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( $_SERVER['HTTP_HOST'] ) ) {
4
- $_SERVER['HTTP_HOST'] = 'unyson.io';
5
- $_SERVER['SERVER_NAME'] = 'unyson';
6
- $_SERVER['SERVER_PORT'] = '80';
7
- }
8
-
9
- if (defined('FW')) {
10
- /**
11
- * The framework is already loaded.
12
- */
13
- } else {
14
- define('FW', true);
15
-
16
- /**
17
- * Load the framework on 'after_setup_theme' action when the theme information is available
18
- * To prevent `undefined constant TEMPLATEPATH` errors when the framework is used as plugin
19
- */
20
- add_action('after_setup_theme', '_action_init_framework');
21
-
22
- function _action_init_framework() {
23
- if (did_action('fw_init')) {
24
- return;
25
- }
26
-
27
- do_action('fw_before_init');
28
-
29
- $dir = dirname(__FILE__);
30
-
31
- require $dir .'/autoload.php';
32
-
33
- // Load helper functions
34
- foreach (array('general', 'meta', 'fw-storage', 'database') as $file) {
35
- require $dir .'/helpers/'. $file .'.php';
36
- }
37
-
38
- // Load core
39
- {
40
- require $dir .'/core/Fw.php';
41
-
42
- fw();
43
- }
44
-
45
- require $dir .'/includes/hooks.php';
46
-
47
- /**
48
- * Init components
49
- */
50
- {
51
- $components = array(
52
- /**
53
- * Load the theme's hooks.php first, to give users the possibility to add_action()
54
- * for `extensions` and `backend` components actions that can happen while their initialization
55
- */
56
- 'theme',
57
- /**
58
- * Load extensions before backend, to give extensions the possibility to add_action()
59
- * for the `backend` component actions that can happen while its initialization
60
- */
61
- 'extensions',
62
- 'backend'
63
- );
64
-
65
- foreach ($components as $component) {
66
- fw()->{$component}->_init();
67
- }
68
-
69
- foreach ($components as $component) {
70
- fw()->{$component}->_after_components_init();
71
- }
72
- }
73
-
74
- /**
75
- * The framework is loaded
76
- */
77
- do_action('fw_init');
78
- }
79
- }
1
+ <?php if (!defined('ABSPATH')) die('Forbidden');
2
+
3
+ if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( $_SERVER['HTTP_HOST'] ) ) {
4
+ $_SERVER['HTTP_HOST'] = 'unyson.io';
5
+ $_SERVER['SERVER_NAME'] = 'unyson';
6
+ $_SERVER['SERVER_PORT'] = '80';
7
+ }
8
+
9
+ if (defined('FW')) {
10
+ /**
11
+ * The framework is already loaded.
12
+ */
13
+ } else {
14
+ define('FW', true);
15
+
16
+ /**
17
+ * Load the framework on 'after_setup_theme' action when the theme information is available
18
+ * To prevent `undefined constant TEMPLATEPATH` errors when the framework is used as plugin
19
+ */
20
+ add_action('after_setup_theme', '_action_init_framework');
21
+
22
+ function _action_init_framework() {
23
+ if (did_action('fw_init')) {
24
+ return;
25
+ }
26
+
27
+ do_action('fw_before_init');
28
+
29
+ $dir = dirname(__FILE__);
30
+
31
+ require $dir .'/autoload.php';
32
+
33
+ // Load helper functions
34
+ foreach (array('general', 'meta', 'fw-storage', 'database') as $file) {
35
+ require $dir .'/helpers/'. $file .'.php';
36
+ }
37
+
38
+ // Load core
39
+ {
40
+ require $dir .'/core/Fw.php';
41
+
42
+ fw();
43
+ }
44
+
45
+ require $dir .'/includes/hooks.php';
46
+
47
+ /**
48
+ * Init components
49
+ */
50
+ {
51
+ $components = array(
52
+ /**
53
+ * Load the theme's hooks.php first, to give users the possibility to add_action()
54
+ * for `extensions` and `backend` components actions that can happen while their initialization
55
+ */
56
+ 'theme',
57
+ /**
58
+ * Load extensions before backend, to give extensions the possibility to add_action()
59
+ * for the `backend` component actions that can happen while its initialization
60
+ */
61
+ 'extensions',
62
+ 'backend'
63
+ );
64
+
65
+ foreach ($components as $component) {
66
+ fw()->{$component}->_init();
67
+ }
68
+
69
+ foreach ($components as $component) {
70
+ fw()->{$component}->_after_components_init();
71
+ }
72
+ }
73
+
74
+ /**
75
+ * The framework is loaded
76
+ */
77
+ do_action('fw_init');
78
+ }
79
+ }
framework/core/Fw.php CHANGED
@@ -1,79 +1,79 @@
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 .'/manifest.php';
38
- /** @var array $manifest */
39
-
40
- $this->manifest = new FW_Framework_Manifest($manifest);
41
-
42
- add_action('fw_init', array($this, '_check_requirements'), 1);
43
- }
44
-
45
- // components
46
- {
47
- $this->extensions = new _FW_Component_Extensions();
48
- $this->backend = new _FW_Component_Backend();
49
- $this->theme = new _FW_Component_Theme();
50
- }
51
- }
52
-
53
- /**
54
- * @internal
55
- */
56
- public function _check_requirements()
57
- {
58
- if (is_admin() && !$this->manifest->check_requirements()) {
59
- FW_Flash_Messages::add(
60
- 'fw_requirements',
61
- __('Framework requirements not met:', 'fw') .' '. $this->manifest->get_not_met_requirement_text(),
62
- 'warning'
63
- );
64
- }
65
- }
66
- }
67
-
68
- /**
69
- * @return _FW Framework instance
70
- */
71
- function fw() {
72
- static $FW = null; // cache
73
-
74
- if ($FW === null) {
75
- $FW = new _Fw();
76
- }
77
-
78
- return $FW;
79
- }
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 .'/manifest.php';
38
+ /** @var array $manifest */
39
+
40
+ $this->manifest = new FW_Framework_Manifest($manifest);
41
+
42
+ add_action('fw_init', array($this, '_check_requirements'), 1);
43
+ }
44
+
45
+ // components
46
+ {
47
+ $this->extensions = new _FW_Component_Extensions();
48
+ $this->backend = new _FW_Component_Backend();
49
+ $this->theme = new _FW_Component_Theme();
50
+ }
51
+ }
52
+
53
+ /**
54
+ * @internal
55
+ */
56
+ public function _check_requirements()
57
+ {
58
+ if (is_admin() && !$this->manifest->check_requirements()) {
59
+ FW_Flash_Messages::add(
60
+ 'fw_requirements',
61
+ __('Framework requirements not met:', 'fw') .' '. $this->manifest->get_not_met_requirement_text(),
62
+ 'warning'
63
+ );
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * @return _FW Framework instance
70
+ */
71
+ function fw() {
72
+ static $FW = null; // cache
73
+
74
+ if ($FW === null) {
75
+ $FW = new _Fw();
76
+ }
77
+
78
+ return $FW;
79
+ }
framework/core/class-fw-manifest.php CHANGED
@@ -1,565 +1,572 @@
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 'php':
127
- if ( ! function_exists( 'phpversion' ) ) {
128
- break;
129
- }
130
- if (
131
- isset($requirements['min_version'])
132
- &&
133
- version_compare(phpversion(), $requirements['min_version'], '<')
134
- ) {
135
- $this->not_met_requirement = array(
136
- 'requirement' => $requirement,
137
- 'requirements' => $requirements
138
- );
139
- $this->not_met_is_final = true;
140
- break 2;
141
- }
142
-
143
- if (
144
- isset($requirements['max_version'])
145
- &&
146
- version_compare(phpversion(), $requirements['max_version'], '>')
147
- ) {
148
- $this->not_met_requirement = array(
149
- 'requirement' => $requirement,
150
- 'requirements' => $requirements
151
- );
152
- $this->not_met_is_final = true;
153
- break 2;
154
- }
155
-
156
- // met
157
- unset($this->requirements_for_verification[$requirement]);
158
- break;
159
- case 'wordpress':
160
- if (
161
- isset($requirements['min_version'])
162
- &&
163
- version_compare($wp_version, $requirements['min_version'], '<')
164
- ) {
165
- $this->not_met_requirement = array(
166
- 'requirement' => $requirement,
167
- 'requirements' => $requirements
168
- );
169
- $this->not_met_is_final = true;
170
- break 2;
171
- }
172
-
173
- if (
174
- isset($requirements['max_version'])
175
- &&
176
- version_compare($wp_version, $requirements['max_version'], '>')
177
- ) {
178
- $this->not_met_requirement = array(
179
- 'requirement' => $requirement,
180
- 'requirements' => $requirements
181
- );
182
- $this->not_met_is_final = true;
183
- break 2;
184
- }
185
-
186
- // met
187
- unset($this->requirements_for_verification[$requirement]);
188
- break;
189
- case 'framework':
190
- if (
191
- isset($requirements['min_version'])
192
- &&
193
- version_compare(fw()->manifest->get_version(), $requirements['min_version'], '<')
194
- ) {
195
- $this->not_met_requirement = array(
196
- 'requirement' => $requirement,
197
- 'requirements' => $requirements
198
- );
199
- $this->not_met_is_final = true;
200
- break 2;
201
- }
202
-
203
- if (
204
- isset($requirements['max_version'])
205
- &&
206
- version_compare(fw()->manifest->get_version(), $requirements['max_version'], '>')
207
- ) {
208
- $this->not_met_requirement = array(
209
- 'requirement' => $requirement,
210
- 'requirements' => $requirements
211
- );
212
- $this->not_met_is_final = true;
213
- break 2;
214
- }
215
-
216
- // met
217
- unset($this->requirements_for_verification[$requirement]);
218
- break;
219
- case 'extensions':
220
- $extensions =& $requirements;
221
-
222
- foreach ($extensions as $extension => $extension_requirements) {
223
- $extension_instance = fw()->extensions->get($extension);
224
-
225
- if (!$extension_instance) {
226
- /**
227
- * extension in requirements does not exists
228
- * maybe try call this method later and maybe will exist, or it really does not exists
229
- */
230
- $this->not_met_requirement = array(
231
- 'requirement' => $requirement,
232
- 'extension' => $extension,
233
- 'requirements' => $extension_requirements
234
- );
235
- break 3;
236
- }
237
-
238
- if (
239
- isset($extension_requirements['min_version'])
240
- &&
241
- version_compare($extension_instance->manifest->get_version(), $extension_requirements['min_version'], '<')
242
- ) {
243
- $this->not_met_requirement = array(
244
- 'requirement' => $requirement,
245
- 'extension' => $extension,
246
- 'requirements' => $extension_requirements
247
- );
248
- $this->not_met_is_final = true;
249
- break 3;
250
- }
251
-
252
- if (
253
- isset($extension_requirements['max_version'])
254
- &&
255
- version_compare($extension_instance->manifest->get_version(), $extension_requirements['max_version'], '>')
256
- ) {
257
- $this->not_met_requirement = array(
258
- 'requirement' => $requirement,
259
- 'extension' => $extension,
260
- 'requirements' => $extension_requirements
261
- );
262
- $this->not_met_is_final = true;
263
- break 3;
264
- }
265
-
266
- // met
267
- unset($this->requirements_for_verification[$requirement][$extension]);
268
- }
269
-
270
- if (empty($this->requirements_for_verification[$requirement])) {
271
- // all extensions requirements met
272
- unset($this->requirements_for_verification[$requirement]);
273
- }
274
- break;
275
- }
276
- }
277
-
278
- return $this->requirements_met();
279
- }
280
-
281
- public function get_version()
282
- {
283
- return $this->manifest['version'];
284
- }
285
-
286
- public function get_name()
287
- {
288
- return $this->manifest['name'];
289
- }
290
-
291
- /**
292
- * @param string $multi_key
293
- * @param mixed $default_value
294
- * @return mixed
295
- */
296
- public function get($multi_key, $default_value = null)
297
- {
298
- return fw_akg($multi_key, $this->manifest, $default_value);
299
- }
300
-
301
- /**
302
- * Call this only after check_requirements() failed
303
- * @return array
304
- */
305
- public function get_not_met_requirement()
306
- {
307
- return $this->not_met_requirement;
308
- }
309
-
310
- /**
311
- * Return user friendly requirement as text
312
- * Call this only after check_requirements() failed
313
- * @return string
314
- */
315
- public function get_not_met_requirement_text()
316
- {
317
- if (!$this->not_met_requirement) {
318
- return '';
319
- }
320
-
321
- $requirement = array();
322
-
323
- foreach ($this->not_met_requirement['requirements'] as $req_key => $req) {
324
- switch ($req_key) {
325
- case 'min_version':
326
- $requirement[] = __('minimum required version is', 'fw') .' '. $req;
327
- break;
328
- case 'max_version':
329
- $requirement[] = __('maximum required version is', 'fw') .' '. $req;
330
- break;
331
- }
332
- }
333
-
334
- $requirement = implode(' '. __('and', 'fw') .' ', $requirement);
335
-
336
- switch ($this->not_met_requirement['requirement']) {
337
- case 'php':
338
- if ( ! function_exists( 'phpversion' ) ) {
339
- break;
340
- }
341
-
342
- $requirement = sprintf(
343
- __('Current PHP version is %s, %s', 'fw'),
344
- phpversion(), $requirement
345
- );
346
- break;
347
- case 'wordpress':
348
- global $wp_version;
349
-
350
- $requirement = sprintf(
351
- __('Current WordPress version is %s, %s', 'fw'),
352
- $wp_version, $requirement
353
- );
354
- break;
355
- case 'framework':
356
- $requirement = sprintf(
357
- __('Current Framework version is %s, %s', 'fw'),
358
- fw()->manifest->get_version(), $requirement
359
- );
360
- break;
361
- case 'extensions':
362
- $extension = fw()->extensions->get($this->not_met_requirement['extension']);
363
-
364
- if ($extension) {
365
- $requirement = sprintf(
366
- __('Current version of the %s extension is %s, %s', 'fw'),
367
- $extension->manifest->get_name(), $extension->manifest->get_version(), $requirement
368
- );
369
- } else {
370
- if (empty($requirement)) {
371
- $requirement = sprintf(
372
- __('%s extension is required', 'fw'),
373
- ucfirst($this->not_met_requirement['extension'])
374
- );
375
- } else {
376
- $requirement = sprintf(
377
- __('%s extension is required (%s)', 'fw'),
378
- ucfirst($this->not_met_requirement['extension']), $requirement
379
- );
380
- }
381
- }
382
- break;
383
- default:
384
- $requirement = 'Unknown requirement "'. $this->not_met_requirement['requirement'] .'"';
385
- }
386
-
387
- return $requirement;
388
- }
389
- }
390
-
391
- class FW_Framework_Manifest extends FW_Manifest
392
- {
393
- public function __construct(array $manifest)
394
- {
395
- if (empty($manifest['name'])) {
396
- $manifest['name'] = __('Framework', 'fw');
397
- }
398
-
399
- parent::__construct($manifest);
400
- }
401
-
402
- protected function get_default_requirements()
403
- {
404
- return array(
405
- 'php' => array(
406
- 'min_version' => '5.2.4',
407
- /*'max_version' => '10000.0.0',*/
408
- ),
409
- 'wordpress' => array(
410
- 'min_version' => '4.0',
411
- /*'max_version' => '10000.0.0',*/
412
- ),
413
- );
414
- }
415
- }
416
-
417
- class FW_Theme_Manifest extends FW_Manifest
418
- {
419
- public function __construct(array $manifest)
420
- {
421
- $manifest_defaults = array(
422
- /**
423
- * You can use this in a wp_option id,
424
- * so that option value will be different on a theme with different id.
425
- *
426
- * fixme: default value should be get_option( 'stylesheet' ) but it can't be changed now
427
- * because there can be themes that has saved Theme Settings in wp_option: 'fw_theme_settings_options:default'
428
- * changing this default value will result in Theme Settings options "reset".
429
- */
430
- 'id' => 'default',
431
- 'supported_extensions' => array(
432
- /*
433
- 'extension_name' => array(),
434
- */
435
- ),
436
- );
437
-
438
- $theme = wp_get_theme();
439
-
440
- foreach(array(
441
- 'name' => 'Name',
442
- 'uri' => 'ThemeURI',
443
- 'description' => 'Description',
444
- 'version' => 'Version',
445
- 'author' => 'Author',
446
- 'author_uri' => 'AuthorURI',
447
- ) as $manifest_key => $stylesheet_header) {
448
- $header_value = trim($theme->get($stylesheet_header));
449
-
450
- if ( is_child_theme() && $theme->parent() ) {
451
- switch ($manifest_key) {
452
- case 'version':
453
- case 'uri':
454
- case 'author':
455
- case 'author_uri':
456
- case 'license':
457
- // force parent theme value
458
- $header_value = $theme->parent()->get($stylesheet_header);
459
- break;
460
- default:
461
- if (!$header_value) {
462
- // use parent theme value only if child theme value is empty
463
- $header_value = $theme->parent()->get($stylesheet_header);
464
- }
465
- }
466
- }
467
-
468
- if ($header_value) {
469
- $manifest_defaults[$manifest_key] = $header_value;
470
- }
471
- }
472
-
473
- parent::__construct(array_merge($manifest_defaults, $manifest));
474
- }
475
-
476
- protected function get_default_requirements()
477
- {
478
- return array(
479
- 'php' => array(
480
- 'min_version' => '5.2.4',
481
- /*'max_version' => '10000.0.0',*/
482
- ),
483
- 'wordpress' => array(
484
- 'min_version' => '4.0',
485
- /*'max_version' => '10000.0.0',*/
486
- ),
487
- 'framework' => array(
488
- /*'min_version' => '0.0.0',
489
- 'max_version' => '1000.0.0'*/
490
- ),
491
- 'extensions' => array(
492
- /*'extension_name' => array(
493
- 'min_version' => '0.0.0',
494
- 'max_version' => '1000.0.0'
495
- )*/
496
- )
497
- );
498
- }
499
-
500
- public function get_id()
501
- {
502
- return $this->manifest['id'];
503
- }
504
- }
505
-
506
- class FW_Extension_Manifest extends FW_Manifest
507
- {
508
- public function __construct(array $manifest)
509
- {
510
- parent::__construct($manifest);
511
-
512
- unset($manifest);
513
-
514
- // unset unnecessary keys
515
- unset($this->manifest['id']);
516
-
517
- $this->manifest = array_merge(array(
518
- /**
519
- * @type bool Display on the Extensions page or it's a hidden extension
520
- */
521
- 'display' => false,
522
- /**
523
- * @type bool If extension can exist alone
524
- * false - There is no sense for it to exist alone, it exists only when is required by some other extension.
525
- * true - Can exist alone without bothering about other extensions.
526
- */
527
- 'standalone' => false,
528
- /**
529
- * @type string Thumbnail used on the Extensions page
530
- * All framework extensions has thumbnails set in the available extensions list
531
- * but if your extension is not in that list and id located in the theme, you can set the thumbnail via this parameter
532
- */
533
- 'thumbnail' => null,
534
- ), $this->manifest);
535
- }
536
-
537
- protected function get_default_requirements()
538
- {
539
- return array(
540
- 'php' => array(
541
- 'min_version' => '5.2.4',
542
- /*'max_version' => '10000.0.0',*/
543
- ),
544
- 'wordpress' => array(
545
- 'min_version' => '4.0',
546
- /*'max_version' => '10000.0.0',*/
547
- ),
548
- 'framework' => array(
549
- /*'min_version' => '0.0.0',
550
- 'max_version' => '1000.0.0'*/
551
- ),
552
- 'extensions' => array(
553
- /*'extension_name' => array(
554
- 'min_version' => '0.0.0',
555
- 'max_version' => '1000.0.0'
556
- )*/
557
- )
558
- );
559
- }
560
-
561
- public function get_required_extensions()
562
- {
563
- return $this->manifest['requirements']['extensions'];
564
- }
 
 
 
 
 
 
 
565
  }
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 'php':
127
+ if ( ! function_exists( 'phpversion' ) ) {
128
+ break;
129
+ }
130
+ if (
131
+ isset($requirements['min_version'])
132
+ &&
133
+ version_compare(phpversion(), $requirements['min_version'], '<')
134
+ ) {
135
+ $this->not_met_requirement = array(
136
+ 'requirement' => $requirement,
137
+ 'requirements' => $requirements
138
+ );
139
+ $this->not_met_is_final = true;
140
+ break 2;
141
+ }
142
+
143
+ if (
144
+ isset($requirements['max_version'])
145
+ &&
146
+ version_compare(phpversion(), $requirements['max_version'], '>')
147
+ ) {
148
+ $this->not_met_requirement = array(
149
+ 'requirement' => $requirement,
150
+ 'requirements' => $requirements
151
+ );
152
+ $this->not_met_is_final = true;
153
+ break 2;
154
+ }
155
+
156
+ // met
157
+ unset($this->requirements_for_verification[$requirement]);
158
+ break;
159
+ case 'wordpress':
160
+ if (
161
+ isset($requirements['min_version'])
162
+ &&
163
+ version_compare($wp_version, $requirements['min_version'], '<')
164
+ ) {
165
+ $this->not_met_requirement = array(
166
+ 'requirement' => $requirement,
167
+ 'requirements' => $requirements
168
+ );
169
+ $this->not_met_is_final = true;
170
+ break 2;
171
+ }
172
+
173
+ if (
174
+ isset($requirements['max_version'])
175
+ &&
176
+ version_compare($wp_version, $requirements['max_version'], '>')
177
+ ) {
178
+ $this->not_met_requirement = array(
179
+ 'requirement' => $requirement,
180
+ 'requirements' => $requirements
181
+ );
182
+ $this->not_met_is_final = true;
183
+ break 2;
184
+ }
185
+
186
+ // met
187
+ unset($this->requirements_for_verification[$requirement]);
188
+ break;
189
+ case 'framework':
190
+ if (
191
+ isset($requirements['min_version'])
192
+ &&
193
+ version_compare(fw()->manifest->get_version(), $requirements['min_version'], '<')
194
+ ) {
195
+ $this->not_met_requirement = array(
196
+ 'requirement' => $requirement,
197
+ 'requirements' => $requirements
198
+ );
199
+ $this->not_met_is_final = true;
200
+ break 2;
201
+ }
202
+
203
+ if (
204
+ isset($requirements['max_version'])
205
+ &&
206
+ version_compare(fw()->manifest->get_version(), $requirements['max_version'], '>')
207
+ ) {
208
+ $this->not_met_requirement = array(
209
+ 'requirement' => $requirement,
210
+ 'requirements' => $requirements
211
+ );
212
+ $this->not_met_is_final = true;
213
+ break 2;
214
+ }
215
+
216
+ // met
217
+ unset($this->requirements_for_verification[$requirement]);
218
+ break;
219
+ case 'extensions':
220
+ $extensions =& $requirements;
221
+
222
+ foreach ($extensions as $extension => $extension_requirements) {
223
+ $extension_instance = fw()->extensions->get($extension);
224
+
225
+ if (!$extension_instance) {
226
+ /**
227
+ * extension in requirements does not exists
228
+ * maybe try call this method later and maybe will exist, or it really does not exists
229
+ */
230
+ $this->not_met_requirement = array(
231
+ 'requirement' => $requirement,
232
+ 'extension' => $extension,
233
+ 'requirements' => $extension_requirements
234
+ );
235
+ break 3;
236
+ }
237
+
238
+ if (
239
+ isset($extension_requirements['min_version'])
240
+ &&
241
+ version_compare($extension_instance->manifest->get_version(), $extension_requirements['min_version'], '<')
242
+ ) {
243
+ $this->not_met_requirement = array(
244
+ 'requirement' => $requirement,
245
+ 'extension' => $extension,
246
+ 'requirements' => $extension_requirements
247
+ );
248
+ $this->not_met_is_final = true;
249
+ break 3;
250
+ }
251
+
252
+ if (
253
+ isset($extension_requirements['max_version'])
254
+ &&
255
+ version_compare($extension_instance->manifest->get_version(), $extension_requirements['max_version'], '>')
256
+ ) {
257
+ $this->not_met_requirement = array(
258
+ 'requirement' => $requirement,
259
+ 'extension' => $extension,
260
+ 'requirements' => $extension_requirements
261
+ );
262
+ $this->not_met_is_final = true;
263
+ break 3;
264
+ }
265
+
266
+ // met
267
+ unset($this->requirements_for_verification[$requirement][$extension]);
268
+ }
269
+
270
+ if (empty($this->requirements_for_verification[$requirement])) {
271
+ // all extensions requirements met
272
+ unset($this->requirements_for_verification[$requirement]);
273
+ }
274
+ break;
275
+ }
276
+ }
277
+
278
+ return $this->requirements_met();
279
+ }
280
+
281
+ public function get_version()
282
+ {
283
+ return $this->manifest['version'];
284
+ }
285
+
286
+ public function get_name()
287
+ {
288
+ return $this->manifest['name'];
289
+ }
290
+
291
+ /**
292
+ * @param string $multi_key
293
+ * @param mixed $default_value
294
+ * @return mixed
295
+ */
296
+ public function get( $multi_key, $default_value = null ) {
297
+ return fw_akg( $multi_key, $this->manifest, $default_value );
298
+ }
299
+
300
+ /**
301
+ * Get entire manifest.
302
+ * @return array
303
+ */
304
+ public function get_manifest() {
305
+ return $this->manifest;
306
+ }
307
+
308
+ /**
309
+ * Call this only after check_requirements() failed
310
+ * @return array
311
+ */
312
+ public function get_not_met_requirement()
313
+ {
314
+ return $this->not_met_requirement;
315
+ }
316
+
317
+ /**
318
+ * Return user friendly requirement as text
319
+ * Call this only after check_requirements() failed
320
+ * @return string
321
+ */
322
+ public function get_not_met_requirement_text()
323
+ {
324
+ if (!$this->not_met_requirement) {
325
+ return '';
326
+ }
327
+
328
+ $requirement = array();
329
+
330
+ foreach ($this->not_met_requirement['requirements'] as $req_key => $req) {
331
+ switch ($req_key) {
332
+ case 'min_version':
333
+ $requirement[] = __('minimum required version is', 'fw') .' '. $req;
334
+ break;
335
+ case 'max_version':
336
+ $requirement[] = __('maximum required version is', 'fw') .' '. $req;
337
+ break;
338
+ }
339
+ }
340
+
341
+ $requirement = implode(' '. __('and', 'fw') .' ', $requirement);
342
+
343
+ switch ($this->not_met_requirement['requirement']) {
344
+ case 'php':
345
+ if ( ! function_exists( 'phpversion' ) ) {
346
+ break;
347
+ }
348
+
349
+ $requirement = sprintf(
350
+ __('Current PHP version is %s, %s', 'fw'),
351
+ phpversion(), $requirement
352
+ );
353
+ break;
354
+ case 'wordpress':
355
+ global $wp_version;
356
+
357
+ $requirement = sprintf(
358
+ __('Current WordPress version is %s, %s', 'fw'),
359
+ $wp_version, $requirement
360
+ );
361
+ break;
362
+ case 'framework':
363
+ $requirement = sprintf(
364
+ __('Current Framework version is %s, %s', 'fw'),
365
+ fw()->manifest->get_version(), $requirement
366
+ );
367
+ break;
368
+ case 'extensions':
369
+ $extension = fw()->extensions->get($this->not_met_requirement['extension']);
370
+
371
+ if ($extension) {
372
+ $requirement = sprintf(
373
+ __('Current version of the %s extension is %s, %s', 'fw'),
374
+ $extension->manifest->get_name(), $extension->manifest->get_version(), $requirement
375
+ );
376
+ } else {
377
+ if (empty($requirement)) {
378
+ $requirement = sprintf(
379
+ __('%s extension is required', 'fw'),
380
+ ucfirst($this->not_met_requirement['extension'])
381
+ );
382
+ } else {
383
+ $requirement = sprintf(
384
+ __('%s extension is required (%s)', 'fw'),
385
+ ucfirst($this->not_met_requirement['extension']), $requirement
386
+ );
387
+ }
388
+ }
389
+ break;
390
+ default:
391
+ $requirement = 'Unknown requirement "'. $this->not_met_requirement['requirement'] .'"';
392
+ }
393
+
394
+ return $requirement;
395
+ }
396
+ }
397
+
398
+ class FW_Framework_Manifest extends FW_Manifest
399
+ {
400
+ public function __construct(array $manifest)
401
+ {
402
+ if (empty($manifest['name'])) {
403
+ $manifest['name'] = __('Framework', 'fw');
404
+ }
405
+
406
+ parent::__construct($manifest);
407
+ }
408
+
409
+ protected function get_default_requirements()
410
+ {
411
+ return array(
412
+ 'php' => array(
413
+ 'min_version' => '5.2.4',
414
+ /*'max_version' => '10000.0.0',*/
415
+ ),
416
+ 'wordpress' => array(
417
+ 'min_version' => '4.0',
418
+ /*'max_version' => '10000.0.0',*/
419
+ ),
420
+ );
421
+ }
422
+ }
423
+
424
+ class FW_Theme_Manifest extends FW_Manifest
425
+ {
426
+ public function __construct(array $manifest)
427
+ {
428
+ $manifest_defaults = array(
429
+ /**
430
+ * You can use this in a wp_option id,
431
+ * so that option value will be different on a theme with different id.
432
+ *
433
+ * fixme: default value should be get_option( 'stylesheet' ) but it can't be changed now
434
+ * because there can be themes that has saved Theme Settings in wp_option: 'fw_theme_settings_options:default'
435
+ * changing this default value will result in Theme Settings options "reset".
436
+ */
437
+ 'id' => 'default',
438
+ 'supported_extensions' => array(
439
+ /*
440
+ 'extension_name' => array(),
441
+ */
442
+ ),
443
+ );
444
+
445
+ $theme = wp_get_theme();
446
+
447
+ foreach(array(
448
+ 'name' => 'Name',
449
+ 'uri' => 'ThemeURI',
450
+ 'description' => 'Description',
451
+ 'version' => 'Version',
452
+ 'author' => 'Author',
453
+ 'author_uri' => 'AuthorURI',
454
+ ) as $manifest_key => $stylesheet_header) {
455
+ $header_value = trim($theme->get($stylesheet_header));
456
+
457
+ if ( is_child_theme() && $theme->parent() ) {
458
+ switch ($manifest_key) {
459
+ case 'version':
460
+ case 'uri':
461
+ case 'author':
462
+ case 'author_uri':
463
+ case 'license':
464
+ // force parent theme value
465
+ $header_value = $theme->parent()->get($stylesheet_header);
466
+ break;
467
+ default:
468
+ if (!$header_value) {
469
+ // use parent theme value only if child theme value is empty
470
+ $header_value = $theme->parent()->get($stylesheet_header);
471
+ }
472
+ }
473
+ }
474
+
475
+ if ($header_value) {
476
+ $manifest_defaults[$manifest_key] = $header_value;
477
+ }
478
+ }
479
+
480
+ parent::__construct(array_merge($manifest_defaults, $manifest));
481
+ }
482
+
483
+ protected function get_default_requirements()
484
+ {
485
+ return array(
486
+ 'php' => array(
487
+ 'min_version' => '5.2.4',
488
+ /*'max_version' => '10000.0.0',*/
489
+ ),
490
+ 'wordpress' => array(
491
+ 'min_version' => '4.0',
492
+ /*'max_version' => '10000.0.0',*/
493
+ ),
494
+ 'framework' => array(
495
+ /*'min_version' => '0.0.0',
496
+ 'max_version' => '1000.0.0'*/
497
+ ),
498
+ 'extensions' => array(
499
+ /*'extension_name' => array(
500
+ 'min_version' => '0.0.0',
501
+ 'max_version' => '1000.0.0'
502
+ )*/
503
+ )
504
+ );
505
+ }
506
+
507
+ public function get_id()
508
+ {
509
+ return $this->manifest['id'];
510
+ }
511
+ }
512
+
513
+ class FW_Extension_Manifest extends FW_Manifest
514
+ {
515
+ public function __construct(array $manifest)
516
+ {
517
+ parent::__construct($manifest);
518
+
519
+ unset($manifest);
520
+
521
+ // unset unnecessary keys
522
+ unset($this->manifest['id']);
523
+
524
+ $this->manifest = array_merge(array(
525
+ /**
526
+ * @type bool Display on the Extensions page or it's a hidden extension
527
+ */
528
+ 'display' => false,
529
+ /**
530
+ * @type bool If extension can exist alone
531
+ * false - There is no sense for it to exist alone, it exists only when is required by some other extension.
532
+ * true - Can exist alone without bothering about other extensions.
533
+ */
534
+ 'standalone' => false,
535
+ /**
536
+ * @type string Thumbnail used on the Extensions page
537
+ * All framework extensions has thumbnails set in the available extensions list
538
+ * but if your extension is not in that list and id located in the theme, you can set the thumbnail via this parameter
539
+ */
540
+ 'thumbnail' => null,
541
+ ), $this->manifest);
542
+ }
543
+
544
+ protected function get_default_requirements()
545
+ {
546
+ return array(
547
+ 'php' => array(
548
+ 'min_version' => '5.2.4',
549
+ /*'max_version' => '10000.0.0',*/
550
+ ),
551
+ 'wordpress' => array(
552
+ 'min_version' => '4.0',
553
+ /*'max_version' => '10000.0.0',*/
554
+ ),
555
+ 'framework' => array(
556
+ /*'min_version' => '0.0.0',
557
+ 'max_version' => '1000.0.0'*/
558
+ ),
559
+ 'extensions' => array(
560
+ /*'extension_name' => array(
561
+ 'min_version' => '0.0.0',
562
+ 'max_version' => '1000.0.0'
563
+ )*/
564
+ )
565
+ );
566
+ }
567
+
568
+ public function get_required_extensions()
569
+ {
570
+ return $this->manifest['requirements']['extensions'];
571
+ }
572
  }
framework/core/components/backend.php CHANGED
@@ -1,2054 +1,2062 @@
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_Settings_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
- * The singleton instance of Parsedown class that is used across
22
- * whole framework.
23
- *
24
- * @since 2.6.9
25
- */
26
- private $markdown_parser = null;
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
- /**
73
- * @return string
74
- * @since 2.6.3
75
- */
76
- public function get_options_name_attr_prefix() {
77
- return 'fw_options';
78
- }
79
-
80
- /**
81
- * @return string
82
- * @since 2.6.3
83
- */
84
- public function get_options_id_attr_prefix() {
85
- return 'fw-option-';
86
- }
87
-
88
- private function get_current_edit_taxonomy() {
89
- static $cache_current_taxonomy_data = null;
90
-
91
- if ( $cache_current_taxonomy_data !== null ) {
92
- return $cache_current_taxonomy_data;
93
- }
94
-
95
- $result = array(
96
- 'taxonomy' => null,
97
- 'term_id' => 0,
98
- );
99
-
100
- do {
101
- if ( ! is_admin() ) {
102
- break;
103
- }
104
-
105
- // code from /wp-admin/admin.php line 110
106
- {
107
- if ( isset( $_REQUEST['taxonomy'] ) && taxonomy_exists( $_REQUEST['taxonomy'] ) ) {
108
- $taxnow = $_REQUEST['taxonomy'];
109
- } else {
110
- $taxnow = '';
111
- }
112
- }
113
-
114
- if ( empty( $taxnow ) ) {
115
- break;
116
- }
117
-
118
- $result['taxonomy'] = $taxnow;
119
-
120
- if ( empty( $_REQUEST['tag_ID'] ) ) {
121
- return $result;
122
- }
123
-
124
- // code from /wp-admin/edit-tags.php
125
- {
126
- $tag_ID = (int) $_REQUEST['tag_ID'];
127
- }
128
-
129
- $result['term_id'] = $tag_ID;
130
- } while ( false );
131
-
132
- $cache_current_taxonomy_data = $result;
133
-
134
- return $cache_current_taxonomy_data;
135
- }
136
-
137
- public function __construct() {
138
- $this->print_meta_box_content_callback = create_function( '$post,$args', 'echo $args["args"];' );
139
- }
140
-
141
- /**
142
- * @internal
143
- */
144
- public function _init() {
145
- if ( is_admin() ) {
146
- $this->settings_form = new FW_Settings_Form_Theme('theme-settings');
147
- }
148
-
149
- $this->add_actions();
150
- $this->add_filters();
151
- }
152
-
153
- /**
154
- * @internal
155
- */
156
- public function _after_components_init() {}
157
-
158
- private function get_access_key()
159
- {
160
- if (!$this->access_key) {
161
- $this->access_key = new FW_Access_Key('fw_backend');
162
- }
163
-
164
- return $this->access_key;
165
- }
166
-
167
- private function add_actions() {
168
- if ( is_admin() ) {
169
- add_action('add_meta_boxes', array($this, '_action_create_post_meta_boxes'), 10, 2);
170
- add_action('init', array($this, '_action_init'), 20);
171
- add_action('admin_enqueue_scripts', array($this, '_action_admin_register_scripts'),
172
- /**
173
- * Usually when someone register/enqueue a script/style to be used in other places
174
- * in 'admin_enqueue_scripts' actions with default (not set) priority 10, they use priority 9.
175
- * Use here priority 8, in case those scripts/styles used in actions with priority 9
176
- * are using scripts/styles registered here
177
- */
178
- 8
179
- );
180
- add_action('admin_enqueue_scripts', array($this, '_action_admin_enqueue_scripts'),
181
- /**
182
- * In case some custom defined option types are using script/styles registered
183
- * in actions with default priority 10 (make sure the enqueue is executed after register)
184
- */
185
- 11
186
- );
187
-
188
- // render and submit options from javascript
189
- {
190
- add_action('wp_ajax_fw_backend_options_render', array($this, '_action_ajax_options_render'));
191
- add_action('wp_ajax_fw_backend_options_get_values', array($this, '_action_ajax_options_get_values'));
192
- add_action('wp_ajax_fw_backend_options_get_values_json', array($this, '_action_ajax_options_get_values_json'));
193
- }
194
- }
195
-
196
- add_action('save_post', array($this, '_action_save_post'), 7, 3);
197
- add_action('wp_restore_post_revision', array($this, '_action_restore_post_revision'), 10, 2);
198
- add_action('_wp_put_post_revision', array($this, '_action__wp_put_post_revision'));
199
-
200
- add_action('customize_register', array($this, '_action_customize_register'), 7);
201
- }
202
-
203
- private function add_filters() {
204
- if ( is_admin() ) {
205
- add_filter('admin_footer_text', array($this, '_filter_admin_footer_text'), 11);
206
- add_filter('update_footer', array($this, '_filter_footer_version'), 11);
207
- }
208
- }
209
-
210
- /**
211
- * @param string|FW_Option_Type $option_type_class
212
- * @param string|null $type
213
- *
214
- * @internal
215
- */
216
- private function register_option_type( $option_type_class, $type = null ) {
217
- if ( $type == null ) {
218
- try {
219
- $type = $this->get_instance( $option_type_class )->get_type();
220
- } catch ( FW_Option_Type_Exception_Invalid_Class $exception ) {
221
- if ( ! is_subclass_of( $option_type_class, 'FW_Option_Type' ) ) {
222
- trigger_error( 'Invalid option type class ' . get_class( $option_type_class ), E_USER_WARNING );
223
-
224
- return;
225
- }
226
- }
227
- }
228
-
229
- if ( isset( $this->option_types[ $type ] ) ) {
230
- trigger_error( 'Option type "' . $type . '" already registered', E_USER_WARNING );
231
-
232
- return;
233
- }
234
-
235
- $this->option_types[$type] = $option_type_class;
236
- }
237
-
238
- /**
239
- * @param string|FW_Container_Type $container_type_class
240
- * @param string|null $type
241
- *
242
- * @internal
243
- */
244
- private function register_container_type( $container_type_class, $type = null ) {
245
- if ( $type == null ) {
246
- try {
247
- $type = $this->get_instance( $container_type_class )->get_type();
248
- } catch ( FW_Option_Type_Exception_Invalid_Class $exception ) {
249
- if ( ! is_subclass_of( $container_type_class, 'FW_Container_Type' ) ) {
250
- trigger_error( 'Invalid container type class ' . get_class( $container_type_class ), E_USER_WARNING );
251
-
252
- return;
253
- }
254
- }
255
- }
256
-
257
- if ( isset( $this->container_types[ $type ] ) ) {
258
- trigger_error( 'Container type "' . $type . '" already registered', E_USER_WARNING );
259
-
260
- return;
261
- }
262
-
263
- $this->container_types[$type] = $container_type_class;
264
- }
265
-
266
- private function register_static() {
267
- if (
268
- !doing_action('admin_enqueue_scripts')
269
- &&
270
- !did_action('admin_enqueue_scripts')
271
- ) {
272
- /**
273
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
274
- * and maybe they are used in dependencies in handles that are going to be enqueued.
275
- * So as a result some handles will not be equeued because of not registered dependecies.
276
- */
277
- return;
278
- }
279
-
280
- if ( $this->static_registered ) {
281
- return;
282
- }
283
-
284
- /**
285
- * Register styles/scripts only in admin area, on frontend it's not allowed to use styles/scripts from framework backend core
286
- * because they are meant to be used only in backend and can be changed in the future.
287
- * If you want to use a style/script from framework backend core, copy it to your theme and enqueue as a theme style/script.
288
- */
289
- if ( ! is_admin() ) {
290
- $this->static_registered = true;
291
-
292
- return;
293
- }
294
-
295
- wp_register_script(
296
- 'fw-events',
297
- fw_get_framework_directory_uri( '/static/js/fw-events.js' ),
298
- array(),
299
- fw()->manifest->get_version(),
300
- true
301
- );
302
-
303
- wp_register_script(
304
- 'fw-ie-fixes',
305
- fw_get_framework_directory_uri( '/static/js/ie-fixes.js' ),
306
- array(),
307
- fw()->manifest->get_version(),
308
- true
309
- );
310
-
311
- {
312
- wp_register_style(
313
- 'qtip',
314
- fw_get_framework_directory_uri( '/static/libs/qtip/css/jquery.qtip.min.css' ),
315
- array(),
316
- fw()->manifest->get_version()
317
- );
318
- wp_register_script(
319
- 'qtip',
320
- fw_get_framework_directory_uri( '/static/libs/qtip/jquery.qtip.min.js' ),
321
- array( 'jquery' ),
322
- fw()->manifest->get_version()
323
- );
324
- }
325
-
326
- /**
327
- * Important!
328
- * Call wp_enqueue_media() before wp_enqueue_script('fw') (or using 'fw' in your script dependencies)
329
- * otherwise fw.OptionsModal won't work
330
- */
331
- {
332
- wp_register_style(
333
- 'fw',
334
- fw_get_framework_directory_uri( '/static/css/fw.css' ),
335
- array( 'qtip' ),
336
- fw()->manifest->get_version()
337
- );
338
-
339
- wp_register_script(
340
- 'fw-reactive-options-registry',
341
- fw_get_framework_directory_uri(
342
- '/static/js/fw-reactive-options-registry.js'
343
- ),
344
- array('fw', 'fw-events'),
345
- false
346
- );
347
-
348
- wp_register_script(
349
- 'fw-reactive-options-simple-options',
350
- fw_get_framework_directory_uri(
351
- '/static/js/fw-reactive-options-simple-options.js'
352
- ),
353
- array('fw', 'fw-events', 'fw-reactive-options-undefined-option'),
354
- false
355
- );
356
-
357
- wp_register_script(
358
- 'fw-reactive-options-undefined-option',
359
- fw_get_framework_directory_uri(
360
- '/static/js/fw-reactive-options-undefined-option.js'
361
- ),
362
- array(
363
- 'fw', 'fw-events', 'fw-reactive-options-registry'
364
- ),
365
- false
366
- );
367
-
368
- wp_register_script(
369
- 'fw-reactive-options',
370
- fw_get_framework_directory_uri('/static/js/fw-reactive-options.js'),
371
- array(
372
- 'fw', 'fw-events', 'fw-reactive-options-undefined-option',
373
- 'fw-reactive-options-simple-options'
374
- ),
375
- false
376
- );
377
-
378
- wp_register_script(
379
- 'fw',
380
- fw_get_framework_directory_uri( '/static/js/fw.js' ),
381
- array( 'jquery', 'fw-events', 'backbone', 'qtip' ),
382
- fw()->manifest->get_version(),
383
- false // false fixes https://github.com/ThemeFuse/Unyson/issues/1625#issuecomment-224219454
384
- );
385
-
386
- wp_localize_script( 'fw', '_fw_localized', array(
387
- 'FW_URI' => fw_get_framework_directory_uri(),
388
- 'SITE_URI' => site_url(),
389
- 'LOADER_URI' => apply_filters( 'fw_loader_image', fw_get_framework_directory_uri() . '/static/img/logo.svg' ),
390
- 'l10n' => array_merge(
391
- $l10n = array(
392
- 'modal_save_btn' => __( 'Save', 'fw' ),
393
- 'done' => __( 'Done', 'fw' ),
394
- 'ah_sorry' => __( 'Ah, Sorry', 'fw' ),
395
- 'reset' => __( 'Reset', 'fw' ),
396
- 'apply' => __( 'Apply', 'fw' ),
397
- 'cancel' => __( 'Cancel', 'fw' ),
398
- 'ok' => __( 'Ok', 'fw' )
399
- ),
400
- /**
401
- * fixes https://github.com/ThemeFuse/Unyson/issues/2381
402
- * @since 2.6.14
403
- */
404
- apply_filters('fw_js_l10n', $l10n)
405
- ),
406
- 'options_modal' => array(
407
- /** @since 2.6.13 */
408
- 'default_reset_bnt_disabled' => apply_filters('fw:option-modal:default:reset-btn-disabled', false)
409
- ),
410
- ) );
411
- }
412
-
413
- {
414
- wp_register_style(
415
- 'fw-backend-options',
416
- fw_get_framework_directory_uri( '/static/css/backend-options.css' ),
417
- array( 'fw' ),
418
- fw()->manifest->get_version()
419
- );
420
-
421
- wp_register_script(
422
- 'fw-backend-options',
423
- fw_get_framework_directory_uri( '/static/js/backend-options.js' ),
424
- array( 'fw', 'fw-events', 'fw-reactive-options', 'postbox', 'jquery-ui-tabs' ),
425
- fw()->manifest->get_version(),
426
- true
427
- );
428
-
429
- wp_localize_script( 'fw', '_fw_backend_options_localized', array(
430
- 'lazy_tabs' => fw()->theme->get_config('lazy_tabs')
431
- ) );
432
- }
433
-
434
- {
435
- wp_register_style(
436
- 'fw-selectize',
437
- fw_get_framework_directory_uri( '/static/libs/selectize/selectize.css' ),
438
- array(),
439
- fw()->manifest->get_version()
440
- );
441
- wp_register_script(
442
- 'fw-selectize',
443
- fw_get_framework_directory_uri( '/static/libs/selectize/selectize.min.js' ),
444
- array( 'jquery', 'fw-ie-fixes' ),
445
- fw()->manifest->get_version(),
446
- true
447
- );
448
- }
449
-
450
- {
451
- wp_register_script(
452
- 'fw-mousewheel',
453
- fw_get_framework_directory_uri( '/static/libs/mousewheel/jquery.mousewheel.min.js' ),
454
- array( 'jquery' ),
455
- fw()->manifest->get_version(),
456
- true
457
- );
458
- }
459
-
460
- {
461
- wp_register_style(
462
- 'fw-jscrollpane',
463
- fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.css' ),
464
- array(),
465
- fw()->manifest->get_version()
466
- );
467
- wp_register_script( 'fw-jscrollpane',
468
- fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.min.js' ),
469
- array( 'jquery', 'fw-mousewheel' ),
470
- fw()->manifest->get_version(),
471
- true
472
- );
473
- }
474
-
475
- wp_register_style(
476
- 'font-awesome',
477
- fw_get_framework_directory_uri( '/static/libs/font-awesome/css/font-awesome.min.css' ),
478
- array(),
479
- fw()->manifest->get_version()
480
- );
481
- /**
482
- * backwards compatibility, in case extensions are not up-to-date
483
- * todo: remove in next major version
484
- * https://github.com/ThemeFuse/Unyson/issues/2198
485
- * @deprecated
486
- */
487
- wp_register_style('fw-font-awesome', fw_get_framework_directory_uri( '/static/libs/font-awesome/css/font-awesome.min.css' ));
488
-
489
- wp_register_script(
490
- 'backbone-relational',
491
- fw_get_framework_directory_uri( '/static/libs/backbone-relational/backbone-relational.js' ),
492
- array( 'backbone' ),
493
- fw()->manifest->get_version(),
494
- true
495
- );
496
-
497
- wp_register_script(
498
- 'fw-uri',
499
- fw_get_framework_directory_uri( '/static/libs/uri/URI.js' ),
500
- array(),
501
- fw()->manifest->get_version(),
502
- true
503
- );
504
-
505
- wp_register_script(
506
- 'fw-moment',
507
- /**
508
- * IMPORTANT: At the end of the script is added this line:
509
- * moment.locale(document.documentElement.lang.slice(0, 2)); // fixes https://github.com/ThemeFuse/Unyson/issues/1767
510
- */
511
- fw_get_framework_directory_uri( '/static/libs/moment/moment-with-locales.min.js' ),
512
- array(),
513
- fw()->manifest->get_version(),
514
- true
515
- );
516
-
517
- wp_register_script(
518
- 'fw-form-helpers',
519
- fw_get_framework_directory_uri( '/static/js/fw-form-helpers.js' ),
520
- array( 'jquery' ),
521
- fw()->manifest->get_version(),
522
- true
523
- );
524
-
525
- wp_register_style(
526
- 'fw-unycon',
527
- fw_get_framework_directory_uri( '/static/libs/unycon/unycon.css' ),
528
- array(),
529
- fw()->manifest->get_version()
530
- );
531
-
532
- $this->static_registered = true;
533
- }
534
-
535
- /**
536
- * @param $class
537
- *
538
- * @return FW_Option_Type
539
- * @throws FW_Option_Type_Exception_Invalid_Class
540
- */
541
- protected function get_instance( $class ) {
542
- if ( ! class_exists( $class )
543
- || (
544
- ! is_subclass_of( $class, 'FW_Option_Type' )
545
- &&
546
- ! is_subclass_of( $class, 'FW_Container_Type' )
547
- )
548
- ) {
549
- throw new FW_Option_Type_Exception_Invalid_Class( $class );
550
- }
551
-
552
- return new $class;
553
- }
554
-
555
- public function _filter_admin_footer_text( $html ) {
556
- if (
557
- (
558
- current_user_can( 'update_themes' )
559
- ||
560
- current_user_can( 'update_plugins' )
561
- )
562
- &&
563
- fw_current_screen_match(array(
564
- 'only' => array(
565
- array('parent_base' => fw()->extensions->manager->get_page_slug()) // Unyson Extensions page
566
- )
567
- ))
568
- ) {
569
- return ( empty( $html ) ? '' : $html . '<br/>' )
570
- . '<em>'
571
- . str_replace(
572
- array(
573
- '{wp_review_link}',
574
- '{facebook_share_link}',
575
- '{twitter_share_link}',
576
- ),
577
- array(
578
- fw_html_tag('a', array(
579
- 'target' => '_blank',
580
- 'href' => 'https://wordpress.org/support/view/plugin-reviews/unyson?filter=5#postform',
581
- ), __('leave a review', 'fw')),
582
- fw_html_tag('a', array(
583
- 'target' => '_blank',
584
- 'href' => 'https://www.facebook.com/sharer/sharer.php?'. http_build_query(array(
585
- 'u' => 'http://unyson.io',
586
- )),
587
- 'onclick' => 'return !window.open(this.href, \'Facebook\', \'width=640,height=300\')',
588
- ), __('Facebook', 'fw')),
589
- fw_html_tag('a', array(
590
- 'target' => '_blank',
591
- 'href' => 'https://twitter.com/home?'. http_build_query(array(
592
- 'status' => __('Unyson WordPress Framework is the fastest and easiest way to develop a premium theme. I highly recommend it', 'fw')
593
- .' http://unyson.io/ #UnysonWP',
594
- )),
595
- 'onclick' => 'return !window.open(this.href, \'Twitter\', \'width=640,height=430\')',
596
- ), __('Twitter', 'fw')),
597
- ),
598
- __('If you like Unyson, {wp_review_link}, share on {facebook_share_link} or {twitter_share_link}.', 'fw')
599
- )
600
- . '</em>';
601
- } else {
602
- return $html;
603
- }
604
- }
605
-
606
- /**
607
- * Print framework version in the admin footer
608
- *
609
- * @param string $html
610
- *
611
- * @return string
612
- * @internal
613
- */
614
- public function _filter_footer_version( $html ) {
615
- if ( current_user_can( 'update_themes' ) || current_user_can( 'update_plugins' ) ) {
616
- return ( empty( $html ) ? '' : $html . ' | ' ) . fw()->manifest->get_name() . ' ' . fw()->manifest->get_version();
617
- } else {
618
- return $html;
619
- }
620
- }
621
-
622
- /**
623
- * @param string $post_type
624
- * @param WP_Post $post
625
- */
626
- public function _action_create_post_meta_boxes( $post_type, $post ) {
627
- if ( 'comment' === $post_type ) {
628
- /**
629
- * This is wrong, comment is not a post(type)
630
- * it is stored in a separate db table and has a separate meta (wp_comments and wp_commentmeta)
631
- */
632
- return;
633
- }
634
-
635
- $options = fw()->theme->get_post_options( $post_type );
636
-
637
- if ( empty( $options ) ) {
638
- return;
639
- }
640
-
641
- $collected = array();
642
-
643
- fw_collect_options( $collected, $options, array(
644
- 'limit_option_types' => false,
645
- 'limit_container_types' => false,
646
- 'limit_level' => 1,
647
- ) );
648
-
649
- if ( empty( $collected ) ) {
650
- return;
651
- }
652
-
653
- $values = fw_get_db_post_option( $post->ID );
654
-
655
- foreach ( $collected as $id => &$option ) {
656
- if ( isset( $option['options'] ) && ( $option['type'] === 'box' || $option['type'] === 'group' ) ) {
657
- $context = isset( $option['context'] ) ? $option['context'] : 'normal';
658
- $priority = isset( $option['priority'] ) ? $option['priority'] : 'default';
659
-
660
- add_meta_box(
661
- "fw-options-box-{$id}",
662
- empty( $option['title'] ) ? ' ' : $option['title'],
663
- $this->print_meta_box_content_callback,
664
- $post_type,
665
- $context,
666
- $priority,
667
- $this->render_options( $option['options'], $values )
668
- );
669
- } else { // this is not a box, wrap it in auto-generated box
670
- add_meta_box(
671
- 'fw-options-box:auto-generated:' . time() . ':' . fw_unique_increment(),
672
- ' ',
673
- $this->print_meta_box_content_callback,
674
- $post_type,
675
- 'normal',
676
- 'default',
677
- $this->render_options( array( $id => $option ), $values )
678
- );
679
- }
680
- }
681
- }
682
-
683
- /**
684
- * @param object $term
685
- */
686
- public function _action_create_taxonomy_options( $term ) {
687
- $options = fw()->theme->get_taxonomy_options( $term->taxonomy );
688
-
689
- if ( empty( $options ) ) {
690
- return;
691
- }
692
-
693
- $collected = array();
694
-
695
- fw_collect_options( $collected, $options, array(
696
- 'limit_option_types' => false,
697
- 'limit_container_types' => false,
698
- 'limit_level' => 1,
699
- ) );
700
-
701
- if ( empty( $collected ) ) {
702
- return;
703
- }
704
-
705
- $values = fw_get_db_term_option( $term->term_id, $term->taxonomy );
706
-
707
- // fixes word_press style: .form-field input { width: 95% }
708
- echo '<style type="text/css">.fw-option-type-radio input, .fw-option-type-checkbox input { width: auto; }</style>';
709
-
710
- do_action( 'fw_backend_options_render:taxonomy:before' );
711
- echo $this->render_options( $collected, $values, array(), 'taxonomy' );
712
- do_action( 'fw_backend_options_render:taxonomy:after' );
713
- }
714
-
715
- /**
716
- * @param string $taxonomy
717
- */
718
- public function _action_create_add_taxonomy_options( $taxonomy ) {
719
- $options = fw()->theme->get_taxonomy_options( $taxonomy );
720
-
721
- if ( empty( $options ) ) {
722
- return;
723
- }
724
-
725
- $collected = array();
726
-
727
- fw_collect_options( $collected, $options, array(
728
- 'limit_option_types' => false,
729
- 'limit_container_types' => false,
730
- 'limit_level' => 1,
731
- ) );
732
-
733
- if ( empty( $collected ) ) {
734
- return;
735
- }
736
-
737
- // fixes word_press style: .form-field input { width: 95% }
738
- echo '<style type="text/css">.fw-option-type-radio input, .fw-option-type-checkbox input { width: auto; }</style>';
739
-
740
- do_action( 'fw_backend_options_render:taxonomy:before' );
741
-
742
- echo '<div class="fw-force-xs">';
743
- echo $this->render_options( $collected, array(), array(), 'taxonomy' );
744
- echo '</div>';
745
-
746
- do_action( 'fw_backend_options_render:taxonomy:after' );
747
-
748
- echo '<script type="text/javascript">'
749
- .'jQuery(function($){'
750
- .' $("#submit").on("click", function(){'
751
- .' $("html, body").animate({ scrollTop: $("#col-left").offset().top });'
752
- .' });'
753
- .'});'
754
- .'</script>';
755
- }
756
-
757
- public function _action_init() {
758
- $current_edit_taxonomy = $this->get_current_edit_taxonomy();
759
-
760
- if ( $current_edit_taxonomy['taxonomy'] ) {
761
- add_action(
762
- $current_edit_taxonomy['taxonomy'] . '_edit_form',
763
- array( $this, '_action_create_taxonomy_options' )
764
- );
765
-
766
- if (fw()->theme->get_config('taxonomy_create_has_unyson_options', true)) {
767
- add_action(
768
- $current_edit_taxonomy['taxonomy'] . '_add_form_fields',
769
- array( $this, '_action_create_add_taxonomy_options' )
770
- );
771
- }
772
- }
773
-
774
- if ( ! empty( $_POST ) ) {
775
- // is form submit
776
- add_action( 'edited_term', array( $this, '_action_term_edit' ), 10, 3 );
777
-
778
- if ($current_edit_taxonomy['taxonomy']) {
779
- add_action(
780
- 'create_' . $current_edit_taxonomy['taxonomy'],
781
- array($this, '_action_save_taxonomy_fields')
782
- );
783
- }
784
- }
785
- }
786
-
787
- /**
788
- * Save meta from $_POST to fw options (post meta)
789
- * @param int $post_id
790
- * @param WP_Post $post
791
- * @param bool $update
792
- */
793
- public function _action_save_post( $post_id, $post, $update ) {
794
- if (
795
- isset($_POST['post_ID'])
796
- &&
797
- intval($_POST['post_ID']) === intval($post_id)
798
- &&
799
- !empty($_POST[ $this->get_options_name_attr_prefix() ]) // this happens on Quick Edit
800
- ) {
801
- /**
802
- * This happens on regular post form submit
803
- * All data from $_POST belongs this $post
804
- * so we save them in its post meta
805
- */
806
-
807
- static $post_options_save_happened = false;
808
- if ($post_options_save_happened) {
809
- /**
810
- * Prevent multiple options save for same post
811
- * It can happen from a recursion or wp_update_post() for same post id
812
- */
813
- return;
814
- } else {
815
- $post_options_save_happened = true;
816
- }
817
-
818
- $old_values = (array)fw_get_db_post_option($post_id);
819
-
820
- fw_set_db_post_option(
821
- $post_id,
822
- null,
823
- fw_get_options_values_from_input(
824
- fw()->theme->get_post_options($post->post_type)
825
- )
826
- );
827
-
828
- /**
829
- * @deprecated
830
- * Use the 'fw_post_options_update' action
831
- */
832
- do_action( 'fw_save_post_options', $post_id, $post, $old_values );
833
- } elseif ($original_post_id = wp_is_post_autosave( $post_id )) {
834
- do {
835
- $parent = get_post($post->post_parent);
836
-
837
- if ( ! $parent instanceof WP_Post ) {
838
- break;
839
- }
840
-
841
- if (
842
- isset($_POST['post_ID'])
843
- &&
844
- intval($_POST['post_ID']) === intval($parent->ID)
845
- ) {} else {
846
- break;
847
- }
848
-
849
- if (empty($_POST[ $this->get_options_name_attr_prefix() ])) {
850
- // this happens on Quick Edit
851
- break;
852
- }
853
-
854
- fw_set_db_post_option(
855
- $post->ID,
856
- null,
857
- fw_get_options_values_from_input(
858
- fw()->theme->get_post_options($parent->post_type)
859
- )
860
- );
861
- } while(false);
862
- } elseif ($original_post_id = wp_is_post_revision( $post_id )) {
863
- /**
864
- * Do nothing, the
865
- * - '_wp_put_post_revision'
866
- * - 'wp_restore_post_revision'
867
- * actions will handle this
868
- */
869
- } else {
870
- /**
871
- * This happens on:
872
- * - post add (auto-draft): do nothing
873
- * - revision restore: do nothing, that is handled by the 'wp_restore_post_revision' action
874
- */
875
- }
876
- }
877
-
878
- /**
879
- * @param $post_id
880
- * @param $revision_id
881
- */
882
- public function _action_restore_post_revision($post_id, $revision_id)
883
- {
884
- /**
885
- * Copy options meta from revision to post
886
- */
887
- fw_set_db_post_option(
888
- $post_id,
889
- null,
890
- (array)fw_get_db_post_option($revision_id, null, array())
891
- );
892
- }
893
-
894
- /**
895
- * @param $revision_id
896
- */
897
- public function _action__wp_put_post_revision($revision_id)
898
- {
899
- /**
900
- * Copy options meta from post to revision
901
- */
902
- fw_set_db_post_option(
903
- $revision_id,
904
- null,
905
- (array)fw_get_db_post_option(
906
- wp_is_post_revision($revision_id),
907
- null,
908
- array()
909
- )
910
- );
911
- }
912
-
913
- /**
914
- * Update all post meta `fw_option:<option-id>` with values from post options that has the 'save-in-separate-meta' parameter
915
- *
916
- * @param int $post_id
917
- *
918
- * @return bool
919
- * @deprecated since 2.5.0
920
- */
921
- public function _sync_post_separate_meta( $post_id ) {
922
- if ( ! ( $post_type = get_post_type( $post_id ) ) ) {
923
- return false;
924
- }
925
-
926
- $meta_prefix = 'fw_option:';
927
- $only_options = fw_extract_only_options( fw()->theme->get_post_options( $post_type ) );
928
- $separate_meta_options = array();
929
-
930
- // Collect all options that needs to be saved in separate meta
931
- {
932
- $options_values = fw_get_db_post_option( $post_id );
933
-
934
- foreach ($only_options as $option_id => $option) {
935
- if (
936
- isset( $option['save-in-separate-meta'] )
937
- &&
938
- $option['save-in-separate-meta']
939
- &&
940
- array_key_exists( $option_id, $options_values )
941
- ) {
942
- if (defined('WP_DEBUG') && WP_DEBUG) {
943
- FW_Flash_Messages::add(
944
- 'save-in-separate-meta:deprecated',
945
- '<p>The <code>save-in-separate-meta</code> option parameter is <strong>deprecated</strong>.</p>'
946
- .'<p>Please replace</p>'
947
- .'<pre>\'save-in-separate-meta\' => true</pre>'
948
- .'<p>with</p>'
949
- .'<pre>\'fw-storage\' => array('
950
- ."\n 'type' => 'post-meta',"
951
- ."\n 'post-meta' => 'fw_option:{your-option-id}',"
952
- ."\n)</pre>"
953
- .'<p>in <code>{theme}'. fw_get_framework_customizations_dir_rel_path('/theme/options/posts/'. $post_type .'.php') .'</code></p>'
954
- .'<p><a href="'. esc_attr('http://manual.unyson.io/en/latest/options/storage.html#content') .'" target="_blank">'. esc_html__('Info about fw-storage', 'fw') .'</a></p>',
955
- 'warning'
956
- );
957
- }
958
-
959
- $separate_meta_options[ $meta_prefix . $option_id ] = $options_values[ $option_id ];
960
- }
961
- }
962
-
963
- unset( $options_values );
964
- }
965
-
966
- // Delete meta that starts with $meta_prefix
967
- {
968
- /** @var wpdb $wpdb */
969
- global $wpdb;
970
-
971
- foreach (
972
- $wpdb->get_results(
973
- $wpdb->prepare(
974
- "SELECT meta_key " .
975
- "FROM {$wpdb->postmeta} " .
976
- "WHERE meta_key LIKE %s AND post_id = %d",
977
- $wpdb->esc_like( $meta_prefix ) . '%',
978
- $post_id
979
- )
980
- ) as $row
981
- ) {
982
- if (
983
- array_key_exists( $row->meta_key, $separate_meta_options )
984
- ||
985
- ( // skip options containing 'fw-storage'
986
- ($option_id = substr($row->meta_key, 10))
987
- &&
988
- isset($only_options[$option_id]['fw-storage'])
989
- )
990
- ) {
991
- /**
992
- * This meta exists and will be updated below.
993
- * Do not delete for performance reasons, instead of delete->insert will be performed only update
994
- */
995
- continue;
996
- } else {
997
- // this option does not exist anymore
998
- delete_post_meta( $post_id, $row->meta_key );
999
- }
1000
- }
1001
- }
1002
-
1003
- foreach ( $separate_meta_options as $meta_key => $option_value ) {
1004
- fw_update_post_meta($post_id, $meta_key, $option_value );
1005
- }
1006
-
1007
- return true;
1008
- }
1009
-
1010
- /**
1011
- * @param int $term_id
1012
- */
1013
- public function _action_save_taxonomy_fields( $term_id ) {
1014
- if (
1015
- isset( $_POST['action'] )
1016
- &&
1017
- 'add-tag' === $_POST['action']
1018
- &&
1019
- isset( $_POST['taxonomy'] )
1020
- &&
1021
- ($taxonomy = get_taxonomy( $_POST['taxonomy'] ))
1022
- &&
1023
- current_user_can($taxonomy->cap->edit_terms)
1024
- ) { /* ok */ } else { return; }
1025
-
1026
- $options = fw()->theme->get_taxonomy_options( $taxonomy->name );
1027
- if ( empty( $options ) ) {
1028
- return;
1029
- }
1030
-
1031
- fw_set_db_term_option(
1032
- $term_id,
1033
- $taxonomy->name,
1034
- null,
1035
- fw_get_options_values_from_input($options)
1036
- );
1037
-
1038
- do_action( 'fw_save_term_options', $term_id, $taxonomy->name, array() );
1039
- }
1040
-
1041
- public function _action_term_edit( $term_id, $tt_id, $taxonomy ) {
1042
- if (
1043
- isset( $_POST['action'] )
1044
- &&
1045
- 'editedtag' === $_POST['action']
1046
- &&
1047
- isset( $_POST['taxonomy'] )
1048
- &&
1049
- ($taxonomy = get_taxonomy( $_POST['taxonomy'] ))
1050
- &&
1051
- current_user_can($taxonomy->cap->edit_terms)
1052
- ) { /* ok */ } else { return; }
1053
-
1054
- if (intval(FW_Request::POST('tag_ID')) != $term_id) {
1055
- // the $_POST values belongs to another term, do not save them into this one
1056
- return;
1057
- }
1058
-
1059
- $options = fw()->theme->get_taxonomy_options( $taxonomy->name );
1060
- if ( empty( $options ) ) {
1061
- return;
1062
- }
1063
-
1064
- $old_values = (array) fw_get_db_term_option( $term_id, $taxonomy->name );
1065
-
1066
- fw_set_db_term_option(
1067
- $term_id,
1068
- $taxonomy->name,
1069
- null,
1070
- fw_get_options_values_from_input($options)
1071
- );
1072
-
1073
- do_action( 'fw_save_term_options', $term_id, $taxonomy->name, $old_values );
1074
- }
1075
-
1076
- public function _action_admin_register_scripts() {
1077
- $this->register_static();
1078
- }
1079
-
1080
- public function _action_admin_enqueue_scripts() {
1081
- /**
1082
- * Enqueue settings options static in <head>
1083
- * @see FW_Settings_Form_Theme::_action_admin_enqueue_scripts()
1084
- */
1085
-
1086
- /**
1087
- * Enqueue post options static in <head>
1088
- */
1089
- {
1090
- if ( 'post' === get_current_screen()->base && get_the_ID() ) {
1091
- fw()->backend->enqueue_options_static(
1092
- fw()->theme->get_post_options( get_post_type() )
1093
- );
1094
-
1095
- do_action( 'fw_admin_enqueue_scripts:post', get_post() );
1096
- }
1097
- }
1098
-
1099
- /**
1100
- * Enqueue term options static in <head>
1101
- */
1102
- {
1103
- if (
1104
- in_array(get_current_screen()->base, array('edit-tags', 'term'), true)
1105
- &&
1106
- get_current_screen()->taxonomy
1107
- ) {
1108
- fw()->backend->enqueue_options_static(
1109
- fw()->theme->get_taxonomy_options( get_current_screen()->taxonomy )
1110
- );
1111
-
1112
- do_action( 'fw_admin_enqueue_scripts:term', get_current_screen()->taxonomy );
1113
- }
1114
- }
1115
- }
1116
-
1117
- /**
1118
- * Render options html from input json
1119
- *
1120
- * POST vars:
1121
- * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1122
- * - values: {option_id: value, option_id: {...}, ...} // Optional // Object
1123
- * - data: {id_prefix: 'fw_options-a-b-', name_prefix: 'fw_options[a][b]'} // Optional // Object
1124
- */
1125
- public function _action_ajax_options_render() {
1126
- // options
1127
- {
1128
- if ( ! isset( $_POST['options'] ) ) {
1129
- wp_send_json_error( array(
1130
- 'message' => 'No options'
1131
- ) );
1132
- }
1133
-
1134
- $options = json_decode( FW_Request::POST( 'options' ), true );
1135
-
1136
- if ( ! $options ) {
1137
- wp_send_json_error( array(
1138
- 'message' => 'Wrong options'
1139
- ) );
1140
- }
1141
- }
1142
-
1143
- // values
1144
- {
1145
- if ( isset( $_POST['values'] ) ) {
1146
- $values = FW_Request::POST( 'values' );
1147
-
1148
- if (is_string($values)) {
1149
- $values = json_decode($values, true);
1150
- }
1151
- } else {
1152
- $values = array();
1153
- }
1154
-
1155
- $values = array_intersect_key($values, fw_extract_only_options($options));
1156
- }
1157
-
1158
- // data
1159
- {
1160
- if ( isset( $_POST['data'] ) ) {
1161
- $data = FW_Request::POST( 'data' );
1162
- } else {
1163
- $data = array();
1164
- }
1165
- }
1166
-
1167
- wp_send_json_success( array(
1168
- 'html' => fw()->backend->render_options( $options, $values, $data ),
1169
- /** @since 2.6.1 */
1170
- 'default_values' => fw_get_options_values_from_input($options, array()),
1171
- ) );
1172
- }
1173
-
1174
- /**
1175
- * Get options values from html generated with 'fw_backend_options_render' ajax action
1176
- *
1177
- * POST vars:
1178
- * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1179
- * - fw_options... // Use a jQuery "ajax form submit" to emulate real form submit
1180
- *
1181
- * Tip: Inside form html, add: <input type="hidden" name="options" value="[...json...]">
1182
- */
1183
- public function _action_ajax_options_get_values() {
1184
- // options
1185
- {
1186
- if ( ! isset( $_POST['options'] ) ) {
1187
- wp_send_json_error( array(
1188
- 'message' => 'No options'
1189
- ) );
1190
- }
1191
-
1192
- $options = FW_Request::POST( 'options' );
1193
-
1194
- if (is_string( $options )) {
1195
- $options = json_decode( FW_Request::POST( 'options' ), true );
1196
- }
1197
-
1198
- if ( ! $options ) {
1199
- wp_send_json_error( array(
1200
- 'message' => 'Wrong options'
1201
- ) );
1202
- }
1203
- }
1204
-
1205
- // name_prefix
1206
- {
1207
- if ( isset( $_POST['name_prefix'] ) ) {
1208
- $name_prefix = FW_Request::POST( 'name_prefix' );
1209
- } else {
1210
- $name_prefix = $this->get_options_name_attr_prefix();
1211
- }
1212
- }
1213
-
1214
- wp_send_json_success( array(
1215
- 'values' => fw_get_options_values_from_input(
1216
- $options,
1217
- FW_Request::POST( fw_html_attr_name_to_array_multi_key( $name_prefix ), array() )
1218
- )
1219
- ) );
1220
- }
1221
-
1222
- /**
1223
- * Get options values from html generated with 'fw_backend_options_render' ajax action
1224
- *
1225
- * POST vars:
1226
- * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1227
- * - values: {option_id: {...}}
1228
- *
1229
- * Tip: Inside form html, add: <input type="hidden" name="options" value="[...json...]">
1230
- */
1231
- public function _action_ajax_options_get_values_json() {
1232
- // options
1233
- {
1234
- if ( ! isset( $_POST['options'] ) ) {
1235
- wp_send_json_error( array(
1236
- 'message' => 'No options'
1237
- ) );
1238
- }
1239
-
1240
- $options = FW_Request::POST( 'options' );
1241
-
1242
- if (is_string( $options )) {
1243
- $options = json_decode( FW_Request::POST( 'options' ), true );
1244
- }
1245
-
1246
- if ( ! $options ) {
1247
- wp_send_json_error( array(
1248
- 'message' => 'Wrong options'
1249
- ) );
1250
- }
1251
- }
1252
-
1253
- // values
1254
- {
1255
- if ( ! isset( $_POST['values'] ) ) {
1256
- wp_send_json_error( array(
1257
- 'message' => 'No values'
1258
- ) );
1259
- }
1260
-
1261
- $values = FW_Request::POST( 'values' );
1262
-
1263
- if (is_string( $values )) {
1264
- $values = json_decode( FW_Request::POST( 'values' ), true );
1265
- }
1266
-
1267
- if (! is_array($values)) {
1268
- if ( ! $values ) {
1269
- wp_send_json_error(array(
1270
- 'message' => 'Wrong values'
1271
- ));
1272
- }
1273
- }
1274
- }
1275
-
1276
- wp_send_json_success( array(
1277
- 'values' => fw_get_options_values_from_input(
1278
- $options,
1279
- $values
1280
- )
1281
- ) );
1282
- }
1283
-
1284
- /**
1285
- * Render options array and return the generated HTML
1286
- *
1287
- * @param array $options
1288
- * @param array $values Correct values returned by fw_get_options_values_from_input()
1289
- * @param array $options_data {id_prefix => ..., name_prefix => ...}
1290
- * @param string $design
1291
- *
1292
- * @return string HTML
1293
- */
1294
- public function render_options( $options, $values = array(), $options_data = array(), $design = null ) {
1295
- if (empty($design)) {
1296
- $design = $this->default_render_design;
1297
- }
1298
-
1299
- if (
1300
- !doing_action('admin_enqueue_scripts')
1301
- &&
1302
- !did_action('admin_enqueue_scripts')
1303
- ) {
1304
- /**
1305
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1306
- * and maybe they are used in dependencies in handles that are going to be enqueued.
1307
- * So as a result some handles will not be equeued because of not registered dependecies.
1308
- */
1309
- } else {
1310
- /**
1311
- * register scripts and styles
1312
- * in case if this method is called before enqueue_scripts action
1313
- * and option types has some of these in their dependencies
1314
- */
1315
- $this->register_static();
1316
-
1317
- wp_enqueue_media();
1318
- wp_enqueue_style( 'fw-backend-options' );
1319
- wp_enqueue_script( 'fw-backend-options' );
1320
- }
1321
-
1322
- $collected = array();
1323
-
1324
- fw_collect_options( $collected, $options, array(
1325
- 'limit_option_types' => false,
1326
- 'limit_container_types' => false,
1327
- 'limit_level' => 1,
1328
- 'info_wrapper' => true,
1329
- ) );
1330
-
1331
- if ( empty( $collected ) ) {
1332
- return false;
1333
- }
1334
-
1335
- $html = '';
1336
-
1337
- $option = reset( $collected );
1338
-
1339
- $collected_type = array(
1340
- 'group' => $option['group'],
1341
- 'type' => $option['option']['type'],
1342
- );
1343
- $collected_type_options = array(
1344
- $option['id'] => &$option['option']
1345
- );
1346
-
1347
- while ( $collected_type_options ) {
1348
- $option = next( $collected );
1349
-
1350
- if ( $option ) {
1351
- if (
1352
- $option['group'] === $collected_type['group']
1353
- &&
1354
- $option['option']['type'] === $collected_type['type']
1355
- ) {
1356
- $collected_type_options[ $option['id'] ] = &$option['option'];
1357
- continue;
1358
- }
1359
- }
1360
-
1361
- switch ( $collected_type['group'] ) {
1362
- case 'container':
1363
- if ($design === 'taxonomy') {
1364
- $html .= fw_render_view(
1365
- fw_get_framework_directory('/views/backend-container-design-'. $design .'.php'),
1366
- array(
1367
- 'type' => $collected_type['type'],
1368
- 'html' => $this->container_type($collected_type['type'])->render(
1369
- $collected_type_options, $values, $options_data
1370
- ),
1371
- )
1372
- );
1373
- } else {
1374
- $html .= $this->container_type($collected_type['type'])->render(
1375
- $collected_type_options, $values, $options_data
1376
- );
1377
- }
1378
- break;
1379
- case 'option':
1380
- foreach ( $collected_type_options as $id => &$_option ) {
1381
- $data = $options_data; // do not change directly to not affect next loops
1382
-
1383
- $data['value'] = isset( $values[ $id ] ) ? $values[ $id ] : null;
1384
-
1385
- $html .= $this->render_option(
1386
- $id,
1387
- $_option,
1388
- $data,
1389
- $design
1390
- );
1391
- }
1392
- unset($_option);
1393
- break;
1394
- default:
1395
- $html .= '<p><em>' . __( 'Unknown collected group', 'fw' ) . ': ' . $collected_type['group'] . '</em></p>';
1396
- }
1397
-
1398
- unset( $collected_type, $collected_type_options );
1399
-
1400
- if ( $option ) {
1401
- $collected_type = array(
1402
- 'group' => $option['group'],
1403
- 'type' => $option['option']['type'],
1404
- );
1405
- $collected_type_options = array(
1406
- $option['id'] => &$option['option']
1407
- );
1408
- } else {
1409
- $collected_type_options = array();
1410
- }
1411
- }
1412
-
1413
- return $html;
1414
- }
1415
-
1416
- /**
1417
- * Enqueue options static
1418
- *
1419
- * Useful when you have dynamic options html on the page (for e.g. options modal)
1420
- * and in order to initialize that html properly, the option types scripts styles must be enqueued on the page
1421
- *
1422
- * @param array $options
1423
- */
1424
- public function enqueue_options_static( $options ) {
1425
- static $static_enqueue = true;
1426
-
1427
- if (
1428
- !doing_action('admin_enqueue_scripts')
1429
- &&
1430
- !did_action('admin_enqueue_scripts')
1431
- ) {
1432
- /**
1433
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1434
- * and maybe they are used in dependencies in handles that are going to be enqueued.
1435
- * So as a result some handles will not be equeued because of not registered dependecies.
1436
- */
1437
- return;
1438
- } else {
1439
- /**
1440
- * register scripts and styles
1441
- * in case if this method is called before enqueue_scripts action
1442
- * and option types has some of these in their dependencies
1443
- */
1444
- if ($static_enqueue) {
1445
- $this->register_static();
1446
-
1447
- wp_enqueue_media();
1448
- wp_enqueue_style( 'fw-backend-options' );
1449
- wp_enqueue_script( 'fw-backend-options' );
1450
-
1451
- $static_enqueue = false;
1452
- }
1453
- }
1454
-
1455
- $collected = array();
1456
-
1457
- fw_collect_options( $collected, $options, array(
1458
- 'limit_option_types' => false,
1459
- 'limit_container_types' => false,
1460
- 'limit_level' => 0,
1461
- 'callback' => array(__CLASS__, '_callback_fw_collect_options_enqueue_static'),
1462
- ) );
1463
-
1464
- unset($collected);
1465
- }
1466
-
1467
- /**
1468
- * @internal
1469
- * @param array $data
1470
- */
1471
- public static function _callback_fw_collect_options_enqueue_static($data) {
1472
- if ($data['group'] === 'option') {
1473
- fw()->backend->option_type($data['option']['type'])->enqueue_static($data['id'], $data['option']);
1474
- } elseif ($data['group'] === 'container') {
1475
- fw()->backend->container_type($data['option']['type'])->enqueue_static($data['id'], $data['option']);
1476
- }
1477
- }
1478
-
1479
- /**
1480
- * Render option enclosed in backend design
1481
- *
1482
- * @param string $id
1483
- * @param array $option
1484
- * @param array $data
1485
- * @param string $design default or taxonomy
1486
- *
1487
- * @return string
1488
- */
1489
- public function render_option( $id, $option, $data = array(), $design = null ) {
1490
- if (empty($design)) {
1491
- $design = $this->default_render_design;
1492
- }
1493
-
1494
- if (
1495
- !doing_action('admin_enqueue_scripts')
1496
- &&
1497
- !did_action('admin_enqueue_scripts')
1498
- ) {
1499
- /**
1500
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1501
- * and maybe they are used in dependencies in handles that are going to be enqueued.
1502
- * So as a result some handles will not be equeued because of not registered dependecies.
1503
- */
1504
- } else {
1505
- $this->register_static();
1506
- }
1507
-
1508
-
1509
- if ( ! in_array( $design, $this->available_render_designs ) ) {
1510
- trigger_error( 'Invalid render design specified: ' . $design, E_USER_WARNING );
1511
- $design = 'post';
1512
- }
1513
-
1514
- if ( ! isset( $data['id_prefix'] ) ) {
1515
- $data['id_prefix'] = $this->get_options_id_attr_prefix();
1516
- }
1517
-
1518
- $data = apply_filters(
1519
- 'fw:backend:option-render:data',
1520
- $data
1521
- );
1522
-
1523
- return fw_render_view(fw_get_framework_directory('/views/backend-option-design-'. $design .'.php'), array(
1524
- 'id' => $id,
1525
- 'option' => $option,
1526
- 'data' => $data,
1527
- ) );
1528
- }
1529
-
1530
- /**
1531
- * Render a meta box
1532
- *
1533
- * @param string $id
1534
- * @param string $title
1535
- * @param string $content HTML
1536
- * @param array $other Optional elements
1537
- *
1538
- * @return string Generated meta box html
1539
- */
1540
- public function render_box( $id, $title, $content, $other = array() ) {
1541
- if ( ! function_exists( 'add_meta_box' ) ) {
1542
- trigger_error( 'Try call this method later (\'admin_init\' action), add_meta_box() function does not exists yet.',
1543
- E_USER_WARNING );
1544
-
1545
- return '';
1546
- }
1547
-
1548
- $other = array_merge( array(
1549
- 'html_before_title' => false,
1550
- 'html_after_title' => false,
1551
- 'attr' => array(),
1552
- ), $other );
1553
-
1554
- {
1555
- $placeholders = array(
1556
- 'id' => '{{meta_box_id}}',
1557
- 'title' => '{{meta_box_title}}',
1558
- 'content' => '{{meta_box_content}}',
1559
- );
1560
-
1561
- // other placeholders
1562
- {
1563
- $placeholders['html_before_title'] = '{{meta_box_html_before_title}}';
1564
- $placeholders['html_after_title'] = '{{meta_box_html_after_title}}';
1565
- $placeholders['attr'] = '{{meta_box_attr}}';
1566
- $placeholders['attr_class'] = '{{meta_box_attr_class}}';
1567
- }
1568
- }
1569
-
1570
- $cache_key = 'fw_meta_box_template';
1571
-
1572
- try {
1573
- $meta_box_template = FW_Cache::get( $cache_key );
1574
- } catch ( FW_Cache_Not_Found_Exception $e ) {
1575
- $temp_screen_id = 'fw-temp-meta-box-screen-id-' . fw_unique_increment();
1576
- $context = 'normal';
1577
-
1578
- add_meta_box(
1579
- $placeholders['id'],
1580
- $placeholders['title'],
1581
- $this->print_meta_box_content_callback,
1582
- $temp_screen_id,
1583
- $context,
1584
- 'default',
1585
- $placeholders['content']
1586
- );
1587
-
1588
- ob_start();
1589
-
1590
- do_meta_boxes( $temp_screen_id, $context, null );
1591
-
1592
- $meta_box_template = ob_get_clean();
1593
-
1594
- remove_meta_box( $id, $temp_screen_id, $context );
1595
-
1596
- // remove wrapper div, leave only meta box div
1597
- {
1598
- // <div ...>
1599
- {
1600
- $meta_box_template = str_replace(
1601
- '<div id="' . $context . '-sortables" class="meta-box-sortables">',
1602
- '',
1603
- $meta_box_template
1604
- );
1605
- }
1606
-
1607
- // </div>
1608
- {
1609
- $meta_box_template = explode( '</div>', $meta_box_template );
1610
- array_pop( $meta_box_template );
1611
- $meta_box_template = implode( '</div>', $meta_box_template );
1612
- }
1613
- }
1614
-
1615
- // add 'fw-postbox' class and some attr related placeholders
1616
- $meta_box_template = str_replace(
1617
- 'class="postbox',
1618
- $placeholders['attr'] . ' class="postbox fw-postbox' . $placeholders['attr_class'],
1619
- $meta_box_template
1620
- );
1621
-
1622
- // add html_before|after_title placeholders
1623
- {
1624
- $meta_box_template = str_replace(
1625
- '<span>' . $placeholders['title'] . '</span>',
1626
-
1627
- /**
1628
- * used <small> not <span> because there is a lot of css and js
1629
- * that thinks inside <h2 class="hndle"> there is only one <span>
1630
- * so do not brake their logic
1631
- */
1632
- '<small class="fw-html-before-title">' . $placeholders['html_before_title'] . '</small>' .
1633
- '<span>' . $placeholders['title'] . '</span>' .
1634
- '<small class="fw-html-after-title">' . $placeholders['html_after_title'] . '</small>',
1635
-
1636
- $meta_box_template
1637
- );
1638
- }
1639
-
1640
- FW_Cache::set( $cache_key, $meta_box_template );
1641
- }
1642
-
1643
- // prepare attributes
1644
- {
1645
- $attr_class = '';
1646
- if ( isset( $other['attr']['class'] ) ) {
1647
- $attr_class = ' ' . $other['attr']['class'];
1648
-
1649
- unset( $other['attr']['class'] );
1650
- }
1651
-
1652
- unset( $other['attr']['id'] );
1653
- }
1654
-
1655
- // replace placeholders with data/content
1656
- return str_replace(
1657
- array(
1658
- $placeholders['id'],
1659
- $placeholders['title'],
1660
- $placeholders['content'],
1661
- $placeholders['html_before_title'],
1662
- $placeholders['html_after_title'],
1663
- $placeholders['attr'],
1664
- $placeholders['attr_class'],
1665
- ),
1666
- array(
1667
- esc_attr( $id ),
1668
- $title,
1669
- $content,
1670
- $other['html_before_title'],
1671
- $other['html_after_title'],
1672
- fw_attr_to_html( $other['attr'] ),
1673
- esc_attr( $attr_class )
1674
- ),
1675
- $meta_box_template
1676
- );
1677
- }
1678
-
1679
- /**
1680
- * @param FW_Access_Key $access_key
1681
- * @param string|FW_Option_Type $option_type_class
1682
- *
1683
- * @internal
1684
- */
1685
- public function _register_option_type( FW_Access_Key $access_key, $option_type_class, $type= null ) {
1686
- if ( $access_key->get_key() !== 'fw_option_type' ) {
1687
- trigger_error( 'Call denied', E_USER_ERROR );
1688
- }
1689
-
1690
- $this->register_option_type( $option_type_class, $type );
1691
- }
1692
-
1693
- /**
1694
- * @param FW_Access_Key $access_key
1695
- * @param string|FW_Container_Type $container_type_class
1696
- *
1697
- * @internal
1698
- */
1699
- public function _register_container_type( FW_Access_Key $access_key, $container_type_class ) {
1700
- if ( $access_key->get_key() !== 'fw_container_type' ) {
1701
- trigger_error( 'Call denied', E_USER_ERROR );
1702
- }
1703
-
1704
- $this->register_container_type( $container_type_class );
1705
- }
1706
-
1707
- /**
1708
- * @param string $type
1709
- * @return FW_Option_Type
1710
- */
1711
- public function option_type( $type ) {
1712
- static $did_options_init = false;
1713
- if ( ! $did_options_init ) {
1714
- $did_options_init = true;
1715
- do_action( 'fw_option_types_init' );
1716
- }
1717
-
1718
- if ( isset( $this->option_types[ $type ] ) ) {
1719
- if (is_string($this->option_types[$type])) {
1720
- $this->option_types[$type] = $this->get_instance($this->option_types[$type]);
1721
- $this->option_types[$type]->_call_init($this->get_access_key());
1722
- }
1723
-
1724
- return $this->option_types[$type];
1725
- } else {
1726
- if ( is_admin() && apply_filters('fw_backend_undefined_option_type_warn_user', true, $type) ) {
1727
- FW_Flash_Messages::add(
1728
- 'fw-get-option-type-undefined-' . $type,
1729
- sprintf( __( 'Undefined option type: %s', 'fw' ), $type ),
1730
- 'warning'
1731
- );
1732
- }
1733
-
1734
- if ( ! $this->undefined_option_type ) {
1735
- $this->undefined_option_type = new FW_Option_Type_Undefined();
1736
- }
1737
-
1738
- return $this->undefined_option_type;
1739
- }
1740
- }
1741
-
1742
- /**
1743
- * Return an array with all option types names
1744
- *
1745
- * @return array
1746
- *
1747
- * @since 2.6.11
1748
- */
1749
- public function get_option_types() {
1750
- $this->option_type('text'); // trigger init
1751
- return array_keys( $this->option_types );
1752
- }
1753
-
1754
- /**
1755
- * Return an array with all container types names
1756
- *
1757
- * @return array
1758
- *
1759
- * @since 2.6.11
1760
- */
1761
- public function get_container_types() {
1762
- $this->container_type('box'); // trigger init
1763
- return array_keys( $this->container_types );
1764
- }
1765
-
1766
- /**
1767
- * @param string $type
1768
- * @return FW_Container_Type
1769
- */
1770
- public function container_type( $type ) {
1771
- static $did_containers_init = false;
1772
- if ( ! $did_containers_init ) {
1773
- $did_containers_init = true;
1774
- do_action( 'fw_container_types_init' );
1775
- }
1776
-
1777
- if ( isset( $this->container_types[ $type ] ) ) {
1778
- if ( is_string( $this->container_types[ $type ] ) ) {
1779
- $this->container_types[ $type ] = $this->get_instance( $this->container_types[$type] );
1780
- $this->container_types[ $type ]->_call_init( $this->get_access_key() );
1781
- }
1782
-
1783
- return $this->container_types[ $type ];
1784
- } else {
1785
- if ( is_admin() ) {
1786
- FW_Flash_Messages::add(
1787
- 'fw-get-container-type-undefined-' . $type,
1788
- sprintf( __( 'Undefined container type: %s', 'fw' ), $type ),
1789
- 'warning'
1790
- );
1791
- }
1792
-
1793
- if ( ! $this->undefined_container_type ) {
1794
- $this->undefined_container_type = new FW_Container_Type_Undefined();
1795
- }
1796
-
1797
- return $this->undefined_container_type;
1798
- }
1799
- }
1800
-
1801
- /**
1802
- * @param WP_Customize_Manager $wp_customize
1803
- * @internal
1804
- */
1805
- public function _action_customize_register($wp_customize) {
1806
- if (is_admin()) {
1807
- add_action('admin_enqueue_scripts', array($this, '_action_enqueue_customizer_static'));
1808
- }
1809
-
1810
- $this->customizer_register_options(
1811
- $wp_customize,
1812
- fw()->theme->get_customizer_options()
1813
- );
1814
- }
1815
-
1816
- /**
1817
- * @internal
1818
- */
1819
- public function _action_enqueue_customizer_static()
1820
- {
1821
- {
1822
- $options_for_enqueue = array();
1823
- $customizer_options = fw()->theme->get_customizer_options();
1824
-
1825
- /**
1826
- * In customizer options is allowed to have container with unspecified (or not existing) 'type'
1827
- * fw()->backend->enqueue_options_static() tries to enqueue both options and container static
1828
- * not existing container types will throw notices.
1829
- * To prevent that, extract and send it only options (without containers)
1830
- */
1831
- fw_collect_options($options_for_enqueue, $customizer_options, array(
1832
- 'callback' => array(__CLASS__, '_callback_fw_collect_options_enqueue_static'),
1833
- ));
1834
-
1835
- unset($options_for_enqueue, $customizer_options);
1836
- }
1837
-
1838
- wp_enqueue_script(
1839
- 'fw-backend-customizer',
1840
- fw_get_framework_directory_uri( '/static/js/backend-customizer.js' ),
1841
- array( 'jquery', 'fw-events', 'backbone', 'fw-backend-options' ),
1842
- fw()->manifest->get_version(),
1843
- true
1844
- );
1845
- wp_localize_script(
1846
- 'fw-backend-customizer',
1847
- '_fw_backend_customizer_localized',
1848
- array(
1849
- 'change_timeout' => apply_filters('fw_customizer_option_change_timeout', 333),
1850
- )
1851
- );
1852
-
1853
- do_action('fw_admin_enqueue_scripts:customizer');
1854
- }
1855
-
1856
- /**
1857
- * @param WP_Customize_Manager $wp_customize
1858
- * @param array $options
1859
- * @param array $parent_data {'type':'...','id':'...'}
1860
- */
1861
- private function customizer_register_options($wp_customize, $options, $parent_data = array()) {
1862
- $collected = array();
1863
-
1864
- fw_collect_options( $collected, $options, array(
1865
- 'limit_option_types' => false,
1866
- 'limit_container_types' => false,
1867
- 'limit_level' => 1,
1868
- 'info_wrapper' => true,
1869
- ) );
1870
-
1871
- if ( empty( $collected ) ) {
1872
- return;
1873
- }
1874
-
1875
- foreach ($collected as &$opt) {
1876
- switch ($opt['group']) {
1877
- case 'container':
1878
- // Check if has container options
1879
- {
1880
- $_collected = array();
1881
-
1882
- fw_collect_options( $_collected, $opt['option']['options'], array(
1883
- 'limit_option_types' => array(),
1884
- 'limit_container_types' => false,
1885
- 'limit_level' => 1,
1886
- 'limit' => 1,
1887
- 'info_wrapper' => false,
1888
- ) );
1889
-
1890
- $has_containers = !empty($_collected);
1891
-
1892
- unset($_collected);
1893
- }
1894
-
1895
- $children_data = array(
1896
- 'group' => 'container',
1897
- 'id' => $opt['id']
1898
- );
1899
-
1900
- $args = array(
1901
- 'title' => empty($opt['option']['title'])
1902
- ? fw_id_to_title($opt['id'])
1903
- : $opt['option']['title'],
1904
- 'description' => empty($opt['option']['desc'])
1905
- ? ''
1906
- : $opt['option']['desc'],
1907
- );
1908
-
1909
- if (isset($opt['option']['wp-customizer-args']) && is_array($opt['option']['wp-customizer-args'])) {
1910
- $args = array_merge($opt['option']['wp-customizer-args'], $args);
1911
- }
1912
-
1913
- if ($has_containers) {
1914
- if ($parent_data) {
1915
- trigger_error($opt['id'] .' panel can\'t have a parent ('. $parent_data['id'] .')', E_USER_WARNING);
1916
- break;
1917
- }
1918
-
1919
- $wp_customize->add_panel($opt['id'], $args);
1920
-
1921
- $children_data['customizer_type'] = 'panel';
1922
- } else {
1923
- if ($parent_data) {
1924
- if ($parent_data['customizer_type'] === 'panel') {
1925
- $args['panel'] = $parent_data['id'];
1926
- } else {
1927
- trigger_error($opt['id'] .' section can have only panel parent ('. $parent_data['id'] .')', E_USER_WARNING);
1928
- break;
1929
- }
1930
- }
1931
-
1932
- $wp_customize->add_section($opt['id'], $args);
1933
-
1934
- $children_data['customizer_type'] = 'section';
1935
- }
1936
-
1937
- $this->customizer_register_options(
1938
- $wp_customize,
1939
- $opt['option']['options'],
1940
- $children_data
1941
- );
1942
-
1943
- unset($children_data);
1944
- break;
1945
- case 'option':
1946
- $setting_id = $this->get_options_name_attr_prefix() .'['. $opt['id'] .']';
1947
-
1948
- {
1949
- $args_control = array(
1950
- 'label' => empty($opt['option']['label'])
1951
- ? fw_id_to_title($opt['id'])
1952
- : $opt['option']['label'],
1953
- 'description' => empty($opt['option']['desc'])
1954
- ? ''
1955
- : $opt['option']['desc'],
1956
- 'settings' => $setting_id,
1957
- );
1958
-
1959
- if (isset($opt['option']['wp-customizer-args']) && is_array($opt['option']['wp-customizer-args'])) {
1960
- $args_control = array_merge($opt['option']['wp-customizer-args'], $args_control);
1961
- }
1962
-
1963
- if ($parent_data) {
1964
- if ($parent_data['customizer_type'] === 'section') {
1965
- $args_control['section'] = $parent_data['id'];
1966
- } else {
1967
- trigger_error('Invalid control parent: '. $parent_data['customizer_type'], E_USER_WARNING);
1968
- break;
1969
- }
1970
- } else { // the option is not placed in a section, create a section automatically
1971
- $args_control['section'] = 'fw_option_auto_section_'. $opt['id'];
1972
-
1973
- $wp_customize->add_section($args_control['section'], array(
1974
- 'title' => empty($opt['option']['label'])
1975
- ? fw_id_to_title($opt['id'])
1976
- : $opt['option']['label'],
1977
- ));
1978
- }
1979
- }
1980
-
1981
- {
1982
- $args_setting = array(
1983
- 'default' => fw()->backend->option_type($opt['option']['type'])->get_value_from_input($opt['option'], null),
1984
- 'fw_option' => $opt['option'],
1985
- 'fw_option_id' => $opt['id'],
1986
- );
1987
-
1988
- if (isset($opt['option']['wp-customizer-setting-args']) && is_array($opt['option']['wp-customizer-setting-args'])) {
1989
- $args_setting = array_merge($opt['option']['wp-customizer-setting-args'], $args_setting);
1990
- }
1991
-
1992
- $wp_customize->add_setting(
1993
- new _FW_Customizer_Setting_Option(
1994
- $wp_customize,
1995
- $setting_id,
1996
- $args_setting
1997
- )
1998
- );
1999
-
2000
- unset($args_setting);
2001
- }
2002
-
2003
- // control must be registered after setting
2004
- $wp_customize->add_control(
2005
- new _FW_Customizer_Control_Option_Wrapper(
2006
- $wp_customize,
2007
- $opt['id'],
2008
- $args_control
2009
- )
2010
- );
2011
- break;
2012
- default:
2013
- trigger_error('Unknown group: '. $opt['group'], E_USER_WARNING);
2014
- }
2015
- }
2016
- }
2017
-
2018
- /**
2019
- * For e.g. an option-type was rendered using 'customizer' design,
2020
- * but inside it uses render_options() but it doesn't know the current render design
2021
- * and the options will be rendered with 'default' design.
2022
- * This method allows to specify the default design that will be used if not specified on render_options()
2023
- * @param null|string $design
2024
- * @internal
2025
- */
2026
- public function _set_default_render_design($design = null)
2027
- {
2028
- if (empty($design) || !in_array($design, $this->available_render_designs)) {
2029
- $this->default_render_design = 'default';
2030
- } else {
2031
- $this->default_render_design = $design;
2032
- }
2033
- }
2034
-
2035
- /**
2036
- * Get markdown parser with autoloading and caching
2037
- *
2038
- * Usage:
2039
- * fw()->backend->get_markdown_parser()
2040
- *
2041
- * @param bool $fresh_instance Whether to force return a fresh instance of the class
2042
- *
2043
- * @return Parsedown
2044
- *
2045
- * @since 2.6.9
2046
- */
2047
- public function get_markdown_parser($fresh_instance = false) {
2048
- if (! $this->markdown_parser || $fresh_instance) {
2049
- $this->markdown_parser = new Parsedown();
2050
- }
2051
-
2052
- return $this->markdown_parser;
2053
- }
2054
- }
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ /**
6
+ * Backend functionality
7
+ */
8
+ final class _FW_Component_Backend {
9
+
10
+ /** @var FW_Settings_Form */
11
+ private $settings_form;
12
+
13
+ private $available_render_designs = array(
14
+ 'default', 'taxonomy', 'customizer', 'empty'
15
+ );
16
+
17
+ private $default_render_design = 'default';
18
+
19
+ /**
20
+ * The singleton instance of Parsedown class that is used across
21
+ * whole framework.
22
+ *
23
+ * @since 2.6.9
24
+ */
25
+ private $markdown_parser = null;
26
+
27
+ /**
28
+ * Contains all option types
29
+ * @var FW_Option_Type[]
30
+ */
31
+ private $option_types = array();
32
+
33
+ /**
34
+ * @var FW_Option_Type_Undefined
35
+ */
36
+ private $undefined_option_type;
37
+
38
+ /**
39
+ * Store container types for registration, until they will be required
40
+ * @var array|false
41
+ * array Can have some pending container types in it
42
+ * false Container types already requested and was registered, so do not use pending anymore
43
+ */
44
+ private $container_types_pending_registration = array();
45
+
46
+ /**
47
+ * Contains all container types
48
+ * @var FW_Container_Type[]
49
+ */
50
+ private $container_types = array();
51
+
52
+ /**
53
+ * @var FW_Container_Type_Undefined
54
+ */
55
+ private $undefined_container_type;
56
+
57
+ private $static_registered = false;
58
+
59
+ /**
60
+ * @var FW_Access_Key
61
+ */
62
+ private $access_key;
63
+
64
+ /**
65
+ * @internal
66
+ */
67
+ public function _get_settings_page_slug() {
68
+ return 'fw-settings';
69
+ }
70
+
71
+ /**
72
+ * @return string
73
+ * @since 2.6.3
74
+ */
75
+ public function get_options_name_attr_prefix() {
76
+ return 'fw_options';
77
+ }
78
+
79
+ /**
80
+ * @return string
81
+ * @since 2.6.3
82
+ */
83
+ public function get_options_id_attr_prefix() {
84
+ return 'fw-option-';
85
+ }
86
+
87
+ private function get_current_edit_taxonomy() {
88
+ static $cache_current_taxonomy_data = null;
89
+
90
+ if ( $cache_current_taxonomy_data !== null ) {
91
+ return $cache_current_taxonomy_data;
92
+ }
93
+
94
+ $result = array(
95
+ 'taxonomy' => null,
96
+ 'term_id' => 0,
97
+ );
98
+
99
+ do {
100
+ if ( ! is_admin() ) {
101
+ break;
102
+ }
103
+
104
+ // code from /wp-admin/admin.php line 110
105
+ {
106
+ if ( isset( $_REQUEST['taxonomy'] ) && taxonomy_exists( $_REQUEST['taxonomy'] ) ) {
107
+ $taxnow = $_REQUEST['taxonomy'];
108
+ } else {
109
+ $taxnow = '';
110
+ }
111
+ }
112
+
113
+ if ( empty( $taxnow ) ) {
114
+ break;
115
+ }
116
+
117
+ $result['taxonomy'] = $taxnow;
118
+
119
+ if ( empty( $_REQUEST['tag_ID'] ) ) {
120
+ return $result;
121
+ }
122
+
123
+ // code from /wp-admin/edit-tags.php
124
+ {
125
+ $tag_ID = (int) $_REQUEST['tag_ID'];
126
+ }
127
+
128
+ $result['term_id'] = $tag_ID;
129
+ } while ( false );
130
+
131
+ $cache_current_taxonomy_data = $result;
132
+
133
+ return $cache_current_taxonomy_data;
134
+ }
135
+
136
+ public function __construct() {}
137
+
138
+ /**
139
+ * @internal
140
+ */
141
+ public function _init() {
142
+ if ( is_admin() ) {
143
+ $this->settings_form = new FW_Settings_Form_Theme('theme-settings');
144
+ }
145
+
146
+ $this->add_actions();
147
+ $this->add_filters();
148
+ }
149
+
150
+ /**
151
+ * @internal
152
+ */
153
+ public function _after_components_init() {}
154
+
155
+ private function get_access_key()
156
+ {
157
+ if (!$this->access_key) {
158
+ $this->access_key = new FW_Access_Key('fw_backend');
159
+ }
160
+
161
+ return $this->access_key;
162
+ }
163
+
164
+ private function add_actions() {
165
+ if ( is_admin() ) {
166
+ add_action('add_meta_boxes', array($this, '_action_create_post_meta_boxes'), 10, 2);
167
+ add_action('init', array($this, '_action_init'), 20);
168
+ add_action('admin_enqueue_scripts', array($this, '_action_admin_register_scripts'),
169
+ /**
170
+ * Usually when someone register/enqueue a script/style to be used in other places
171
+ * in 'admin_enqueue_scripts' actions with default (not set) priority 10, they use priority 9.
172
+ * Use here priority 8, in case those scripts/styles used in actions with priority 9
173
+ * are using scripts/styles registered here
174
+ */
175
+ 8
176
+ );
177
+ add_action('admin_enqueue_scripts', array($this, '_action_admin_enqueue_scripts'),
178
+ /**
179
+ * In case some custom defined option types are using script/styles registered
180
+ * in actions with default priority 10 (make sure the enqueue is executed after register)
181
+ */
182
+ 11
183
+ );
184
+
185
+ // render and submit options from javascript
186
+ {
187
+ add_action('wp_ajax_fw_backend_options_render', array($this, '_action_ajax_options_render'));
188
+ add_action('wp_ajax_fw_backend_options_get_values', array($this, '_action_ajax_options_get_values'));
189
+ add_action('wp_ajax_fw_backend_options_get_values_json', array($this, '_action_ajax_options_get_values_json'));
190
+ }
191
+ }
192
+
193
+ add_action('save_post', array($this, '_action_save_post'), 7, 3);
194
+ add_action('wp_restore_post_revision', array($this, '_action_restore_post_revision'), 10, 2);
195
+ add_action('_wp_put_post_revision', array($this, '_action__wp_put_post_revision'));
196
+
197
+ add_action('customize_register', array($this, '_action_customize_register'), 7);
198
+ }
199
+
200
+ private function add_filters() {
201
+ if ( is_admin() ) {
202
+ add_filter('admin_footer_text', array($this, '_filter_admin_footer_text'), 11);
203
+ add_filter('update_footer', array($this, '_filter_footer_version'), 11);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @param string|FW_Option_Type $option_type_class
209
+ * @param string|null $type
210
+ *
211
+ * @internal
212
+ */
213
+ private function register_option_type( $option_type_class, $type = null ) {
214
+ if ( $type == null ) {
215
+ try {
216
+ $type = $this->get_instance( $option_type_class )->get_type();
217
+ } catch ( FW_Option_Type_Exception_Invalid_Class $exception ) {
218
+ if ( ! is_subclass_of( $option_type_class, 'FW_Option_Type' ) ) {
219
+ trigger_error( 'Invalid option type class ' . get_class( $option_type_class ), E_USER_WARNING );
220
+
221
+ return;
222
+ }
223
+ }
224
+ }
225
+
226
+ if ( isset( $this->option_types[ $type ] ) ) {
227
+ trigger_error( 'Option type "' . $type . '" already registered', E_USER_WARNING );
228
+
229
+ return;
230
+ }
231
+
232
+ $this->option_types[$type] = $option_type_class;
233
+ }
234
+
235
+ /**
236
+ * @param string|FW_Container_Type $container_type_class
237
+ * @param string|null $type
238
+ *
239
+ * @internal
240
+ */
241
+ private function register_container_type( $container_type_class, $type = null ) {
242
+ if ( $type == null ) {
243
+ try {
244
+ $type = $this->get_instance( $container_type_class )->get_type();
245
+ } catch ( FW_Option_Type_Exception_Invalid_Class $exception ) {
246
+ if ( ! is_subclass_of( $container_type_class, 'FW_Container_Type' ) ) {
247
+ trigger_error( 'Invalid container type class ' . get_class( $container_type_class ), E_USER_WARNING );
248
+
249
+ return;
250
+ }
251
+ }
252
+ }
253
+
254
+ if ( isset( $this->container_types[ $type ] ) ) {
255
+ trigger_error( 'Container type "' . $type . '" already registered', E_USER_WARNING );
256
+
257
+ return;
258
+ }
259
+
260
+ $this->container_types[$type] = $container_type_class;
261
+ }
262
+
263
+ private function register_static() {
264
+ if (
265
+ !doing_action('admin_enqueue_scripts')
266
+ &&
267
+ !did_action('admin_enqueue_scripts')
268
+ ) {
269
+ /**
270
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
271
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
272
+ * So as a result some handles will not be equeued because of not registered dependecies.
273
+ */
274
+ return;
275
+ }
276
+
277
+ if ( $this->static_registered ) {
278
+ return;
279
+ }
280
+
281
+ /**
282
+ * Register styles/scripts only in admin area, on frontend it's not allowed to use styles/scripts from framework backend core
283
+ * because they are meant to be used only in backend and can be changed in the future.
284
+ * If you want to use a style/script from framework backend core, copy it to your theme and enqueue as a theme style/script.
285
+ */
286
+ if ( ! is_admin() ) {
287
+ $this->static_registered = true;
288
+
289
+ return;
290
+ }
291
+
292
+ wp_register_script(
293
+ 'fw-events',
294
+ fw_get_framework_directory_uri( '/static/js/fw-events.js' ),
295
+ array(),
296
+ fw()->manifest->get_version(),
297
+ true
298
+ );
299
+
300
+ wp_register_script(
301
+ 'fw-ie-fixes',
302
+ fw_get_framework_directory_uri( '/static/js/ie-fixes.js' ),
303
+ array(),
304
+ fw()->manifest->get_version(),
305
+ true
306
+ );
307
+
308
+ {
309
+ wp_register_style(
310
+ 'qtip',
311
+ fw_get_framework_directory_uri( '/static/libs/qtip/css/jquery.qtip.min.css' ),
312
+ array(),
313
+ fw()->manifest->get_version()
314
+ );
315
+ wp_register_script(
316
+ 'qtip',
317
+ fw_get_framework_directory_uri( '/static/libs/qtip/jquery.qtip.min.js' ),
318
+ array( 'jquery' ),
319
+ fw()->manifest->get_version()
320
+ );
321
+ }
322
+
323
+ /**
324
+ * Important!
325
+ * Call wp_enqueue_media() before wp_enqueue_script('fw') (or using 'fw' in your script dependencies)
326
+ * otherwise fw.OptionsModal won't work
327
+ */
328
+ {
329
+ wp_register_style(
330
+ 'fw',
331
+ fw_get_framework_directory_uri( '/static/css/fw.css' ),
332
+ array( 'qtip' ),
333
+ fw()->manifest->get_version()
334
+ );
335
+
336
+ wp_register_script(
337
+ 'fw-reactive-options-registry',
338
+ fw_get_framework_directory_uri(
339
+ '/static/js/fw-reactive-options-registry.js'
340
+ ),
341
+ array('fw', 'fw-events'),
342
+ false
343
+ );
344
+
345
+ wp_register_script(
346
+ 'fw-reactive-options-simple-options',
347
+ fw_get_framework_directory_uri(
348
+ '/static/js/fw-reactive-options-simple-options.js'
349
+ ),
350
+ array('fw', 'fw-events', 'fw-reactive-options-undefined-option'),
351
+ false
352
+ );
353
+
354
+ wp_register_script(
355
+ 'fw-reactive-options-undefined-option',
356
+ fw_get_framework_directory_uri(
357
+ '/static/js/fw-reactive-options-undefined-option.js'
358
+ ),
359
+ array(
360
+ 'fw', 'fw-events', 'fw-reactive-options-registry'
361
+ ),
362
+ false
363
+ );
364
+
365
+ wp_register_script(
366
+ 'fw-reactive-options',
367
+ fw_get_framework_directory_uri('/static/js/fw-reactive-options.js'),
368
+ array(
369
+ 'fw', 'fw-events', 'fw-reactive-options-undefined-option',
370
+ 'fw-reactive-options-simple-options'
371
+ ),
372
+ false
373
+ );
374
+
375
+ wp_register_script(
376
+ 'fw',
377
+ fw_get_framework_directory_uri( '/static/js/fw.js' ),
378
+ array( 'jquery', 'fw-events', 'backbone', 'qtip' ),
379
+ fw()->manifest->get_version(),
380
+ false // false fixes https://github.com/ThemeFuse/Unyson/issues/1625#issuecomment-224219454
381
+ );
382
+
383
+ wp_localize_script( 'fw', '_fw_localized', array(
384
+ 'FW_URI' => fw_get_framework_directory_uri(),
385
+ 'SITE_URI' => site_url(),
386
+ 'LOADER_URI' => apply_filters( 'fw_loader_image', fw_get_framework_directory_uri() . '/static/img/logo.svg' ),
387
+ 'l10n' => array_merge(
388
+ $l10n = array(
389
+ 'modal_save_btn' => __( 'Save', 'fw' ),
390
+ 'done' => __( 'Done', 'fw' ),
391
+ 'ah_sorry' => __( 'Ah, Sorry', 'fw' ),
392
+ 'reset' => __( 'Reset', 'fw' ),
393
+ 'apply' => __( 'Apply', 'fw' ),
394
+ 'cancel' => __( 'Cancel', 'fw' ),
395
+ 'ok' => __( 'Ok', 'fw' )
396
+ ),
397
+ /**
398
+ * fixes https://github.com/ThemeFuse/Unyson/issues/2381
399
+ * @since 2.6.14
400
+ */
401
+ apply_filters('fw_js_l10n', $l10n)
402
+ ),
403
+ 'options_modal' => array(
404
+ /** @since 2.6.13 */
405
+ 'default_reset_bnt_disabled' => apply_filters('fw:option-modal:default:reset-btn-disabled', false)
406
+ ),
407
+ ) );
408
+ }
409
+
410
+ {
411
+ wp_register_style(
412
+ 'fw-backend-options',
413
+ fw_get_framework_directory_uri( '/static/css/backend-options.css' ),
414
+ array( 'fw' ),
415
+ fw()->manifest->get_version()
416
+ );
417
+
418
+ wp_register_script(
419
+ 'fw-backend-options',
420
+ fw_get_framework_directory_uri( '/static/js/backend-options.js' ),
421
+ array( 'fw', 'fw-events', 'fw-reactive-options', 'postbox', 'jquery-ui-tabs' ),
422
+ fw()->manifest->get_version(),
423
+ true
424
+ );
425
+
426
+ wp_localize_script( 'fw', '_fw_backend_options_localized', array(
427
+ 'lazy_tabs' => fw()->theme->get_config('lazy_tabs')
428
+ ) );
429
+ }
430
+
431
+ {
432
+ wp_register_style(
433
+ 'fw-selectize',
434
+ fw_get_framework_directory_uri( '/static/libs/selectize/selectize.css' ),
435
+ array(),
436
+ fw()->manifest->get_version()
437
+ );
438
+ wp_register_script(
439
+ 'fw-selectize',
440
+ fw_get_framework_directory_uri( '/static/libs/selectize/selectize.min.js' ),
441
+ array( 'jquery', 'fw-ie-fixes' ),
442
+ fw()->manifest->get_version(),
443
+ true
444
+ );
445
+ }
446
+
447
+ {
448
+ wp_register_script(
449
+ 'fw-mousewheel',
450
+ fw_get_framework_directory_uri( '/static/libs/mousewheel/jquery.mousewheel.min.js' ),
451
+ array( 'jquery' ),
452
+ fw()->manifest->get_version(),
453
+ true
454
+ );
455
+ }
456
+
457
+ {
458
+ wp_register_style(
459
+ 'fw-jscrollpane',
460
+ fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.css' ),
461
+ array(),
462
+ fw()->manifest->get_version()
463
+ );
464
+ wp_register_script( 'fw-jscrollpane',
465
+ fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.min.js' ),
466
+ array( 'jquery', 'fw-mousewheel' ),
467
+ fw()->manifest->get_version(),
468
+ true
469
+ );
470
+ }
471
+
472
+ wp_register_style(
473
+ 'font-awesome',
474
+ fw_get_framework_directory_uri( '/static/libs/font-awesome/css/font-awesome.min.css' ),
475
+ array(),
476
+ fw()->manifest->get_version()
477
+ );
478
+ /**
479
+ * backwards compatibility, in case extensions are not up-to-date
480
+ * todo: remove in next major version
481
+ * https://github.com/ThemeFuse/Unyson/issues/2198
482
+ * @deprecated
483
+ */
484
+ wp_register_style('fw-font-awesome', fw_get_framework_directory_uri( '/static/libs/font-awesome/css/font-awesome.min.css' ));
485
+
486
+ wp_register_script(
487
+ 'backbone-relational',
488
+ fw_get_framework_directory_uri( '/static/libs/backbone-relational/backbone-relational.js' ),
489
+ array( 'backbone' ),
490
+ fw()->manifest->get_version(),
491
+ true
492
+ );
493
+
494
+ wp_register_script(
495
+ 'fw-uri',
496
+ fw_get_framework_directory_uri( '/static/libs/uri/URI.js' ),
497
+ array(),
498
+ fw()->manifest->get_version(),
499
+ true
500
+ );
501
+
502
+ wp_register_script(
503
+ 'fw-moment',
504
+ /**
505
+ * IMPORTANT: At the end of the script is added this line:
506
+ * moment.locale(document.documentElement.lang.slice(0, 2)); // fixes https://github.com/ThemeFuse/Unyson/issues/1767
507
+ */
508
+ fw_get_framework_directory_uri( '/static/libs/moment/moment-with-locales.min.js' ),
509
+ array(),
510
+ fw()->manifest->get_version(),
511
+ true
512
+ );
513
+
514
+ wp_register_script(
515
+ 'fw-form-helpers',
516
+ fw_get_framework_directory_uri( '/static/js/fw-form-helpers.js' ),
517
+ array( 'jquery' ),
518
+ fw()->manifest->get_version(),
519
+ true
520
+ );
521
+
522
+ wp_register_style(
523
+ 'fw-unycon',
524
+ fw_get_framework_directory_uri( '/static/libs/unycon/unycon.css' ),
525
+ array(),
526
+ fw()->manifest->get_version()
527
+ );
528
+
529
+ $this->static_registered = true;
530
+ }
531
+
532
+ /**
533
+ * @param $class
534
+ *
535
+ * @return FW_Option_Type
536
+ * @throws FW_Option_Type_Exception_Invalid_Class
537
+ */
538
+ protected function get_instance( $class ) {
539
+ if ( ! class_exists( $class )
540
+ || (
541
+ ! is_subclass_of( $class, 'FW_Option_Type' )
542
+ &&
543
+ ! is_subclass_of( $class, 'FW_Container_Type' )
544
+ )
545
+ ) {
546
+ throw new FW_Option_Type_Exception_Invalid_Class( $class );
547
+ }
548
+
549
+ return new $class;
550
+ }
551
+
552
+ public function _filter_admin_footer_text( $html ) {
553
+ if (
554
+ (
555
+ current_user_can( 'update_themes' )
556
+ ||
557
+ current_user_can( 'update_plugins' )
558
+ )
559
+ &&
560
+ fw_current_screen_match(array(
561
+ 'only' => array(
562
+ array('parent_base' => fw()->extensions->manager->get_page_slug()) // Unyson Extensions page
563
+ )
564
+ ))
565
+ ) {
566
+ return ( empty( $html ) ? '' : $html . '<br/>' )
567
+ . '<em>'
568
+ . str_replace(
569
+ array(
570
+ '{wp_review_link}',
571
+ '{facebook_share_link}',
572
+ '{twitter_share_link}',
573
+ ),
574
+ array(
575
+ fw_html_tag('a', array(
576
+ 'target' => '_blank',
577
+ 'href' => 'https://wordpress.org/support/view/plugin-reviews/unyson?filter=5#postform',
578
+ ), __('leave a review', 'fw')),
579
+ fw_html_tag('a', array(
580
+ 'target' => '_blank',
581
+ 'href' => 'https://www.facebook.com/sharer/sharer.php?'. http_build_query(array(
582
+ 'u' => 'http://unyson.io',
583
+ )),
584
+ 'onclick' => 'return !window.open(this.href, \'Facebook\', \'width=640,height=300\')',
585
+ ), __('Facebook', 'fw')),
586
+ fw_html_tag('a', array(
587
+ 'target' => '_blank',
588
+ 'href' => 'https://twitter.com/home?'. http_build_query(array(
589
+ 'status' => __('Unyson WordPress Framework is the fastest and easiest way to develop a premium theme. I highly recommend it', 'fw')
590
+ .' http://unyson.io/ #UnysonWP',
591
+ )),
592
+ 'onclick' => 'return !window.open(this.href, \'Twitter\', \'width=640,height=430\')',
593
+ ), __('Twitter', 'fw')),
594
+ ),
595
+ __('If you like Unyson, {wp_review_link}, share on {facebook_share_link} or {twitter_share_link}.', 'fw')
596
+ )
597
+ . '</em>';
598
+ } else {
599
+ return $html;
600
+ }
601
+ }
602
+
603
+ /**
604
+ * Print framework version in the admin footer
605
+ *
606
+ * @param string $html
607
+ *
608
+ * @return string
609
+ * @internal
610
+ */
611
+ public function _filter_footer_version( $html ) {
612
+ if ( current_user_can( 'update_themes' ) || current_user_can( 'update_plugins' ) ) {
613
+ return ( empty( $html ) ? '' : $html . ' | ' ) . fw()->manifest->get_name() . ' ' . fw()->manifest->get_version();
614
+ } else {
615
+ return $html;
616
+ }
617
+ }
618
+
619
+ /**
620
+ * @param string $post_type
621
+ * @param WP_Post $post
622
+ */
623
+ public function _action_create_post_meta_boxes( $post_type, $post ) {
624
+ if ( 'comment' === $post_type || ( isset( $_GET['vc_action'] ) && $_GET['vc_action'] === 'vc_inline' ) ) {
625
+ /**
626
+ * 1. https://github.com/ThemeFuse/Unyson/issues/3052
627
+ * 2. This is wrong, comment is not a post(type) it is stored in a separate db table and has a separate meta (wp_comments and wp_commentmeta)
628
+ */
629
+ return;
630
+ }
631
+
632
+ $options = fw()->theme->get_post_options( $post_type );
633
+
634
+ if ( empty( $options ) ) {
635
+ return;
636
+ }
637
+
638
+ $collected = array();
639
+
640
+ fw_collect_options( $collected, $options, array(
641
+ 'limit_option_types' => false,
642
+ 'limit_container_types' => false,
643
+ 'limit_level' => 1,
644
+ ) );
645
+
646
+ if ( empty( $collected ) ) {
647
+ return;
648
+ }
649
+
650
+ $values = fw_get_db_post_option( $post->ID );
651
+
652
+ foreach ( $collected as $id => &$option ) {
653
+ if ( isset( $option['options'] ) && ( $option['type'] === 'box' || $option['type'] === 'group' ) ) {
654
+ $context = isset( $option['context'] ) ? $option['context'] : 'normal';
655
+ $priority = isset( $option['priority'] ) ? $option['priority'] : 'default';
656
+
657
+ add_meta_box(
658
+ "fw-options-box-{$id}",
659
+ empty( $option['title'] ) ? ' ' : $option['title'],
660
+ array( $this, 'render_meta_box' ),
661
+ $post_type,
662
+ $context,
663
+ $priority,
664
+ $this->render_options( $option['options'], $values )
665
+ );
666
+ } else { // this is not a box, wrap it in auto-generated box
667
+ add_meta_box(
668
+ 'fw-options-box:auto-generated:' . time() . ':' . fw_unique_increment(),
669
+ ' ',
670
+ array( $this, 'render_meta_box' ),
671
+ $post_type,
672
+ 'normal',
673
+ 'default',
674
+ $this->render_options( array( $id => $option ), $values )
675
+ );
676
+ }
677
+ }
678
+ }
679
+
680
+ public function render_meta_box( $post, $args ) {
681
+ echo $args['args'];
682
+ }
683
+
684
+ /**
685
+ * @param object $term
686
+ */
687
+ public function _action_create_taxonomy_options( $term ) {
688
+ $options = fw()->theme->get_taxonomy_options( $term->taxonomy );
689
+
690
+ if ( empty( $options ) ) {
691
+ return;
692
+ }
693
+
694
+ $collected = array();
695
+
696
+ fw_collect_options( $collected, $options, array(
697
+ 'limit_option_types' => false,
698
+ 'limit_container_types' => false,
699
+ 'limit_level' => 1,
700
+ ) );
701
+
702
+ if ( empty( $collected ) ) {
703
+ return;
704
+ }
705
+
706
+ $values = fw_get_db_term_option( $term->term_id, $term->taxonomy );
707
+
708
+ // fixes word_press style: .form-field input { width: 95% }
709
+ echo '<style type="text/css">.fw-option-type-radio input, .fw-option-type-checkbox input { width: auto; }</style>';
710
+
711
+ do_action( 'fw_backend_options_render:taxonomy:before' );
712
+ echo $this->render_options( $collected, $values, array(), 'taxonomy' );
713
+ do_action( 'fw_backend_options_render:taxonomy:after' );
714
+ }
715
+
716
+ /**
717
+ * @param string $taxonomy
718
+ */
719
+ public function _action_create_add_taxonomy_options( $taxonomy ) {
720
+ $options = fw()->theme->get_taxonomy_options( $taxonomy );
721
+
722
+ if ( empty( $options ) ) {
723
+ return;
724
+ }
725
+
726
+ $collected = array();
727
+
728
+ fw_collect_options( $collected, $options, array(
729
+ 'limit_option_types' => false,
730
+ 'limit_container_types' => false,
731
+ 'limit_level' => 1,
732
+ ) );
733
+
734
+ if ( empty( $collected ) ) {
735
+ return;
736
+ }
737
+
738
+ // fixes word_press style: .form-field input { width: 95% }
739
+ echo '<style type="text/css">.fw-option-type-radio input, .fw-option-type-checkbox input { width: auto; }</style>';
740
+
741
+ do_action( 'fw_backend_options_render:taxonomy:before' );
742
+
743
+ echo '<div class="fw-force-xs">';
744
+ echo $this->render_options( $collected, array(), array(), 'taxonomy' );
745
+ echo '</div>';
746
+
747
+ do_action( 'fw_backend_options_render:taxonomy:after' );
748
+
749
+ echo '<script type="text/javascript">'
750
+ .'jQuery(function($){'
751
+ .' $("#submit").on("click", function(){'
752
+ .' $("html, body").animate({ scrollTop: $("#col-left").offset().top });'
753
+ .' });'
754
+ .'});'
755
+ .'</script>';
756
+ }
757
+
758
+ public function _action_init() {
759
+ $current_edit_taxonomy = $this->get_current_edit_taxonomy();
760
+
761
+ if ( $current_edit_taxonomy['taxonomy'] ) {
762
+ add_action(
763
+ $current_edit_taxonomy['taxonomy'] . '_edit_form',
764
+ array( $this, '_action_create_taxonomy_options' )
765
+ );
766
+
767
+ if (fw()->theme->get_config('taxonomy_create_has_unyson_options', true)) {
768
+ add_action(
769
+ $current_edit_taxonomy['taxonomy'] . '_add_form_fields',
770
+ array( $this, '_action_create_add_taxonomy_options' )
771
+ );
772
+ }
773
+ }
774
+
775
+ if ( ! empty( $_POST ) ) {
776
+ // is form submit
777
+ add_action( 'edited_term', array( $this, '_action_term_edit' ), 10, 3 );
778
+
779
+ if ($current_edit_taxonomy['taxonomy']) {
780
+ add_action(
781
+ 'create_' . $current_edit_taxonomy['taxonomy'],
782
+ array($this, '_action_save_taxonomy_fields')
783
+ );
784
+ }
785
+ }
786
+ }
787
+
788
+ /**
789
+ * Save meta from $_POST to fw options (post meta)
790
+ * @param int $post_id
791
+ * @param WP_Post $post
792
+ * @param bool $update
793
+ */
794
+ public function _action_save_post( $post_id, $post, $update ) {
795
+ if (
796
+ isset($_POST['post_ID'])
797
+ &&
798
+ intval($_POST['post_ID']) === intval($post_id)
799
+ &&
800
+ !empty($_POST[ $this->get_options_name_attr_prefix() ]) // this happens on Quick Edit
801
+ ) {
802
+ /**
803
+ * This happens on regular post form submit
804
+ * All data from $_POST belongs this $post
805
+ * so we save them in its post meta
806
+ */
807
+
808
+ static $post_options_save_happened = false;
809
+ if ($post_options_save_happened) {
810
+ /**
811
+ * Prevent multiple options save for same post
812
+ * It can happen from a recursion or wp_update_post() for same post id
813
+ */
814
+ return;
815
+ } else {
816
+ $post_options_save_happened = true;
817
+ }
818
+
819
+ $old_values = (array)fw_get_db_post_option($post_id);
820
+
821
+ fw_set_db_post_option(
822
+ $post_id,
823
+ null,
824
+ fw_get_options_values_from_input(
825
+ fw()->theme->get_post_options($post->post_type)
826
+ )
827
+ );
828
+
829
+ /**
830
+ * @deprecated
831
+ * Use the 'fw_post_options_update' action
832
+ */
833
+ do_action( 'fw_save_post_options', $post_id, $post, $old_values );
834
+ } elseif ($original_post_id = wp_is_post_autosave( $post_id )) {
835
+ do {
836
+ $parent = get_post($post->post_parent);
837
+
838
+ if ( ! $parent instanceof WP_Post ) {
839
+ break;
840
+ }
841
+
842
+ if (
843
+ isset($_POST['post_ID'])
844
+ &&
845
+ intval($_POST['post_ID']) === intval($parent->ID)
846
+ ) {} else {
847
+ break;
848
+ }
849
+
850
+ if (empty($_POST[ $this->get_options_name_attr_prefix() ])) {
851
+ // this happens on Quick Edit
852
+ break;
853
+ }
854
+
855
+ fw_set_db_post_option(
856
+ $post->ID,
857
+ null,
858
+ fw_get_options_values_from_input(
859
+ fw()->theme->get_post_options($parent->post_type)
860
+ )
861
+ );
862
+ } while(false);
863
+ } elseif ($original_post_id = wp_is_post_revision( $post_id )) {
864
+ /**
865
+ * Do nothing, the
866
+ * - '_wp_put_post_revision'
867
+ * - 'wp_restore_post_revision'
868
+ * actions will handle this
869
+ */
870
+ } else {
871
+ /**
872
+ * This happens on:
873
+ * - post add (auto-draft): do nothing
874
+ * - revision restore: do nothing, that is handled by the 'wp_restore_post_revision' action
875
+ */
876
+ }
877
+ }
878
+
879
+ /**
880
+ * @param $post_id
881
+ * @param $revision_id
882
+ */
883
+ public function _action_restore_post_revision($post_id, $revision_id)
884
+ {
885
+ /**
886
+ * Copy options meta from revision to post
887
+ */
888
+ fw_set_db_post_option(
889
+ $post_id,
890
+ null,
891
+ (array)fw_get_db_post_option($revision_id, null, array())
892
+ );
893
+ }
894
+
895
+ /**
896
+ * @param $revision_id
897
+ */
898
+ public function _action__wp_put_post_revision($revision_id)
899
+ {
900
+ /**
901
+ * Copy options meta from post to revision
902
+ */
903
+ fw_set_db_post_option(
904
+ $revision_id,
905
+ null,
906
+ (array)fw_get_db_post_option(
907
+ wp_is_post_revision($revision_id),
908
+ null,
909
+ array()
910
+ )
911
+ );
912
+ }
913
+
914
+ /**
915
+ * Update all post meta `fw_option:<option-id>` with values from post options that has the 'save-in-separate-meta' parameter
916
+ *
917
+ * @param int $post_id
918
+ *
919
+ * @return bool
920
+ * @deprecated since 2.5.0
921
+ */
922
+ public function _sync_post_separate_meta( $post_id ) {
923
+ if ( ! ( $post_type = get_post_type( $post_id ) ) ) {
924
+ return false;
925
+ }
926
+
927
+ $meta_prefix = 'fw_option:';
928
+ $only_options = fw_extract_only_options( fw()->theme->get_post_options( $post_type ) );
929
+ $separate_meta_options = array();
930
+
931
+ // Collect all options that needs to be saved in separate meta
932
+ {
933
+ $options_values = fw_get_db_post_option( $post_id );
934
+
935
+ foreach ($only_options as $option_id => $option) {
936
+ if (
937
+ isset( $option['save-in-separate-meta'] )
938
+ &&
939
+ $option['save-in-separate-meta']
940
+ &&
941
+ array_key_exists( $option_id, $options_values )
942
+ ) {
943
+ if (defined('WP_DEBUG') && WP_DEBUG) {
944
+ FW_Flash_Messages::add(
945
+ 'save-in-separate-meta:deprecated',
946
+ '<p>The <code>save-in-separate-meta</code> option parameter is <strong>deprecated</strong>.</p>'
947
+ .'<p>Please replace</p>'
948
+ .'<pre>\'save-in-separate-meta\' => true</pre>'
949
+ .'<p>with</p>'
950
+ .'<pre>\'fw-storage\' => array('
951
+ ."\n 'type' => 'post-meta',"
952
+ ."\n 'post-meta' => 'fw_option:{your-option-id}',"
953
+ ."\n)</pre>"
954
+ .'<p>in <code>{theme}'. fw_get_framework_customizations_dir_rel_path('/theme/options/posts/'. $post_type .'.php') .'</code></p>'
955
+ .'<p><a href="'. esc_attr('http://manual.unyson.io/en/latest/options/storage.html#content') .'" target="_blank">'. esc_html__('Info about fw-storage', 'fw') .'</a></p>',
956
+ 'warning'
957
+ );
958
+ }
959
+
960
+ $separate_meta_options[ $meta_prefix . $option_id ] = $options_values[ $option_id ];
961
+ }
962
+ }
963
+
964
+ unset( $options_values );
965
+ }
966
+
967
+ // Delete meta that starts with $meta_prefix
968
+ {
969
+ /** @var wpdb $wpdb */
970
+ global $wpdb;
971
+
972
+ foreach (
973
+ $wpdb->get_results(
974
+ $wpdb->prepare(
975
+ "SELECT meta_key " .
976
+ "FROM {$wpdb->postmeta} " .
977
+ "WHERE meta_key LIKE %s AND post_id = %d",
978
+ $wpdb->esc_like( $meta_prefix ) . '%',
979
+ $post_id
980
+ )
981
+ ) as $row
982
+ ) {
983
+ if (
984
+ array_key_exists( $row->meta_key, $separate_meta_options )
985
+ ||
986
+ ( // skip options containing 'fw-storage'
987
+ ($option_id = substr($row->meta_key, 10))
988
+ &&
989
+ isset($only_options[$option_id]['fw-storage'])
990
+ )
991
+ ) {
992
+ /**
993
+ * This meta exists and will be updated below.
994
+ * Do not delete for performance reasons, instead of delete->insert will be performed only update
995
+ */
996
+ continue;
997
+ } else {
998
+ // this option does not exist anymore
999
+ delete_post_meta( $post_id, $row->meta_key );
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ foreach ( $separate_meta_options as $meta_key => $option_value ) {
1005
+ fw_update_post_meta($post_id, $meta_key, $option_value );
1006
+ }
1007
+
1008
+ return true;
1009
+ }
1010
+
1011
+ /**
1012
+ * @param int $term_id
1013
+ */
1014
+ public function _action_save_taxonomy_fields( $term_id ) {
1015
+ if (
1016
+ isset( $_POST['action'] )
1017
+ &&
1018
+ 'add-tag' === $_POST['action']
1019
+ &&
1020
+ isset( $_POST['taxonomy'] )
1021
+ &&
1022
+ ($taxonomy = get_taxonomy( $_POST['taxonomy'] ))
1023
+ &&
1024
+ current_user_can($taxonomy->cap->edit_terms)
1025
+ ) { /* ok */ } else { return; }
1026
+
1027
+ $options = fw()->theme->get_taxonomy_options( $taxonomy->name );
1028
+ if ( empty( $options ) ) {
1029
+ return;
1030
+ }
1031
+
1032
+ fw_set_db_term_option(
1033
+ $term_id,
1034
+ $taxonomy->name,
1035
+ null,
1036
+ fw_get_options_values_from_input($options)
1037
+ );
1038
+
1039
+ do_action( 'fw_save_term_options', $term_id, $taxonomy->name, array() );
1040
+ }
1041
+
1042
+ public function _action_term_edit( $term_id, $tt_id, $taxonomy ) {
1043
+ if (
1044
+ isset( $_POST['action'] )
1045
+ &&
1046
+ 'editedtag' === $_POST['action']
1047
+ &&
1048
+ isset( $_POST['taxonomy'] )
1049
+ &&
1050
+ ($taxonomy = get_taxonomy( $_POST['taxonomy'] ))
1051
+ &&
1052
+ current_user_can($taxonomy->cap->edit_terms)
1053
+ ) { /* ok */ } else { return; }
1054
+
1055
+ if (intval(FW_Request::POST('tag_ID')) != $term_id) {
1056
+ // the $_POST values belongs to another term, do not save them into this one
1057
+ return;
1058
+ }
1059
+
1060
+ $options = fw()->theme->get_taxonomy_options( $taxonomy->name );
1061
+ if ( empty( $options ) ) {
1062
+ return;
1063
+ }
1064
+
1065
+ $old_values = (array) fw_get_db_term_option( $term_id, $taxonomy->name );
1066
+
1067
+ fw_set_db_term_option(
1068
+ $term_id,
1069
+ $taxonomy->name,
1070
+ null,
1071
+ fw_get_options_values_from_input($options)
1072
+ );
1073
+
1074
+ do_action( 'fw_save_term_options', $term_id, $taxonomy->name, $old_values );
1075
+ }
1076
+
1077
+ public function _action_admin_register_scripts() {
1078
+ $this->register_static();
1079
+ }
1080
+
1081
+ public function _action_admin_enqueue_scripts() {
1082
+ /**
1083
+ * Enqueue settings options static in <head>
1084
+ * @see FW_Settings_Form_Theme::_action_admin_enqueue_scripts()
1085
+ */
1086
+
1087
+ /**
1088
+ * Enqueue post options static in <head>
1089
+ */
1090
+ {
1091
+ if ( 'post' === get_current_screen()->base && get_the_ID() ) {
1092
+ fw()->backend->enqueue_options_static(
1093
+ fw()->theme->get_post_options( get_post_type() )
1094
+ );
1095
+
1096
+ do_action( 'fw_admin_enqueue_scripts:post', get_post() );
1097
+ }
1098
+ }
1099
+
1100
+ /**
1101
+ * Enqueue term options static in <head>
1102
+ */
1103
+ {
1104
+ if (
1105
+ in_array(get_current_screen()->base, array('edit-tags', 'term'), true)
1106
+ &&
1107
+ get_current_screen()->taxonomy
1108
+ ) {
1109
+ fw()->backend->enqueue_options_static(
1110
+ fw()->theme->get_taxonomy_options( get_current_screen()->taxonomy )
1111
+ );
1112
+
1113
+ do_action( 'fw_admin_enqueue_scripts:term', get_current_screen()->taxonomy );
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ /**
1119
+ * Render options html from input json
1120
+ *
1121
+ * POST vars:
1122
+ * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1123
+ * - values: {option_id: value, option_id: {...}, ...} // Optional // Object
1124
+ * - data: {id_prefix: 'fw_options-a-b-', name_prefix: 'fw_options[a][b]'} // Optional // Object
1125
+ */
1126
+ public function _action_ajax_options_render() {
1127
+ // options
1128
+ {
1129
+ if ( ! isset( $_POST['options'] ) ) {
1130
+ wp_send_json_error( array(
1131
+ 'message' => 'No options'
1132
+ ) );
1133
+ }
1134
+
1135
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1136
+
1137
+ if ( ! $options ) {
1138
+ wp_send_json_error( array(
1139
+ 'message' => 'Wrong options'
1140
+ ) );
1141
+ }
1142
+ }
1143
+
1144
+ // values
1145
+ {
1146
+ if ( isset( $_POST['values'] ) ) {
1147
+ $values = FW_Request::POST( 'values' );
1148
+
1149
+ if (is_string($values)) {
1150
+ $values = json_decode($values, true);
1151
+ }
1152
+ } else {
1153
+ $values = array();
1154
+ }
1155
+
1156
+ $values = array_intersect_key($values, fw_extract_only_options($options));
1157
+ }
1158
+
1159
+ // data
1160
+ {
1161
+ if ( isset( $_POST['data'] ) ) {
1162
+ $data = FW_Request::POST( 'data' );
1163
+ } else {
1164
+ $data = array();
1165
+ }
1166
+ }
1167
+
1168
+ wp_send_json_success( array(
1169
+ 'html' => fw()->backend->render_options( $options, $values, $data ),
1170
+ /** @since 2.6.1 */
1171
+ 'default_values' => fw_get_options_values_from_input($options, array()),
1172
+ ) );
1173
+ }
1174
+
1175
+ /**
1176
+ * Get options values from html generated with 'fw_backend_options_render' ajax action
1177
+ *
1178
+ * POST vars:
1179
+ * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1180
+ * - fw_options... // Use a jQuery "ajax form submit" to emulate real form submit
1181
+ *
1182
+ * Tip: Inside form html, add: <input type="hidden" name="options" value="[...json...]">
1183
+ */
1184
+ public function _action_ajax_options_get_values() {
1185
+ // options
1186
+ {
1187
+ if ( ! isset( $_POST['options'] ) ) {
1188
+ wp_send_json_error( array(
1189
+ 'message' => 'No options'
1190
+ ) );
1191
+ }
1192
+
1193
+ $options = FW_Request::POST( 'options' );
1194
+
1195
+ if (is_string( $options )) {
1196
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1197
+ }
1198
+
1199
+ if ( ! $options ) {
1200
+ wp_send_json_error( array(
1201
+ 'message' => 'Wrong options'
1202
+ ) );
1203
+ }
1204
+ }
1205
+
1206
+ // name_prefix
1207
+ {
1208
+ if ( isset( $_POST['name_prefix'] ) ) {
1209
+ $name_prefix = FW_Request::POST( 'name_prefix' );
1210
+ } else {
1211
+ $name_prefix = $this->get_options_name_attr_prefix();
1212
+ }
1213
+ }
1214
+
1215
+ wp_send_json_success( array(
1216
+ 'values' => fw_get_options_values_from_input(
1217
+ $options,
1218
+ FW_Request::POST( fw_html_attr_name_to_array_multi_key( $name_prefix ), array() )
1219
+ )
1220
+ ) );
1221
+ }
1222
+
1223
+ /**
1224
+ * Get options values from html generated with 'fw_backend_options_render' ajax action
1225
+ *
1226
+ * POST vars:
1227
+ * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1228
+ * - values: {option_id: {...}}
1229
+ *
1230
+ * Tip: Inside form html, add: <input type="hidden" name="options" value="[...json...]">
1231
+ */
1232
+ public function _action_ajax_options_get_values_json() {
1233
+ // options
1234
+ {
1235
+ if ( ! isset( $_POST['options'] ) ) {
1236
+ wp_send_json_error( array(
1237
+ 'message' => 'No options'
1238
+ ) );
1239
+ }
1240
+
1241
+ $options = FW_Request::POST( 'options' );
1242
+
1243
+ if (is_string( $options )) {
1244
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1245
+ }
1246
+
1247
+ if ( ! $options ) {
1248
+ wp_send_json_error( array(
1249
+ 'message' => 'Wrong options'
1250
+ ) );
1251
+ }
1252
+ }
1253
+
1254
+ // values
1255
+ {
1256
+ if ( ! isset( $_POST['values'] ) ) {
1257
+ wp_send_json_error( array(
1258
+ 'message' => 'No values'
1259
+ ) );
1260
+ }
1261
+
1262
+ $values = FW_Request::POST( 'values' );
1263
+
1264
+ if (is_string( $values )) {
1265
+ $values = json_decode( FW_Request::POST( 'values' ), true );
1266
+ }
1267
+
1268
+ if (! is_array($values)) {
1269
+ if ( ! $values ) {
1270
+ wp_send_json_error(array(
1271
+ 'message' => 'Wrong values'
1272
+ ));
1273
+ }
1274
+ }
1275
+ }
1276
+
1277
+ wp_send_json_success( array(
1278
+ 'values' => fw_get_options_values_from_input(
1279
+ $options,
1280
+ $values
1281
+ )
1282
+ ) );
1283
+ }
1284
+
1285
+ /**
1286
+ * Render options array and return the generated HTML
1287
+ *
1288
+ * @param array $options
1289
+ * @param array $values Correct values returned by fw_get_options_values_from_input()
1290
+ * @param array $options_data {id_prefix => ..., name_prefix => ...}
1291
+ * @param string $design
1292
+ *
1293
+ * @return string HTML
1294
+ */
1295
+ public function render_options( $options, $values = array(), $options_data = array(), $design = null ) {
1296
+ if (empty($design)) {
1297
+ $design = $this->default_render_design;
1298
+ }
1299
+
1300
+ if (
1301
+ !doing_action('admin_enqueue_scripts')
1302
+ &&
1303
+ !did_action('admin_enqueue_scripts')
1304
+ ) {
1305
+ /**
1306
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1307
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
1308
+ * So as a result some handles will not be equeued because of not registered dependecies.
1309
+ */
1310
+ } else {
1311
+ /**
1312
+ * register scripts and styles
1313
+ * in case if this method is called before enqueue_scripts action
1314
+ * and option types has some of these in their dependencies
1315
+ */
1316
+ $this->register_static();
1317
+
1318
+ wp_enqueue_media();
1319
+ wp_enqueue_style( 'fw-backend-options' );
1320
+ wp_enqueue_script( 'fw-backend-options' );
1321
+ }
1322
+
1323
+ $collected = array();
1324
+
1325
+ fw_collect_options( $collected, $options, array(
1326
+ 'limit_option_types' => false,
1327
+ 'limit_container_types' => false,
1328
+ 'limit_level' => 1,
1329
+ 'info_wrapper' => true,
1330
+ ) );
1331
+
1332
+ if ( empty( $collected ) ) {
1333
+ return false;
1334
+ }
1335
+
1336
+ $html = '';
1337
+
1338
+ $option = reset( $collected );
1339
+
1340
+ $collected_type = array(
1341
+ 'group' => $option['group'],
1342
+ 'type' => $option['option']['type'],
1343
+ );
1344
+ $collected_type_options = array(
1345
+ $option['id'] => &$option['option']
1346
+ );
1347
+
1348
+ while ( $collected_type_options ) {
1349
+ $option = next( $collected );
1350
+
1351
+ if ( $option ) {
1352
+ if (
1353
+ $option['group'] === $collected_type['group']
1354
+ &&
1355
+ $option['option']['type'] === $collected_type['type']
1356
+ ) {
1357
+ $collected_type_options[ $option['id'] ] = &$option['option'];
1358
+ continue;
1359
+ }
1360
+ }
1361
+
1362
+ switch ( $collected_type['group'] ) {
1363
+ case 'container':
1364
+ if ($design === 'taxonomy') {
1365
+ $html .= fw_render_view(
1366
+ fw_get_framework_directory('/views/backend-container-design-'. $design .'.php'),
1367
+ array(
1368
+ 'type' => $collected_type['type'],
1369
+ 'html' => $this->container_type($collected_type['type'])->render(
1370
+ $collected_type_options, $values, $options_data
1371
+ ),
1372
+ )
1373
+ );
1374
+ } else {
1375
+ $html .= $this->container_type($collected_type['type'])->render(
1376
+ $collected_type_options, $values, $options_data
1377
+ );
1378
+ }
1379
+ break;
1380
+ case 'option':
1381
+ foreach ( $collected_type_options as $id => &$_option ) {
1382
+ $data = $options_data; // do not change directly to not affect next loops
1383
+
1384
+ $data['value'] = isset( $values[ $id ] ) ? $values[ $id ] : null;
1385
+
1386
+ $html .= $this->render_option(
1387
+ $id,
1388
+ $_option,
1389
+ $data,
1390
+ $design
1391
+ );
1392
+ }
1393
+ unset($_option);
1394
+ break;
1395
+ default:
1396
+ $html .= '<p><em>' . __( 'Unknown collected group', 'fw' ) . ': ' . $collected_type['group'] . '</em></p>';
1397
+ }
1398
+
1399
+ unset( $collected_type, $collected_type_options );
1400
+
1401
+ if ( $option ) {
1402
+ $collected_type = array(
1403
+ 'group' => $option['group'],
1404
+ 'type' => $option['option']['type'],
1405
+ );
1406
+ $collected_type_options = array(
1407
+ $option['id'] => &$option['option']
1408
+ );
1409
+ } else {
1410
+ $collected_type_options = array();
1411
+ }
1412
+ }
1413
+
1414
+ return $html;
1415
+ }
1416
+
1417
+ /**
1418
+ * Enqueue options static
1419
+ *
1420
+ * Useful when you have dynamic options html on the page (for e.g. options modal)
1421
+ * and in order to initialize that html properly, the option types scripts styles must be enqueued on the page
1422
+ *
1423
+ * @param array $options
1424
+ */
1425
+ public function enqueue_options_static( $options ) {
1426
+ static $static_enqueue = true;
1427
+
1428
+ if (
1429
+ !doing_action('admin_enqueue_scripts')
1430
+ &&
1431
+ !did_action('admin_enqueue_scripts')
1432
+ ) {
1433
+ /**
1434
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1435
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
1436
+ * So as a result some handles will not be equeued because of not registered dependecies.
1437
+ */
1438
+ return;
1439
+ } else {
1440
+ /**
1441
+ * register scripts and styles
1442
+ * in case if this method is called before enqueue_scripts action
1443
+ * and option types has some of these in their dependencies
1444
+ */
1445
+ if ($static_enqueue) {
1446
+ $this->register_static();
1447
+
1448
+ wp_enqueue_media();
1449
+ wp_enqueue_style( 'fw-backend-options' );
1450
+ wp_enqueue_script( 'fw-backend-options' );
1451
+
1452
+ $static_enqueue = false;
1453
+ }
1454
+ }
1455
+
1456
+ $collected = array();
1457
+
1458
+ fw_collect_options( $collected, $options, array(
1459
+ 'limit_option_types' => false,
1460
+ 'limit_container_types' => false,
1461
+ 'limit_level' => 0,
1462
+ 'callback' => array(__CLASS__, '_callback_fw_collect_options_enqueue_static'),
1463
+ ) );
1464
+
1465
+ unset($collected);
1466
+ }
1467
+
1468
+ /**
1469
+ * @internal
1470
+ * @param array $data
1471
+ */
1472
+ public static function _callback_fw_collect_options_enqueue_static($data) {
1473
+ if ($data['group'] === 'option') {
1474
+ fw()->backend->option_type($data['option']['type'])->enqueue_static($data['id'], $data['option']);
1475
+ } elseif ($data['group'] === 'container') {
1476
+ fw()->backend->container_type($data['option']['type'])->enqueue_static($data['id'], $data['option']);
1477
+ }
1478
+ }
1479
+
1480
+ /**
1481
+ * Render option enclosed in backend design
1482
+ *
1483
+ * @param string $id
1484
+ * @param array $option
1485
+ * @param array $data
1486
+ * @param string $design default or taxonomy
1487
+ *
1488
+ * @return string
1489
+ */
1490
+ public function render_option( $id, $option, $data = array(), $design = null ) {
1491
+
1492
+ $maybe_forced_design = fw()->backend->option_type( $option['type'] )->get_forced_render_design();
1493
+
1494
+ if (empty($design)) {
1495
+ $design = $this->default_render_design;
1496
+ }
1497
+
1498
+ if ($maybe_forced_design) {
1499
+ $design = $maybe_forced_design;
1500
+ }
1501
+
1502
+ if (
1503
+ !doing_action('admin_enqueue_scripts')
1504
+ &&
1505
+ !did_action('admin_enqueue_scripts')
1506
+ ) {
1507
+ /**
1508
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1509
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
1510
+ * So as a result some handles will not be equeued because of not registered dependecies.
1511
+ */
1512
+ } else {
1513
+ $this->register_static();
1514
+ }
1515
+
1516
+
1517
+ if ( ! in_array( $design, $this->available_render_designs ) ) {
1518
+ trigger_error( 'Invalid render design specified: ' . $design, E_USER_WARNING );
1519
+ $design = 'post';
1520
+ }
1521
+
1522
+ if ( ! isset( $data['id_prefix'] ) ) {
1523
+ $data['id_prefix'] = $this->get_options_id_attr_prefix();
1524
+ }
1525
+
1526
+ $data = apply_filters(
1527
+ 'fw:backend:option-render:data',
1528
+ $data
1529
+ );
1530
+
1531
+ return fw_render_view(fw_get_framework_directory('/views/backend-option-design-'. $design .'.php'), array(
1532
+ 'id' => $id,
1533
+ 'option' => $option,
1534
+ 'data' => $data,
1535
+ ) );
1536
+ }
1537
+
1538
+ /**
1539
+ * Render a meta box
1540
+ *
1541
+ * @param string $id
1542
+ * @param string $title
1543
+ * @param string $content HTML
1544
+ * @param array $other Optional elements
1545
+ *
1546
+ * @return string Generated meta box html
1547
+ */
1548
+ public function render_box( $id, $title, $content, $other = array() ) {
1549
+ if ( ! function_exists( 'add_meta_box' ) ) {
1550
+ trigger_error( 'Try call this method later (\'admin_init\' action), add_meta_box() function does not exists yet.',
1551
+ E_USER_WARNING );
1552
+
1553
+ return '';
1554
+ }
1555
+
1556
+ $other = array_merge( array(
1557
+ 'html_before_title' => false,
1558
+ 'html_after_title' => false,
1559
+ 'attr' => array(),
1560
+ ), $other );
1561
+
1562
+ {
1563
+ $placeholders = array(
1564
+ 'id' => '{{meta_box_id}}',
1565
+ 'title' => '{{meta_box_title}}',
1566
+ 'content' => '{{meta_box_content}}',
1567
+ );
1568
+
1569
+ // other placeholders
1570
+ {
1571
+ $placeholders['html_before_title'] = '{{meta_box_html_before_title}}';
1572
+ $placeholders['html_after_title'] = '{{meta_box_html_after_title}}';
1573
+ $placeholders['attr'] = '{{meta_box_attr}}';
1574
+ $placeholders['attr_class'] = '{{meta_box_attr_class}}';
1575
+ }
1576
+ }
1577
+
1578
+ $cache_key = 'fw_meta_box_template';
1579
+
1580
+ try {
1581
+ $meta_box_template = FW_Cache::get( $cache_key );
1582
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
1583
+ $temp_screen_id = 'fw-temp-meta-box-screen-id-' . fw_unique_increment();
1584
+ $context = 'normal';
1585
+
1586
+ add_meta_box(
1587
+ $placeholders['id'],
1588
+ $placeholders['title'],
1589
+ array( $this, 'render_meta_box' ),
1590
+ $temp_screen_id,
1591
+ $context,
1592
+ 'default',
1593
+ $placeholders['content']
1594
+ );
1595
+
1596
+ ob_start();
1597
+
1598
+ do_meta_boxes( $temp_screen_id, $context, null );
1599
+
1600
+ $meta_box_template = ob_get_clean();
1601
+
1602
+ remove_meta_box( $id, $temp_screen_id, $context );
1603
+
1604
+ // remove wrapper div, leave only meta box div
1605
+ {
1606
+ // <div ...>
1607
+ {
1608
+ $meta_box_template = str_replace(
1609
+ '<div id="' . $context . '-sortables" class="meta-box-sortables">',
1610
+ '',
1611
+ $meta_box_template
1612
+ );
1613
+ }
1614
+
1615
+ // </div>
1616
+ {
1617
+ $meta_box_template = explode( '</div>', $meta_box_template );
1618
+ array_pop( $meta_box_template );
1619
+ $meta_box_template = implode( '</div>', $meta_box_template );
1620
+ }
1621
+ }
1622
+
1623
+ // add 'fw-postbox' class and some attr related placeholders
1624
+ $meta_box_template = str_replace(
1625
+ 'class="postbox',
1626
+ $placeholders['attr'] . ' class="postbox fw-postbox' . $placeholders['attr_class'],
1627
+ $meta_box_template
1628
+ );
1629
+
1630
+ // add html_before|after_title placeholders
1631
+ {
1632
+ $meta_box_template = str_replace(
1633
+ '<span>' . $placeholders['title'] . '</span>',
1634
+
1635
+ /**
1636
+ * used <small> not <span> because there is a lot of css and js
1637
+ * that thinks inside <h2 class="hndle"> there is only one <span>
1638
+ * so do not brake their logic
1639
+ */
1640
+ '<small class="fw-html-before-title">' . $placeholders['html_before_title'] . '</small>' .
1641
+ '<span>' . $placeholders['title'] . '</span>' .
1642
+ '<small class="fw-html-after-title">' . $placeholders['html_after_title'] . '</small>',
1643
+
1644
+ $meta_box_template
1645
+ );
1646
+ }
1647
+
1648
+ FW_Cache::set( $cache_key, $meta_box_template );
1649
+ }
1650
+
1651
+ // prepare attributes
1652
+ {
1653
+ $attr_class = '';
1654
+ if ( isset( $other['attr']['class'] ) ) {
1655
+ $attr_class = ' ' . $other['attr']['class'];
1656
+
1657
+ unset( $other['attr']['class'] );
1658
+ }
1659
+
1660
+ unset( $other['attr']['id'] );
1661
+ }
1662
+
1663
+ // replace placeholders with data/content
1664
+ return str_replace(
1665
+ array(
1666
+ $placeholders['id'],
1667
+ $placeholders['title'],
1668
+ $placeholders['content'],
1669
+ $placeholders['html_before_title'],
1670
+ $placeholders['html_after_title'],
1671
+ $placeholders['attr'],
1672
+ $placeholders['attr_class'],
1673
+ ),
1674
+ array(
1675
+ esc_attr( $id ),
1676
+ $title,
1677
+ $content,
1678
+ $other['html_before_title'],
1679
+ $other['html_after_title'],
1680
+ fw_attr_to_html( $other['attr'] ),
1681
+ esc_attr( $attr_class )
1682
+ ),
1683
+ $meta_box_template
1684
+ );
1685
+ }
1686
+
1687
+ /**
1688
+ * @param FW_Access_Key $access_key
1689
+ * @param string|FW_Option_Type $option_type_class
1690
+ *
1691
+ * @internal
1692
+ */
1693
+ public function _register_option_type( FW_Access_Key $access_key, $option_type_class, $type= null ) {
1694
+ if ( $access_key->get_key() !== 'fw_option_type' ) {
1695
+ trigger_error( 'Call denied', E_USER_ERROR );
1696
+ }
1697
+
1698
+ $this->register_option_type( $option_type_class, $type );
1699
+ }
1700
+
1701
+ /**
1702
+ * @param FW_Access_Key $access_key
1703
+ * @param string|FW_Container_Type $container_type_class
1704
+ *
1705
+ * @internal
1706
+ */
1707
+ public function _register_container_type( FW_Access_Key $access_key, $container_type_class ) {
1708
+ if ( $access_key->get_key() !== 'fw_container_type' ) {
1709
+ trigger_error( 'Call denied', E_USER_ERROR );
1710
+ }
1711
+
1712
+ $this->register_container_type( $container_type_class );
1713
+ }
1714
+
1715
+ /**
1716
+ * @param string $type
1717
+ * @return FW_Option_Type
1718
+ */
1719
+ public function option_type( $type ) {
1720
+ static $did_options_init = false;
1721
+ if ( ! $did_options_init ) {
1722
+ $did_options_init = true;
1723
+ do_action( 'fw_option_types_init' );
1724
+ }
1725
+
1726
+ if ( isset( $this->option_types[ $type ] ) ) {
1727
+ if (is_string($this->option_types[$type])) {
1728
+ $this->option_types[$type] = $this->get_instance($this->option_types[$type]);
1729
+ $this->option_types[$type]->_call_init($this->get_access_key());
1730
+ }
1731
+
1732
+ return $this->option_types[$type];
1733
+ } else {
1734
+ if ( is_admin() && apply_filters('fw_backend_undefined_option_type_warn_user', true, $type) ) {
1735
+ FW_Flash_Messages::add(
1736
+ 'fw-get-option-type-undefined-' . $type,
1737
+ sprintf( __( 'Undefined option type: %s', 'fw' ), $type ),
1738
+ 'warning'
1739
+ );
1740
+ }
1741
+
1742
+ if ( ! $this->undefined_option_type ) {
1743
+ $this->undefined_option_type = new FW_Option_Type_Undefined();
1744
+ }
1745
+
1746
+ return $this->undefined_option_type;
1747
+ }
1748
+ }
1749
+
1750
+ /**
1751
+ * Return an array with all option types names
1752
+ *
1753
+ * @return array
1754
+ *
1755
+ * @since 2.6.11
1756
+ */
1757
+ public function get_option_types() {
1758
+ $this->option_type('text'); // trigger init
1759
+ return array_keys( $this->option_types );
1760
+ }
1761
+
1762
+ /**
1763
+ * Return an array with all container types names
1764
+ *
1765
+ * @return array
1766
+ *
1767
+ * @since 2.6.11
1768
+ */
1769
+ public function get_container_types() {
1770
+ $this->container_type('box'); // trigger init
1771
+ return array_keys( $this->container_types );
1772
+ }
1773
+
1774
+ /**
1775
+ * @param string $type
1776
+ * @return FW_Container_Type
1777
+ */
1778
+ public function container_type( $type ) {
1779
+ static $did_containers_init = false;
1780
+ if ( ! $did_containers_init ) {
1781
+ $did_containers_init = true;
1782
+ do_action( 'fw_container_types_init' );
1783
+ }
1784
+
1785
+ if ( isset( $this->container_types[ $type ] ) ) {
1786
+ if ( is_string( $this->container_types[ $type ] ) ) {
1787
+ $this->container_types[ $type ] = $this->get_instance( $this->container_types[$type] );
1788
+ $this->container_types[ $type ]->_call_init( $this->get_access_key() );
1789
+ }
1790
+
1791
+ return $this->container_types[ $type ];
1792
+ } else {
1793
+ if ( is_admin() ) {
1794
+ FW_Flash_Messages::add(
1795
+ 'fw-get-container-type-undefined-' . $type,
1796
+ sprintf( __( 'Undefined container type: %s', 'fw' ), $type ),
1797
+ 'warning'
1798
+ );
1799
+ }
1800
+
1801
+ if ( ! $this->undefined_container_type ) {
1802
+ $this->undefined_container_type = new FW_Container_Type_Undefined();
1803
+ }
1804
+
1805
+ return $this->undefined_container_type;
1806
+ }
1807
+ }
1808
+
1809
+ /**
1810
+ * @param WP_Customize_Manager $wp_customize
1811
+ * @internal
1812
+ */
1813
+ public function _action_customize_register($wp_customize) {
1814
+ if (is_admin()) {
1815
+ add_action('admin_enqueue_scripts', array($this, '_action_enqueue_customizer_static'));
1816
+ }
1817
+
1818
+ $this->customizer_register_options(
1819
+ $wp_customize,
1820
+ fw()->theme->get_customizer_options()
1821
+ );
1822
+ }
1823
+
1824
+ /**
1825
+ * @internal
1826
+ */
1827
+ public function _action_enqueue_customizer_static()
1828
+ {
1829
+ {
1830
+ $options_for_enqueue = array();
1831
+ $customizer_options = fw()->theme->get_customizer_options();
1832
+
1833
+ /**
1834
+ * In customizer options is allowed to have container with unspecified (or not existing) 'type'
1835
+ * fw()->backend->enqueue_options_static() tries to enqueue both options and container static
1836
+ * not existing container types will throw notices.
1837
+ * To prevent that, extract and send it only options (without containers)
1838
+ */
1839
+ fw_collect_options($options_for_enqueue, $customizer_options, array(
1840
+ 'callback' => array(__CLASS__, '_callback_fw_collect_options_enqueue_static'),
1841
+ ));
1842
+
1843
+ unset($options_for_enqueue, $customizer_options);
1844
+ }
1845
+
1846
+ wp_enqueue_script(
1847
+ 'fw-backend-customizer',
1848
+ fw_get_framework_directory_uri( '/static/js/backend-customizer.js' ),
1849
+ array( 'jquery', 'fw-events', 'backbone', 'fw-backend-options' ),
1850
+ fw()->manifest->get_version(),
1851
+ true
1852
+ );
1853
+ wp_localize_script(
1854
+ 'fw-backend-customizer',
1855
+ '_fw_backend_customizer_localized',
1856
+ array(
1857
+ 'change_timeout' => apply_filters('fw_customizer_option_change_timeout', 333),
1858
+ )
1859
+ );
1860
+
1861
+ do_action('fw_admin_enqueue_scripts:customizer');
1862
+ }
1863
+
1864
+ /**
1865
+ * @param WP_Customize_Manager $wp_customize
1866
+ * @param array $options
1867
+ * @param array $parent_data {'type':'...','id':'...'}
1868
+ */
1869
+ private function customizer_register_options($wp_customize, $options, $parent_data = array()) {
1870
+ $collected = array();
1871
+
1872
+ fw_collect_options( $collected, $options, array(
1873
+ 'limit_option_types' => false,
1874
+ 'limit_container_types' => false,
1875
+ 'limit_level' => 1,
1876
+ 'info_wrapper' => true,
1877
+ ) );
1878
+
1879
+ if ( empty( $collected ) ) {
1880
+ return;
1881
+ }
1882
+
1883
+ foreach ($collected as &$opt) {
1884
+ switch ($opt['group']) {
1885
+ case 'container':
1886
+ // Check if has container options
1887
+ {
1888
+ $_collected = array();
1889
+
1890
+ fw_collect_options( $_collected, $opt['option']['options'], array(
1891
+ 'limit_option_types' => array(),
1892
+ 'limit_container_types' => false,
1893
+ 'limit_level' => 1,
1894
+ 'limit' => 1,
1895
+ 'info_wrapper' => false,
1896
+ ) );
1897
+
1898
+ $has_containers = !empty($_collected);
1899
+
1900
+ unset($_collected);
1901
+ }
1902
+
1903
+ $children_data = array(
1904
+ 'group' => 'container',
1905
+ 'id' => $opt['id']
1906
+ );
1907
+
1908
+ $args = array(
1909
+ 'title' => empty($opt['option']['title'])
1910
+ ? fw_id_to_title($opt['id'])
1911
+ : $opt['option']['title'],
1912
+ 'description' => empty($opt['option']['desc'])
1913
+ ? ''
1914
+ : $opt['option']['desc'],
1915
+ );
1916
+
1917
+ if (isset($opt['option']['wp-customizer-args']) && is_array($opt['option']['wp-customizer-args'])) {
1918
+ $args = array_merge($opt['option']['wp-customizer-args'], $args);
1919
+ }
1920
+
1921
+ if ($has_containers) {
1922
+ if ($parent_data) {
1923
+ trigger_error($opt['id'] .' panel can\'t have a parent ('. $parent_data['id'] .')', E_USER_WARNING);
1924
+ break;
1925
+ }
1926
+
1927
+ $wp_customize->add_panel($opt['id'], $args);
1928
+
1929
+ $children_data['customizer_type'] = 'panel';
1930
+ } else {
1931
+ if ($parent_data) {
1932
+ if ($parent_data['customizer_type'] === 'panel') {
1933
+ $args['panel'] = $parent_data['id'];
1934
+ } else {
1935
+ trigger_error($opt['id'] .' section can have only panel parent ('. $parent_data['id'] .')', E_USER_WARNING);
1936
+ break;
1937
+ }
1938
+ }
1939
+
1940
+ $wp_customize->add_section($opt['id'], $args);
1941
+
1942
+ $children_data['customizer_type'] = 'section';
1943
+ }
1944
+
1945
+ $this->customizer_register_options(
1946
+ $wp_customize,
1947
+ $opt['option']['options'],
1948
+ $children_data
1949
+ );
1950
+
1951
+ unset($children_data);
1952
+ break;
1953
+ case 'option':
1954
+ $setting_id = $this->get_options_name_attr_prefix() .'['. $opt['id'] .']';
1955
+
1956
+ {
1957
+ $args_control = array(
1958
+ 'label' => empty($opt['option']['label'])
1959
+ ? fw_id_to_title($opt['id'])
1960
+ : $opt['option']['label'],
1961
+ 'description' => empty($opt['option']['desc'])
1962
+ ? ''
1963
+ : $opt['option']['desc'],
1964
+ 'settings' => $setting_id,
1965
+ );
1966
+
1967
+ if (isset($opt['option']['wp-customizer-args']) && is_array($opt['option']['wp-customizer-args'])) {
1968
+ $args_control = array_merge($opt['option']['wp-customizer-args'], $args_control);
1969
+ }
1970
+
1971
+ if ($parent_data) {
1972
+ if ($parent_data['customizer_type'] === 'section') {
1973
+ $args_control['section'] = $parent_data['id'];
1974
+ } else {
1975
+ trigger_error('Invalid control parent: '. $parent_data['customizer_type'], E_USER_WARNING);
1976
+ break;
1977
+ }
1978
+ } else { // the option is not placed in a section, create a section automatically
1979
+ $args_control['section'] = 'fw_option_auto_section_'. $opt['id'];
1980
+
1981
+ $wp_customize->add_section($args_control['section'], array(
1982
+ 'title' => empty($opt['option']['label'])
1983
+ ? fw_id_to_title($opt['id'])
1984
+ : $opt['option']['label'],
1985
+ ));
1986
+ }
1987
+ }
1988
+
1989
+ {
1990
+ $args_setting = array(
1991
+ 'default' => fw()->backend->option_type($opt['option']['type'])->get_value_from_input($opt['option'], null),
1992
+ 'fw_option' => $opt['option'],
1993
+ 'fw_option_id' => $opt['id'],
1994
+ );
1995
+
1996
+ if (isset($opt['option']['wp-customizer-setting-args']) && is_array($opt['option']['wp-customizer-setting-args'])) {
1997
+ $args_setting = array_merge($opt['option']['wp-customizer-setting-args'], $args_setting);
1998
+ }
1999
+
2000
+ $wp_customize->add_setting(
2001
+ new _FW_Customizer_Setting_Option(
2002
+ $wp_customize,
2003
+ $setting_id,
2004
+ $args_setting
2005
+ )
2006
+ );
2007
+
2008
+ unset($args_setting);
2009
+ }
2010
+
2011
+ // control must be registered after setting
2012
+ $wp_customize->add_control(
2013
+ new _FW_Customizer_Control_Option_Wrapper(
2014
+ $wp_customize,
2015
+ $opt['id'],
2016
+ $args_control
2017
+ )
2018
+ );
2019
+ break;
2020
+ default:
2021
+ trigger_error('Unknown group: '. $opt['group'], E_USER_WARNING);
2022
+ }
2023
+ }
2024
+ }
2025
+
2026
+ /**
2027
+ * For e.g. an option-type was rendered using 'customizer' design,
2028
+ * but inside it uses render_options() but it doesn't know the current render design
2029
+ * and the options will be rendered with 'default' design.
2030
+ * This method allows to specify the default design that will be used if not specified on render_options()
2031
+ * @param null|string $design
2032
+ * @internal
2033
+ */
2034
+ public function _set_default_render_design($design = null)
2035
+ {
2036
+ if (empty($design) || !in_array($design, $this->available_render_designs)) {
2037
+ $this->default_render_design = 'default';
2038
+ } else {
2039
+ $this->default_render_design = $design;
2040
+ }
2041
+ }
2042
+
2043
+ /**
2044
+ * Get markdown parser with autoloading and caching
2045
+ *
2046
+ * Usage:
2047
+ * fw()->backend->get_markdown_parser()
2048
+ *
2049
+ * @param bool $fresh_instance Whether to force return a fresh instance of the class
2050
+ *
2051
+ * @return Parsedown
2052
+ *
2053
+ * @since 2.6.9
2054
+ */
2055
+ public function get_markdown_parser($fresh_instance = false) {
2056
+ if (! $this->markdown_parser || $fresh_instance) {
2057
+ $this->markdown_parser = new Parsedown();
2058
+ }
2059
+
2060
+ return $this->markdown_parser;
2061
+ }
2062
+ }
framework/core/components/backend/class-fw-settings-form-theme.php CHANGED
@@ -1,218 +1,218 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Used in fw()->backend
5
- * @internal
6
- */
7
- class FW_Settings_Form_Theme extends FW_Settings_Form {
8
- protected function _init() {
9
- $this
10
- ->set_is_ajax_submit( fw()->theme->get_config('settings_form_ajax_submit') )
11
- ->set_is_side_tabs( fw()->theme->get_config('settings_form_side_tabs') )
12
- ->set_string( 'title', __('Theme Settings', 'fw') );
13
-
14
- {
15
- add_action('admin_init', array($this, '_action_get_title_from_menu'));
16
- add_action('admin_menu', array($this, '_action_admin_menu'));
17
- add_action('admin_enqueue_scripts', array($this, '_action_admin_enqueue_scripts'),
18
- /**
19
- * In case some custom defined option types are using script/styles registered
20
- * in actions with default priority 10 (make sure the enqueue is executed after register)
21
- * @see _FW_Component_Backend::add_actions()
22
- */
23
- 11
24
- );
25
- }
26
- }
27
-
28
- public function get_options() {
29
- return fw()->theme->get_settings_options();
30
- }
31
-
32
- public function set_values($values) {
33
- fw_set_db_settings_option(null, $values);
34
-
35
- return $this;
36
- }
37
-
38
- public function get_values() {
39
- return fw_get_db_settings_option();
40
- }
41
-
42
- /**
43
- * User can overwrite Theme Settings menu, move it and change its title
44
- * extract that title from WP menu
45
- * @internal
46
- */
47
- public function _action_get_title_from_menu() {
48
- if ($this->get_is_side_tabs()) {
49
- $title = fw()->theme->manifest->get_name();
50
-
51
- if (fw()->theme->manifest->get('author')) {
52
- if (fw()->theme->manifest->get('author_uri')) {
53
- $title .= ' '. fw_html_tag('a', array(
54
- 'href' => fw()->theme->manifest->get('author_uri'),
55
- 'target' => '_blank'
56
- ), '<small>' . __('by', 'fw') . ' ' . fw()->theme->manifest->get('author') . '</small>');
57
- } else {
58
- $title .= ' <small>' . fw()->theme->manifest->get('author') . '</small>';
59
- }
60
- }
61
-
62
- $this->set_string('title', $title);
63
- } else {
64
- // Extract page title from menu title
65
- do {
66
- global $menu, $submenu;
67
-
68
- if (is_array($menu)) {
69
- foreach ($menu as $_menu) {
70
- if ($_menu[2] === fw()->backend->_get_settings_page_slug()) {
71
- $title = $_menu[0];
72
- break 2;
73
- }
74
- }
75
- }
76
-
77
- if (is_array($submenu)) {
78
- foreach ($submenu as $_menu) {
79
- foreach ($_menu as $_submenu) {
80
- if ($_submenu[2] === fw()->backend->_get_settings_page_slug()) {
81
- $title = $_submenu[0];
82
- break 3;
83
- }
84
- }
85
- }
86
- }
87
- } while(false);
88
-
89
- if (isset($title)) {
90
- $this->set_string('title', $title);
91
- }
92
- }
93
- }
94
-
95
- /**
96
- * @internal
97
- */
98
- public function _action_admin_menu() {
99
- $data = array(
100
- 'capability' => 'manage_options',
101
- 'slug' => fw()->backend->_get_settings_page_slug(),
102
- 'content_callback' => array( $this, 'render' ),
103
- );
104
-
105
- if ( ! current_user_can( $data['capability'] ) ) {
106
- return;
107
- }
108
-
109
- if ( ! fw()->theme->locate_path('/options/settings.php') ) {
110
- return;
111
- }
112
-
113
- /**
114
- * Collect $hookname that contains $data['slug'] before the action
115
- * and skip them in verification after action
116
- */
117
- {
118
- global $_registered_pages;
119
-
120
- $found_hooknames = array();
121
-
122
- if ( ! empty( $_registered_pages ) ) {
123
- foreach ( $_registered_pages as $hookname => $b ) {
124
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
125
- $found_hooknames[ $hookname ] = true;
126
- }
127
- }
128
- }
129
- }
130
-
131
- /**
132
- * Use this action if you what to add the settings page in a custom place in menu
133
- * Usage example http://pastebin.com/gvAjGRm1
134
- */
135
- do_action( 'fw_backend_add_custom_settings_menu', $data );
136
-
137
- /**
138
- * Check if settings menu was added in the action above
139
- */
140
- {
141
- $menu_exists = false;
142
-
143
- if ( ! empty( $_registered_pages ) ) {
144
- foreach ( $_registered_pages as $hookname => $b ) {
145
- if ( isset( $found_hooknames[ $hookname ] ) ) {
146
- continue;
147
- }
148
-
149
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
150
- $menu_exists = true;
151
- break;
152
- }
153
- }
154
- }
155
- }
156
-
157
- if ( $menu_exists ) {
158
- return;
159
- }
160
-
161
- add_theme_page(
162
- __( 'Theme Settings', 'fw' ),
163
- __( 'Theme Settings', 'fw' ),
164
- $data['capability'],
165
- $data['slug'],
166
- $data['content_callback']
167
- );
168
-
169
- add_action( 'admin_menu', array( $this, '_action_admin_change_theme_settings_order' ), 9999 );
170
- }
171
-
172
- /**
173
- * @internal
174
- */
175
- public function _action_admin_change_theme_settings_order() {
176
- global $submenu;
177
-
178
- if ( ! isset( $submenu['themes.php'] ) ) {
179
- // probably current user doesn't have this item in menu
180
- return;
181
- }
182
-
183
- $id = fw()->backend->_get_settings_page_slug();
184
- $index = null;
185
-
186
- foreach ( $submenu['themes.php'] as $key => $sm ) {
187
- if ( $sm[2] == $id ) {
188
- $index = $key;
189
- break;
190
- }
191
- }
192
-
193
- if ( ! empty( $index ) ) {
194
- $item = $submenu['themes.php'][ $index ];
195
- unset( $submenu['themes.php'][ $index ] );
196
- array_unshift( $submenu['themes.php'], $item );
197
- }
198
- }
199
-
200
- /**
201
- * @internal
202
- */
203
- public function _action_admin_enqueue_scripts()
204
- {
205
- global $plugin_page;
206
-
207
- /**
208
- * Enqueue settings options static in <head>
209
- */
210
- {
211
- if (fw()->backend->_get_settings_page_slug() === $plugin_page) {
212
- $this->enqueue_static();
213
-
214
- do_action('fw_admin_enqueue_scripts:settings');
215
- }
216
- }
217
- }
218
  }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Used in fw()->backend
5
+ * @internal
6
+ */
7
+ class FW_Settings_Form_Theme extends FW_Settings_Form {
8
+ protected function _init() {
9
+ $this
10
+ ->set_is_ajax_submit( fw()->theme->get_config('settings_form_ajax_submit') )
11
+ ->set_is_side_tabs( fw()->theme->get_config('settings_form_side_tabs') )
12
+ ->set_string( 'title', __('Theme Settings', 'fw') );
13
+
14
+ {
15
+ add_action('admin_init', array($this, '_action_get_title_from_menu'));
16
+ add_action('admin_menu', array($this, '_action_admin_menu'));
17
+ add_action('admin_enqueue_scripts', array($this, '_action_admin_enqueue_scripts'),
18
+ /**
19
+ * In case some custom defined option types are using script/styles registered
20
+ * in actions with default priority 10 (make sure the enqueue is executed after register)
21
+ * @see _FW_Component_Backend::add_actions()
22
+ */
23
+ 11
24
+ );
25
+ }
26
+ }
27
+
28
+ public function get_options() {
29
+ return fw()->theme->get_settings_options();
30
+ }
31
+
32
+ public function set_values($values) {
33
+ fw_set_db_settings_option(null, $values);
34
+
35
+ return $this;
36
+ }
37
+
38
+ public function get_values() {
39
+ return fw_get_db_settings_option();
40
+ }
41
+
42
+ /**
43
+ * User can overwrite Theme Settings menu, move it and change its title
44
+ * extract that title from WP menu
45
+ * @internal
46
+ */
47
+ public function _action_get_title_from_menu() {
48
+ if ($this->get_is_side_tabs()) {
49
+ $title = fw()->theme->manifest->get_name();
50
+
51
+ if (fw()->theme->manifest->get('author')) {
52
+ if (fw()->theme->manifest->get('author_uri')) {
53
+ $title .= ' '. fw_html_tag('a', array(
54
+ 'href' => fw()->theme->manifest->get('author_uri'),
55
+ 'target' => '_blank'
56
+ ), '<small>' . __('by', 'fw') . ' ' . fw()->theme->manifest->get('author') . '</small>');
57
+ } else {
58
+ $title .= ' <small>' . fw()->theme->manifest->get('author') . '</small>';
59
+ }
60
+ }
61
+
62
+ $this->set_string('title', $title);
63
+ } else {
64
+ // Extract page title from menu title
65
+ do {
66
+ global $menu, $submenu;
67
+
68
+ if (is_array($menu)) {
69
+ foreach ($menu as $_menu) {
70
+ if ($_menu[2] === fw()->backend->_get_settings_page_slug()) {
71
+ $title = $_menu[0];
72
+ break 2;
73
+ }
74
+ }
75
+ }
76
+
77
+ if (is_array($submenu)) {
78
+ foreach ($submenu as $_menu) {
79
+ foreach ($_menu as $_submenu) {
80
+ if ($_submenu[2] === fw()->backend->_get_settings_page_slug()) {
81
+ $title = $_submenu[0];
82
+ break 3;
83
+ }
84
+ }
85
+ }
86
+ }
87
+ } while(false);
88
+
89
+ if (isset($title)) {
90
+ $this->set_string('title', $title);
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * @internal
97
+ */
98
+ public function _action_admin_menu() {
99
+ $data = array(
100
+ 'capability' => 'manage_options',
101
+ 'slug' => fw()->backend->_get_settings_page_slug(),
102
+ 'content_callback' => array( $this, 'render' ),
103
+ );
104
+
105
+ if ( ! current_user_can( $data['capability'] ) ) {
106
+ return;
107
+ }
108
+
109
+ if ( ! fw()->theme->locate_path('/options/settings.php') ) {
110
+ return;
111
+ }
112
+
113
+ /**
114
+ * Collect $hookname that contains $data['slug'] before the action
115
+ * and skip them in verification after action
116
+ */
117
+ {
118
+ global $_registered_pages;
119
+
120
+ $found_hooknames = array();
121
+
122
+ if ( ! empty( $_registered_pages ) ) {
123
+ foreach ( $_registered_pages as $hookname => $b ) {
124
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
125
+ $found_hooknames[ $hookname ] = true;
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Use this action if you what to add the settings page in a custom place in menu
133
+ * Usage example http://pastebin.com/gvAjGRm1
134
+ */
135
+ do_action( 'fw_backend_add_custom_settings_menu', $data );
136
+
137
+ /**
138
+ * Check if settings menu was added in the action above
139
+ */
140
+ {
141
+ $menu_exists = false;
142
+
143
+ if ( ! empty( $_registered_pages ) ) {
144
+ foreach ( $_registered_pages as $hookname => $b ) {
145
+ if ( isset( $found_hooknames[ $hookname ] ) ) {
146
+ continue;
147
+ }
148
+
149
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
150
+ $menu_exists = true;
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ if ( $menu_exists ) {
158
+ return;
159
+ }
160
+
161
+ add_theme_page(
162
+ __( 'Theme Settings', 'fw' ),
163
+ __( 'Theme Settings', 'fw' ),
164
+ $data['capability'],
165
+ $data['slug'],
166
+ $data['content_callback']
167
+ );
168
+
169
+ add_action( 'admin_menu', array( $this, '_action_admin_change_theme_settings_order' ), 9999 );
170
+ }
171
+
172
+ /**
173
+ * @internal
174
+ */
175
+ public function _action_admin_change_theme_settings_order() {
176
+ global $submenu;
177
+
178
+ if ( ! isset( $submenu['themes.php'] ) ) {
179
+ // probably current user doesn't have this item in menu
180
+ return;
181
+ }
182
+
183
+ $id = fw()->backend->_get_settings_page_slug();
184
+ $index = null;
185
+
186
+ foreach ( $submenu['themes.php'] as $key => $sm ) {
187
+ if ( $sm[2] == $id ) {
188
+ $index = $key;
189
+ break;
190
+ }
191
+ }
192
+
193
+ if ( ! empty( $index ) ) {
194
+ $item = $submenu['themes.php'][ $index ];
195
+ unset( $submenu['themes.php'][ $index ] );
196
+ array_unshift( $submenu['themes.php'], $item );
197
+ }
198
+ }
199
+
200
+ /**
201
+ * @internal
202
+ */
203
+ public function _action_admin_enqueue_scripts()
204
+ {
205
+ global $plugin_page;
206
+
207
+ /**
208
+ * Enqueue settings options static in <head>
209
+ */
210
+ {
211
+ if (fw()->backend->_get_settings_page_slug() === $plugin_page) {
212
+ $this->enqueue_static();
213
+
214
+ do_action('fw_admin_enqueue_scripts:settings');
215
+ }
216
+ }
217
+ }
218
  }
framework/core/components/extensions.php CHANGED
@@ -1,685 +1,688 @@
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
- /** @var FW_Extension_Manifest[] All extensions manifests */
62
- private static $manifests = array();
63
-
64
- /**
65
- * @var null|_FW_Extensions_Manager
66
- */
67
- public $manager;
68
-
69
- /**
70
- * Option name that stores the active extensions array
71
- * @internal
72
- */
73
- public function _get_active_extensions_db_option_name()
74
- {
75
- return 'fw_active_extensions';
76
- }
77
-
78
- /**
79
- * @param null|string $extension_name Check if an extension is set as active in database
80
- * @internal
81
- * @return array|bool
82
- */
83
- public function _get_db_active_extensions($extension_name = null)
84
- {
85
- $extensions = get_option($this->_get_active_extensions_db_option_name(), array());
86
-
87
- if ($extension_name) {
88
- return isset($extensions[$extension_name]);
89
- } else {
90
- return $extensions;
91
- }
92
- }
93
-
94
- public function __construct() {
95
- $this->manager = new _FW_Extensions_Manager();
96
- }
97
-
98
- /**
99
- * @param string $extension_name
100
- * @param FW_Access_Key $access_key
101
- * @return FW_Extension_Manifest|null
102
- * @internal
103
- * @since 2.6.9
104
- */
105
- public static function _get_manifest($extension_name, FW_Access_Key $access_key) {
106
- if (!in_array($access_key->get_key(), array('extension', self::$access_key->get_key()), true)) {
107
- trigger_error('Method call denied', E_USER_ERROR);
108
- }
109
-
110
- if (isset(self::$all_extensions[$extension_name])) {
111
- if (!isset(self::$manifests[$extension_name])) {
112
- $manifest = fw_get_variables_from_file(
113
- self::$all_extensions[$extension_name]['path'] .'/manifest.php', array('manifest' => array())
114
- );
115
- $manifest = $manifest['manifest'];
116
-
117
- if (empty($manifest['name'])) {
118
- $manifest['name'] = fw_id_to_title($extension_name);
119
- }
120
-
121
- self::$manifests[$extension_name] = new FW_Extension_Manifest($manifest);
122
- }
123
-
124
- return self::$manifests[$extension_name];
125
- } else {
126
- return null;
127
- }
128
- }
129
-
130
- /**
131
- * Load extension from directory
132
- *
133
- * @param array $data
134
- */
135
- private static function load_extensions($data)
136
- {
137
- /**
138
- * Do not check all keys
139
- * if one not set, then sure others are not set (this is a private method)
140
- */
141
- if (!isset($data['all_extensions_tree'])) {
142
- $data['all_extensions_tree'] = &self::$all_extensions_tree;
143
- $data['all_extensions'] = &self::$all_extensions;
144
- $data['current_depth'] = 1;
145
- $data['rel_path'] = '';
146
- $data['parent'] = null;
147
- }
148
-
149
- $dirs = glob($data['path'] .'/*', GLOB_ONLYDIR);
150
-
151
- if (empty($dirs)) {
152
- return;
153
- }
154
-
155
- if ($data['current_depth'] > 1) {
156
- $customizations_locations = array();
157
-
158
- foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
159
- $customizations_locations[ $customization_path .'/extensions' ] = $customization_uri .'/extensions';
160
- }
161
-
162
- $data['customizations_locations'] = $customizations_locations;
163
- }
164
-
165
- foreach ($dirs as $extension_dir) {
166
- $extension_name = basename($extension_dir);
167
-
168
- {
169
- $customizations_locations = array();
170
-
171
- foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
172
- $customizations_locations[ $customization_path .'/'. $extension_name ] = $customization_uri .'/'. $extension_name;
173
- }
174
- }
175
-
176
- if (isset($data['all_extensions'][$extension_name])) {
177
- if ($data['all_extensions'][$extension_name]['parent'] !== $data['parent']) {
178
- // extension with the same name exists in another tree
179
- trigger_error(
180
- 'Extension "'. $extension_name .'" is already defined '.
181
- 'in "'. $data['all_extensions'][$extension_name]['path'] .'" '.
182
- 'found again in "'. $extension_dir .'"',
183
- E_USER_ERROR
184
- );
185
- }
186
-
187
- // this is a directory with customizations for an extension
188
-
189
- self::load_extensions(array(
190
- 'rel_path' => $data['rel_path'] .'/'. $extension_name .'/extensions',
191
- 'path' => $data['path'] .'/'. $extension_name .'/extensions',
192
- 'uri' => $data['uri'] .'/'. $extension_name .'/extensions',
193
- 'customizations_locations' => $customizations_locations,
194
-
195
- 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
196
- 'all_extensions' => &$data['all_extensions'],
197
- 'current_depth' => $data['current_depth'] + 1,
198
- 'parent' => $extension_name,
199
- ));
200
- } else {
201
- if (file_exists($extension_dir .'/manifest.php')) {
202
- $data['all_extensions_tree'][$extension_name] = array();
203
-
204
- self::$extension_to_all_tree[$extension_name] = &$data['all_extensions_tree'][$extension_name];
205
-
206
- $data['all_extensions'][$extension_name] = array(
207
- 'rel_path' => $data['rel_path'] .'/'. $extension_name,
208
- 'path' => $data['path'] .'/'. $extension_name,
209
- 'uri' => $data['uri'] .'/'. $extension_name,
210
- 'parent' => $data['parent'],
211
- 'depth' => $data['current_depth'],
212
- 'customizations_locations' => $customizations_locations,
213
- 'instance' => null, // created on activation
214
- );
215
- } else {
216
- /**
217
- * The manifest file does not exist, do not load this extension.
218
- * Maybe it's a directory with configurations for a not existing extension.
219
- */
220
- continue;
221
- }
222
-
223
- self::load_extensions(array(
224
- 'rel_path' => $data['all_extensions'][$extension_name]['rel_path'] .'/extensions',
225
- 'path' => $data['all_extensions'][$extension_name]['path'] .'/extensions',
226
- 'uri' => $data['all_extensions'][$extension_name]['uri'] .'/extensions',
227
- 'customizations_locations' => $customizations_locations,
228
-
229
- 'parent' => $extension_name,
230
- 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
231
- 'all_extensions' => &$data['all_extensions'],
232
- 'current_depth' => $data['current_depth'] + 1,
233
- ));
234
- }
235
- }
236
- }
237
-
238
- /**
239
- * Include file from all extension's locations: framework, parent, child
240
- * @param string|FW_Extension $extension
241
- * @param string $file_rel_path
242
- * @param bool $themeFirst
243
- * false - [framework, parent, child]
244
- * true - [child, parent, framework]
245
- * @param bool $onlyFirstFound
246
- */
247
- private static function include_extension_file_all_locations($extension, $file_rel_path, $themeFirst = false, $onlyFirstFound = false)
248
- {
249
- if (is_string($extension)) {
250
- $extension = fw()->extensions->get($extension);
251
- }
252
-
253
- $paths = $extension->get_customizations_locations();
254
- $paths[$extension->get_path()] = $extension->get_uri();
255
-
256
- if (!$themeFirst) {
257
- $paths = array_reverse($paths);
258
- }
259
-
260
- foreach ($paths as $path => $uri) {
261
- if (fw_include_file_isolated($path . $file_rel_path)) {
262
- if ($onlyFirstFound) {
263
- return;
264
- }
265
- }
266
- }
267
- }
268
-
269
- /**
270
- * Include all files from directory, from all extension's locations: framework, child, parent
271
- * @param string|FW_Extension $extension
272
- * @param string $dir_rel_path
273
- * @param bool $themeFirst
274
- * false - [framework, parent, child]
275
- * true - [child, parent, framework]
276
- */
277
- private static function include_extension_directory_all_locations($extension, $dir_rel_path, $themeFirst = false)
278
- {
279
- if (is_string($extension)) {
280
- $extension = fw()->extensions->get($extension);
281
- }
282
-
283
- $paths = $extension->get_customizations_locations();
284
- $paths[$extension->get_path()] = $extension->get_uri();
285
-
286
- if (!$themeFirst) {
287
- $paths = array_reverse($paths);
288
- }
289
-
290
- foreach ($paths as $path => $uri) {
291
- $files = glob($path . $dir_rel_path .'/*.php');
292
-
293
- if ($files) {
294
- foreach ($files as $dir_file_path) {
295
- fw_include_file_isolated($dir_file_path);
296
- }
297
- }
298
- }
299
- }
300
-
301
- public function get_locations()
302
- {
303
- $cache_key = 'fw_extensions_locations';
304
-
305
- try {
306
- return FW_Cache::get($cache_key);
307
- } catch (FW_Cache_Not_Found_Exception $e) {
308
- /**
309
- * { '/hello/world/extensions' => 'https://hello.com/world/extensions' }
310
- */
311
- $custom_locations = apply_filters('fw_extensions_locations', array());
312
-
313
- {
314
- $customizations_locations = array();
315
-
316
- if (is_child_theme()) {
317
- $customizations_locations[fw_get_stylesheet_customizations_directory('/extensions')]
318
- = fw_get_stylesheet_customizations_directory_uri('/extensions');
319
- }
320
-
321
- $customizations_locations[fw_get_template_customizations_directory('/extensions')]
322
- = fw_get_template_customizations_directory_uri('/extensions');
323
-
324
- $customizations_locations += $custom_locations;
325
- }
326
-
327
- $locations = array();
328
-
329
- $locations[ fw_get_framework_directory('/extensions') ] = array(
330
- 'path' => fw_get_framework_directory('/extensions'),
331
- 'uri' => fw_get_framework_directory_uri('/extensions'),
332
- 'customizations_locations' => $customizations_locations,
333
- 'is' => array(
334
- 'framework' => true,
335
- 'custom' => false,
336
- 'theme' => false,
337
- ),
338
- );
339
-
340
- foreach ($custom_locations as $path => $uri) {
341
- unset($customizations_locations[$path]);
342
- $locations[ $path ] = array(
343
- 'path' => $path,
344
- 'uri' => $uri,
345
- 'customizations_locations' => $customizations_locations,
346
- 'is' => array(
347
- 'framework' => false,
348
- 'custom' => true,
349
- 'theme' => false,
350
- ),
351
- );
352
- }
353
-
354
- array_pop($customizations_locations);
355
- $locations[ fw_get_template_customizations_directory('/extensions') ] = array(
356
- 'path' => fw_get_template_customizations_directory('/extensions'),
357
- 'uri' => fw_get_template_customizations_directory_uri('/extensions'),
358
- 'customizations_locations' => $customizations_locations,
359
- 'is' => array(
360
- 'framework' => false,
361
- 'custom' => false,
362
- 'theme' => true,
363
- ),
364
- );
365
-
366
- if (is_child_theme()) {
367
- array_pop($customizations_locations);
368
- $locations[ fw_get_stylesheet_customizations_directory('/extensions') ] = array(
369
- 'path' => fw_get_stylesheet_customizations_directory('/extensions'),
370
- 'uri' => fw_get_stylesheet_customizations_directory_uri('/extensions'),
371
- 'customizations_locations' => $customizations_locations,
372
- 'is' => array(
373
- 'framework' => false,
374
- 'custom' => false,
375
- 'theme' => true,
376
- ),
377
- );
378
- }
379
-
380
- /**
381
- * @since 2.6.9
382
- */
383
- $locations = apply_filters('fw_extensions_locations_after', $locations);
384
-
385
- FW_Cache::set($cache_key, $locations);
386
-
387
- return $locations;
388
- }
389
- }
390
-
391
- private function load_all_extensions()
392
- {
393
- foreach ($this->get_locations() as $location) {
394
- self::load_extensions(array(
395
- 'path' => $location['path'],
396
- 'uri' => $location['uri'],
397
- 'customizations_locations' => $location['customizations_locations'],
398
- ));
399
- }
400
- }
401
-
402
- /**
403
- * Activate extensions from given tree point
404
- *
405
- * @param null|string $parent_extension_name
406
- */
407
- private function activate_extensions($parent_extension_name = null)
408
- {
409
- if ($parent_extension_name === null) {
410
- $all_tree = &self::$all_extensions_tree;
411
- } else {
412
- $all_tree = &self::$extension_to_all_tree[$parent_extension_name];
413
- }
414
-
415
- foreach ($all_tree as $extension_name => &$sub_extensions) {
416
- if (fw()->extensions->get($extension_name)) {
417
- continue; // already active
418
- }
419
-
420
- $manifest = self::_get_manifest($extension_name, self::$access_key);
421
-
422
- {
423
- $class_file_name = 'class-fw-extension-'. $extension_name .'.php';
424
-
425
- if (fw_include_file_isolated(self::$all_extensions[$extension_name]['path'] .'/'. $class_file_name)) {
426
- $class_name = 'FW_Extension_'. fw_dirname_to_classname($extension_name);
427
- } else {
428
- $parent_class_name = get_class(
429
- fw()->extensions->get(self::$all_extensions[$extension_name]['parent'])
430
- );
431
-
432
- // check if parent extension has been defined custom Default class for its child extensions
433
- if (class_exists($parent_class_name .'_Default')) {
434
- $class_name = $parent_class_name .'_Default';
435
- } else {
436
- $class_name = 'FW_Extension_Default';
437
- }
438
- }
439
-
440
- if (!is_subclass_of($class_name, 'FW_Extension')) {
441
- trigger_error('Extension "'. $extension_name .'" must extend FW_Extension class', E_USER_ERROR);
442
- }
443
-
444
- self::$all_extensions[$extension_name]['instance'] = new $class_name(array(
445
- 'rel_path' => self::$all_extensions[$extension_name]['rel_path'],
446
- 'path' => self::$all_extensions[$extension_name]['path'],
447
- 'uri' => self::$all_extensions[$extension_name]['uri'],
448
- 'parent' => fw()->extensions->get(self::$all_extensions[$extension_name]['parent']),
449
- 'depth' => self::$all_extensions[$extension_name]['depth'],
450
- 'customizations_locations' => self::$all_extensions[$extension_name]['customizations_locations'],
451
- ));
452
- }
453
-
454
- $extension = &self::$all_extensions[$extension_name]['instance'];
455
-
456
- if ($manifest->check_requirements()) {
457
- if (!$this->_get_db_active_extensions($extension_name)) {
458
- // extension is not set as active
459
- } elseif (
460
- $extension->get_parent()
461
- &&
462
- !$extension->get_parent()->_child_extension_is_valid($extension)
463
- ) {
464
- // extension does not pass parent extension rules
465
- if (is_admin()) {
466
- // show warning only in admin side
467
- FW_Flash_Messages::add(
468
- 'fw-invalid-extension',
469
- sprintf(__('Extension %s is invalid.', 'fw'), $extension->get_name()),
470
- 'warning'
471
- );
472
- }
473
- } else {
474
- // all requirements met, activate extension
475
- $this->activate_extension($extension_name);
476
- }
477
- } else {
478
- // requirements not met, tell required extensions that this extension is waiting for them
479
-
480
- foreach ($manifest->get_required_extensions() as $required_extension_name => $requirements) {
481
- if (!isset(self::$extensions_required_by_extensions[$required_extension_name])) {
482
- self::$extensions_required_by_extensions[$required_extension_name] = array();
483
- }
484
-
485
- self::$extensions_required_by_extensions[$required_extension_name][] = $extension_name;
486
- }
487
- }
488
- }
489
- unset($sub_extensions);
490
- }
491
-
492
- /**
493
- * @param string $extension_name
494
- * @return bool
495
- */
496
- private function activate_extension($extension_name)
497
- {
498
- if (fw()->extensions->get($extension_name)) {
499
- return false; // already active
500
- }
501
-
502
- if (!self::_get_manifest($extension_name, self::$access_key)->requirements_met()) {
503
- trigger_error('Wrong '. __METHOD__ .' call', E_USER_WARNING);
504
- return false;
505
- }
506
-
507
- /**
508
- * Add to active extensions so inside includes/ and extension it will be accessible from fw()->extensions->get(...)
509
- * self::$all_extensions[$extension_name]['instance'] is created in $this->activate_extensions()
510
- */
511
- self::$active_extensions[$extension_name] = &self::$all_extensions[$extension_name]['instance'];
512
-
513
- $parent = self::$all_extensions[$extension_name]['instance']->get_parent();
514
-
515
- if ($parent) {
516
- self::$extension_to_active_tree[ $parent->get_name() ][$extension_name] = array();
517
- self::$extension_to_active_tree[$extension_name] = &self::$extension_to_active_tree[ $parent->get_name() ][$extension_name];
518
- } else {
519
- self::$active_extensions_tree[$extension_name] = array();
520
- self::$extension_to_active_tree[$extension_name] = &self::$active_extensions_tree[$extension_name];
521
- }
522
-
523
- self::include_extension_directory_all_locations($extension_name, '/includes');
524
- self::include_extension_file_all_locations($extension_name, '/helpers.php');
525
- self::include_extension_file_all_locations($extension_name, '/hooks.php');
526
-
527
- if (self::$all_extensions[$extension_name]['instance']->_call_init(self::$access_key) !== false) {
528
- $this->activate_extensions($extension_name);
529
- }
530
-
531
- // check if other extensions are waiting for this extension and try to activate them
532
- if (isset(self::$extensions_required_by_extensions[$extension_name])) {
533
- foreach (self::$extensions_required_by_extensions[$extension_name] as $waiting_extension_name) {
534
- if (self::_get_manifest($waiting_extension_name, self::$access_key)->check_requirements()) {
535
- $waiting_extension = self::$all_extensions[$waiting_extension_name]['instance'];
536
-
537
- if (!$this->_get_db_active_extensions($waiting_extension_name)) {
538
- // extension is set as active
539
- } elseif (
540
- $waiting_extension->get_parent()
541
- &&
542
- !$waiting_extension->get_parent()->_child_extension_is_valid($waiting_extension)
543
- ) {
544
- // extension does not pass parent extension rules
545
- if (is_admin()) {
546
- // show warning only in admin side
547
- FW_Flash_Messages::add(
548
- 'fw-invalid-extension',
549
- sprintf(__('Extension %s is invalid.', 'fw'), $waiting_extension_name),
550
- 'warning'
551
- );
552
- }
553
- } else {
554
- $this->activate_extension($waiting_extension_name);
555
- }
556
- }
557
- }
558
-
559
- unset(self::$extensions_required_by_extensions[$extension_name]);
560
- }
561
-
562
- return true;
563
- }
564
-
565
- private function add_actions()
566
- {
567
- add_action('init', array($this, '_action_init'));
568
- add_action('wp_enqueue_scripts', array($this, '_action_enqueue_scripts'));
569
- add_action('admin_enqueue_scripts', array($this, '_action_enqueue_scripts'));
570
- }
571
-
572
- /**
573
- * Give extensions possibility to access their active_tree
574
- * @internal
575
- *
576
- * @param FW_Access_Key $access_key
577
- * @param $extension_name
578
- *
579
- * @return array
580
- */
581
- public function _get_extension_tree(FW_Access_Key $access_key, $extension_name)
582
- {
583
- if ($access_key->get_key() !== 'extension') {
584
- trigger_error('Call denied', E_USER_ERROR);
585
- }
586
-
587
- return self::$extension_to_active_tree[$extension_name];
588
- }
589
-
590
- /**
591
- * @internal
592
- */
593
- public function _init()
594
- {
595
- self::$access_key = new FW_Access_Key('fw_extensions');
596
-
597
- /**
598
- * Extensions are about to activate.
599
- * You can add subclasses to FW_Extension at this point.
600
- */
601
- do_action('fw_extensions_before_init');
602
-
603
- $this->load_all_extensions();
604
- $this->add_actions();
605
- }
606
-
607
- /**
608
- * @internal
609
- */
610
- public function _after_components_init()
611
- {
612
- $this->activate_extensions();
613
-
614
- /**
615
- * Extensions are activated
616
- * Now $this->get_children() inside extensions is available
617
- */
618
- do_action('fw_extensions_init');
619
- }
620
-
621
- public function _action_init()
622
- {
623
- foreach (self::$active_extensions as &$extension) {
624
- /** register posts and taxonomies */
625
- self::include_extension_file_all_locations($extension, '/posts.php');
626
- }
627
- }
628
-
629
- public function _action_enqueue_scripts()
630
- {
631
- foreach (self::$active_extensions as &$extension) {
632
- /** js and css */
633
- self::include_extension_file_all_locations($extension, '/static.php', true, true);
634
- }
635
- }
636
-
637
- /**
638
- * @param string $extension_name returned by FW_Extension::get_name()
639
- * @return FW_Extension|null
640
- */
641
- public function get($extension_name)
642
- {
643
- if (isset(self::$active_extensions[$extension_name])) {
644
- return self::$active_extensions[$extension_name];
645
- } else {
646
- return null;
647
- }
648
- }
649
-
650
- /**
651
- * Get all active extensions
652
- * @return FW_Extension[]
653
- */
654
- public function get_all()
655
- {
656
- return self::$active_extensions;
657
- }
658
-
659
- /**
660
- * Get extensions tree (how they are arranged in directories)
661
- * @return array
662
- */
663
- public function get_tree()
664
- {
665
- return self::$active_extensions_tree;
666
- }
667
-
668
- /**
669
- * @return false
670
- * @deprecated Use $extension->locate_path()
671
- */
672
- public function locate_path()
673
- {
674
- return false;
675
- }
676
-
677
- /**
678
- * @return false
679
- * @deprecated Use $extension->locate_URI()
680
- */
681
- public function locate_path_URI()
682
- {
683
- return false;
684
- }
685
- }
 
 
 
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
+ /** @var FW_Extension_Manifest[] All extensions manifests */
62
+ private static $manifests = array();
63
+
64
+ /**
65
+ * @var null|_FW_Extensions_Manager
66
+ */
67
+ public $manager;
68
+
69
+ /**
70
+ * Option name that stores the active extensions array
71
+ * @internal
72
+ */
73
+ public function _get_active_extensions_db_option_name()
74
+ {
75
+ return 'fw_active_extensions';
76
+ }
77
+
78
+ /**
79
+ * @param null|string $extension_name Check if an extension is set as active in database
80
+ * @internal
81
+ * @return array|bool
82
+ */
83
+ public function _get_db_active_extensions($extension_name = null)
84
+ {
85
+ $extensions = get_option($this->_get_active_extensions_db_option_name(), array());
86
+
87
+ if ($extension_name) {
88
+ return isset($extensions[$extension_name]);
89
+ } else {
90
+ return $extensions;
91
+ }
92
+ }
93
+
94
+ public function __construct() {
95
+ $this->manager = new _FW_Extensions_Manager();
96
+ }
97
+
98
+ /**
99
+ * @param string $extension_name
100
+ * @param FW_Access_Key $access_key
101
+ * @return FW_Extension_Manifest|null
102
+ * @internal
103
+ * @since 2.6.9
104
+ */
105
+ public static function _get_manifest($extension_name, FW_Access_Key $access_key) {
106
+ if ( ! in_array( $access_key->get_key(), array( 'extension', self::$access_key->get_key() ), true ) ) {
107
+ trigger_error( 'Method call denied', E_USER_ERROR );
108
+ }
109
+
110
+ if (isset(self::$all_extensions[$extension_name])) {
111
+ if (!isset(self::$manifests[$extension_name])) {
112
+ $manifest = fw_get_variables_from_file(
113
+ self::$all_extensions[$extension_name]['path'] .'/manifest.php', array('manifest' => array())
114
+ );
115
+ $manifest = $manifest['manifest'];
116
+
117
+ if (empty($manifest['name'])) {
118
+ $manifest['name'] = fw_id_to_title($extension_name);
119
+ }
120
+
121
+ self::$manifests[$extension_name] = new FW_Extension_Manifest($manifest);
122
+ }
123
+
124
+ return self::$manifests[$extension_name];
125
+ } else {
126
+ return null;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Load extension from directory
132
+ *
133
+ * @param array $data
134
+ */
135
+ private static function load_extensions($data)
136
+ {
137
+ /**
138
+ * Do not check all keys
139
+ * if one not set, then sure others are not set (this is a private method)
140
+ */
141
+ if (!isset($data['all_extensions_tree'])) {
142
+ $data['all_extensions_tree'] = &self::$all_extensions_tree;
143
+ $data['all_extensions'] = &self::$all_extensions;
144
+ $data['current_depth'] = 1;
145
+ $data['rel_path'] = '';
146
+ $data['parent'] = null;
147
+ }
148
+
149
+ $dirs = glob($data['path'] .'/*', GLOB_ONLYDIR);
150
+
151
+ if (empty($dirs)) {
152
+ return;
153
+ }
154
+
155
+ if ($data['current_depth'] > 1) {
156
+ $customizations_locations = array();
157
+
158
+ foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
159
+ $customizations_locations[ $customization_path .'/extensions' ] = $customization_uri .'/extensions';
160
+ }
161
+
162
+ $data['customizations_locations'] = $customizations_locations;
163
+ }
164
+
165
+ foreach ($dirs as $extension_dir) {
166
+ $extension_name = basename($extension_dir);
167
+
168
+ {
169
+ $customizations_locations = array();
170
+
171
+ foreach ($data['customizations_locations'] as $customization_path => $customization_uri) {
172
+ $customizations_locations[ $customization_path .'/'. $extension_name ] = $customization_uri .'/'. $extension_name;
173
+ }
174
+ }
175
+
176
+ if (isset($data['all_extensions'][$extension_name])) {
177
+ if ($data['all_extensions'][$extension_name]['parent'] !== $data['parent']) {
178
+ // extension with the same name exists in another tree
179
+ trigger_error(
180
+ 'Extension "'. $extension_name .'" is already defined '.
181
+ 'in "'. $data['all_extensions'][$extension_name]['path'] .'" '.
182
+ 'found again in "'. $extension_dir .'"',
183
+ E_USER_ERROR
184
+ );
185
+ }
186
+
187
+ // this is a directory with customizations for an extension
188
+
189
+ self::load_extensions(array(
190
+ 'rel_path' => $data['rel_path'] .'/'. $extension_name .'/extensions',
191
+ 'path' => $data['path'] .'/'. $extension_name .'/extensions',
192
+ 'uri' => $data['uri'] .'/'. $extension_name .'/extensions',
193
+ 'customizations_locations' => $customizations_locations,
194
+
195
+ 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
196
+ 'all_extensions' => &$data['all_extensions'],
197
+ 'current_depth' => $data['current_depth'] + 1,
198
+ 'parent' => $extension_name,
199
+ ));
200
+ } else {
201
+ if (file_exists($extension_dir .'/manifest.php')) {
202
+ $data['all_extensions_tree'][$extension_name] = array();
203
+
204
+ self::$extension_to_all_tree[$extension_name] = &$data['all_extensions_tree'][$extension_name];
205
+
206
+ $data['all_extensions'][$extension_name] = array(
207
+ 'rel_path' => $data['rel_path'] .'/'. $extension_name,
208
+ 'path' => $data['path'] .'/'. $extension_name,
209
+ 'uri' => $data['uri'] .'/'. $extension_name,
210
+ 'parent' => $data['parent'],
211
+ 'depth' => $data['current_depth'],
212
+ 'customizations_locations' => $customizations_locations,
213
+ 'instance' => null, // created on activation
214
+ );
215
+ } else {
216
+ /**
217
+ * The manifest file does not exist, do not load this extension.
218
+ * Maybe it's a directory with configurations for a not existing extension.
219
+ */
220
+ continue;
221
+ }
222
+
223
+ self::load_extensions(array(
224
+ 'rel_path' => $data['all_extensions'][$extension_name]['rel_path'] .'/extensions',
225
+ 'path' => $data['all_extensions'][$extension_name]['path'] .'/extensions',
226
+ 'uri' => $data['all_extensions'][$extension_name]['uri'] .'/extensions',
227
+ 'customizations_locations' => $customizations_locations,
228
+
229
+ 'parent' => $extension_name,
230
+ 'all_extensions_tree' => &$data['all_extensions_tree'][$extension_name],
231
+ 'all_extensions' => &$data['all_extensions'],
232
+ 'current_depth' => $data['current_depth'] + 1,
233
+ ));
234
+ }
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Include file from all extension's locations: framework, parent, child
240
+ * @param string|FW_Extension $extension
241
+ * @param string $file_rel_path
242
+ * @param bool $themeFirst
243
+ * false - [framework, parent, child]
244
+ * true - [child, parent, framework]
245
+ * @param bool $onlyFirstFound
246
+ */
247
+ private static function include_extension_file_all_locations($extension, $file_rel_path, $themeFirst = false, $onlyFirstFound = false)
248
+ {
249
+ if (is_string($extension)) {
250
+ $extension = fw()->extensions->get($extension);
251
+ }
252
+
253
+ $paths = $extension->get_customizations_locations();
254
+ $paths[$extension->get_path()] = $extension->get_uri();
255
+
256
+ if (!$themeFirst) {
257
+ $paths = array_reverse($paths);
258
+ }
259
+
260
+ foreach ($paths as $path => $uri) {
261
+ if (fw_include_file_isolated($path . $file_rel_path)) {
262
+ if ($onlyFirstFound) {
263
+ return;
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Include all files from directory, from all extension's locations: framework, child, parent
271
+ * @param string|FW_Extension $extension
272
+ * @param string $dir_rel_path
273
+ * @param bool $themeFirst
274
+ * false - [framework, parent, child]
275
+ * true - [child, parent, framework]
276
+ */
277
+ private static function include_extension_directory_all_locations($extension, $dir_rel_path, $themeFirst = false)
278
+ {
279
+ if (is_string($extension)) {
280
+ $extension = fw()->extensions->get($extension);
281
+ }
282
+
283
+ $paths = $extension->get_customizations_locations();
284
+ $paths[$extension->get_path()] = $extension->get_uri();
285
+
286
+ if (!$themeFirst) {
287
+ $paths = array_reverse($paths);
288
+ }
289
+
290
+ foreach ($paths as $path => $uri) {
291
+ $files = glob($path . $dir_rel_path .'/*.php');
292
+
293
+ if ($files) {
294
+ foreach ($files as $dir_file_path) {
295
+ fw_include_file_isolated($dir_file_path);
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ public function get_locations()
302
+ {
303
+ $cache_key = 'fw_extensions_locations';
304
+
305
+ try {
306
+ return FW_Cache::get($cache_key);
307
+ } catch (FW_Cache_Not_Found_Exception $e) {
308
+ /**
309
+ * { '/hello/world/extensions' => 'https://hello.com/world/extensions' }
310
+ */
311
+ $custom_locations = apply_filters('fw_extensions_locations', array());
312
+
313
+ {
314
+ $customizations_locations = array();
315
+
316
+ if (is_child_theme()) {
317
+ $customizations_locations[fw_get_stylesheet_customizations_directory('/extensions')]
318
+ = fw_get_stylesheet_customizations_directory_uri('/extensions');
319
+ }
320
+
321
+ $customizations_locations[fw_get_template_customizations_directory('/extensions')]
322
+ = fw_get_template_customizations_directory_uri('/extensions');
323
+
324
+ $customizations_locations += $custom_locations;
325
+ }
326
+
327
+ $locations = array();
328
+
329
+ $locations[ fw_get_framework_directory('/extensions') ] = array(
330
+ 'path' => fw_get_framework_directory('/extensions'),
331
+ 'uri' => fw_get_framework_directory_uri('/extensions'),
332
+ 'customizations_locations' => $customizations_locations,
333
+ 'is' => array(
334
+ 'framework' => true,
335
+ 'custom' => false,
336
+ 'theme' => false,
337
+ ),
338
+ );
339
+
340
+ foreach ($custom_locations as $path => $uri) {
341
+ unset($customizations_locations[$path]);
342
+ $locations[ $path ] = array(
343
+ 'path' => $path,
344
+ 'uri' => $uri,
345
+ 'customizations_locations' => $customizations_locations,
346
+ 'is' => array(
347
+ 'framework' => false,
348
+ 'custom' => true,
349
+ 'theme' => false,
350
+ ),
351
+ );
352
+ }
353
+
354
+ array_pop($customizations_locations);
355
+ $locations[ fw_get_template_customizations_directory('/extensions') ] = array(
356
+ 'path' => fw_get_template_customizations_directory('/extensions'),
357
+ 'uri' => fw_get_template_customizations_directory_uri('/extensions'),
358
+ 'customizations_locations' => $customizations_locations,
359
+ 'is' => array(
360
+ 'framework' => false,
361
+ 'custom' => false,
362
+ 'theme' => true,
363
+ ),
364
+ );
365
+
366
+ if (is_child_theme()) {
367
+ array_pop($customizations_locations);
368
+ $locations[ fw_get_stylesheet_customizations_directory('/extensions') ] = array(
369
+ 'path' => fw_get_stylesheet_customizations_directory('/extensions'),
370
+ 'uri' => fw_get_stylesheet_customizations_directory_uri('/extensions'),
371
+ 'customizations_locations' => $customizations_locations,
372
+ 'is' => array(
373
+ 'framework' => false,
374
+ 'custom' => false,
375
+ 'theme' => true,
376
+ ),
377
+ );
378
+ }
379
+
380
+ /**
381
+ * @since 2.6.9
382
+ */
383
+ $locations = apply_filters('fw_extensions_locations_after', $locations);
384
+
385
+ FW_Cache::set($cache_key, $locations);
386
+
387
+ return $locations;
388
+ }
389
+ }
390
+
391
+ private function load_all_extensions()
392
+ {
393
+ foreach ($this->get_locations() as $location) {
394
+ self::load_extensions(array(
395
+ 'path' => $location['path'],
396
+ 'uri' => $location['uri'],
397
+ 'customizations_locations' => $location['customizations_locations'],
398
+ ));
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Activate extensions from given tree point
404
+ *
405
+ * @param null|string $parent_extension_name
406
+ */
407
+ private function activate_extensions($parent_extension_name = null)
408
+ {
409
+ if ($parent_extension_name === null) {
410
+ $all_tree = &self::$all_extensions_tree;
411
+ } else {
412
+ $all_tree = &self::$extension_to_all_tree[$parent_extension_name];
413
+ }
414
+
415
+ foreach ($all_tree as $extension_name => &$sub_extensions) {
416
+ if (fw()->extensions->get($extension_name)) {
417
+ continue; // already active
418
+ }
419
+
420
+ $manifest = self::_get_manifest($extension_name, self::$access_key);
421
+
422
+ {
423
+ $class_file_name = 'class-fw-extension-'. $extension_name .'.php';
424
+
425
+ if (fw_include_file_isolated(self::$all_extensions[$extension_name]['path'] .'/'. $class_file_name)) {
426
+ $class_name = 'FW_Extension_'. fw_dirname_to_classname($extension_name);
427
+
428
+ } else {
429
+
430
+ $parent_class_name = '';
431
+ if ( self::$all_extensions[ $extension_name ]['parent'] ) {
432
+ $parent_class_name = get_class( fw()->extensions->get( self::$all_extensions[ $extension_name ]['parent'] ) );
433
+ }
434
+
435
+ // check if parent extension has been defined custom Default class for its child extensions
436
+ if ( class_exists( $parent_class_name . '_Default' ) ) {
437
+ $class_name = $parent_class_name . '_Default';
438
+ } else {
439
+ $class_name = 'FW_Extension_Default';
440
+ }
441
+ }
442
+
443
+ if (!is_subclass_of($class_name, 'FW_Extension')) {
444
+ trigger_error('Extension "'. $extension_name .'" must extend FW_Extension class', E_USER_ERROR);
445
+ }
446
+
447
+ self::$all_extensions[$extension_name]['instance'] = new $class_name(array(
448
+ 'rel_path' => self::$all_extensions[$extension_name]['rel_path'],
449
+ 'path' => self::$all_extensions[$extension_name]['path'],
450
+ 'uri' => self::$all_extensions[$extension_name]['uri'],
451
+ 'parent' => fw()->extensions->get(self::$all_extensions[$extension_name]['parent']),
452
+ 'depth' => self::$all_extensions[$extension_name]['depth'],
453
+ 'customizations_locations' => self::$all_extensions[$extension_name]['customizations_locations'],
454
+ ));
455
+ }
456
+
457
+ $extension = &self::$all_extensions[$extension_name]['instance'];
458
+
459
+ if ($manifest->check_requirements()) {
460
+ if (!$this->_get_db_active_extensions($extension_name)) {
461
+ // extension is not set as active
462
+ } elseif (
463
+ $extension->get_parent()
464
+ &&
465
+ !$extension->get_parent()->_child_extension_is_valid($extension)
466
+ ) {
467
+ // extension does not pass parent extension rules
468
+ if (is_admin()) {
469
+ // show warning only in admin side
470
+ FW_Flash_Messages::add(
471
+ 'fw-invalid-extension',
472
+ sprintf(__('Extension %s is invalid.', 'fw'), $extension->get_name()),
473
+ 'warning'
474
+ );
475
+ }
476
+ } else {
477
+ // all requirements met, activate extension
478
+ $this->activate_extension($extension_name);
479
+ }
480
+ } else {
481
+ // requirements not met, tell required extensions that this extension is waiting for them
482
+
483
+ foreach ($manifest->get_required_extensions() as $required_extension_name => $requirements) {
484
+ if (!isset(self::$extensions_required_by_extensions[$required_extension_name])) {
485
+ self::$extensions_required_by_extensions[$required_extension_name] = array();
486
+ }
487
+
488
+ self::$extensions_required_by_extensions[$required_extension_name][] = $extension_name;
489
+ }
490
+ }
491
+ }
492
+ unset($sub_extensions);
493
+ }
494
+
495
+ /**
496
+ * @param string $extension_name
497
+ * @return bool
498
+ */
499
+ private function activate_extension($extension_name)
500
+ {
501
+ if (fw()->extensions->get($extension_name)) {
502
+ return false; // already active
503
+ }
504
+
505
+ if (!self::_get_manifest($extension_name, self::$access_key)->requirements_met()) {
506
+ trigger_error('Wrong '. __METHOD__ .' call', E_USER_WARNING);
507
+ return false;
508
+ }
509
+
510
+ /**
511
+ * Add to active extensions so inside includes/ and extension it will be accessible from fw()->extensions->get(...)
512
+ * self::$all_extensions[$extension_name]['instance'] is created in $this->activate_extensions()
513
+ */
514
+ self::$active_extensions[$extension_name] = &self::$all_extensions[$extension_name]['instance'];
515
+
516
+ $parent = self::$all_extensions[$extension_name]['instance']->get_parent();
517
+
518
+ if ($parent) {
519
+ self::$extension_to_active_tree[ $parent->get_name() ][$extension_name] = array();
520
+ self::$extension_to_active_tree[$extension_name] = &self::$extension_to_active_tree[ $parent->get_name() ][$extension_name];
521
+ } else {
522
+ self::$active_extensions_tree[$extension_name] = array();
523
+ self::$extension_to_active_tree[$extension_name] = &self::$active_extensions_tree[$extension_name];
524
+ }
525
+
526
+ self::include_extension_directory_all_locations($extension_name, '/includes');
527
+ self::include_extension_file_all_locations($extension_name, '/helpers.php');
528
+ self::include_extension_file_all_locations($extension_name, '/hooks.php');
529
+
530
+ if (self::$all_extensions[$extension_name]['instance']->_call_init(self::$access_key) !== false) {
531
+ $this->activate_extensions($extension_name);
532
+ }
533
+
534
+ // check if other extensions are waiting for this extension and try to activate them
535
+ if (isset(self::$extensions_required_by_extensions[$extension_name])) {
536
+ foreach (self::$extensions_required_by_extensions[$extension_name] as $waiting_extension_name) {
537
+ if (self::_get_manifest($waiting_extension_name, self::$access_key)->check_requirements()) {
538
+ $waiting_extension = self::$all_extensions[$waiting_extension_name]['instance'];
539
+
540
+ if (!$this->_get_db_active_extensions($waiting_extension_name)) {
541
+ // extension is set as active
542
+ } elseif (
543
+ $waiting_extension->get_parent()
544
+ &&
545
+ !$waiting_extension->get_parent()->_child_extension_is_valid($waiting_extension)
546
+ ) {
547
+ // extension does not pass parent extension rules
548
+ if (is_admin()) {
549
+ // show warning only in admin side
550
+ FW_Flash_Messages::add(
551
+ 'fw-invalid-extension',
552
+ sprintf(__('Extension %s is invalid.', 'fw'), $waiting_extension_name),
553
+ 'warning'
554
+ );
555
+ }
556
+ } else {
557
+ $this->activate_extension($waiting_extension_name);
558
+ }
559
+ }
560
+ }
561
+
562
+ unset(self::$extensions_required_by_extensions[$extension_name]);
563
+ }
564
+
565
+ return true;
566
+ }
567
+
568
+ private function add_actions()
569
+ {
570
+ add_action('init', array($this, '_action_init'));
571
+ add_action('wp_enqueue_scripts', array($this, '_action_enqueue_scripts'));
572
+ add_action('admin_enqueue_scripts', array($this, '_action_enqueue_scripts'));
573
+ }
574
+
575
+ /**
576
+ * Give extensions possibility to access their active_tree
577
+ * @internal
578
+ *
579
+ * @param FW_Access_Key $access_key
580
+ * @param $extension_name
581
+ *
582
+ * @return array
583
+ */
584
+ public function _get_extension_tree(FW_Access_Key $access_key, $extension_name)
585
+ {
586
+ if ($access_key->get_key() !== 'extension') {
587
+ trigger_error('Call denied', E_USER_ERROR);
588
+ }
589
+
590
+ return self::$extension_to_active_tree[$extension_name];
591
+ }
592
+
593
+ /**
594
+ * @internal
595
+ */
596
+ public function _init()
597
+ {
598
+ self::$access_key = new FW_Access_Key('fw_extensions');
599
+
600
+ /**
601
+ * Extensions are about to activate.
602
+ * You can add subclasses to FW_Extension at this point.
603
+ */
604
+ do_action('fw_extensions_before_init');
605
+
606
+ $this->load_all_extensions();
607
+ $this->add_actions();
608
+ }
609
+
610
+ /**
611
+ * @internal
612
+ */
613
+ public function _after_components_init()
614
+ {
615
+ $this->activate_extensions();
616
+
617
+ /**
618
+ * Extensions are activated
619
+ * Now $this->get_children() inside extensions is available
620
+ */
621
+ do_action('fw_extensions_init');
622
+ }
623
+
624
+ public function _action_init()
625
+ {
626
+ foreach (self::$active_extensions as &$extension) {
627
+ /** register posts and taxonomies */
628
+ self::include_extension_file_all_locations($extension, '/posts.php');
629
+ }
630
+ }
631
+
632
+ public function _action_enqueue_scripts()
633
+ {
634
+ foreach (self::$active_extensions as &$extension) {
635
+ /** js and css */
636
+ self::include_extension_file_all_locations($extension, '/static.php', true, true);
637
+ }
638
+ }
639
+
640
+ /**
641
+ * @param string $extension_name returned by FW_Extension::get_name()
642
+ * @return FW_Extension|null
643
+ */
644
+ public function get($extension_name)
645
+ {
646
+ if (isset(self::$active_extensions[$extension_name])) {
647
+ return self::$active_extensions[$extension_name];
648
+ } else {
649
+ return null;
650
+ }
651
+ }
652
+
653
+ /**
654
+ * Get all active extensions
655
+ * @return FW_Extension[]
656
+ */
657
+ public function get_all()
658
+ {
659
+ return self::$active_extensions;
660
+ }
661
+
662
+ /**
663
+ * Get extensions tree (how they are arranged in directories)
664
+ * @return array
665
+ */
666
+ public function get_tree()
667
+ {
668
+ return self::$active_extensions_tree;
669
+ }
670
+
671
+ /**
672
+ * @return false
673
+ * @deprecated Use $extension->locate_path()
674
+ */
675
+ public function locate_path()
676
+ {
677
+ return false;
678
+ }
679
+
680
+ /**
681
+ * @return false
682
+ * @deprecated Use $extension->locate_URI()
683
+ */
684
+ public function locate_path_URI()
685
+ {
686
+ return false;
687
+ }
688
+ }
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,334 +1,336 @@
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
- 'page-builder' => array(
10
- 'display' => true,
11
- 'parent' => 'shortcodes',
12
- 'name' => __( 'Page Builder', 'fw' ),
13
- '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' ),
14
- 'thumbnail' => $thumbnails_uri . '/page-builder.jpg',
15
- 'download' => array(
16
- 'source' => 'github',
17
- 'opts' => array(
18
- 'user_repo' => $github_account . '/Unyson-PageBuilder-Extension'
19
- )
20
- ),
21
- ),
22
-
23
- 'wp-shortcodes' => array(
24
- 'display' => true,
25
- 'parent' => 'shortcodes',
26
- 'name' => __( 'WordPress Shortcodes', 'fw' ),
27
- 'description' => __(
28
- 'Lets you insert Unyson shortcodes inside any wp-editor',
29
- 'fw'
30
- ),
31
- 'thumbnail' => $thumbnails_uri . '/wp-shortcodes.jpg',
32
- 'download' => array(
33
- 'source' => 'github',
34
- 'opts' => array(
35
- 'user_repo' => 'ThemeFuse/Unyson-WP-Shortcodes-Extension',
36
- ),
37
- ),
38
- ),
39
-
40
- 'backups' => array(
41
- 'display' => true,
42
- 'parent' => null,
43
- 'name' => __( 'Backup & Demo Content', 'fw' ),
44
- 'description' => __( 'This extension lets you create an automated backup schedule, import demo content or even create a demo content archive for migration purposes.', 'fw' ),
45
- 'thumbnail' => $thumbnails_uri . '/backups.jpg',
46
- 'download' => array(
47
- 'source' => 'github',
48
- 'opts' => array(
49
- 'user_repo' => $github_account . '/Unyson-Backups-Extension',
50
- ),
51
- ),
52
- ),
53
-
54
- 'sidebars' => array(
55
- 'display' => true,
56
- 'parent' => null,
57
- 'name' => __( 'Sidebars', 'fw' ),
58
- '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' ),
59
- 'thumbnail' => $thumbnails_uri . '/sidebars.jpg',
60
- 'download' => array(
61
- 'source' => 'github',
62
- 'opts' => array(
63
- 'user_repo' => $github_account . '/Unyson-Sidebars-Extension',
64
- ),
65
- ),
66
- ),
67
-
68
- 'slider' => array(
69
- 'display' => true,
70
- 'parent' => 'media',
71
- 'name' => __( 'Sliders', 'fw' ),
72
- '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' ),
73
- 'thumbnail' => $thumbnails_uri . '/sliders.jpg',
74
- 'download' => array(
75
- 'source' => 'github',
76
- 'opts' => array(
77
- 'user_repo' => $github_account . '/Unyson-Sliders-Extension',
78
- ),
79
- ),
80
- ),
81
-
82
- 'portfolio' => array(
83
- 'display' => true,
84
- 'parent' => null,
85
- 'name' => __( 'Portfolio', 'fw' ),
86
- 'description' => __( 'This extension will add a fully fledged portfolio module that will let you display your projects using the built in portfolio pages.', 'fw' ),
87
- 'thumbnail' => $thumbnails_uri . '/portfolio.jpg',
88
- 'download' => array(
89
- 'source' => 'github',
90
- 'opts' => array(
91
- 'user_repo' => $github_account . '/Unyson-Portfolio-Extension',
92
- ),
93
- ),
94
- ),
95
-
96
- 'megamenu' => array(
97
- 'display' => true,
98
- 'parent' => null,
99
- 'name' => __( 'Mega Menu', 'fw' ),
100
- 'description' => __( 'The Mega Menu extension adds a user-friendly drop down menu that will let you easily create highly customized menu configurations.', 'fw' ),
101
- 'thumbnail' => $thumbnails_uri . '/mega-menu.jpg',
102
- 'download' => array(
103
- 'source' => 'github',
104
- 'opts' => array(
105
- 'user_repo' => $github_account . '/Unyson-MegaMenu-Extension',
106
- ),
107
- ),
108
- ),
109
-
110
- 'breadcrumbs' => array(
111
- 'display' => true,
112
- 'parent' => null,
113
- 'name' => __( 'Breadcrumbs', 'fw' ),
114
- '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' ),
115
- 'thumbnail' => $thumbnails_uri . '/breadcrumbs.jpg',
116
- 'download' => array(
117
- 'source' => 'github',
118
- 'opts' => array(
119
- 'user_repo' => $github_account . '/Unyson-Breadcrumbs-Extension',
120
- ),
121
- ),
122
- ),
123
-
124
- 'seo' => array(
125
- 'display' => true,
126
- 'parent' => null,
127
- 'name' => __( 'SEO', 'fw' ),
128
- 'description' => __( 'This extension will enable you to have a fully optimized WordPress website by adding optimized meta titles, keywords and descriptions.', 'fw' ),
129
- 'thumbnail' => $thumbnails_uri . '/seo.jpg',
130
- 'download' => array(
131
- 'source' => 'github',
132
- 'opts' => array(
133
- 'user_repo' => $github_account . '/Unyson-SEO-Extension',
134
- ),
135
- ),
136
- ),
137
-
138
- 'events' => array(
139
- 'display' => true,
140
- 'parent' => null,
141
- 'name' => __( 'Events', 'fw' ),
142
- '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' ),
143
- 'thumbnail' => $thumbnails_uri . '/events.jpg',
144
- 'download' => array(
145
- 'source' => 'github',
146
- 'opts' => array(
147
- 'user_repo' => $github_account . '/Unyson-Events-Extension',
148
- ),
149
- ),
150
- ),
151
-
152
- 'analytics' => array(
153
- 'display' => true,
154
- 'parent' => null,
155
- 'name' => __( 'Analytics', 'fw' ),
156
- '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' ),
157
- 'thumbnail' => $thumbnails_uri . '/analytics.jpg',
158
- 'download' => array(
159
- 'source' => 'github',
160
- 'opts' => array(
161
- 'user_repo' => $github_account . '/Unyson-Analytics-Extension',
162
- ),
163
- ),
164
- ),
165
-
166
- 'feedback' => array(
167
- 'display' => true,
168
- 'parent' => null,
169
- 'name' => __( 'Feedback', 'fw' ),
170
- 'description' => __( 'Adds the possibility to leave feedback (comments, reviews and rating) about your products, articles, etc. This replaces the default comments system.', 'fw' ),
171
- 'thumbnail' => $thumbnails_uri . '/feedback.jpg',
172
- 'download' => array(
173
- 'source' => 'github',
174
- 'opts' => array(
175
- 'user_repo' => $github_account . '/Unyson-Feedback-Extension',
176
- ),
177
- ),
178
- ),
179
-
180
- 'learning' => array(
181
- 'display' => true,
182
- 'parent' => null,
183
- 'name' => __( 'Learning', 'fw' ),
184
- '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' ),
185
- 'thumbnail' => $thumbnails_uri . '/learning.jpg',
186
- 'download' => array(
187
- 'source' => 'github',
188
- 'opts' => array(
189
- 'user_repo' => $github_account . '/Unyson-Learning-Extension',
190
- ),
191
- ),
192
- ),
193
-
194
- 'shortcodes' => array(
195
- 'display' => false,
196
- 'parent' => null,
197
- 'name' => __( 'Shortcodes', 'fw' ),
198
- 'description' => '',
199
- 'thumbnail' => 'about:blank',
200
- 'download' => array(
201
- 'source' => 'github',
202
- 'opts' => array(
203
- 'user_repo' => $github_account . '/Unyson-Shortcodes-Extension',
204
- ),
205
- ),
206
- ),
207
-
208
- 'builder' => array(
209
- 'display' => false,
210
- 'parent' => null,
211
- 'name' => __( 'Builder', 'fw' ),
212
- 'description' => '',
213
- 'thumbnail' => 'about:blank',
214
- 'download' => array(
215
- 'source' => 'github',
216
- 'opts' => array(
217
- 'user_repo' => $github_account . '/Unyson-Builder-Extension',
218
- ),
219
- ),
220
- ),
221
-
222
- 'forms' => array(
223
- 'display' => false,
224
- 'parent' => null,
225
- 'name' => __( 'Forms', 'fw' ),
226
- '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' ),
227
- 'thumbnail' => $thumbnails_uri . '/forms.jpg',
228
- 'download' => array(
229
- 'source' => 'github',
230
- 'opts' => array(
231
- 'user_repo' => $github_account . '/Unyson-Forms-Extension',
232
- ),
233
- ),
234
- ),
235
-
236
- 'mailer' => array(
237
- 'display' => false,
238
- 'parent' => null,
239
- 'name' => __( 'Mailer', 'fw' ),
240
- 'description' => __( 'This extension will let you set some global email options and it is used by other extensions (like Forms) to send emails.', 'fw' ),
241
- 'thumbnail' => $thumbnails_uri . '/mailer.jpg',
242
- 'download' => array(
243
- 'source' => 'github',
244
- 'opts' => array(
245
- 'user_repo' => $github_account . '/Unyson-Mailer-Extension',
246
- ),
247
- ),
248
- ),
249
-
250
- 'social' => array(
251
- 'display' => true,
252
- 'parent' => null,
253
- 'name' => __( 'Social', 'fw' ),
254
- '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' ),
255
- 'thumbnail' => $thumbnails_uri . '/social.jpg',
256
- 'download' => array(
257
- 'source' => 'github',
258
- 'opts' => array(
259
- 'user_repo' => $github_account . '/Unyson-Social-Extension',
260
- ),
261
- ),
262
- ),
263
-
264
- 'backup' => array(
265
- 'display' => true,
266
- 'parent' => null,
267
- 'name' => __( 'Backup', 'fw' ),
268
- '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' ),
269
- 'thumbnail' => $thumbnails_uri . '/backup.jpg',
270
- 'download' => array(
271
- 'source' => 'github',
272
- 'opts' => array(
273
- 'user_repo' => $github_account . '/Unyson-Backup-Extension',
274
- ),
275
- ),
276
- ),
277
-
278
- 'media' => array(
279
- 'display' => false,
280
- 'parent' => null,
281
- 'name' => __( 'Media', 'fw' ),
282
- 'description' => '',
283
- 'thumbnail' => 'about:blank',
284
- 'download' => array(
285
- 'source' => 'github',
286
- 'opts' => array(
287
- 'user_repo' => $github_account . '/Unyson-Empty-Extension',
288
- ),
289
- ),
290
- ),
291
-
292
- 'population-method' => array(
293
- 'display' => false,
294
- 'parent' => 'media',
295
- 'name' => __( 'Population method', 'fw' ),
296
- 'description' => '',
297
- 'thumbnail' => 'about:blank',
298
- 'download' => array(
299
- 'source' => 'github',
300
- 'opts' => array(
301
- 'user_repo' => $github_account . '/Unyson-PopulationMethods-Extension',
302
- ),
303
- ),
304
- ),
305
-
306
- 'styling' => array(
307
- 'display' => true,
308
- 'parent' => null,
309
- 'name' => __( 'Styling', 'fw' ),
310
- 'description' => __( 'This extension lets you control the website visual style. Starting from predefined styles to changing specific fonts and colors across the website.', 'fw' ),
311
- 'thumbnail' => $thumbnails_uri . '/styling.jpg',
312
- 'download' => array(
313
- 'source' => 'github',
314
- 'opts' => array(
315
- 'user_repo' => $github_account . '/Unyson-Styling-Extension',
316
- ),
317
- ),
318
- ),
319
-
320
- 'translation' => array(
321
- 'display' => true,
322
- 'parent' => null,
323
- 'name' => __( 'Translations', 'fw' ),
324
- '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' ),
325
- 'thumbnail' => $thumbnails_uri . '/translation.jpg',
326
- 'download' => array(
327
- 'source' => 'github',
328
- 'opts' => array(
329
- 'user_repo' => $github_account . '/Unyson-Translation-Extension',
330
- )
331
- ),
332
- ),
333
- );
334
-
 
 
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
+ 'page-builder' => array(
10
+ 'display' => true,
11
+ 'parent' => 'shortcodes',
12
+ 'name' => __( 'Page Builder', 'fw' ),
13
+ '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' ),
14
+ 'thumbnail' => $thumbnails_uri . '/page-builder.jpg',
15
+ 'download' => array(
16
+ 'source' => 'github',
17
+ 'opts' => array(
18
+ 'user_repo' => $github_account . '/Unyson-PageBuilder-Extension'
19
+ )
20
+ ),
21
+ ),
22
+
23
+ 'wp-shortcodes' => array(
24
+ 'display' => true,
25
+ 'parent' => 'shortcodes',
26
+ 'name' => __( 'WordPress Shortcodes', 'fw' ),
27
+ 'description' => __(
28
+ 'Lets you insert Unyson shortcodes inside any wp-editor',
29
+ 'fw'
30
+ ),
31
+ 'thumbnail' => $thumbnails_uri . '/wp-shortcodes.jpg',
32
+ 'download' => array(
33
+ 'source' => 'github',
34
+ 'opts' => array(
35
+ 'user_repo' => 'ThemeFuse/Unyson-WP-Shortcodes-Extension',
36
+ ),
37
+ ),
38
+ ),
39
+
40
+ 'backups' => array(
41
+ 'display' => true,
42
+ 'parent' => null,
43
+ 'name' => __( 'Backup & Demo Content', 'fw' ),
44
+ 'description' => __( 'This extension lets you create an automated backup schedule, import demo content or even create a demo content archive for migration purposes.', 'fw' ),
45
+ 'thumbnail' => $thumbnails_uri . '/backups.jpg',
46
+ 'download' => array(
47
+ 'source' => 'github',
48
+ 'opts' => array(
49
+ 'user_repo' => $github_account . '/Unyson-Backups-Extension',
50
+ ),
51
+ ),
52
+ ),
53
+
54
+ 'sidebars' => array(
55
+ 'display' => true,
56
+ 'parent' => null,
57
+ 'name' => __( 'Sidebars', 'fw' ),
58
+ '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' ),
59
+ 'thumbnail' => $thumbnails_uri . '/sidebars.jpg',
60
+ 'download' => array(
61
+ 'source' => 'github',
62
+ 'opts' => array(
63
+ 'user_repo' => $github_account . '/Unyson-Sidebars-Extension',
64
+ ),
65
+ ),
66
+ ),
67
+
68
+ 'slider' => array(
69
+ 'display' => true,
70
+ 'parent' => 'media',
71
+ 'name' => __( 'Sliders', 'fw' ),
72
+ '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' ),
73
+ 'thumbnail' => $thumbnails_uri . '/sliders.jpg',
74
+ 'download' => array(
75
+ 'source' => 'github',
76
+ 'opts' => array(
77
+ 'user_repo' => $github_account . '/Unyson-Sliders-Extension',
78
+ ),
79
+ ),
80
+ ),
81
+
82
+ 'portfolio' => array(
83
+ 'display' => true,
84
+ 'parent' => null,
85
+ 'name' => __( 'Portfolio', 'fw' ),
86
+ 'description' => __( 'This extension will add a fully fledged portfolio module that will let you display your projects using the built in portfolio pages.', 'fw' ),
87
+ 'thumbnail' => $thumbnails_uri . '/portfolio.jpg',
88
+ 'download' => array(
89
+ 'source' => 'github',
90
+ 'opts' => array(
91
+ 'user_repo' => $github_account . '/Unyson-Portfolio-Extension',
92
+ ),
93
+ ),
94
+ ),
95
+
96
+ 'megamenu' => array(
97
+ 'display' => true,
98
+ 'parent' => null,
99
+ 'name' => __( 'Mega Menu', 'fw' ),
100
+ 'description' => __( 'The Mega Menu extension adds a user-friendly drop down menu that will let you easily create highly customized menu configurations.', 'fw' ),
101
+ 'thumbnail' => $thumbnails_uri . '/mega-menu.jpg',
102
+ 'download' => array(
103
+ 'source' => 'github',
104
+ 'opts' => array(
105
+ 'user_repo' => $github_account . '/Unyson-MegaMenu-Extension',
106
+ ),
107
+ ),
108
+ ),
109
+
110
+ 'breadcrumbs' => array(
111
+ 'display' => true,
112
+ 'parent' => null,
113
+ 'name' => __( 'Breadcrumbs', 'fw' ),
114
+ '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' ),
115
+ 'thumbnail' => $thumbnails_uri . '/breadcrumbs.jpg',
116
+ 'download' => array(
117
+ 'source' => 'github',
118
+ 'opts' => array(
119
+ 'user_repo' => $github_account . '/Unyson-Breadcrumbs-Extension',
120
+ ),
121
+ ),
122
+ ),
123
+
124
+ 'seo' => array(
125
+ 'display' => true,
126
+ 'parent' => null,
127
+ 'name' => __( 'SEO', 'fw' ),
128
+ 'description' => __( 'This extension will enable you to have a fully optimized WordPress website by adding optimized meta titles, keywords and descriptions.', 'fw' ),
129
+ 'thumbnail' => $thumbnails_uri . '/seo.jpg',
130
+ 'download' => array(
131
+ 'source' => 'github',
132
+ 'opts' => array(
133
+ 'user_repo' => $github_account . '/Unyson-SEO-Extension',
134
+ ),
135
+ ),
136
+ ),
137
+
138
+ 'events' => array(
139
+ 'display' => true,
140
+ 'parent' => null,
141
+ 'name' => __( 'Events', 'fw' ),
142
+ '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' ),
143
+ 'thumbnail' => $thumbnails_uri . '/events.jpg',
144
+ 'download' => array(
145
+ 'source' => 'github',
146
+ 'opts' => array(
147
+ 'user_repo' => $github_account . '/Unyson-Events-Extension',
148
+ ),
149
+ ),
150
+ ),
151
+
152
+ 'analytics' => array(
153
+ 'display' => true,
154
+ 'parent' => null,
155
+ 'name' => __( 'Analytics', 'fw' ),
156
+ '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' ),
157
+ 'thumbnail' => $thumbnails_uri . '/analytics.jpg',
158
+ 'download' => array(
159
+ 'source' => 'github',
160
+ 'opts' => array(
161
+ 'user_repo' => $github_account . '/Unyson-Analytics-Extension',
162
+ ),
163
+ ),
164
+ ),
165
+
166
+ 'feedback' => array(
167
+ 'display' => true,
168
+ 'parent' => null,
169
+ 'name' => __( 'Feedback', 'fw' ),
170
+ 'description' => __( 'Adds the possibility to leave feedback (comments, reviews and rating) about your products, articles, etc. This replaces the default comments system.', 'fw' ),
171
+ 'thumbnail' => $thumbnails_uri . '/feedback.jpg',
172
+ 'download' => array(
173
+ 'source' => 'github',
174
+ 'opts' => array(
175
+ 'user_repo' => $github_account . '/Unyson-Feedback-Extension',
176
+ ),
177
+ ),
178
+ ),
179
+
180
+ 'learning' => array(
181
+ 'display' => true,
182
+ 'parent' => null,
183
+ 'name' => __( 'Learning', 'fw' ),
184
+ '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' ),
185
+ 'thumbnail' => $thumbnails_uri . '/learning.jpg',
186
+ 'download' => array(
187
+ 'source' => 'github',
188
+ 'opts' => array(
189
+ 'user_repo' => $github_account . '/Unyson-Learning-Extension',
190
+ ),
191
+ ),
192
+ ),
193
+
194
+ 'shortcodes' => array(
195
+ 'display' => false,
196
+ 'parent' => null,
197
+ 'name' => __( 'Shortcodes', 'fw' ),
198
+ 'description' => '',
199
+ 'thumbnail' => 'about:blank',
200
+ 'download' => array(
201
+ 'source' => 'github',
202
+ 'opts' => array(
203
+ 'user_repo' => $github_account . '/Unyson-Shortcodes-Extension',
204
+ ),
205
+ ),
206
+ ),
207
+
208
+ 'builder' => array(
209
+ 'display' => false,
210
+ 'parent' => null,
211
+ 'name' => __( 'Builder', 'fw' ),
212
+ 'description' => '',
213
+ 'thumbnail' => 'about:blank',
214
+ 'download' => array(
215
+ 'source' => 'github',
216
+ 'opts' => array(
217
+ 'user_repo' => $github_account . '/Unyson-Builder-Extension',
218
+ ),
219
+ ),
220
+ ),
221
+
222
+ 'forms' => array(
223
+ 'display' => false,
224
+ 'parent' => null,
225
+ 'name' => __( 'Forms', 'fw' ),
226
+ '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' ),
227
+ 'thumbnail' => $thumbnails_uri . '/forms.jpg',
228
+ 'download' => array(
229
+ 'source' => 'github',
230
+ 'opts' => array(
231
+ 'user_repo' => $github_account . '/Unyson-Forms-Extension',
232
+ ),
233
+ ),
234
+ ),
235
+
236
+ 'mailer' => array(
237
+ 'display' => false,
238
+ 'parent' => null,
239
+ 'name' => __( 'Mailer', 'fw' ),
240
+ 'description' => __( 'This extension will let you set some global email options and it is used by other extensions (like Forms) to send emails.', 'fw' ),
241
+ 'thumbnail' => $thumbnails_uri . '/mailer.jpg',
242
+ 'download' => array(
243
+ 'source' => 'github',
244
+ 'opts' => array(
245
+ 'user_repo' => $github_account . '/Unyson-Mailer-Extension',
246
+ ),
247
+ ),
248
+ ),
249
+
250
+ 'social' => array(
251
+ 'display' => true,
252
+ 'parent' => null,
253
+ 'name' => __( 'Social', 'fw' ),
254
+ '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' ),
255
+ 'thumbnail' => $thumbnails_uri . '/social.jpg',
256
+ 'download' => array(
257
+ 'source' => 'github',
258
+ 'opts' => array(
259
+ 'user_repo' => $github_account . '/Unyson-Social-Extension',
260
+ ),
261
+ ),
262
+ ),
263
+
264
+ 'backup' => array(
265
+ 'display' => true,
266
+ 'parent' => null,
267
+ 'name' => __( 'Backup', 'fw' ),
268
+ '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' ),
269
+ 'thumbnail' => $thumbnails_uri . '/backup.jpg',
270
+ 'download' => array(
271
+ 'source' => 'github',
272
+ 'opts' => array(
273
+ 'user_repo' => $github_account . '/Unyson-Backup-Extension',
274
+ ),
275
+ ),
276
+ ),
277
+
278
+ 'media' => array(
279
+ 'display' => false,
280
+ 'parent' => null,
281
+ 'name' => __( 'Media', 'fw' ),
282
+ 'description' => '',
283
+ 'thumbnail' => 'about:blank',
284
+ 'download' => array(
285
+ 'source' => 'github',
286
+ 'opts' => array(
287
+ 'user_repo' => $github_account . '/Unyson-Empty-Extension',
288
+ ),
289
+ ),
290
+ ),
291
+
292
+ 'population-method' => array(
293
+ 'display' => false,
294
+ 'parent' => 'media',
295
+ 'name' => __( 'Population method', 'fw' ),
296
+ 'description' => '',
297
+ 'thumbnail' => 'about:blank',
298
+ 'download' => array(
299
+ 'source' => 'github',
300
+ 'opts' => array(
301
+ 'user_repo' => $github_account . '/Unyson-PopulationMethods-Extension',
302
+ ),
303
+ ),
304
+ ),
305
+
306
+ 'translatepress' => array(
307
+ 'display' => true,
308
+ 'parent' => null,
309
+ 'name' => __( 'Translate Press', 'fw' ),
310
+ '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' ),
311
+ 'thumbnail' => $thumbnails_uri . '/translation.jpg',
312
+ 'download' => array(
313
+ 'source' => 'custom',
314
+ 'url_set' => 'options-general.php?page=translate-press',
315
+ 'opts' => array(
316
+ 'plugin' => 'translatepress-multilingual/index.php',
317
+ 'remote' => 'https://downloads.wordpress.org/plugin/translatepress-multilingual'
318
+ )
319
+ )
320
+ ),
321
+
322
+ 'styling' => array(
323
+ 'display' => true,
324
+ 'parent' => null,
325
+ 'name' => __( 'Styling', 'fw' ),
326
+ 'description' => __( 'This extension lets you control the website visual style. Starting from predefined styles to changing specific fonts and colors across the website.', 'fw' ),
327
+ 'thumbnail' => $thumbnails_uri . '/styling.jpg',
328
+ 'download' => array(
329
+ 'source' => 'github',
330
+ 'opts' => array(
331
+ 'user_repo' => $github_account . '/Unyson-Styling-Extension',
332
+ ),
333
+ ),
334
+ )
335
+ );
336
+
framework/core/components/extensions/manager/class--fw-extensions-manager.php CHANGED
@@ -1,3671 +1,3670 @@
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
- private $manifest_default_values = array(
15
- 'display' => false,
16
- 'standalone' => false,
17
- );
18
-
19
- private $default_thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
20
-
21
- /**
22
- * @var FW_Access_Key
23
- */
24
- private static $access_key;
25
-
26
- private static function get_access_key() {
27
- if (!self::$access_key) {
28
- self::$access_key = new FW_Access_Key('fw_ext_manager');
29
- }
30
-
31
- return self::$access_key;
32
- }
33
-
34
- public function __construct()
35
- {
36
- // In any case/permission, make sure to not miss the plugin update actions to prevent extensions delete
37
- {
38
- add_action('fw_plugin_pre_update', array($this, '_action_plugin_pre_update'));
39
- add_action('fw_plugin_post_update', array($this, '_action_plugin_post_update'));
40
- }
41
-
42
- // Preserve {theme}/framework-customizations/theme/available-extensions.php
43
- {
44
- add_filter('upgrader_pre_install', array($this, '_filter_theme_available_extensions_copy'), 999, 2);
45
-
46
- /**
47
- * Must be executed after
48
- * https://github.com/WordPress/WordPress/blob/4.6/wp-admin/includes/class-theme-upgrader.php#L204-L205
49
- */
50
- add_action('upgrader_process_complete', array($this, '_action_theme_available_extensions_restore'), 999, 2);
51
- }
52
-
53
- add_action('fw_plugin_activate', array($this, '_action_plugin_activate_install_compatible_extensions'), 100);
54
- add_action('fw_after_plugin_activate', array($this, '_action_after_plugin_activate'), 100);
55
- add_action('after_switch_theme', array($this, '_action_theme_switch'));
56
-
57
- if (!is_admin()) {
58
- return;
59
- }
60
-
61
- if (!$this->can_activate() && !$this->can_install()) {
62
- return;
63
- }
64
-
65
- /** Actions */
66
- {
67
- add_action('fw_init', array($this, '_action_fw_init'));
68
- add_action('admin_menu', array($this, '_action_admin_menu'));
69
- add_action('network_admin_menu', array($this, '_action_admin_menu'));
70
- add_action('admin_footer', array($this, '_action_admin_footer'));
71
- add_action('admin_enqueue_scripts', array($this, '_action_enqueue_scripts'));
72
- add_action('admin_notices', array($this, '_action_admin_notices'));
73
-
74
- if ($this->can_install()) {
75
- add_action('wp_ajax_fw_extensions_check_direct_fs_access', array($this, '_action_ajax_check_direct_fs_access'));
76
- add_action('wp_ajax_fw_extensions_install', array($this, '_action_ajax_install'));
77
- add_action('wp_ajax_fw_extensions_uninstall', array($this, '_action_ajax_uninstall'));
78
- }
79
- }
80
-
81
- /** Filters */
82
- {
83
- add_filter('fw_plugin_action_list', array($this, '_filter_plugin_action_list'));
84
- }
85
- }
86
-
87
- /**
88
- * If current user can:
89
- * - activate extension
90
- * - disable extensions
91
- * - save extension settings options
92
- * @return bool
93
- */
94
- public function can_activate()
95
- {
96
- if ( fw_is_cli() ) {
97
- return true;
98
- }
99
-
100
- $can_activate = current_user_can('manage_options');
101
-
102
- if ($can_activate) {
103
- // also you can use this method to get the capability
104
- $can_activate = 'manage_options';
105
- }
106
-
107
- if (!$can_activate) {
108
- // make sure if can install, then also can activate. (can install) > (can activate)
109
- $can_activate = $this->can_install();
110
- }
111
-
112
- return $can_activate;
113
- }
114
-
115
- /**
116
- * If current user can:
117
- * - install extensions
118
- * - delete extensions
119
- * @return bool
120
- */
121
- public function can_install()
122
- {
123
- if ( fw_is_cli() ) {
124
- return true;
125
- }
126
-
127
- $capability = 'install_plugins';
128
-
129
- if (is_multisite()) {
130
- // only network admin can change files that affects the entire network
131
- $can_install = current_user_can_for_blog(get_current_blog_id(), $capability);
132
- } else {
133
- $can_install = current_user_can($capability);
134
- }
135
-
136
- if ($can_install) {
137
- // also you can use this method to get the capability
138
- $can_install = $capability;
139
- }
140
-
141
- return $can_install;
142
- }
143
-
144
- public function get_page_slug()
145
- {
146
- return 'fw-extensions';
147
- }
148
-
149
- private function get_cache_key($sub_key)
150
- {
151
- return 'fw_extensions_manager/'. $sub_key;
152
- }
153
-
154
- private function get_uri($append = '')
155
- {
156
- return fw_get_framework_directory_uri('/core/components/extensions/manager'. $append);
157
- }
158
-
159
- private function get_nonce($form) {
160
- switch ($form) {
161
- case 'install':
162
- return array(
163
- 'name' => '_nonce_fw_extensions_install',
164
- 'action' => 'install',
165
- );
166
- case 'delete':
167
- return array(
168
- 'name' => '_nonce_fw_extensions_delete',
169
- 'action' => 'delete',
170
- );
171
- case 'activate':
172
- return array(
173
- 'name' => '_nonce_fw_extensions_activate',
174
- 'action' => 'activate',
175
- );
176
- case 'deactivate':
177
- return array(
178
- 'name' => '_nonce_fw_extensions_deactivate',
179
- 'action' => 'deactivate',
180
- );
181
- default:
182
- return array(
183
- 'name' => '_nonce_fw_extensions',
184
- 'action' => 'default',
185
- );
186
- }
187
- }
188
-
189
- /**
190
- * Extensions available for download
191
- * @return array {name => data}
192
- *
193
- * @since 2.6.9
194
- */
195
- public function get_available_extensions()
196
- {
197
- try {
198
- $cache_key = $this->get_cache_key( 'available_extensions' );
199
-
200
- return FW_Cache::get($cache_key);
201
- } catch (FW_Cache_Not_Found_Exception $e) {
202
- $extensions = fw_get_variables_from_file(
203
- dirname( __FILE__ ) . '/available-extensions.php',
204
- array( 'extensions' => array() )
205
- );
206
- $extensions = $extensions['extensions'];
207
-
208
- // Allow theme to register available extensions
209
- if (file_exists(
210
- $theme_available_ext_file = fw_fix_path(get_template_directory())
211
- . fw_get_framework_customizations_dir_rel_path( '/theme/available-extensions.php' )
212
- )) {
213
- $register = new _FW_Available_Extensions_Register(self::get_access_key()->get_key());
214
-
215
- /**
216
- * Usage:
217
- * Create {theme}/framework-customizations/theme/available-extensions.php with the following contents:
218
- * $extension = new FW_Available_Extension();
219
- * $extension->set_...();
220
- * $register->register($extension);
221
- */
222
- fw_get_variables_from_file($theme_available_ext_file, array(), array('register' => $register));
223
-
224
- foreach ($register->_get_types(self::$access_key) as $extension) {
225
- /** @var FW_Available_Extension $extension */
226
- if (isset($extensions[ $extension->get_name() ])) {
227
- trigger_error(
228
- 'Overwriting default extension "'. $extension->get_name() .'" is not allowed',
229
- E_USER_WARNING
230
- );
231
- continue;
232
- } elseif (!$extension->is_valid()) {
233
- trigger_error(
234
- 'Theme extension "'. $extension->get_name() .'" is not valid',
235
- E_USER_WARNING
236
- );
237
- continue;
238
- } else {
239
- $extensions[ $extension->get_name() ] = array(
240
- 'theme' => true, // Registered by theme
241
- 'display' => $extension->get_display(),
242
- 'parent' => $extension->get_parent(),
243
- 'name' => $extension->get_title(),
244
- 'description' => $extension->get_description(),
245
- 'thumbnail' => $extension->get_thumbnail(),
246
- 'download' => $extension->get_download_source(),
247
- );
248
- }
249
- }
250
- }
251
-
252
- {
253
- $installed_extensions = $this->get_installed_extensions();
254
- $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
255
-
256
- if (isset($installed_extensions['backup'])) {
257
- // make sure only Backup or Backups can be installed
258
- unset($extensions['backups']);
259
- }
260
-
261
- foreach (
262
- array('backup', 'styling', 'translation', 'learning')
263
- as $obsolete_extension
264
- ) {
265
- if (
266
- !isset($supported_extensions[$obsolete_extension])
267
- &&
268
- !isset($installed_extensions[$obsolete_extension])
269
- ) {
270
- unset($extensions[$obsolete_extension]);
271
- }
272
- }
273
- }
274
-
275
- FW_Cache::set($cache_key, $extensions);
276
-
277
- return $extensions;
278
- }
279
- }
280
-
281
- /**
282
- * @internal
283
- */
284
- public function _action_ajax_check_direct_fs_access()
285
- {
286
- if (!$this->can_install()) {
287
- // if can't install, no need to know if has access or not
288
- wp_send_json_error();
289
- }
290
-
291
- if (FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
292
- wp_send_json_success();
293
- } else {
294
- wp_send_json_error();
295
- }
296
- }
297
-
298
- /**
299
- * @internal
300
- */
301
- public function _action_ajax_install()
302
- {
303
- if (!$this->can_install()) {
304
- // if can't install, no need to know if has access or not
305
- wp_send_json_error();
306
- }
307
-
308
- if (!FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
309
- wp_send_json_error();
310
- }
311
-
312
- $extension = (string)FW_Request::POST('extension');
313
-
314
- $install_result = $this->install_extensions(array(
315
- $extension => array()
316
- ), array(
317
- 'cancel_on_error' => true
318
- ));
319
-
320
- if ($install_result === true) {
321
- wp_send_json_success();
322
- } else {
323
- wp_send_json_error($install_result);
324
- }
325
- }
326
-
327
- /**
328
- * @internal
329
- */
330
- public function _action_ajax_uninstall()
331
- {
332
- if (!$this->can_install()) {
333
- // if can't install, no need to know if has access or not
334
- wp_send_json_error();
335
- }
336
-
337
- if (!FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
338
- wp_send_json_error();
339
- }
340
-
341
- $extension = (string)FW_Request::POST('extension');
342
-
343
- $install_result = $this->uninstall_extensions(array(
344
- $extension => array()
345
- ), array(
346
- 'cancel_on_error' => true
347
- ));
348
-
349
- if ($install_result === true) {
350
- wp_send_json_success();
351
- } else {
352
- wp_send_json_error($install_result);
353
- }
354
- }
355
-
356
- /**
357
- * @internal
358
- */
359
- public function _action_after_plugin_activate()
360
- {
361
- $this->activate_theme_extensions();
362
- $this->activate_extensions(
363
- array_fill_keys(
364
- array_keys(fw()->theme->manifest->get('supported_extensions', array())),
365
- array()
366
- )
367
- );
368
-
369
- do_action('fw_after_plugin_activate:before_potential_redirect');
370
-
371
- if (is_admin() && $this->can_install() && $this->get_supported_extensions_for_install()) {
372
- wp_redirect($this->get_link() . '&sub-page=install&supported');
373
- exit;
374
- }
375
- }
376
-
377
- /**
378
- * Copy all extensions to a temp backup directory
379
- * @internal
380
- */
381
- public function _action_plugin_pre_update()
382
- {
383
- /** @var WP_Filesystem_Base $wp_filesystem */
384
- global $wp_filesystem;
385
-
386
- if (!FW_WP_Filesystem::is_ready()) {
387
- return;
388
- }
389
-
390
- // a directory outside the plugin
391
- $tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
392
- fw_fix_path(WP_CONTENT_DIR) .'/tmp/fw-plugin-update-extensions-backup'
393
- );
394
- $extensions_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
395
- fw_get_framework_directory('/extensions')
396
- );
397
-
398
- $error = false;
399
-
400
- do {
401
- if ($wp_filesystem->exists($tmp_dir)) {
402
- if (!$wp_filesystem->delete($tmp_dir, true, 'd')) {
403
- $error = __('Cannot remove the old extensions backup dir', 'fw');
404
- break;
405
- }
406
- }
407
-
408
- if (!FW_WP_Filesystem::mkdir_recursive($tmp_dir)) {
409
- $error = __('Cannot create the extensions backup dir', 'fw');
410
- break;
411
- }
412
-
413
- if (true !== copy_dir($extensions_dir, $tmp_dir)) {
414
- $error = __('Cannot backup the extensions', 'fw');
415
- break;
416
- }
417
- } while(false);
418
-
419
- if ($error) {
420
- trigger_error($error, E_USER_WARNING);
421
-
422
- $wp_filesystem->delete($tmp_dir, true, 'd');
423
- }
424
- }
425
-
426
- /**
427
- * Copy all extensions from the temp backup directory to the framework extensions directory (recover)
428
- * @internal
429
- */
430
- public function _action_plugin_post_update()
431
- {
432
- /** @var WP_Filesystem_Base $wp_filesystem */
433
- global $wp_filesystem;
434
-
435
- if (!FW_WP_Filesystem::is_ready()) {
436
- return;
437
- }
438
-
439
- // a directory outside the plugin
440
- $tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
441
- fw_fix_path( WP_CONTENT_DIR ) .'/tmp/fw-plugin-update-extensions-backup'
442
- );
443
- $extensions_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
444
- fw_get_framework_directory( '/extensions' )
445
- );
446
-
447
- if (!$wp_filesystem->exists($tmp_dir) || !$wp_filesystem->exists($extensions_dir)) {
448
- return;
449
- }
450
-
451
- $error = false;
452
-
453
- do {
454
- if ($wp_filesystem->exists($extensions_dir)) {
455
- /**
456
- * Make sure to remove framework initial extensions
457
- * The user do not need them because he already used the framework and has in backup the extensions he uses
458
- */
459
- if (!$wp_filesystem->delete( $extensions_dir, true, 'd' )) {
460
- $error = __( 'Cannot clear the extensions directory', 'fw' );
461
- break;
462
- }
463
-
464
- if ( ! FW_WP_Filesystem::mkdir_recursive( $extensions_dir ) ) {
465
- $error = __( 'Cannot recreate the extensions directory', 'fw' );
466
- break;
467
- }
468
- }
469
-
470
- if (true !== copy_dir($tmp_dir, $extensions_dir)) {
471
- $error = __('Cannot recover the extensions', 'fw');
472
- break;
473
- }
474
- } while(false);
475
-
476
- if ($error) {
477
- trigger_error($error, E_USER_WARNING);
478
- } else {
479
- // extensions successfully recovered, the backup is not needed anymore
480
- $wp_filesystem->delete($tmp_dir, true, 'd');
481
- }
482
- }
483
-
484
- /**
485
- * Scan all directories for extensions
486
- *
487
- * @param bool $reset_cache
488
- * @return array
489
- *
490
- * @since 2.6.9
491
- */
492
- public function get_installed_extensions($reset_cache = false)
493
- {
494
- $cache_key = $this->get_cache_key('installed_extensions');
495
-
496
- if ($reset_cache) {
497
- FW_Cache::del($cache_key);
498
- }
499
-
500
- try {
501
- return FW_Cache::get($cache_key);
502
- } catch (FW_Cache_Not_Found_Exception $e) {
503
- $extensions = array();
504
-
505
- foreach (fw()->extensions->get_locations() as $location) {
506
- // leave only used keys
507
- $location = array(
508
- 'path' => $location['path'],
509
- 'is' => $location['is'],
510
- );
511
-
512
- $this->read_extensions($location, $extensions);
513
- }
514
-
515
- FW_Cache::set($cache_key, $extensions);
516
-
517
- return $extensions;
518
- }
519
- }
520
-
521
- /**
522
- * used by $this->get_installed_extensions()
523
- * @param string $location
524
- * @param array $list
525
- * @param null|string $parent_extension_name
526
- */
527
- private function read_extensions($location, &$list, $parent_extension_name = null)
528
- {
529
- $paths = glob($location['path'] .'/*', GLOB_ONLYDIR | GLOB_NOSORT);
530
-
531
- if (empty($paths)) {
532
- return;
533
- }
534
-
535
- foreach ($paths as $extension_path) {
536
- $extension_name = basename($extension_path);
537
-
538
- if (isset($list[$extension_name])) {
539
- // extension already found
540
- } elseif (file_exists($extension_path .'/manifest.php')) {
541
- $vars = fw_get_variables_from_file($extension_path .'/manifest.php', array(
542
- 'manifest' => array(),
543
- ));
544
-
545
- $list[$extension_name] = array(
546
- 'path' => $extension_path,
547
- 'manifest' => $vars['manifest'],
548
- 'children' => array(),
549
- 'active' => (bool)fw()->extensions->get($extension_name),
550
- 'parent' => $parent_extension_name,
551
- 'is' => $location['is'],
552
- );
553
-
554
- if ($parent_extension_name) {
555
- $list[ $parent_extension_name ]['children'][$extension_name] = array();
556
- }
557
- } else {
558
- // it's a directory with customizations for an extension
559
- continue;
560
- }
561
-
562
- $sub_extension_location = $location;
563
- $sub_extension_location['path'] .= '/'. $extension_name .'/extensions';
564
-
565
- $this->read_extensions(
566
- $sub_extension_location,
567
- $list,
568
- $extension_name
569
- );
570
- }
571
- }
572
-
573
- private function get_tmp_dir($append = '')
574
- {
575
- return apply_filters('fw_tmp_dir', fw_fix_path(WP_CONTENT_DIR) .'/tmp') . $append;
576
- }
577
-
578
- /**
579
- * @internal
580
- */
581
- public function _action_fw_init()
582
- {
583
- $this->extension_settings_form = new FW_Form('fw_extension_settings', array(
584
- 'render' => array($this, '_extension_settings_form_render'),
585
- 'validate' => array($this, '_extension_settings_form_validate'),
586
- 'save' => array($this, '_extension_settings_form_save'),
587
- ));
588
-
589
- if (is_admin() && $this->can_activate()) {
590
- $db_wp_option_name = 'fw_extensions_activation';
591
-
592
- if ($db_wp_option_value = get_option($db_wp_option_name, array())) {
593
- $db_wp_option_value = array_merge(array(
594
- 'activated' => array(),
595
- 'deactivated' => array(),
596
- ), $db_wp_option_value);
597
-
598
- /**
599
- * Fire the 'fw_extensions_after_activation' action
600
- */
601
- if ($db_wp_option_value['activated']) {
602
- $succeeded_extensions = $failed_extensions = array();
603
-
604
- foreach ($db_wp_option_value['activated'] as $extension_name => $not_used_var) {
605
- if (fw_ext($extension_name)) {
606
- $succeeded_extensions[$extension_name] = array();
607
- } else {
608
- $failed_extensions[$extension_name] = array();
609
- }
610
- }
611
-
612
- if (!empty($succeeded_extensions)) {
613
- do_action('fw_extensions_after_activation', $succeeded_extensions);
614
- }
615
- if (!empty($failed_extensions)) {
616
- do_action('fw_extensions_activation_failed', $failed_extensions);
617
- }
618
- }
619
-
620
- /**
621
- * Fire the 'fw_extensions_after_deactivation' action
622
- */
623
- if ($db_wp_option_value['deactivated']) {
624
- $succeeded_extensions = $failed_extensions = array();
625
-
626
- foreach ($db_wp_option_value['deactivated'] as $extension_name => $not_used_var) {
627
- if (!fw_ext($extension_name)) {
628
- $succeeded_extensions[$extension_name] = array();
629
- } else {
630
- $failed_extensions[$extension_name] = array();
631
- }
632
- }
633
-
634
- if (!empty($succeeded_extensions)) {
635
- do_action('fw_extensions_after_deactivation', $succeeded_extensions);
636
- }
637
- if (!empty($failed_extensions)) {
638
- do_action('fw_extensions_deactivation_failed', $failed_extensions);
639
- }
640
- }
641
-
642
- delete_option($db_wp_option_name);
643
- }
644
- }
645
- }
646
-
647
- /**
648
- * Activate extensions with $manifest['display'] = false; $manifest['standalone'] = true;
649
- * - First level extensions
650
- * - Child extensions of the active extensions
651
- */
652
- private function activate_hidden_standalone_extensions()
653
- {
654
- if (!is_admin()) {
655
- return;
656
- }
657
-
658
- if (!$this->can_activate()) {
659
- return;
660
- }
661
-
662
- $activate_extensions = array();
663
-
664
- foreach (
665
- // all disabled extensions
666
- array_diff_key($this->get_installed_extensions(), fw()->extensions->get_all())
667
- as $ext_name => $ext_data
668
- ) {
669
- if ($ext_data['parent'] && !fw_ext($ext_data['parent'])) {
670
- // child extensions of an inactive extension
671
- continue;
672
- }
673
-
674
- if (false !== fw_akg(
675
- 'display',
676
- $ext_data['manifest'],
677
- $this->manifest_default_values['display']
678
- )) {
679
- // is visible
680
- continue;
681
- }
682
-
683
- if (true !== fw_akg(
684
- 'standalone',
685
- $ext_data['manifest'],
686
- $this->manifest_default_values['standalone']
687
- )) {
688
- // not standalone
689
- continue;
690
- }
691
-
692
- $collected = $this->get_extensions_for_activation($ext_name);
693
-
694
- if (is_wp_error($collected)) {
695
- if (defined('WP_DEBUG') && WP_DEBUG) {
696
- if ($this->is_extensions_page()) {
697
- // display this warning only on Unyson extensions page
698
- FW_Flash_Messages::add('fw_ext_auto_activate_hidden_standalone',
699
- sprintf(__('Cannot activate hidden standalone extension %s', 'fw'),
700
- fw_akg('name', $ext_data['manifest'], fw_id_to_title($ext_name))
701
- ),
702
- 'error'
703
- );
704
- }
705
- }
706
- return;
707
- }
708
-
709
- $activate_extensions = array_merge($activate_extensions, $collected);
710
- }
711
-
712
- if (empty($activate_extensions)) {
713
- return;
714
- }
715
-
716
- $option_name = fw()->extensions->_get_active_extensions_db_option_name();
717
-
718
- $db_active_extensions = array_merge(get_option($option_name, array()), $activate_extensions);
719
-
720
- update_option($option_name, $db_active_extensions);
721
- }
722
-
723
- /**
724
- * @internal
725
- */
726
- public function _action_admin_menu()
727
- {
728
- $capability = $this->can_activate();
729
-
730
- if (!$capability) {
731
- return;
732
- }
733
-
734
- $data = array(
735
- 'title' => fw()->manifest->get_name(),
736
- 'capability' => $capability,
737
- 'slug' => $this->get_page_slug(),
738
- 'content_callback' => array($this, '_display_page'),
739
- );
740
-
741
- /**
742
- * Collect $hookname that contains $data['slug'] before the action
743
- * and skip them in verification after action
744
- */
745
- {
746
- global $_registered_pages;
747
-
748
- $found_hooknames = array();
749
-
750
- if (!empty($_registered_pages)) {
751
- foreach ( $_registered_pages as $hookname => $b ) {
752
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
753
- $found_hooknames[$hookname] = true;
754
- }
755
- }
756
- }
757
- }
758
-
759
- /**
760
- * Use this action if you what to add the extensions page in a custom place in menu
761
- * Usage example http://pastebin.com/2iWVRPAU
762
- */
763
- do_action('fw_backend_add_custom_extensions_menu', $data);
764
-
765
- /**
766
- * Check if menu was added in the action above
767
- */
768
- {
769
- $menu_exists = false;
770
-
771
- if (!empty($_registered_pages)) {
772
- foreach ( $_registered_pages as $hookname => $b ) {
773
- if (isset($found_hooknames[$hookname])) {
774
- continue;
775
- }
776
-
777
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
778
- $menu_exists = true;
779
- break;
780
- }
781
- }
782
- }
783
- }
784
-
785
- if ($menu_exists) {
786
- // do nothing
787
- } else {
788
- add_menu_page(
789
- $data['title'],
790
- $data['title'],
791
- $data['capability'],
792
- $data['slug'],
793
- $data['content_callback'],
794
- 'none',
795
- 3
796
- );
797
- }
798
- }
799
-
800
- /**
801
- * If output already started, we cannot set the redirect header, do redirect from js
802
- */
803
- private function js_redirect()
804
- {
805
- echo
806
- '<script type="text/javascript">'.
807
- 'window.location.replace("'. esc_js($this->get_link()) .'");'.
808
- '</script>';
809
- }
810
-
811
- /**
812
- * @internal
813
- */
814
- public function _display_page()
815
- {
816
- $page = FW_Request::GET('sub-page');
817
-
818
- switch ($page) {
819
- case 'install':
820
- $this->display_install_page();
821
- break;
822
- case 'delete':
823
- $this->display_delete_page();
824
- break;
825
- case 'extension':
826
- $this->display_extension_page();
827
- break;
828
- case 'activate':
829
- $this->display_activate_page();
830
- break;
831
- case 'deactivate':
832
- $this->display_deactivate_page();
833
- break;
834
- default:
835
- $this->display_list_page();
836
- }
837
- }
838
-
839
- private function display_list_page()
840
- {
841
- // note: static is enqueued in 'admin_enqueue_scripts' action
842
-
843
- /** Prepare extensions list for view */
844
- {
845
- $lists = array(
846
- 'active' => array(),
847
- 'disabled' => array(),
848
- 'installed' => array(),
849
- 'available' => array(),
850
- 'supported' => array(),
851
- );
852
-
853
- foreach ($this->get_installed_extensions() as $ext_name => $ext_data) {
854
- $lists[ $ext_data['active'] ? 'active' : 'disabled' ][$ext_name] = $ext_data;
855
- }
856
-
857
- $lists['installed'] = $lists['active'] + $lists['disabled'];
858
-
859
- unset($ext_data); // prevent change by reference
860
-
861
- foreach ($this->get_available_extensions() as $ext_name => $ext_data) {
862
- $lists['available'][$ext_name] = array(
863
- 'name' => $ext_data['name'],
864
- 'description' => $ext_data['description'],
865
- 'thumbnail' => isset($ext_data['thumbnail'])
866
- ? $ext_data['thumbnail']
867
- : (isset($lists['installed'][$ext_name])
868
- ? fw_akg('thumbnail', $lists['installed'][$ext_name]['manifest'], $this->default_thumbnail)
869
- : $this->default_thumbnail),
870
- 'display' => isset($ext_data['display'])
871
- ? $ext_data['display']
872
- : $this->manifest_default_values['display'],
873
- 'theme' => isset($ext_data['theme']) && $ext_data['theme'],
874
- );
875
-
876
- if ($lists['available'][$ext_name]['theme']) {
877
- $lists['supported'][$ext_name] = array(
878
- 'name' => $lists['available'][$ext_name]['name'],
879
- 'description' => $lists['available'][$ext_name]['description'],
880
- );
881
- }
882
- }
883
-
884
- foreach (fw()->theme->manifest->get('supported_extensions', array()) as $required_ext_name => $required_ext_data) {
885
- if (isset($lists['installed'][ $required_ext_name ])) {
886
- $lists['supported'][ $required_ext_name ] = array(
887
- 'name' => fw_akg( 'name', $lists['installed'][ $required_ext_name ]['manifest'], fw_id_to_title( $required_ext_name ) ),
888
- 'description' => fw_akg( 'description', $lists['installed'][ $required_ext_name ]['manifest'], '' ),
889
- );
890
- } elseif (isset($lists['available'][$required_ext_name])) {
891
- $lists['supported'][ $required_ext_name ] = array(
892
- 'name' => $lists['available'][ $required_ext_name ]['name'],
893
- 'description' => $lists['available'][ $required_ext_name ]['description'],
894
- );
895
- } else {
896
- $lists['supported'][ $required_ext_name ] = array(
897
- 'name' => fw_id_to_title( $required_ext_name ),
898
- 'description' => '',
899
- );
900
- }
901
- }
902
- }
903
-
904
- echo '<div class="wrap">';
905
-
906
- echo '<h2>'. sprintf(__('%s Extensions', 'fw'), fw()->manifest->get_name()) .'</h2><br/>';
907
-
908
- echo '<div id="fw-extensions-list-wrapper">';
909
-
910
- fw_render_view(dirname(__FILE__) .'/views/extensions-page.php', array(
911
- 'lists' => &$lists,
912
- 'link' => $this->get_link(),
913
- 'display_default_value' => $this->manifest_default_values['display'],
914
- 'default_thumbnail' => $this->default_thumbnail,
915
- 'nonces' => array(
916
- 'delete' => $this->get_nonce('delete'),
917
- 'install' => $this->get_nonce('install'),
918
- 'activate' => $this->get_nonce('activate'),
919
- 'deactivate' => $this->get_nonce('deactivate'),
920
- ),
921
- 'can_install' => $this->can_install(),
922
- ), false);
923
-
924
- echo '</div>';
925
-
926
- echo '</div>';
927
- }
928
-
929
- private function display_install_page()
930
- {
931
- $flash_id = 'fw_extensions_install';
932
-
933
- if (!$this->can_install()) {
934
- FW_Flash_Messages::add(
935
- $flash_id,
936
- __('You are not allowed to install extensions.', 'fw'),
937
- 'error'
938
- );
939
- $this->js_redirect();
940
- return;
941
- }
942
-
943
- if (array_key_exists('supported', $_GET)) {
944
- $supported = true;
945
- $extensions = array_fill_keys(
946
- array_keys($this->get_supported_extensions_for_install()),
947
- array()
948
- );
949
-
950
- if (empty($extensions)) {
951
- FW_Flash_Messages::add(
952
- $flash_id,
953
- __('All supported extensions are already installed.', 'fw'),
954
- 'info'
955
- );
956
- $this->js_redirect();
957
- return;
958
- }
959
- } else {
960
- $supported = false;
961
-
962
- $extensions = array_fill_keys(
963
- array_map( 'trim', explode( ',', FW_Request::GET( 'extension', '' ) )),
964
- array()
965
- );
966
-
967
- // activate already installed extensions
968
- $this->activate_extensions($extensions);
969
- }
970
-
971
- {
972
- $skin = new _FW_Extensions_Install_Upgrader_Skin(array(
973
- 'title' => $supported
974
- ? _n('Install Compatible Extension', 'Install Compatible Extensions', count($extensions), 'fw')
975
- : _n('Install Extension', 'Install Extensions', count($extensions), 'fw'),
976
- ));
977
- }
978
-
979
- $skin->header();
980
-
981
- do {
982
- $nonce = $this->get_nonce('install');
983
-
984
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
985
- if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
986
- $skin->error(__('Invalid nonce.', 'fw'));
987
- break;
988
- }
989
-
990
- if (!FW_WP_Filesystem::request_access(
991
- fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
992
- )) {
993
- break;
994
- }
995
-
996
- $install_result = $this->install_extensions($extensions, array('verbose' => $skin));
997
-
998
- if (is_wp_error($install_result)) {
999
- $skin->error($install_result);
1000
- } elseif (is_array($install_result)) {
1001
- $error = array();
1002
-
1003
- foreach ($install_result as $extension_name => $extension_result) {
1004
- if (is_wp_error($extension_result)) {
1005
- $error[] = $extension_result->get_error_message();
1006
- }
1007
- }
1008
-
1009
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1010
-
1011
- $skin->error($error);
1012
- } elseif ($install_result === true) {
1013
- $skin->set_result(true);
1014
- }
1015
-
1016
- /** @var WP_Filesystem_Base $wp_filesystem */
1017
- global $wp_filesystem;
1018
-
1019
- $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
1020
-
1021
- if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
1022
- if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
1023
- $skin->error(
1024
- sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
1025
- );
1026
- }
1027
- }
1028
-
1029
- $skin->after(array(
1030
- 'extensions_page_link' => $this->get_link()
1031
- ));
1032
-
1033
- if ($supported && $install_result === true) {
1034
- /**
1035
- * @since 2.6.14
1036
- * Fixes https://github.com/ThemeFuse/Unyson/issues/2330
1037
- */
1038
- do_action( 'fw_after_supported_extensions_install_success' );
1039
- }
1040
- } else {
1041
- echo '<form method="post">';
1042
-
1043
- wp_nonce_field($nonce['action'], $nonce['name']);
1044
-
1045
- $extension_titles = array();
1046
- foreach ($extensions as $extension_name => $not_used_var) {
1047
- $extension_titles[$extension_name] = $this->get_extension_title($extension_name);
1048
- }
1049
-
1050
- fw_render_view(dirname(__FILE__) .'/views/install-form.php', array(
1051
- 'extension_titles' => $extension_titles,
1052
- 'list_page_link' => $this->get_link(),
1053
- 'supported' => $supported
1054
- ), false);
1055
-
1056
- echo '</form>';
1057
- }
1058
- } while(false);
1059
-
1060
- $skin->footer();
1061
- }
1062
-
1063
- /**
1064
- * Download (and activate) extensions
1065
- * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1066
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1067
- * @param array $opts
1068
- * @return WP_Error|bool|array
1069
- * true: when all extensions succeeded
1070
- * array: when some/all failed
1071
- */
1072
- public function install_extensions(array $extensions, $opts = array())
1073
- {
1074
- {
1075
- $opts = array_merge(array(
1076
- /**
1077
- * @type bool
1078
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1079
- * true: return first WP_Error or true on success
1080
- */
1081
- 'cancel_on_error' => false,
1082
- /**
1083
- * @type bool Activate installed extensions
1084
- */
1085
- 'activate' => true,
1086
- /**
1087
- * @type bool|WP_Upgrader_Skin
1088
- */
1089
- 'verbose' => false,
1090
- ), $opts);
1091
-
1092
- $cancel_on_error = $opts['cancel_on_error']; // fixme: remove successfully installed extensions before error?
1093
- $activate = $opts['activate'];
1094
- $verbose = $opts['verbose'];
1095
-
1096
- unset($opts);
1097
- }
1098
-
1099
- if (!$this->can_install()) {
1100
- return new WP_Error(
1101
- 'access_denied',
1102
- __('You have no permissions to install extensions', 'fw')
1103
- );
1104
- }
1105
-
1106
- if (empty($extensions)) {
1107
- return new WP_Error(
1108
- 'no_extensions',
1109
- __('No extensions provided', 'fw')
1110
- );
1111
- }
1112
-
1113
- global $wp_filesystem;
1114
-
1115
- if (!FW_WP_Filesystem::is_ready()) {
1116
- return new WP_Error(
1117
- 'fs_not_initialized',
1118
- __('WP Filesystem is not initialized', 'fw')
1119
- );
1120
- }
1121
-
1122
- if (function_exists('ini_get')) {
1123
- $timeout = intval(ini_get('max_execution_time'));
1124
- } else {
1125
- $timeout = false;
1126
- }
1127
-
1128
- $available_extensions = $this->get_available_extensions();
1129
- $installed_extensions = $this->get_installed_extensions();
1130
-
1131
- $result = $downloaded_extensions = array();
1132
- $has_errors = false;
1133
-
1134
- while (!empty($extensions)) {
1135
- $not_used_var = reset($extensions);
1136
- $extension_name = key($extensions);
1137
- unset($extensions[$extension_name]);
1138
-
1139
- $extensions_before_install = array_keys($installed_extensions);
1140
-
1141
- if (isset($installed_extensions[$extension_name])) {
1142
- $result[$extension_name] = new WP_Error(
1143
- 'extension_installed',
1144
- sprintf(__('Extension "%s" is already installed.', 'fw'), $this->get_extension_title($extension_name))
1145
- );
1146
- $has_errors = true;
1147
-
1148
- if ($cancel_on_error) {
1149
- break;
1150
- } else {
1151
- continue;
1152
- }
1153
- }
1154
-
1155
- if (!isset($available_extensions[ $extension_name ])) {
1156
- $result[$extension_name] = new WP_Error(
1157
- 'extension_not_available',
1158
- sprintf(
1159
- __('Extension "%s" is not available for install.', 'fw'),
1160
- $this->get_extension_title($extension_name)
1161
- )
1162
- );
1163
- $has_errors = true;
1164
-
1165
- if ($cancel_on_error) {
1166
- break;
1167
- } else {
1168
- continue;
1169
- }
1170
- }
1171
-
1172
- /**
1173
- * Find parent extensions
1174
- * they will be installed if does not exist
1175
- */
1176
- {
1177
- $parents = array($extension_name);
1178
-
1179
- $current_parent = $extension_name;
1180
- while (!empty($available_extensions[$current_parent]['parent'])) {
1181
- $current_parent = $available_extensions[$current_parent]['parent'];
1182
-
1183
- if (!isset($available_extensions[$current_parent])) {
1184
- $result[$extension_name] = new WP_Error(
1185
- 'parent_extension_not_available',
1186
- sprintf(
1187
- __('Parent extension "%s" not available.', 'fw'),
1188
- $this->get_extension_title($current_parent)
1189
- )
1190
- );
1191
- $has_errors = true;
1192
-
1193
- if ($cancel_on_error) {
1194
- break 2;
1195
- } else {
1196
- continue 2;
1197
- }
1198
- }
1199
-
1200
- $parents[] = $current_parent;
1201
- }
1202
-
1203
- $parents = array_reverse($parents);
1204
- }
1205
-
1206
- /**
1207
- * Install parent extensions and the extension
1208
- */
1209
- {
1210
- $destination_path = array(
1211
- 'framework' => fw_get_framework_directory(),
1212
- 'theme' => fw_fix_path(get_template_directory()) . fw_get_framework_customizations_dir_rel_path()
1213
- );
1214
- $current_extension_path = '';
1215
-
1216
- foreach ($parents as $parent_extension_name) {
1217
- $current_extension_path .= '/extensions/'. $parent_extension_name;
1218
- $destination = (
1219
- isset($available_extensions[$parent_extension_name]['theme'])
1220
- &&
1221
- $available_extensions[$parent_extension_name]['theme']
1222
- ) ? 'theme' : 'framework';
1223
-
1224
- if (isset($installed_extensions[$parent_extension_name])) {
1225
- continue; // skip already installed extensions
1226
- }
1227
-
1228
- if ($verbose) {
1229
- $verbose_message = sprintf(__('Downloading the "%s" extension...', 'fw'),
1230
- $this->get_extension_title($parent_extension_name)
1231
- );
1232
-
1233
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1234
- $verbose->feedback($verbose_message);
1235
- } else {
1236
- echo fw_html_tag('p', array(), $verbose_message);
1237
- }
1238
- }
1239
-
1240
- // increase timeout
1241
- if ($timeout !== false && function_exists('set_time_limit')) {
1242
- $timeout += 30;
1243
- set_time_limit($timeout);
1244
- }
1245
-
1246
- $wp_fw_downloaded_dir = $this->download(
1247
- $parent_extension_name,
1248
- $available_extensions[$parent_extension_name]
1249
- );
1250
-
1251
- if (is_wp_error($wp_fw_downloaded_dir)) {
1252
- if ($verbose) {
1253
- $verbose_message = $wp_fw_downloaded_dir->get_error_message();
1254
-
1255
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1256
- $verbose->error($verbose_message);
1257
- } else {
1258
- echo fw_html_tag('p', array(), $verbose_message);
1259
- }
1260
- }
1261
-
1262
- $result[$extension_name] = $wp_fw_downloaded_dir;
1263
- $has_errors = true;
1264
-
1265
- if ($cancel_on_error) {
1266
- break 2;
1267
- } else {
1268
- continue 2;
1269
- }
1270
- }
1271
-
1272
- if ($verbose) {
1273
- $verbose_message = sprintf(__('Installing the "%s" extension...', 'fw'),
1274
- $this->get_extension_title($parent_extension_name)
1275
- );
1276
-
1277
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1278
- $verbose->feedback($verbose_message);
1279
- } else {
1280
- echo fw_html_tag('p', array(), $verbose_message);
1281
- }
1282
- }
1283
-
1284
- $merge_result = $this->merge_extension(
1285
- $wp_fw_downloaded_dir,
1286
- FW_WP_Filesystem::real_path_to_filesystem_path(
1287
- $destination_path[$destination] . $current_extension_path
1288
- )
1289
- );
1290
-
1291
- if (is_wp_error($merge_result)) {
1292
- if ($verbose) {
1293
- $verbose_message = $merge_result->get_error_message();
1294
-
1295
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1296
- $verbose->error($verbose_message);
1297
- } else {
1298
- echo fw_html_tag('p', array(), $verbose_message);
1299
- }
1300
- }
1301
-
1302
- $result[$extension_name] = $merge_result;
1303
- $has_errors = true;
1304
-
1305
- if ($cancel_on_error) {
1306
- break 2;
1307
- } else {
1308
- continue 2;
1309
- }
1310
- }
1311
-
1312
- if ($verbose) {
1313
- $verbose_message = sprintf(__('The %s extension has been successfully installed.', 'fw'),
1314
- $this->get_extension_title($parent_extension_name)
1315
- );
1316
-
1317
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1318
- $verbose->feedback($verbose_message);
1319
- } else {
1320
- echo fw_html_tag('p', array(), $verbose_message);
1321
- }
1322
- }
1323
-
1324
- $downloaded_extensions[$parent_extension_name] = array();
1325
-
1326
- /**
1327
- * Read again all extensions
1328
- * The downloaded extension may contain more sub extensions
1329
- */
1330
- {
1331
- unset($installed_extensions);
1332
- $installed_extensions = $this->get_installed_extensions(true);
1333
- }
1334
- }
1335
- }
1336
-
1337
- $result[$extension_name] = true;
1338
-
1339
- /**
1340
- * Collect required extensions of the newly installed extensions
1341
- */
1342
- foreach (
1343
- // new extensions
1344
- array_diff(
1345
- array_keys($installed_extensions),
1346
- $extensions_before_install
1347
- )
1348
- as $new_extension_name
1349
- ) {
1350
- foreach (
1351
- array_keys(
1352
- fw_akg(
1353
- 'requirements/extensions',
1354
- $installed_extensions[$new_extension_name]['manifest'],
1355
- array()
1356
- )
1357
- )
1358
- as $required_extension_name
1359
- ) {
1360
- if (isset($installed_extensions[$required_extension_name])) {
1361
- // already installed
1362
- continue;
1363
- }
1364
-
1365
- $extensions[$required_extension_name] = array();
1366
- }
1367
- }
1368
- }
1369
-
1370
- if ($activate) {
1371
- $activate_extensions = array();
1372
-
1373
- foreach ($result as $extension_name => $extension_result) {
1374
- if (!is_wp_error($extension_result)) {
1375
- $activate_extensions[$extension_name] = array();
1376
- }
1377
- }
1378
-
1379
- if (!empty($activate_extensions)) {
1380
- if ($verbose) {
1381
- $verbose_message = _n(
1382
- 'Activating extension...',
1383
- 'Activating extensions...',
1384
- count($activate_extensions),
1385
- 'fw'
1386
- );
1387
-
1388
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1389
- $verbose->feedback($verbose_message);
1390
- } else {
1391
- echo fw_html_tag('p', array(), $verbose_message);
1392
- }
1393
- }
1394
-
1395
- $activation_result = $this->activate_extensions($activate_extensions);
1396
-
1397
- if ($verbose) {
1398
- if (is_wp_error($activation_result)) {
1399
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1400
- $verbose->error($activation_result->get_error_message());
1401
- } else {
1402
- echo fw_html_tag('p', array(), $activation_result->get_error_message());
1403
- }
1404
- } elseif (is_array($activation_result)) {
1405
- $verbose_message = array();
1406
-
1407
- foreach ($activation_result as $extension_name => $extension_result) {
1408
- if (is_wp_error($extension_result)) {
1409
- $verbose_message[] = $extension_result->get_error_message();
1410
- }
1411
- }
1412
-
1413
- $verbose_message = '<ul><li>' . implode('</li><li>', $verbose_message) . '</li></ul>';
1414
-
1415
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1416
- $verbose->error($verbose_message);
1417
- } else {
1418
- echo fw_html_tag('p', array(), $verbose_message);
1419
- }
1420
- } elseif ($activation_result === true) {
1421
- $verbose_message = _n(
1422
- 'Extension has been successfully activated.',
1423
- 'Extensions has been successfully activated.',
1424
- count($activate_extensions),
1425
- 'fw'
1426
- );
1427
-
1428
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1429
- $verbose->feedback($verbose_message);
1430
- } else {
1431
- echo fw_html_tag('p', array(), $verbose_message);
1432
- }
1433
- }
1434
- }
1435
- }
1436
- }
1437
-
1438
- do_action('fw_extensions_install', $result);
1439
-
1440
- if (
1441
- $cancel_on_error
1442
- &&
1443
- $has_errors
1444
- ) {
1445
- if (
1446
- ($last_result = end($result))
1447
- &&
1448
- is_wp_error($last_result)
1449
- ) {
1450
- return $last_result;
1451
- } else {
1452
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
1453
- return new WP_Error(
1454
- 'installation_failed',
1455
- _n('Cannot install extension', 'Cannot install extensions', count($extensions), 'fw')
1456
- );
1457
- }
1458
- }
1459
-
1460
- if ($has_errors) {
1461
- return $result;
1462
- } else {
1463
- return true;
1464
- }
1465
- }
1466
-
1467
- private function display_delete_page()
1468
- {
1469
- $flash_id = 'fw_extensions_delete';
1470
-
1471
- if (!$this->can_install()) {
1472
- FW_Flash_Messages::add(
1473
- $flash_id,
1474
- __('You are not allowed to delete extensions.', 'fw'),
1475
- 'error'
1476
- );
1477
- $this->js_redirect();
1478
- return;
1479
- }
1480
-
1481
- $extensions = array_fill_keys(array_map('trim', explode(',', FW_Request::GET('extension', ''))), array());
1482
-
1483
- {
1484
- $skin = new _FW_Extensions_Delete_Upgrader_Skin(array(
1485
- 'title' => _n('Delete Extension', 'Delete Extensions', count($extensions), 'fw'),
1486
- ));
1487
- }
1488
-
1489
- $skin->header();
1490
-
1491
- do {
1492
- $nonce = $this->get_nonce('delete');
1493
-
1494
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1495
- if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1496
- $skin->error(__('Invalid nonce.', 'fw'));
1497
- break;
1498
- }
1499
-
1500
- if (!FW_WP_Filesystem::request_access(
1501
- fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
1502
- )) {
1503
- break;
1504
- }
1505
-
1506
- $uninstall_result = $this->uninstall_extensions($extensions, array('verbose' => $skin));
1507
-
1508
- if (is_wp_error($uninstall_result)) {
1509
- $skin->error($uninstall_result);
1510
- } elseif (is_array($uninstall_result)) {
1511
- $error = array();
1512
-
1513
- foreach ($uninstall_result as $extension_name => $extension_result) {
1514
- if (is_wp_error($extension_result)) {
1515
- $error[] = $extension_result->get_error_message();
1516
- }
1517
- }
1518
-
1519
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1520
-
1521
- $skin->error($error);
1522
- } elseif ($uninstall_result === true) {
1523
- $skin->set_result(true);
1524
- }
1525
-
1526
- $skin->after(array(
1527
- 'extensions_page_link' => $this->get_link()
1528
- ));
1529
- } else {
1530
- echo '<form method="post">';
1531
-
1532
- wp_nonce_field($nonce['action'], $nonce['name']);
1533
-
1534
- fw_render_view(dirname(__FILE__) .'/views/delete-form.php', array(
1535
- 'extension_names' => array_keys($extensions),
1536
- 'installed_extensions' => $this->get_installed_extensions(),
1537
- 'list_page_link' => $this->get_link(),
1538
- ), false);
1539
-
1540
- echo '</form>';
1541
- }
1542
- } while(false);
1543
-
1544
- $skin->footer();
1545
- }
1546
-
1547
- /**
1548
- * Remove extensions
1549
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1550
- * @param array $opts
1551
- * @return WP_Error|bool|array
1552
- * true: when all extensions succeeded
1553
- * array: when some/all failed
1554
- */
1555
- public function uninstall_extensions(array $extensions, $opts = array())
1556
- {
1557
- {
1558
- $opts = array_merge(array(
1559
- /**
1560
- * @type bool
1561
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1562
- * true: return first WP_Error or true on success
1563
- */
1564
- 'cancel_on_error' => false,
1565
- /**
1566
- * @type bool|WP_Upgrader_Skin
1567
- */
1568
- 'verbose' => false,
1569
- ), $opts);
1570
-
1571
- $cancel_on_error = $opts['cancel_on_error']; // fixme: install back successfully removed extensions before error?
1572
- $verbose = $opts['verbose'];
1573
-
1574
- unset($opts);
1575
- }
1576
-
1577
- if (!$this->can_install()) {
1578
- return new WP_Error(
1579
- 'access_denied',
1580
- __('You have no permissions to uninstall extensions', 'fw')
1581
- );
1582
- }
1583
-
1584
- if (empty($extensions)) {
1585
- return new WP_Error(
1586
- 'no_extensions',
1587
- __('No extensions provided', 'fw')
1588
- );
1589
- }
1590
-
1591
- /** @var WP_Filesystem_Base $wp_filesystem */
1592
- global $wp_filesystem;
1593
-
1594
- if (!FW_WP_Filesystem::is_ready()) {
1595
- return new WP_Error(
1596
- 'fs_not_initialized',
1597
- __('WP Filesystem is not initialized', 'fw')
1598
- );
1599
- }
1600
-
1601
- $installed_extensions = $this->get_installed_extensions();
1602
- $extensions_before_uninstall = array_fill_keys(array_keys($installed_extensions), array());
1603
-
1604
- $result = $uninstalled_extensions = array();
1605
- $has_errors = false;
1606
-
1607
- while (!empty($extensions)) {
1608
- $not_used_var = reset($extensions);
1609
- $extension_name = key($extensions);
1610
- unset($extensions[$extension_name]);
1611
-
1612
- $extension_title = $this->get_extension_title($extension_name);
1613
-
1614
- if (!isset($installed_extensions[ $extension_name ])) {
1615
- // already deleted
1616
- $result[$extension_name] = true;
1617
- continue;
1618
- }
1619
-
1620
- if (
1621
- !isset($installed_extensions[ $extension_name ]['path'])
1622
- ||
1623
- empty($installed_extensions[ $extension_name ]['path'])
1624
- ) {
1625
- /**
1626
- * This happens sometimes, but I don't know why
1627
- * If the script will continue, it will delete the root folder
1628
- */
1629
- fw_print(
1630
- 'Please report this to https://github.com/ThemeFuse/Unyson/issues',
1631
- $extension_name,
1632
- $installed_extensions
1633
- );
1634
- die;
1635
- }
1636
-
1637
- $wp_fs_extension_path = FW_WP_Filesystem::real_path_to_filesystem_path(
1638
- $installed_extensions[ $extension_name ]['path']
1639
- );
1640
-
1641
- if (!$wp_filesystem->exists($wp_fs_extension_path)) {
1642
- // already deleted, maybe because it was a sub-extension of an deleted extension
1643
- $result[$extension_name] = true;
1644
- continue;
1645
- }
1646
-
1647
- if ($verbose) {
1648
- $verbose_message = sprintf(__('Deleting the "%s" extension...', 'fw'), $extension_title);
1649
-
1650
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1651
- $verbose->feedback($verbose_message);
1652
- } else {
1653
- echo fw_html_tag('p', array(), $verbose_message);
1654
- }
1655
- }
1656
-
1657
- if (!$wp_filesystem->delete($wp_fs_extension_path, true, 'd')) {
1658
- $result[$extension_name] = new WP_Error(
1659
- 'cannot_delete_directory',
1660
- sprintf(__('Cannot delete the "%s" extension.', 'fw'), $extension_title)
1661
- );
1662
- $has_errors = true;
1663
-
1664
- if ($cancel_on_error) {
1665
- break;
1666
- } else {
1667
- continue;
1668
- }
1669
- } else {
1670
- if ($verbose) {
1671
- $verbose_message = sprintf(
1672
- __('The %s extension has been successfully deleted.', 'fw'),
1673
- $extension_title
1674
- );
1675
-
1676
- if (is_subclass_of($verbose, 'WP_Upgrader_Skin')) {
1677
- $verbose->feedback($verbose_message);
1678
- } else {
1679
- echo fw_html_tag('p', array(), $verbose_message);
1680
- }
1681
- }
1682
-
1683
- $result[$extension_name] = true;
1684
- }
1685
-
1686
- /**
1687
- * Read again all extensions
1688
- * The delete extension may contain more sub extensions
1689
- */
1690
- {
1691
- unset($installed_extensions);
1692
- $installed_extensions = $this->get_installed_extensions(true);
1693
- }
1694
-
1695
- /**
1696
- * Add for deletion not used extensions
1697
- * For e.g. standalone=false extension that were required by the deleted extension
1698
- * and now are not required by any other extension
1699
- */
1700
- {
1701
- $not_used_extensions = array_fill_keys(
1702
- array_keys(
1703
- array_diff_key(
1704
- $installed_extensions,
1705
- $this->get_used_extensions($extensions, array_keys($installed_extensions))
1706
- )
1707
- ),
1708
- array()
1709
- );
1710
-
1711
- $extensions = array_merge($extensions, $not_used_extensions);
1712
- }
1713
- }
1714
-
1715
- do_action('fw_extensions_uninstall', $result);
1716
-
1717
- if (
1718
- $cancel_on_error
1719
- &&
1720
- $has_errors
1721
- ) {
1722
- if (
1723
- ($last_result = end($result))
1724
- &&
1725
- is_wp_error($last_result)
1726
- ) {
1727
- return $last_result;
1728
- } else {
1729
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
1730
- return new WP_Error(
1731
- 'uninstall_failed',
1732
- _n('Cannot uninstall extension', 'Cannot uninstall extensions', count($extensions), 'fw')
1733
- );
1734
- }
1735
- }
1736
-
1737
- // remove from active list the deleted extensions
1738
- {
1739
- update_option(
1740
- fw()->extensions->_get_active_extensions_db_option_name(),
1741
- array_diff_key(
1742
- fw()->extensions->_get_db_active_extensions(),
1743
- array_diff_key(
1744
- $extensions_before_uninstall,
1745
- $installed_extensions
1746
- )
1747
- )
1748
- );
1749
- }
1750
-
1751
- if ($has_errors) {
1752
- return $result;
1753
- } else {
1754
- return true;
1755
- }
1756
- }
1757
-
1758
- private function display_extension_page()
1759
- {
1760
- // note: static is enqueued in 'admin_enqueue_scripts' action
1761
-
1762
- $extension_name = trim(FW_Request::GET('extension', ''));
1763
-
1764
- $installed_extensions = $this->get_installed_extensions();
1765
-
1766
- $flash_id = 'fw_extension_page';
1767
-
1768
- {
1769
- $error = '';
1770
-
1771
- do {
1772
- if (empty($extension_name)) {
1773
- $error = __('Extension not specified.', 'fw');
1774
- break;
1775
- }
1776
-
1777
- if (!isset($installed_extensions[$extension_name])) {
1778
- $error = sprintf(__('Extension "%s" is not installed.', 'fw'), $this->get_extension_title($extension_name));
1779
- break;
1780
- }
1781
- } while(false);
1782
-
1783
- if ($error) {
1784
- FW_Flash_Messages::add($flash_id, $error, 'error');
1785
- $this->js_redirect();
1786
- return;
1787
- }
1788
- }
1789
-
1790
- {
1791
- $tab = fw_akg('tab', $_GET, 'settings');
1792
-
1793
- if (!in_array($tab, array('settings', 'docs'))) {
1794
- $tab = 'settings';
1795
- }
1796
- }
1797
-
1798
- $extension_title = $this->get_extension_title($extension_name);
1799
- $link = $this->get_link();
1800
-
1801
- echo '<div class="wrap" id="fw-extension-page">';
1802
-
1803
- fw_render_view(dirname(__FILE__) .'/views/extension-page-header.php', array(
1804
- 'extension_name' => $extension_name,
1805
- 'extension_data' => $installed_extensions[$extension_name],
1806
- 'link_delete' => $link .'&sub-page=delete',
1807
- 'link_extension' => $link .'&sub-page=extension',
1808
- 'extension_title' => $extension_title,
1809
- 'tab' => $tab,
1810
- 'is_supported' =>
1811
- fw()->theme->manifest->get('supported_extensions/'. $extension_name, false) !== false
1812
- ||
1813
- $installed_extensions[$extension_name]['is']['theme']
1814
- ), false);
1815
-
1816
- unset($installed_extensions);
1817
-
1818
- echo '<div id="fw-extension-tab-content">';
1819
- {
1820
- $method_data = array();
1821
-
1822
- switch ($tab) {
1823
- case 'settings':
1824
- $error = $this->display_extension_settings_page($extension_name, $method_data);
1825
- break;
1826
- case 'docs':
1827
- $error = $this->display_extension_docs_page($extension_name, $method_data);
1828
- break;
1829
- }
1830
- }
1831
- echo '</div>';
1832
-
1833
- echo '</div>';
1834
-
1835
- if ($error) {
1836
- FW_Flash_Messages::add($flash_id, $error, 'error');
1837
- $this->js_redirect();
1838
- return;
1839
- }
1840
- }
1841
-
1842
- private function display_extension_settings_page($extension_name, $data)
1843
- {
1844
- if (!fw()->extensions->get($extension_name)) {
1845
- return sprintf(
1846
- __('Extension "%s" does not exist or is not active.', 'fw'),
1847
- fw_htmlspecialchars($extension_name)
1848
- );
1849
- }
1850
-
1851
- $extension = fw()->extensions->get($extension_name);
1852
-
1853
- if (!$extension->get_settings_options()) {
1854
- return sprintf(
1855
- __('%s extension does not have settings.', 'fw'),
1856
- $extension->manifest->get_name()
1857
- );
1858
- }
1859
-
1860
- echo '<div id="fw-extension-settings">';
1861
-
1862
- echo $this->extension_settings_form->render(array(
1863
- 'extension' => $extension,
1864
- ));
1865
-
1866
- echo '</div>';
1867
- }
1868
-
1869
- private function display_extension_docs_page($extension_name, $data)
1870
- {
1871
- $ext = fw_ext($extension_name);
1872
- $docs = $ext->get_rendered_docs();
1873
-
1874
- if (! $docs) {
1875
- return __(
1876
- 'Extension has no documentation. Maybe ask its developer to write some?',
1877
- 'fw'
1878
- );
1879
- }
1880
-
1881
- echo fw()->backend->render_box(
1882
- 'fw-extension-docs',
1883
- '',
1884
- fw()->backend->render_options(array(
1885
- 'docs' => array(
1886
- 'label' => false,
1887
- 'type' => 'html-full',
1888
- 'html' => $docs
1889
- ),
1890
- ))
1891
- );
1892
- }
1893
-
1894
- private function display_activate_page()
1895
- {
1896
- $error = '';
1897
-
1898
- do {
1899
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
1900
- $error = __('Invalid request method.', 'fw');
1901
- break;
1902
- }
1903
-
1904
- $nonce = $this->get_nonce('activate');
1905
-
1906
- if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
1907
- $error = __('Invalid nonce.', 'fw');
1908
- break;
1909
- }
1910
-
1911
- if (!isset($_GET['extension'])) {
1912
- $error = __('No extension specified.', 'fw');
1913
- break;
1914
- }
1915
-
1916
- $activation_result = $this->activate_extensions(
1917
- array_fill_keys(explode(',', $_GET['extension']), array())
1918
- );
1919
-
1920
- if (is_wp_error($activation_result)) {
1921
- $error = $activation_result->get_error_message();
1922
- } elseif (is_array($activation_result)) {
1923
- $error = array();
1924
-
1925
- foreach ($activation_result as $extension_name => $extension_result) {
1926
- if (is_wp_error($extension_result)) {
1927
- $error[] = $extension_result->get_error_message();
1928
- }
1929
- }
1930
-
1931
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1932
- }
1933
- } while(false);
1934
-
1935
- if ($error) {
1936
- FW_Flash_Messages::add(
1937
- 'fw_extensions_activate_page',
1938
- $error,
1939
- 'error'
1940
- );
1941
- $this->js_redirect();
1942
- return;
1943
- }
1944
-
1945
- $this->js_redirect();
1946
- }
1947
-
1948
- /**
1949
- * Add extensions to active extensions list in database
1950
- * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1951
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1952
- * @param bool $cancel_on_error
1953
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1954
- * true: return first WP_Error or true on success
1955
- * @return WP_Error|bool|array
1956
- * true: when all extensions succeeded
1957
- * array: when some/all failed
1958
- */
1959
- public function activate_extensions(array $extensions, $cancel_on_error = false)
1960
- {
1961
- if (!$this->can_activate()) {
1962
- return new WP_Error(
1963
- 'access_denied',
1964
- __('You have no permissions to activate extensions', 'fw')
1965
- );
1966
- }
1967
-
1968
- if (empty($extensions)) {
1969
- return new WP_Error(
1970
- 'no_extensions',
1971
- __('No extensions provided', 'fw')
1972
- );
1973
- }
1974
-
1975
- $installed_extensions = $this->get_installed_extensions();
1976
-
1977
- $result = $extensions_for_activation = array();
1978
- $has_errors = false;
1979
-
1980
- foreach ($extensions as $extension_name => $not_used_var) {
1981
- if (!isset($installed_extensions[$extension_name])) {
1982
- $result[$extension_name] = new WP_Error(
1983
- 'extension_not_installed',
1984
- sprintf(__('Extension "%s" does not exist.', 'fw'), $this->get_extension_title($extension_name))
1985
- );
1986
- $has_errors = true;
1987
-
1988
- if ($cancel_on_error) {
1989
- break;
1990
- } else {
1991
- continue;
1992
- }
1993
- }
1994
-
1995
- $collected = $this->get_extensions_for_activation($extension_name);
1996
-
1997
- if (is_wp_error($collected)) {
1998
- $result[$extension_name] = $collected;
1999
- $has_errors = true;
2000
-
2001
- if ($cancel_on_error) {
2002
- break;
2003
- } else {
2004
- continue;
2005
- }
2006
- }
2007
-
2008
- $extensions_for_activation = array_merge($extensions_for_activation, $collected);
2009
-
2010
- $result[$extension_name] = true;
2011
- }
2012
-
2013
- if (
2014
- $cancel_on_error
2015
- &&
2016
- $has_errors
2017
- ) {
2018
- if (
2019
- ($last_result = end($result))
2020
- &&
2021
- is_wp_error($last_result)
2022
- ) {
2023
- return $last_result;
2024
- } else {
2025
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
2026
- return new WP_Error(
2027
- 'activation_failed',
2028
- _n('Cannot activate extension', 'Cannot activate extensions', count($extensions), 'fw')
2029
- );
2030
- }
2031
- }
2032
-
2033
- update_option(
2034
- fw()->extensions->_get_active_extensions_db_option_name(),
2035
- array_merge(fw()->extensions->_get_db_active_extensions(), $extensions_for_activation)
2036
- );
2037
-
2038
- // remove already active extensions
2039
- foreach ($extensions_for_activation as $extension_name => $not_used_var) {
2040
- if (fw_ext($extension_name)) {
2041
- unset($extensions_for_activation[$extension_name]);
2042
- }
2043
- }
2044
-
2045
- /**
2046
- * Prepare db wp option used to fire the 'fw_extensions_after_activation' action on next refresh
2047
- */
2048
- {
2049
- $db_wp_option_name = 'fw_extensions_activation';
2050
- $db_wp_option_value = get_option($db_wp_option_name, array(
2051
- 'activated' => array(),
2052
- 'deactivated' => array(),
2053
- ));
2054
-
2055
- /**
2056
- * Keep adding to the existing value instead of resetting it on each method call
2057
- * in case the method will be called multiple times
2058
- */
2059
- $db_wp_option_value['activated'] = array_merge($db_wp_option_value['activated'], $extensions_for_activation);
2060
-
2061
- /**
2062
- * Remove activated extensions from deactivated
2063
- */
2064
- $db_wp_option_value['deactivated'] = array_diff_key($db_wp_option_value['deactivated'], $db_wp_option_value['activated']);
2065
-
2066
- update_option($db_wp_option_name, $db_wp_option_value, false);
2067
- }
2068
-
2069
- do_action('fw_extensions_before_activation', $extensions_for_activation);
2070
-
2071
- if ($has_errors) {
2072
- return $result;
2073
- } else {
2074
- return true;
2075
- }
2076
- }
2077
-
2078
- private function collect_sub_extensions($ext_name, &$installed_extensions)
2079
- {
2080
- $result = array();
2081
-
2082
- foreach ($installed_extensions[$ext_name]['children'] as $child_ext_name => $child_ext_data) {
2083
- $result[$child_ext_name] = array();
2084
-
2085
- $result += $this->collect_sub_extensions($child_ext_name, $installed_extensions);
2086
- }
2087
-
2088
- return $result;
2089
- }
2090
-
2091
- private function collect_required_extensions($ext_name, &$installed_extensions, &$collected)
2092
- {
2093
- if (!isset($installed_extensions[$ext_name])) {
2094
- return;
2095
- }
2096
-
2097
- foreach (fw_akg('requirements/extensions', $installed_extensions[$ext_name]['manifest'], array()) as $req_ext_name => $req_ext_data) {
2098
- if (isset($collected[$req_ext_name])) {
2099
- // prevent requirements recursion
2100
- continue;
2101
- }
2102
-
2103
- $collected[$req_ext_name] = array();
2104
-
2105
- $this->collect_required_extensions($req_ext_name, $installed_extensions, $collected);
2106
- }
2107
- }
2108
-
2109
- private function display_deactivate_page()
2110
- {
2111
- $installed_extensions = $this->get_installed_extensions();
2112
-
2113
- $error = '';
2114
-
2115
- do {
2116
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
2117
- $error = __('Invalid request method.', 'fw');
2118
- break;
2119
- }
2120
-
2121
- $nonce = $this->get_nonce('deactivate');
2122
-
2123
- if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
2124
- $error = __('Invalid nonce.', 'fw');
2125
- break;
2126
- }
2127
-
2128
- if (!isset($_GET['extension'])) {
2129
- $error = __('No extension specified.', 'fw');
2130
- break;
2131
- }
2132
-
2133
- $deactivation_result = $this->deactivate_extensions(
2134
- array_fill_keys(explode(',', $_GET['extension']), array())
2135
- );
2136
-
2137
- if (is_wp_error($deactivation_result)) {
2138
- $error = $deactivation_result->get_error_message();
2139
- } elseif (is_array($deactivation_result)) {
2140
- $error = array();
2141
-
2142
- foreach ($deactivation_result as $extension_name => $extension_result) {
2143
- if (is_wp_error($extension_result)) {
2144
- $error[] = $extension_result->get_error_message();
2145
- }
2146
- }
2147
-
2148
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
2149
- }
2150
- } while(false);
2151
-
2152
- if ($error) {
2153
- FW_Flash_Messages::add(
2154
- 'fw_extensions_activate_page',
2155
- $error,
2156
- 'error'
2157
- );
2158
- }
2159
-
2160
- $this->js_redirect();
2161
- }
2162
-
2163
- /**
2164
- * Remove extensions from active extensions list in database
2165
- * After refresh they will be inactive
2166
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
2167
- * @param bool $cancel_on_error
2168
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
2169
- * true: return first WP_Error or true on success
2170
- * @return WP_Error|bool|array
2171
- * true: when all extensions succeeded
2172
- * array: when some/all failed
2173
- */
2174
- public function deactivate_extensions(array $extensions, $cancel_on_error = false)
2175
- {
2176
- if (!$this->can_activate()) {
2177
- return new WP_Error(
2178
- 'access_denied',
2179
- __('You have no permissions to deactivate extensions', 'fw')
2180
- );
2181
- }
2182
-
2183
- if (empty($extensions)) {
2184
- return new WP_Error(
2185
- 'no_extensions',
2186
- __('No extensions provided', 'fw')
2187
- );
2188
- }
2189
-
2190
- $installed_extensions = $this->get_installed_extensions();
2191
-
2192
- $result = $extensions_for_deactivation = array();
2193
- $has_errors = false;
2194
-
2195
- foreach ($extensions as $extension_name => $not_used_var) {
2196
- if (!isset($installed_extensions[$extension_name])) {
2197
- // anyway remove from the active list
2198
- $extensions_for_deactivation[$extension_name] = array();
2199
-
2200
- $result[$extension_name] = new WP_Error(
2201
- 'extension_not_installed',
2202
- sprintf(__( 'Extension "%s" does not exist.' , 'fw' ), $this->get_extension_title($extension_name))
2203
- );
2204
- $has_errors = true;
2205
-
2206
- if ($cancel_on_error) {
2207
- break;
2208
- } else {
2209
- continue;
2210
- }
2211
- }
2212
-
2213
- $current_deactivating_extensions = array(
2214
- $extension_name => array()
2215
- );
2216
-
2217
- // add sub-extensions for deactivation
2218
- foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2219
- $current_deactivating_extensions[ $sub_extension_name ] = array();
2220
- }
2221
-
2222
- // add extensions that requires deactivated extensions
2223
- $this->collect_extensions_that_requires($current_deactivating_extensions, $current_deactivating_extensions);
2224
-
2225
- $extensions_for_deactivation = array_merge(
2226
- $extensions_for_deactivation,
2227
- $current_deactivating_extensions
2228
- );
2229
-
2230
- unset($current_deactivating_extensions);
2231
-
2232
- $result[$extension_name] = true;
2233
- }
2234
-
2235
- if (
2236
- $cancel_on_error
2237
- &&
2238
- $has_errors
2239
- ) {
2240
- if (
2241
- ($last_result = end($result))
2242
- &&
2243
- is_wp_error($last_result)
2244
- ) {
2245
- return $last_result;
2246
- } else {
2247
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
2248
- return new WP_Error(
2249
- 'deactivation_failed',
2250
- _n('Cannot deactivate extension', 'Cannot activate extensions', count($extensions), 'fw')
2251
- );
2252
- }
2253
- }
2254
-
2255
- // add not used extensions for deactivation
2256
- $extensions_for_deactivation = array_merge($extensions_for_deactivation,
2257
- array_fill_keys(
2258
- array_keys(
2259
- array_diff_key(
2260
- $installed_extensions,
2261
- $this->get_used_extensions($extensions_for_deactivation, array_keys(fw()->extensions->get_all()))
2262
- )
2263
- ),
2264
- array()
2265
- )
2266
- );
2267
-
2268
- update_option(
2269
- fw()->extensions->_get_active_extensions_db_option_name(),
2270
- array_diff_key(
2271
- fw()->extensions->_get_db_active_extensions(),
2272
- $extensions_for_deactivation
2273
- )
2274
- );
2275
-
2276
- // remove already inactive extensions
2277
- foreach ($extensions_for_deactivation as $extension_name => $not_used_var) {
2278
- if (!fw_ext($extension_name)) {
2279
- unset($extensions_for_deactivation[$extension_name]);
2280
- }
2281
- }
2282
-
2283
- /**
2284
- * Prepare db wp option used to fire the 'fw_extensions_after_deactivation' action on next refresh
2285
- */
2286
- {
2287
- $db_wp_option_name = 'fw_extensions_activation';
2288
- $db_wp_option_value = get_option($db_wp_option_name, array(
2289
- 'activated' => array(),
2290
- 'deactivated' => array(),
2291
- ));
2292
-
2293
- /**
2294
- * Keep adding to the existing value instead of resetting it on each method call
2295
- * in case the method will be called multiple times
2296
- */
2297
- $db_wp_option_value['deactivated'] = array_merge($db_wp_option_value['deactivated'], $extensions_for_deactivation);
2298
-
2299
- /**
2300
- * Remove deactivated extensions from activated
2301
- */
2302
- $db_wp_option_value['activated'] = array_diff_key($db_wp_option_value['activated'], $db_wp_option_value['deactivated']);
2303
-
2304
- update_option($db_wp_option_name, $db_wp_option_value, false);
2305
- }
2306
-
2307
- do_action('fw_extensions_before_deactivation', $extensions_for_deactivation);
2308
-
2309
- if ($has_errors) {
2310
- return $result;
2311
- } else {
2312
- return true;
2313
- }
2314
- }
2315
-
2316
- /**
2317
- * @param array $data
2318
- * @return array
2319
- * @internal
2320
- */
2321
- public function _extension_settings_form_render($data)
2322
- {
2323
- /**
2324
- * @var FW_Extension $extension
2325
- */
2326
- $extension = $data['data']['extension'];
2327
-
2328
- do_action('fw_extension_settings_form_render:'. $extension->get_name());
2329
-
2330
- echo fw_html_tag('input', array(
2331
- 'type' => 'hidden',
2332
- 'name' => 'fw_extension_name',
2333
- 'value' => $extension->get_name(),
2334
- ), true);
2335
-
2336
- echo fw()->backend->render_options(
2337
- $extension->get_settings_options(),
2338
- fw_get_db_ext_settings_option($extension->get_name())
2339
- );
2340
-
2341
- $data['submit']['html'] = '';
2342
-
2343
- echo '<p>';
2344
- echo fw_html_tag('input', array(
2345
- 'type' => 'submit',
2346
- 'class' => 'button-primary',
2347
- 'value' => __('Save', 'fw'),
2348
- ));
2349
- echo '&nbsp;&nbsp;&nbsp;&nbsp;';
2350
- echo fw_html_tag('a', array(
2351
- 'href' => $this->get_link(),
2352
- ), __('Cancel', 'fw'));
2353
- echo '</p>';
2354
-
2355
- return $data;
2356
- }
2357
-
2358
- /**
2359
- * @param array $errors
2360
- * @return array
2361
- * @internal
2362
- */
2363
- public function _extension_settings_form_validate($errors)
2364
- {
2365
- do {
2366
- if (!current_user_can($this->can_activate())) {
2367
- $errors[] = __('You are not allowed to save extensions settings.', 'fw');
2368
- break;
2369
- }
2370
-
2371
- $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2372
-
2373
- if (!$extension) {
2374
- $errors[] = __('Invalid extension.', 'fw');
2375
- break;
2376
- }
2377
-
2378
- if (!$extension->get_settings_options()) {
2379
- $errors[] = __('Extension does not have settings options.', 'fw');
2380
- break;
2381
- }
2382
- } while(false);
2383
-
2384
- return $errors;
2385
- }
2386
-
2387
- /**
2388
- * @param array $data
2389
- * @return array
2390
- * @internal
2391
- */
2392
- public function _extension_settings_form_save($data)
2393
- {
2394
- $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2395
-
2396
- $options_before_save = (array)fw_get_db_ext_settings_option($extension->get_name());
2397
-
2398
- fw_set_db_ext_settings_option(
2399
- $extension->get_name(),
2400
- null,
2401
- array_merge(
2402
- $options_before_save,
2403
- fw_get_options_values_from_input(
2404
- $extension->get_settings_options()
2405
- )
2406
- )
2407
- );
2408
-
2409
- FW_Flash_Messages::add(
2410
- 'fw_extension_settings_saved',
2411
- __('Extensions settings successfully saved.', 'fw'),
2412
- 'success'
2413
- );
2414
-
2415
- $data['redirect'] = fw_current_url();
2416
-
2417
- do_action('fw_extension_settings_form_saved:'. $extension->get_name(), $options_before_save);
2418
-
2419
- return $data;
2420
- }
2421
-
2422
- /**
2423
- * Download an extension
2424
- *
2425
- * global $wp_filesystem; must be initialized
2426
- *
2427
- * @param string $extension_name
2428
- * @param array $data Extension data from the "available extensions" array
2429
- * @return string|WP_Error WP Filesystem path to the downloaded directory
2430
- */
2431
- private function download($extension_name, $data)
2432
- {
2433
- $wp_error_id = 'fw_extension_download';
2434
-
2435
- // TODO: more checks for $data['download']
2436
- if (empty($data['download'])) {
2437
- return new WP_Error(
2438
- $wp_error_id,
2439
- sprintf(__('Extension "%s" has no download sources.', 'fw'), $this->get_extension_title($extension_name))
2440
- );
2441
- }
2442
-
2443
- /** @var WP_Filesystem_Base $wp_filesystem */
2444
- global $wp_filesystem;
2445
-
2446
- // create temporary directory
2447
- {
2448
- $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
2449
-
2450
- if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
2451
- // just in case it already exists, clear everything, it may contain old files
2452
- if (!$wp_filesystem->rmdir($wp_fs_tmp_dir, true)) {
2453
- return new WP_Error(
2454
- $wp_error_id,
2455
- sprintf(__('Cannot remove temporary directory: %s', 'fw'), $wp_fs_tmp_dir)
2456
- );
2457
- }
2458
- }
2459
-
2460
- if (!FW_WP_Filesystem::mkdir_recursive($wp_fs_tmp_dir)) {
2461
- return new WP_Error(
2462
- $wp_error_id,
2463
- sprintf(__('Cannot create temporary directory: %s', 'fw'), $wp_fs_tmp_dir)
2464
- );
2465
- }
2466
- }
2467
-
2468
- require_once dirname( __FILE__ ) . '/includes/download-source/types/init.php';
2469
-
2470
- $register = new _FW_Ext_Download_Source_Register(self::get_access_key()->get_key());
2471
-
2472
- /**
2473
- * Register download sources for extensions.
2474
- *
2475
- * Usage:
2476
- * $download_source = new FW_Ext_Download_Source();
2477
- * $register->register($download_source);
2478
- */
2479
- do_action( 'fw_register_ext_download_sources', $register );
2480
-
2481
- $download_source = $register->_get_type(
2482
- self::get_access_key(), $data['download']['source']
2483
- );
2484
-
2485
- if (!$download_source) {
2486
- return new WP_Error(
2487
- 'invalid_dl_source',
2488
- sprintf(__('Invalid download source: %s', 'fw'), $data['download']['source'])
2489
- );
2490
- }
2491
-
2492
- return $this->perform_zip_download(
2493
- $download_source,
2494
- array_merge(array(
2495
- 'extension_name' => $extension_name,
2496
- 'extension_title' => $this->get_extension_title($extension_name)
2497
- ), $data['download']['opts']),
2498
- $wp_fs_tmp_dir
2499
- );
2500
- }
2501
-
2502
- private function perform_zip_download(FW_Ext_Download_Source $download_source, array $opts, $wp_fs_tmp_dir)
2503
- {
2504
- $wp_error_id = 'fw_extension_download';
2505
-
2506
- /** @var WP_Filesystem_Base $wp_filesystem */
2507
- global $wp_filesystem;
2508
-
2509
- $zip_path = $wp_fs_tmp_dir . '/temp.zip';
2510
-
2511
- $download_result = $download_source->download($opts, $zip_path);
2512
-
2513
- /**
2514
- * Pass further the error, if the service returned one.
2515
- */
2516
- if (is_wp_error($download_result)) {
2517
- return $download_result;
2518
- }
2519
-
2520
- $extension_name = $opts['extension_name'];
2521
-
2522
- $unzip_result = unzip_file(
2523
- FW_WP_Filesystem::filesystem_path_to_real_path($zip_path),
2524
- $wp_fs_tmp_dir
2525
- );
2526
-
2527
- if (is_wp_error($unzip_result)) {
2528
- return $unzip_result;
2529
- }
2530
-
2531
- // remove zip file
2532
- if (!$wp_filesystem->delete($zip_path, false, 'f')) {
2533
- return new WP_Error(
2534
- $wp_error_id,
2535
- sprintf(__('Cannot remove the "%s" extension downloaded zip.', 'fw'), $this->get_extension_title($extension_name))
2536
- );
2537
- }
2538
-
2539
- $unzipped_dir_files = $wp_filesystem->dirlist($wp_fs_tmp_dir);
2540
-
2541
- if (!$unzipped_dir_files) {
2542
- return new WP_Error(
2543
- $wp_error_id,
2544
- __('Cannot access the unzipped directory files.', 'fw')
2545
- );
2546
- }
2547
-
2548
- /**
2549
- * get first found directory
2550
- * (if everything worked well, there should be only one directory)
2551
- */
2552
- foreach ($unzipped_dir_files as $file) {
2553
- if ($file['type'] == 'd') {
2554
- return $wp_fs_tmp_dir .'/'. $file['name'];
2555
- }
2556
- }
2557
-
2558
- return new WP_Error(
2559
- $wp_error_id,
2560
- sprintf(__('The unzipped "%s" extension directory not found.', 'fw'), $this->get_extension_title($extension_name))
2561
- );
2562
- }
2563
-
2564
- /**
2565
- * Merge the downloaded extension directory with the existing directory
2566
- *
2567
- * @param string $source_wp_fs_dir Downloaded extension directory
2568
- * @param string $destination_wp_fs_dir
2569
- *
2570
- * @return null|WP_Error
2571
- */
2572
- private function merge_extension($source_wp_fs_dir, $destination_wp_fs_dir)
2573
- {
2574
- /** @var WP_Filesystem_Base $wp_filesystem */
2575
- global $wp_filesystem;
2576
-
2577
- $wp_error_id = 'fw_extensions_merge';
2578
-
2579
- // check source
2580
- {
2581
- $source_files = $wp_filesystem->dirlist($source_wp_fs_dir);
2582
-
2583
- if ($source_files === false) {
2584
- return new WP_Error(
2585
- $wp_error_id,
2586
- sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir)
2587
- );
2588
- }
2589
-
2590
- if (empty($source_files)) {
2591
- return; // directory is empty, nothing to move
2592
- }
2593
- }
2594
-
2595
- /**
2596
- * Prepare destination directory
2597
- * Remove everything except the extensions/ directory
2598
- */
2599
- if ($wp_filesystem->exists($destination_wp_fs_dir)) {
2600
- $destination_files = $wp_filesystem->dirlist($destination_wp_fs_dir);
2601
-
2602
- if ($destination_files === false) {
2603
- return new WP_Error(
2604
- $wp_error_id,
2605
- sprintf(__('Cannot read directory "%s".', 'fw'), $destination_wp_fs_dir)
2606
- );
2607
- }
2608
-
2609
- if (!empty($destination_files)) {
2610
- if (
2611
- count($source_files) == 1
2612
- &&
2613
- ($file = reset($source_files))
2614
- &&
2615
- $file['name'] === 'extensions'
2616
- &&
2617
- $file['type'] === 'd'
2618
- ) {
2619
- /**
2620
- * Source extension is empty
2621
- * It happens when you merge a directory which contains child extensions
2622
- * Do not delete current destination files, just go in the next child extensions level
2623
- * Used by https://github.com/ThemeFuse/Unyson/issues/1874
2624
- */
2625
- } else {
2626
- // the directory contains some files, delete everything
2627
- foreach ($destination_files as $file) {
2628
- if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2629
- // do not touch the extensions/ directory
2630
- continue;
2631
- }
2632
-
2633
- if (!$wp_filesystem->delete(
2634
- $destination_wp_fs_dir .'/'. $file['name'],
2635
- true,
2636
- $file['type']
2637
- )) {
2638
- return new WP_Error(
2639
- $wp_error_id,
2640
- sprintf(
2641
- __('Cannot delete "%s".', 'fw'),
2642
- $destination_wp_fs_dir .'/'. $file['name']
2643
- )
2644
- );
2645
- }
2646
- }
2647
- }
2648
-
2649
- unset($destination_files);
2650
- }
2651
- } else {
2652
- if (!FW_WP_Filesystem::mkdir_recursive($destination_wp_fs_dir)) {
2653
- return new WP_Error(
2654
- $wp_error_id,
2655
- sprintf(__('Cannot create the "%s" directory.', 'fw'), $destination_wp_fs_dir)
2656
- );
2657
- }
2658
- }
2659
-
2660
- // Move files from source to destination
2661
- {
2662
- $has_sub_extensions = false;
2663
-
2664
- foreach ($source_files as $file) {
2665
- if ($file['name'] === 'extensions' && $file['type'] === 'd') {
2666
- $has_sub_extensions = true; // do not touch the extensions/ directory
2667
- continue;
2668
- }
2669
-
2670
- if (!$wp_filesystem->move($source_wp_fs_dir .'/'. $file['name'], $destination_wp_fs_dir .'/'. $file['name'])) {
2671
- return new WP_Error(
2672
- $wp_error_id,
2673
- sprintf(
2674
- __('Cannot move "%s" to "%s".', 'fw'),
2675
- $source_wp_fs_dir .'/'. $file['name'],
2676
- $destination_wp_fs_dir .'/'. $file['name']
2677
- )
2678
- );
2679
- }
2680
- }
2681
-
2682
- unset($source_files);
2683
- }
2684
-
2685
- if (!$has_sub_extensions) {
2686
- return;
2687
- }
2688
-
2689
- $sub_extensions = $wp_filesystem->dirlist($source_wp_fs_dir .'/extensions');
2690
-
2691
- if ($sub_extensions === false) {
2692
- return new WP_Error(
2693
- $wp_error_id,
2694
- sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir .'/extensions')
2695
- );
2696
- }
2697
-
2698
- if (empty($sub_extensions)) {
2699
- // directory is empty, nothing to remove
2700
- return;
2701
- }
2702
-
2703
- foreach ($sub_extensions as $file) {
2704
- if ($file['type'] !== 'd') {
2705
- // wrong, only directories must exist in the extensions/ directory
2706
- continue;
2707
- }
2708
-
2709
- $merge_result = $this->merge_extension(
2710
- $source_wp_fs_dir .'/extensions/'. $file['name'],
2711
- $destination_wp_fs_dir .'/extensions/'. $file['name']
2712
- );
2713
-
2714
- if (is_wp_error($merge_result)) {
2715
- return $merge_result;
2716
- }
2717
- }
2718
- }
2719
-
2720
- /**
2721
- * @since 2.6.9
2722
- */
2723
- public function get_supported_extensions()
2724
- {
2725
- $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2726
-
2727
- // Add Available Extensions registered by the theme
2728
- foreach ($this->get_available_extensions() as $name => $extension) {
2729
- if (isset($extension['theme']) && $extension['theme']) {
2730
- $supported_extensions[$name] = array();
2731
- }
2732
- }
2733
-
2734
- if (empty($supported_extensions)) {
2735
- return array();
2736
- }
2737
-
2738
- // remove not available extensions
2739
- $supported_extensions = array_intersect_key($supported_extensions, $this->get_available_extensions());
2740
-
2741
- if (empty($supported_extensions)) {
2742
- return array();
2743
- }
2744
-
2745
- if (empty($supported_extensions)) {
2746
- return array();
2747
- }
2748
-
2749
- return $supported_extensions;
2750
- }
2751
-
2752
- /**
2753
- * @since 2.6.9
2754
- */
2755
- public function get_supported_extensions_for_install()
2756
- {
2757
- // remove already installed extensions
2758
- return array_diff_key(
2759
- $this->get_supported_extensions(),
2760
- $this->get_installed_extensions()
2761
- );
2762
- }
2763
-
2764
- /**
2765
- * @param $actions
2766
- * @return array
2767
- * @internal
2768
- */
2769
- public function _filter_plugin_action_list($actions)
2770
- {
2771
- return array_merge(
2772
- array(
2773
- 'fw-extensions' => fw_html_tag('a', array(
2774
- 'href' => $this->get_link(),
2775
- ), fw()->manifest->get_name()),
2776
- ),
2777
- $actions
2778
- );
2779
- }
2780
-
2781
- /**
2782
- * @return string Extensions page link
2783
- */
2784
- private function get_link()
2785
- {
2786
- static $cache_link = null;
2787
-
2788
- if ($cache_link === null) {
2789
- $cache_link = menu_page_url( $this->get_page_slug(), false );
2790
-
2791
- // https://core.trac.wordpress.org/ticket/28226
2792
- if (is_multisite() && is_network_admin()) {
2793
- $cache_link = self_admin_url(
2794
- // extract relative link
2795
- preg_replace('/^'. preg_quote(admin_url(), '/') .'/', '', $cache_link)
2796
- );
2797
- }
2798
- }
2799
-
2800
- return $cache_link;
2801
- }
2802
-
2803
- /**
2804
- * @param array $skip_extensions {'ext' => mixed}
2805
- * @param array $check_for_deps ['ext', 'ext', ...] Extensions to check if has in dependencies the used extensions
2806
- *
2807
- * @return array
2808
- */
2809
- private function get_used_extensions($skip_extensions, $check_for_deps)
2810
- {
2811
- $used_extensions = array();
2812
-
2813
- $installed_extensions = $this->get_installed_extensions();
2814
-
2815
- foreach ($installed_extensions as $inst_ext_name => &$inst_ext_data) {
2816
- if (isset($skip_extensions[ $inst_ext_name ])) {
2817
- continue;
2818
- }
2819
-
2820
- if (isset($used_extensions[$inst_ext_name])) {
2821
- // already marked as used
2822
- continue;
2823
- }
2824
-
2825
- do {
2826
- foreach ($check_for_deps as $deps_ext) {
2827
- if (isset($skip_extensions[$deps_ext])) {
2828
- continue;
2829
- }
2830
-
2831
- if (false !== fw_akg(
2832
- 'requirements/extensions/'. $inst_ext_name,
2833
- $installed_extensions[$deps_ext]['manifest'],
2834
- false
2835
- )) {
2836
- // is required by an active extension
2837
- break 2;
2838
- }
2839
- }
2840
-
2841
- if ( true === fw_akg(
2842
- 'standalone',
2843
- $inst_ext_data['manifest'],
2844
- $this->manifest_default_values['standalone']
2845
- ) ) {
2846
- // can exist alone
2847
- break;
2848
- }
2849
-
2850
- // not used
2851
- continue 2;
2852
- } while(false);
2853
-
2854
- $used_extensions[$inst_ext_name] = array();
2855
-
2856
- // Set all sub-extensions as used
2857
- foreach ($this->collect_sub_extensions($inst_ext_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2858
- if (isset($skip_extensions[$sub_extension_name])) {
2859
- continue;
2860
- }
2861
-
2862
- $used_extensions[ $sub_extension_name ] = array();
2863
- }
2864
-
2865
- // Set all parents as used
2866
- {
2867
- $current_parent = $inst_ext_name;
2868
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
2869
- $used_extensions[$current_parent] = array();
2870
- }
2871
- }
2872
- }
2873
- unset($inst_ext_data);
2874
-
2875
- // remove all skipped extensions and sub-extension from used extensions
2876
- foreach (array_keys($skip_extensions) as $skip_extension_name) {
2877
- unset($used_extensions[$skip_extension_name]);
2878
-
2879
- if (isset($installed_extensions[$skip_extension_name])) {
2880
- foreach ($this->collect_sub_extensions($skip_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2881
- unset($used_extensions[$sub_extension_name]);
2882
- }
2883
- }
2884
- }
2885
-
2886
- return $used_extensions;
2887
- }
2888
-
2889
- /**
2890
- * @internal
2891
- */
2892
- public function _action_admin_footer()
2893
- {
2894
- $this->activate_hidden_standalone_extensions();
2895
- }
2896
-
2897
- public function get_extension_title($extension_name)
2898
- {
2899
- $installed_extensions = $this->get_installed_extensions();
2900
-
2901
- if (isset($installed_extensions[$extension_name])) {
2902
- return fw_akg('name', $installed_extensions[$extension_name]['manifest'], fw_id_to_title($extension_name));
2903
- }
2904
-
2905
- unset($installed_extensions);
2906
-
2907
- $available_extensions = $this->get_available_extensions();
2908
-
2909
- if (isset($available_extensions[$extension_name])) {
2910
- return $available_extensions[$extension_name]['name'];
2911
- }
2912
-
2913
- return fw_id_to_title($extension_name);
2914
- }
2915
-
2916
- public function is_extensions_page()
2917
- {
2918
- $current_screen = get_current_screen();
2919
-
2920
- if (empty($current_screen)) {
2921
- return false;
2922
- }
2923
-
2924
- return (
2925
- property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2926
- &&
2927
- property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2928
- &&
2929
- !isset($_GET['sub-page'])
2930
- );
2931
- }
2932
-
2933
- public function is_extension_page()
2934
- {
2935
- $current_screen = get_current_screen();
2936
-
2937
- if (empty($current_screen)) {
2938
- return false;
2939
- }
2940
-
2941
- return (
2942
- property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2943
- &&
2944
- property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2945
- &&
2946
- isset($_GET['sub-page']) && $_GET['sub-page'] === 'extension'
2947
- );
2948
- }
2949
-
2950
- /**
2951
- * @internal
2952
- */
2953
- public function _action_enqueue_scripts()
2954
- {
2955
- wp_enqueue_style(
2956
- 'fw-extensions-menu-icon',
2957
- $this->get_uri('/static/unyson-font-icon/style.css'),
2958
- array(),
2959
- fw()->manifest->get_version()
2960
- );
2961
-
2962
- /**
2963
- * Enqueue only on Extensions List page
2964
- */
2965
- if ($this->is_extensions_page()) {
2966
- wp_enqueue_style(
2967
- 'fw-extensions-page',
2968
- $this->get_uri('/static/extensions-page.css'),
2969
- array(
2970
- 'fw',
2971
- 'fw-unycon', 'font-awesome', // in case some extension has font-icon thumbnail
2972
- ),
2973
- fw()->manifest->get_version()
2974
- );
2975
- wp_enqueue_script(
2976
- 'fw-extensions-page',
2977
- $this->get_uri('/static/extensions-page.js'),
2978
- array('fw'),
2979
- fw()->manifest->get_version(),
2980
- true
2981
- );
2982
- wp_localize_script('fw-extensions-page', '_fw_extensions_script_data', array(
2983
- 'link' => $this->get_link(),
2984
- ));
2985
-
2986
- /**
2987
- * this is needed for fw.soleModal design
2988
- * it is displayed when extension ajax install returns an error
2989
- */
2990
- wp_enqueue_media();
2991
- }
2992
-
2993
- if ($this->is_extension_page()) {
2994
- wp_enqueue_style(
2995
- 'fw-extension-page',
2996
- $this->get_uri('/static/extension-page.css'),
2997
- array('fw'),
2998
- fw()->manifest->get_version()
2999
- );
3000
- wp_enqueue_script(
3001
- 'fw-extension-page',
3002
- $this->get_uri('/static/extension-page.js'),
3003
- array('fw'),
3004
- fw()->manifest->get_version(),
3005
- true
3006
- );
3007
-
3008
- /**
3009
- * Enqueue extension settings options static
3010
- */
3011
- if (
3012
- isset($_GET['extension'])
3013
- &&
3014
- is_string($extension_name = $_GET['extension'])
3015
- &&
3016
- fw()->extensions->get($extension_name)
3017
- &&
3018
- ($extension_settings_options = fw()->extensions->get($extension_name)->get_settings_options())
3019
- ) {
3020
- fw()->backend->enqueue_options_static($extension_settings_options);
3021
- }
3022
- }
3023
- }
3024
-
3025
- private function activate_theme_extensions()
3026
- {
3027
- $db_active_extensions = fw()->extensions->_get_db_active_extensions();
3028
-
3029
- foreach ($this->get_installed_extensions() as $extension_name => $extension) {
3030
- if ($extension['is']['theme']) {
3031
- $db_active_extensions[ $extension_name ] = array();
3032
- }
3033
- }
3034
-
3035
- update_option(
3036
- fw()->extensions->_get_active_extensions_db_option_name(),
3037
- $db_active_extensions
3038
- );
3039
- }
3040
-
3041
- /**
3042
- * @internal
3043
- */
3044
- public function _action_theme_switch()
3045
- {
3046
- $this->activate_theme_extensions();
3047
- $this->activate_extensions(
3048
- array_fill_keys(
3049
- array_keys(fw()->theme->manifest->get('supported_extensions', array())),
3050
- array()
3051
- )
3052
- );
3053
- }
3054
-
3055
- /**
3056
- * @param array $collected The found extensions {'extension_name' => array()}
3057
- * @param array $extensions {'extension_name' => array()}
3058
- * @param bool $check_all Check all extensions or only active extensions
3059
- */
3060
- private function collect_extensions_that_requires(&$collected, $extensions, $check_all = false)
3061
- {
3062
- if (empty($extensions)) {
3063
- return;
3064
- }
3065
-
3066
- $found_extensions = array();
3067
-
3068
- foreach ($this->get_installed_extensions() as $extension_name => $extension_data) {
3069
- if (isset($collected[$extension_name])) {
3070
- continue;
3071
- }
3072
-
3073
- if (!$check_all) {
3074
- if (!fw_ext($extension_name)) {
3075
- continue;
3076
- }
3077
- }
3078
-
3079
- if (
3080
- array_intersect_key(
3081
- $extensions,
3082
- fw_akg(
3083
- 'requirements/extensions',
3084
- $extension_data['manifest'],
3085
- array()
3086
- )
3087
- )
3088
- ) {
3089
- $found_extensions[$extension_name] = $collected[$extension_name] = array();
3090
- }
3091
- }
3092
-
3093
- $this->collect_extensions_that_requires($collected, $found_extensions, $check_all);
3094
- }
3095
-
3096
- /**
3097
- * Get extension settings page link
3098
- * @param string $extension_name
3099
- * @return string
3100
- */
3101
- public function get_extension_link($extension_name)
3102
- {
3103
- return $this->get_link() .'&sub-page=extension&extension='. $extension_name;
3104
- }
3105
-
3106
- /**
3107
- * @param string $extension_name
3108
- * @return array|WP_Error Extensions to merge with db active extensions list
3109
- */
3110
- private function get_extensions_for_activation($extension_name)
3111
- {
3112
- $installed_extensions = $this->get_installed_extensions();
3113
-
3114
- $wp_error_id = 'fw_ext_activation';
3115
-
3116
- if (!isset($installed_extensions[$extension_name])) {
3117
- return new WP_Error($wp_error_id,
3118
- sprintf(
3119
- __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3120
- $this->get_extension_title($extension_name),
3121
- fw_html_tag('a', array(
3122
- 'href' => $this->get_link() .'&sub-page=install&extension='. $extension_name
3123
- ), __('Install', 'fw'))
3124
- )
3125
- );
3126
- }
3127
-
3128
- {
3129
- $extension_parents = array($extension_name);
3130
-
3131
- $current_parent = $extension_name;
3132
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
3133
- $extension_parents[] = $current_parent;
3134
- }
3135
-
3136
- $extension_parents = array_reverse($extension_parents);
3137
- }
3138
-
3139
- $extensions = array();
3140
-
3141
- foreach ($extension_parents as $parent_extension_name) {
3142
- $extensions[ $parent_extension_name ] = array();
3143
- }
3144
-
3145
- // search sub-extensions
3146
- foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3147
- $extensions[ $sub_extension_name ] = array();
3148
- }
3149
-
3150
- // search required extensions
3151
- {
3152
- $pending_required_search = $extensions;
3153
-
3154
- while ($pending_required_search) {
3155
- foreach (array_keys($pending_required_search) as $pend_req_extension_name) {
3156
- unset($pending_required_search[$pend_req_extension_name]);
3157
-
3158
- unset($required_extensions); // reset reference
3159
- $required_extensions = array();
3160
- $this->collect_required_extensions($pend_req_extension_name, $installed_extensions, $required_extensions);
3161
-
3162
- foreach ($required_extensions as $required_extension_name => $required_extension_data) {
3163
- if (!isset($installed_extensions[$required_extension_name])) {
3164
- return new WP_Error($wp_error_id,
3165
- sprintf(
3166
- __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3167
- $this->get_extension_title($required_extension_name),
3168
- fw_html_tag('a', array(
3169
- 'href' => $this->get_link() .'&sub-page=install&extension='. $required_extension_name
3170
- ), __('Install', 'fw'))
3171
- )
3172
- );
3173
- }
3174
-
3175
- $extensions[$required_extension_name] = array();
3176
-
3177
- // search sub-extensions
3178
- foreach ($this->collect_sub_extensions($required_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3179
- if (isset($extensions[$sub_extension_name])) {
3180
- continue;
3181
- }
3182
-
3183
- $extensions[$sub_extension_name] = array();
3184
-
3185
- $pending_required_search[$sub_extension_name] = array();
3186
- }
3187
- }
3188
- }
3189
- }
3190
- }
3191
-
3192
- return $extensions;
3193
- }
3194
-
3195
- /**
3196
- * @internal
3197
- */
3198
- public function _action_admin_notices() {
3199
- $should_notify = apply_filters(
3200
- 'fw_notify_about_missing_extensions',
3201
- true
3202
- );
3203
-
3204
- /**
3205
- * In v2.4.12 was done a terrible mistake https://github.com/ThemeFuse/Unyson-Extensions-Approval/issues/160
3206
- * Show a warning with link to install theme supported extensions
3207
- */
3208
- if (
3209
- $should_notify
3210
- &&
3211
- !isset($_GET['supported']) // already on 'Install Supported Extensions' page
3212
- &&
3213
- $this->can_install()
3214
- &&
3215
- (($installed_extensions = $this->get_installed_extensions()) || true)
3216
- &&
3217
- !isset($installed_extensions['page-builder'])
3218
- &&
3219
- $this->get_supported_extensions_for_install()
3220
- ) {
3221
- echo '<div class="error"> <p>'
3222
- , fw_html_tag('a', array('href' => $this->get_link() .'&sub-page=install&supported'),
3223
- __('Install theme compatible extensions', 'fw'))
3224
- , '</p></div>';
3225
- }
3226
- }
3227
-
3228
- /**
3229
- * Copy Theme Available Extensions to a tmp directory
3230
- * Used before theme update
3231
- * @since 2.6.0
3232
- * @return null|WP_Error
3233
- */
3234
- public function theme_available_extensions_copy() {
3235
- /** @var WP_Filesystem_Base $wp_filesystem */
3236
- global $wp_filesystem;
3237
-
3238
- if (!FW_WP_Filesystem::is_ready()) {
3239
- return new WP_Error(
3240
- 'fs_not_initialized',
3241
- __('WP Filesystem is not initialized', 'fw')
3242
- );
3243
- }
3244
-
3245
- // Prepare temporary directory
3246
- {
3247
- $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3248
- $this->get_tmp_dir('/theme-ext')
3249
- );
3250
-
3251
- if (
3252
- $wp_filesystem->exists( $wpfs_tmp_dir )
3253
- &&
3254
- ! $wp_filesystem->rmdir( $wpfs_tmp_dir, true )
3255
- ) {
3256
- return new WP_Error(
3257
- 'tmp_dir_rm_fail',
3258
- sprintf(__('Temporary directory cannot be removed: %s', 'fw'), $wpfs_tmp_dir)
3259
- );
3260
- }
3261
-
3262
- if ( ! FW_WP_Filesystem::mkdir_recursive( $wpfs_tmp_dir ) ) {
3263
- return new WP_Error(
3264
- 'tmp_dir_rm_fail',
3265
- sprintf(__('Temporary directory cannot be created: %s', 'fw'), $wpfs_tmp_dir)
3266
- );
3267
- }
3268
- }
3269
-
3270
- $available_extensions = $this->get_available_extensions();
3271
- $installed_extensions = $this->get_installed_extensions(true);
3272
- $base_dir = fw_get_template_customizations_directory('/extensions');
3273
-
3274
- foreach ($installed_extensions as $name => $ext) {
3275
- if ( ! (
3276
- isset($available_extensions[$name])
3277
- &&
3278
- isset($available_extensions[$name]['theme'])
3279
- &&
3280
- $available_extensions[$name]['theme']
3281
- ) ) {
3282
- continue;
3283
- }
3284
-
3285
- if ( ($rel_path = preg_replace('/^'. preg_quote($base_dir, '/') .'/', '', $ext['path'])) === $base_dir ) {
3286
- return new WP_Error(
3287
- 'rel_path_failed',
3288
- sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3289
- );
3290
- }
3291
-
3292
- if ( ($wpfs_path = FW_WP_Filesystem::real_path_to_filesystem_path($ext['path'])) === false) {
3293
- return new WP_Error(
3294
- 'real_to_wpfs_filed',
3295
- sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3296
- );
3297
- }
3298
-
3299
- $wpfs_dest_dir = $wpfs_tmp_dir . $rel_path;
3300
-
3301
- if ( ! FW_WP_Filesystem::mkdir_recursive($wpfs_dest_dir) ) {
3302
- return new WP_Error(
3303
- 'dest_dir_mk_fail',
3304
- sprintf(__('Failed to create directory %s', 'fw'), $wpfs_dest_dir)
3305
- );
3306
- }
3307
-
3308
- if ( is_wp_error( $copy_result = copy_dir($wpfs_path, $wpfs_dest_dir) ) ) {
3309
- /** @var WP_Error $copy_result */
3310
- return new WP_Error(
3311
- 'ext_copy_failed',
3312
- sprintf( __('Failed to copy extension to %s', 'fw'), $wpfs_dest_dir )
3313
- );
3314
- }
3315
- }
3316
- }
3317
-
3318
- /**
3319
- * Copy Theme Available Extensions from tmp directory to theme
3320
- * Used after theme update
3321
- * @since 2.6.0
3322
- * @return null|WP_Error
3323
- */
3324
- public function theme_available_extensions_restore() {
3325
- /** @var WP_Filesystem_Base $wp_filesystem */
3326
- global $wp_filesystem;
3327
-
3328
- if (!FW_WP_Filesystem::is_ready()) {
3329
- return new WP_Error(
3330
- 'fs_not_initialized',
3331
- __('WP Filesystem is not initialized', 'fw')
3332
- );
3333
- }
3334
-
3335
- if ( ! $wp_filesystem->exists(
3336
- $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3337
- $this->get_tmp_dir('/theme-ext')
3338
- )
3339
- ) ) {
3340
- return new WP_Error(
3341
- 'no_tmp_dir',
3342
- sprintf(__('Temporary directory does not exist: %s', 'fw'), $wpfs_tmp_dir)
3343
- );
3344
- }
3345
-
3346
- /**
3347
- * Fixes the case when the theme path before update was
3348
- * wp-content/themes/theme-name/theme-name-parent
3349
- * but after update it became
3350
- * wp-content/themes/theme-name-parent
3351
- *
3352
- * and at this point get_template_directory() returns old theme directory
3353
- * so fw_get_template_customizations_directory() also returns old path
3354
- */
3355
- $theme_dir = wp_get_theme()->get_theme_root() .'/'. wp_get_theme()->get_template();
3356
-
3357
- if ( ! ($wpfs_base_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3358
- $base_dir = $theme_dir . fw_get_framework_customizations_dir_rel_path('/extensions')
3359
- ) ) ) {
3360
- return new WP_Error(
3361
- 'base_dir_to_wpfs_fail',
3362
- sprintf( __('Cannot obtain WP Filesystem dir for %s', 'fw'), $base_dir )
3363
- );
3364
- }
3365
-
3366
- if ( ! ( $dirlist = $wp_filesystem->dirlist($wpfs_tmp_dir) ) ) {
3367
- return;
3368
- }
3369
-
3370
- foreach ( $dirlist as $filename => $fileinfo ) {
3371
- if ( 'd' !== $fileinfo['type'] ) {
3372
- continue;
3373
- }
3374
-
3375
- if ( is_wp_error($merge_result = $this->merge_extension(
3376
- $wpfs_tmp_dir .'/'. $filename,
3377
- $wpfs_base_dir .'/'. $filename
3378
- )) ) {
3379
- return $merge_result;
3380
- }
3381
- }
3382
-
3383
- $wp_filesystem->rmdir( $wpfs_tmp_dir, true );
3384
- }
3385
-
3386
- /**
3387
- * Copy Theme Available Extensions to tmp dir
3388
- * @param bool|WP_Error $result
3389
- * @param array $data
3390
- *
3391
- * @return bool|WP_Error
3392
- */
3393
- public function _filter_theme_available_extensions_copy($result, $data) {
3394
- if (
3395
- !is_wp_error($result)
3396
- &&
3397
- is_array($data)
3398
- &&
3399
- isset($data['theme'])
3400
- &&
3401
- $data['theme'] === wp_get_theme()->get_template()
3402
- ) {
3403
- if ( is_wp_error( $copy_result = fw()->extensions->manager->theme_available_extensions_copy() ) ) {
3404
- return $copy_result;
3405
- }
3406
- }
3407
-
3408
- return $result;
3409
- }
3410
-
3411
- /**
3412
- * Restore Theme Available Extensions from tmp dir
3413
- * @param Theme_Upgrader $instance
3414
- * @param array $data
3415
- *
3416
- * @return bool|WP_Error
3417
- */
3418
- public function _action_theme_available_extensions_restore($instance, $data) {
3419
- if (
3420
- !is_wp_error($instance->skin->result)
3421
- &&
3422
- is_array($data)
3423
- &&
3424
- isset($data['action']) && $data['action'] === 'update'
3425
- &&
3426
- isset($data['type']) && $data['type'] === 'theme'
3427
- &&
3428
- isset($data['themes'])
3429
- &&
3430
- ($template = wp_get_theme()->get_template())
3431
- &&
3432
- (
3433
- in_array($template, $data['themes'])
3434
- ||
3435
- /**
3436
- * Fixes the case when the theme path before update was
3437
- * wp-content/themes/theme-name/theme-name-parent
3438
- * but after update it became
3439
- * wp-content/themes/theme-name-parent
3440
- */
3441
- ( preg_match($regex = '/\-parent$/', $template)
3442
- ? in_array( preg_replace($regex, '', $template) .'/'. $template, $data['themes'] )
3443
- : false )
3444
- )
3445
- ) {
3446
- fw()->extensions->manager->theme_available_extensions_restore();
3447
- }
3448
- }
3449
-
3450
- /**
3451
- * Install compatible extensions on plugin install -> activate
3452
- *
3453
- * In order for this to work, int TGM config must be set: 'is_automatic' => true
3454
- * http://tgmpluginactivation.com/configuration/
3455
- *
3456
- * @internal
3457
- */
3458
- public function _action_plugin_activate_install_compatible_extensions() {
3459
- if (!FW_WP_Filesystem::is_ready()) {
3460
- return;
3461
- }
3462
-
3463
- if ($compatible_extensions = $this->get_supported_extensions_for_install()) {
3464
- $this->install_extensions($compatible_extensions);
3465
- // the result is not used because we don't know here if we can print the errors or not
3466
- }
3467
- }
3468
-
3469
- /**
3470
- * @since 2.6.9
3471
- */
3472
- public function collect_extension_requirements($extension_name, $can_install = null) {
3473
- $installed_extensions = $this->get_installed_extensions();
3474
-
3475
- if (is_null($can_install)) {
3476
- $can_install = $this->can_install();
3477
- }
3478
-
3479
- if (! isset($installed_extensions[$extension_name])) {
3480
- return array();
3481
- } else {
3482
- $data = $installed_extensions[$extension_name];
3483
- }
3484
-
3485
- $result = array();
3486
-
3487
- $manifest_requirements = fw_akg('requirements', $data['manifest'], array());
3488
-
3489
- foreach ($manifest_requirements as $req_name => $req_data) {
3490
- switch ($req_name) {
3491
- case 'php':
3492
- if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3493
- break;
3494
- }
3495
-
3496
- if ( ! empty( $req_data['min_version'] ) ) {
3497
- if (!version_compare($req_data['min_version'], phpversion(), '<=')) {
3498
- $result[] = sprintf(
3499
- __( 'PHP needs to be updated to %s', 'fw' ),
3500
- $req_data['min_version']
3501
- );
3502
- }
3503
- }
3504
-
3505
- if ( ! empty( $req_data['max_version'] ) ) {
3506
- if (!version_compare($req_data['max_version'], phpversion(), '>=')) {
3507
- $result[] = sprintf(
3508
- __('Maximum supported PHP version is %s', 'fw'),
3509
- $req_data['max_version']
3510
- );
3511
- }
3512
- }
3513
-
3514
- break;
3515
-
3516
- case 'wordpress':
3517
- if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3518
- break;
3519
- }
3520
-
3521
- global $wp_version;
3522
-
3523
- if ( ! empty( $req_data['min_version'] ) ) {
3524
- if (!version_compare($req_data['min_version'], $wp_version, '<=')) {
3525
- if ($can_install) {
3526
- $result[] = sprintf(
3527
- __( 'You need to update WordPress to %s: %s', 'fw' ),
3528
- $req_data['min_version'],
3529
- fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ), __( 'Update WordPress', 'fw' ) )
3530
- );
3531
- } else {
3532
- $result[] = sprintf(
3533
- __( 'WordPress needs to be updated to %s', 'fw' ),
3534
- $req_data['min_version']
3535
- );
3536
- }
3537
- }
3538
- }
3539
-
3540
- if ( ! empty( $req_data['max_version'] ) ) {
3541
- if (!version_compare($req_data['max_version'], $wp_version, '>=')) {
3542
- $result[] = sprintf(
3543
- __('Maximum supported WordPress version is %s', 'fw'),
3544
- $req_data['max_version']
3545
- );
3546
- }
3547
- }
3548
-
3549
- break;
3550
-
3551
- case 'framework':
3552
- if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3553
- break;
3554
- }
3555
-
3556
- if ( ! empty( $req_data['min_version'] ) ) {
3557
- if (!version_compare($req_data['min_version'], fw()->manifest->get_version(), '<=')) {
3558
- if ($can_install) {
3559
- $result[] = sprintf(
3560
- __( 'You need to update %s to %s: %s', 'fw' ),
3561
- fw()->manifest->get_name(),
3562
- $req_data['min_version'],
3563
- fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ),
3564
- sprintf( __( 'Update %s', 'fw' ), fw()->manifest->get_name() )
3565
- )
3566
- );
3567
- } else {
3568
- $result[] = sprintf(
3569
- __( '%s needs to be updated to %s', 'fw' ),
3570
- fw()->manifest->get_name(),
3571
- $req_data['min_version']
3572
- );
3573
- }
3574
- }
3575
- }
3576
-
3577
- if ( ! empty( $req_data['max_version'] ) ) {
3578
- if (!version_compare($req_data['max_version'], fw()->manifest->get_version(), '>=')) {
3579
- $result[] = sprintf(
3580
- __( 'Maximum supported %s version is %s', 'fw' ),
3581
- fw()->manifest->get_name(),
3582
- $req_data['max_version']
3583
- );
3584
- }
3585
- }
3586
-
3587
- break;
3588
-
3589
- case 'extensions':
3590
- foreach ($req_data as $req_ext => $req_ext_data) {
3591
- if ($ext = fw()->extensions->get($req_ext)) {
3592
- if (empty($req_ext_data['min_version']) && empty($req_ext_data['max_version'])) {
3593
- continue;
3594
- }
3595
-
3596
- if ( ! empty( $req_ext_data['min_version'] ) ) {
3597
- if (!version_compare($req_ext_data['min_version'], $ext->manifest->get_version(), '<=')) {
3598
- if ($can_install) {
3599
- $result[] = sprintf(
3600
- __('You need to update the %s extension to %s: %s', 'fw'),
3601
- $ext->manifest->get_name(),
3602
- $req_ext_data['min_version'],
3603
- fw_html_tag('a', array('href' => self_admin_url('update-core.php')),
3604
- sprintf(__('Update %s', 'fw'), $ext->manifest->get_name())
3605
- )
3606
- );
3607
- } else {
3608
- $result[] = sprintf(
3609
- __('The %s extension needs to be updated to %s', 'fw'),
3610
- $ext->manifest->get_name(),
3611
- $req_ext_data['min_version']
3612
- );
3613
- }
3614
- }
3615
- }
3616
-
3617
- if ( ! empty( $req_ext_data['max_version'] ) ) {
3618
- if (!version_compare($req_ext_data['max_version'], $ext->manifest->get_version(), '>=')) {
3619
- $result[] = sprintf(
3620
- __( 'Maximum supported %s extension version is %s', 'fw' ),
3621
- $ext->manifest->get_name(),
3622
- $req_ext_data['max_version']
3623
- );
3624
- }
3625
- }
3626
- } else {
3627
- $ext_title = fw_id_to_title($req_ext);
3628
-
3629
- if (isset($lists['installed'][$req_ext])) {
3630
- $ext_title = fw_akg('name', $lists['installed'][$req_ext]['manifest'], $ext_title);
3631
-
3632
- ob_start(); ?>
3633
- <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($req_ext) ?>" method="post" style="display: inline;">
3634
- <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
3635
- <?php echo sprintf(__( 'The %s extension is disabled', 'fw' ), $ext_title); ?>:
3636
- <a href="#" onclick="jQuery(this).closest('form').submit(); return false;"><?php echo sprintf(__('Activate %s', 'fw'), $ext_title); ?></a>
3637
- </form>
3638
- <?php
3639
- $result[] = ob_get_clean();
3640
- } else {
3641
- if ($can_install && isset($lists['available'][$req_ext])) {
3642
- $ext_title = $lists['available'][ $req_ext ]['name'];
3643
-
3644
- $result[] = sprintf(
3645
- __( 'The %s extension is not installed: %s', 'fw' ),
3646
- $ext_title,
3647
- fw_html_tag( 'a', array( 'href' => $link . '&sub-page=install&extension=' . $req_ext ),
3648
- sprintf( __( 'Install %s', 'fw' ), $ext_title )
3649
- )
3650
- );
3651
- } else {
3652
- $result[] = sprintf(
3653
- __( 'The %s extension is not installed', 'fw' ),
3654
- $ext_title
3655
- );
3656
- }
3657
- }
3658
- }
3659
- }
3660
-
3661
- break;
3662
-
3663
- default:
3664
- trigger_error('Invalid requirement: '. $req_name, E_USER_WARNING);
3665
- continue;
3666
- }
3667
- }
3668
-
3669
- return $result;
3670
- }
3671
- }
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
+ private $manifest_default_values = array(
15
+ 'display' => false,
16
+ 'standalone' => false,
17
+ );
18
+
19
+ private $default_thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
20
+
21
+ /**
22
+ * @var FW_Access_Key
23
+ */
24
+ private static $access_key;
25
+
26
+ private static function get_access_key() {
27
+ if (!self::$access_key) {
28
+ self::$access_key = new FW_Access_Key('fw_ext_manager');
29
+ }
30
+
31
+ return self::$access_key;
32
+ }
33
+
34
+ public function __construct()
35
+ {
36
+ // In any case/permission, make sure to not miss the plugin update actions to prevent extensions delete
37
+ {
38
+ add_action('fw_plugin_pre_update', array($this, '_action_plugin_pre_update'));
39
+ add_action('fw_plugin_post_update', array($this, '_action_plugin_post_update'));
40
+ }
41
+
42
+ // Preserve {theme}/framework-customizations/theme/available-extensions.php
43
+ {
44
+ add_filter('upgrader_pre_install', array($this, '_filter_theme_available_extensions_copy'), 999, 2);
45
+
46
+ /**
47
+ * Must be executed after
48
+ * https://github.com/WordPress/WordPress/blob/4.6/wp-admin/includes/class-theme-upgrader.php#L204-L205
49
+ */
50
+ add_action('upgrader_process_complete', array($this, '_action_theme_available_extensions_restore'), 999, 2);
51
+ }
52
+
53
+ add_action('fw_plugin_activate', array($this, '_action_plugin_activate_install_compatible_extensions'), 100);
54
+ add_action('fw_after_plugin_activate', array($this, '_action_after_plugin_activate'), 100);
55
+ add_action('after_switch_theme', array($this, '_action_theme_switch'));
56
+
57
+ if (!is_admin()) {
58
+ return;
59
+ }
60
+
61
+ if (!$this->can_activate() && !$this->can_install()) {
62
+ return;
63
+ }
64
+
65
+ /** Actions */
66
+ {
67
+ add_action('fw_init', array($this, '_action_fw_init'));
68
+ add_action('admin_menu', array($this, '_action_admin_menu'));
69
+ add_action('network_admin_menu', array($this, '_action_admin_menu'));
70
+ add_action('admin_footer', array($this, '_action_admin_footer'));
71
+ add_action('admin_enqueue_scripts', array($this, '_action_enqueue_scripts'));
72
+ add_action('admin_notices', array($this, '_action_admin_notices'));
73
+
74
+ if ($this->can_install()) {
75
+ add_action('wp_ajax_fw_extensions_check_direct_fs_access', array($this, '_action_ajax_check_direct_fs_access'));
76
+ add_action('wp_ajax_fw_extensions_install', array($this, '_action_ajax_install'));
77
+ add_action('wp_ajax_fw_extensions_uninstall', array($this, '_action_ajax_uninstall'));
78
+ }
79
+ }
80
+
81
+ /** Filters */
82
+ {
83
+ add_filter('fw_plugin_action_list', array($this, '_filter_plugin_action_list'));
84
+ }
85
+ }
86
+
87
+ /**
88
+ * If current user can:
89
+ * - activate extension
90
+ * - disable extensions
91
+ * - save extension settings options
92
+ * @return bool
93
+ */
94
+ public function can_activate()
95
+ {
96
+ if ( fw_is_cli() ) {
97
+ return true;
98
+ }
99
+
100
+ $can_activate = current_user_can('manage_options');
101
+
102
+ if ($can_activate) {
103
+ // also you can use this method to get the capability
104
+ $can_activate = 'manage_options';
105
+ }
106
+
107
+ if (!$can_activate) {
108
+ // make sure if can install, then also can activate. (can install) > (can activate)
109
+ $can_activate = $this->can_install();
110
+ }
111
+
112
+ return $can_activate;
113
+ }
114
+
115
+ /**
116
+ * If current user can:
117
+ * - install extensions
118
+ * - delete extensions
119
+ * @return bool
120
+ */
121
+ public function can_install()
122
+ {
123
+ if ( fw_is_cli() ) {
124
+ return true;
125
+ }
126
+
127
+ $capability = 'install_plugins';
128
+
129
+ if (is_multisite()) {
130
+ // only network admin can change files that affects the entire network
131
+ $can_install = current_user_can_for_blog(get_current_blog_id(), $capability);
132
+ } else {
133
+ $can_install = current_user_can($capability);
134
+ }
135
+
136
+ if ($can_install) {
137
+ // also you can use this method to get the capability
138
+ $can_install = $capability;
139
+ }
140
+
141
+ return $can_install;
142
+ }
143
+
144
+ public function get_page_slug()
145
+ {
146
+ return 'fw-extensions';
147
+ }
148
+
149
+ private function get_cache_key($sub_key)
150
+ {
151
+ return 'fw_extensions_manager/'. $sub_key;
152
+ }
153
+
154
+ private function get_uri($append = '')
155
+ {
156
+ return fw_get_framework_directory_uri('/core/components/extensions/manager'. $append);
157
+ }
158
+
159
+ private function get_nonce($form) {
160
+ switch ($form) {
161
+ case 'install':
162
+ return array(
163
+ 'name' => '_nonce_fw_extensions_install',
164
+ 'action' => 'install',
165
+ );
166
+ case 'delete':
167
+ return array(
168
+ 'name' => '_nonce_fw_extensions_delete',
169
+ 'action' => 'delete',
170
+ );
171
+ case 'activate':
172
+ return array(
173
+ 'name' => '_nonce_fw_extensions_activate',
174
+ 'action' => 'activate',
175
+ );
176
+ case 'deactivate':
177
+ return array(
178
+ 'name' => '_nonce_fw_extensions_deactivate',
179
+ 'action' => 'deactivate',
180
+ );
181
+ default:
182
+ return array(
183
+ 'name' => '_nonce_fw_extensions',
184
+ 'action' => 'default',
185
+ );
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Extensions available for download
191
+ * @return array {name => data}
192
+ *
193
+ * @since 2.6.9
194
+ */
195
+ public function get_available_extensions() {
196
+ try {
197
+ $cache_key = $this->get_cache_key( 'available_extensions' );
198
+
199
+ return FW_Cache::get( $cache_key );
200
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
201
+ $available = fw_get_variables_from_file( dirname( __FILE__ ) . '/available-extensions.php', array( 'extensions' => array() ) );
202
+ $available = $available['extensions'];
203
+
204
+ // Allow theme to register available extensions
205
+ $theme_available_ext_file = fw_fix_path( get_template_directory() ) . fw_get_framework_customizations_dir_rel_path( '/theme/available-extensions.php' );
206
+
207
+ if ( file_exists( $theme_available_ext_file ) ) {
208
+
209
+ $register = new _FW_Available_Extensions_Register( self::get_access_key()->get_key() );
210
+
211
+ /**
212
+ * Usage: https://github.com/ThemeFuse/Unyson/issues/2900
213
+ * Create {theme}/framework-customizations/theme/available-extensions.php with the following contents:
214
+ * $extension = new FW_Available_Extension();
215
+ * $extension->set_...();
216
+ * $register->register($extension);
217
+ */
218
+ $theme_exts = fw_get_variables_from_file( $theme_available_ext_file, array( 'extensions' => array() ), array( 'register' => $register ) );
219
+ $available = array_merge( $available, $theme_exts['extensions'] );
220
+
221
+ foreach ( $register->_get_types( self::$access_key ) as $extension ) {
222
+ /** @var FW_Available_Extension $extension */
223
+ if ( isset( $available[ $extension->get_name() ] ) ) {
224
+ trigger_error(
225
+ 'Overwriting default extension "' . $extension->get_name() . '" is not allowed',
226
+ E_USER_WARNING
227
+ );
228
+ continue;
229
+ } elseif ( ! $extension->is_valid() ) {
230
+ trigger_error(
231
+ 'Theme extension "' . $extension->get_name() . '" is not valid',
232
+ E_USER_WARNING
233
+ );
234
+ continue;
235
+ } else {
236
+ $available[ $extension->get_name() ] = array(
237
+ 'theme' => true, // Registered by theme
238
+ 'display' => $extension->get_display(),
239
+ 'parent' => $extension->get_parent(),
240
+ 'name' => $extension->get_title(),
241
+ 'description' => $extension->get_description(),
242
+ 'thumbnail' => $extension->get_thumbnail(),
243
+ 'download' => $extension->get_download_source(),
244
+ );
245
+ }
246
+ }
247
+ }
248
+
249
+ {
250
+ $installed_extensions = $this->get_installed_extensions();
251
+ $supported_extensions = fw()->theme->manifest->get( 'supported_extensions', array() );
252
+
253
+ if ( isset( $installed_extensions['backup'] ) ) {
254
+ // make sure only Backup or Backups can be installed
255
+ unset( $available['backups'] );
256
+ }
257
+
258
+ foreach ( array( 'backup', 'styling', 'learning' ) as $obsolete_extension ) {
259
+ if (
260
+ ! isset( $supported_extensions[ $obsolete_extension ] )
261
+ &&
262
+ ! isset( $installed_extensions[ $obsolete_extension ] )
263
+ ) {
264
+ unset( $available[ $obsolete_extension ] );
265
+ }
266
+ }
267
+ }
268
+
269
+ FW_Cache::set( $cache_key, $available );
270
+
271
+ return $available;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * @internal
277
+ */
278
+ public function _action_ajax_check_direct_fs_access()
279
+ {
280
+ if (!$this->can_install()) {
281
+ // if can't install, no need to know if has access or not
282
+ wp_send_json_error();
283
+ }
284
+
285
+ if (FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
286
+ wp_send_json_success();
287
+ } else {
288
+ wp_send_json_error();
289
+ }
290
+ }
291
+
292
+ /**
293
+ * @internal
294
+ */
295
+ public function _action_ajax_install()
296
+ {
297
+ if ( ! $this->can_install() ) {
298
+ // if can't install, no need to know if has access or not
299
+ wp_send_json_error();
300
+ }
301
+
302
+ if ( ! FW_WP_Filesystem::has_direct_access( fw_get_framework_directory( '/extensions' ) ) ) {
303
+ wp_send_json_error();
304
+ }
305
+
306
+ $extension = (string) FW_Request::POST( 'extension' );
307
+
308
+ $install_result = $this->install_extensions( array( $extension => array() ), array( 'cancel_on_error' => true ) );
309
+
310
+ if ( $install_result === true ) {
311
+ wp_send_json_success();
312
+ } else {
313
+ wp_send_json_error( $install_result );
314
+ }
315
+ }
316
+
317
+ /**
318
+ * @internal
319
+ */
320
+ public function _action_ajax_uninstall()
321
+ {
322
+ if (!$this->can_install()) {
323
+ // if can't install, no need to know if has access or not
324
+ wp_send_json_error();
325
+ }
326
+
327
+ if (!FW_WP_Filesystem::has_direct_access(fw_get_framework_directory('/extensions'))) {
328
+ wp_send_json_error();
329
+ }
330
+
331
+ $extension = (string)FW_Request::POST('extension');
332
+
333
+ $install_result = $this->uninstall_extensions(array(
334
+ $extension => array()
335
+ ), array(
336
+ 'cancel_on_error' => true
337
+ ));
338
+
339
+ if ($install_result === true) {
340
+ wp_send_json_success();
341
+ } else {
342
+ wp_send_json_error($install_result);
343
+ }
344
+ }
345
+
346
+ /**
347
+ * @internal
348
+ */
349
+ public function _action_after_plugin_activate()
350
+ {
351
+ $this->activate_theme_extensions();
352
+ $this->activate_extensions(
353
+ array_fill_keys(
354
+ array_keys(fw()->theme->manifest->get('supported_extensions', array())),
355
+ array()
356
+ )
357
+ );
358
+
359
+ do_action('fw_after_plugin_activate:before_potential_redirect');
360
+
361
+ if (is_admin() && $this->can_install() && $this->get_supported_extensions_for_install()) {
362
+ wp_redirect($this->get_link() . '&sub-page=install&supported');
363
+ exit;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Copy all extensions to a temp backup directory
369
+ * @internal
370
+ */
371
+ public function _action_plugin_pre_update()
372
+ {
373
+ /** @var WP_Filesystem_Base $wp_filesystem */
374
+ global $wp_filesystem;
375
+
376
+ if (!FW_WP_Filesystem::is_ready()) {
377
+ return;
378
+ }
379
+
380
+ // a directory outside the plugin
381
+ $tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
382
+ fw_fix_path(WP_CONTENT_DIR) .'/tmp/fw-plugin-update-extensions-backup'
383
+ );
384
+ $extensions_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
385
+ fw_get_framework_directory('/extensions')
386
+ );
387
+
388
+ $error = false;
389
+
390
+ do {
391
+ if ($wp_filesystem->exists($tmp_dir)) {
392
+ if (!$wp_filesystem->delete($tmp_dir, true, 'd')) {
393
+ $error = __('Cannot remove the old extensions backup dir', 'fw');
394
+ break;
395
+ }
396
+ }
397
+
398
+ if (!FW_WP_Filesystem::mkdir_recursive($tmp_dir)) {
399
+ $error = __('Cannot create the extensions backup dir', 'fw');
400
+ break;
401
+ }
402
+
403
+ if (true !== copy_dir($extensions_dir, $tmp_dir)) {
404
+ $error = __('Cannot backup the extensions', 'fw');
405
+ break;
406
+ }
407
+ } while(false);
408
+
409
+ if ($error) {
410
+ trigger_error($error, E_USER_WARNING);
411
+
412
+ $wp_filesystem->delete($tmp_dir, true, 'd');
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Copy all extensions from the temp backup directory to the framework extensions directory (recover)
418
+ * @internal
419
+ */
420
+ public function _action_plugin_post_update()
421
+ {
422
+ /** @var WP_Filesystem_Base $wp_filesystem */
423
+ global $wp_filesystem;
424
+
425
+ if (!FW_WP_Filesystem::is_ready()) {
426
+ return;
427
+ }
428
+
429
+ // a directory outside the plugin
430
+ $tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
431
+ fw_fix_path( WP_CONTENT_DIR ) .'/tmp/fw-plugin-update-extensions-backup'
432
+ );
433
+ $extensions_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
434
+ fw_get_framework_directory( '/extensions' )
435
+ );
436
+
437
+ if (!$wp_filesystem->exists($tmp_dir) || !$wp_filesystem->exists($extensions_dir)) {
438
+ return;
439
+ }
440
+
441
+ $error = false;
442
+
443
+ do {
444
+ if ($wp_filesystem->exists($extensions_dir)) {
445
+ /**
446
+ * Make sure to remove framework initial extensions
447
+ * The user do not need them because he already used the framework and has in backup the extensions he uses
448
+ */
449
+ if (!$wp_filesystem->delete( $extensions_dir, true, 'd' )) {
450
+ $error = __( 'Cannot clear the extensions directory', 'fw' );
451
+ break;
452
+ }
453
+
454
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $extensions_dir ) ) {
455
+ $error = __( 'Cannot recreate the extensions directory', 'fw' );
456
+ break;
457
+ }
458
+ }
459
+
460
+ if (true !== copy_dir($tmp_dir, $extensions_dir)) {
461
+ $error = __('Cannot recover the extensions', 'fw');
462
+ break;
463
+ }
464
+ } while(false);
465
+
466
+ if ($error) {
467
+ trigger_error($error, E_USER_WARNING);
468
+ } else {
469
+ // extensions successfully recovered, the backup is not needed anymore
470
+ $wp_filesystem->delete($tmp_dir, true, 'd');
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Scan all directories for extensions
476
+ *
477
+ * @param bool $reset_cache
478
+ * @return array
479
+ *
480
+ * @since 2.6.9
481
+ */
482
+ public function get_installed_extensions($reset_cache = false)
483
+ {
484
+ $cache_key = $this->get_cache_key('installed_extensions');
485
+
486
+ if ($reset_cache) {
487
+ FW_Cache::del($cache_key);
488
+ }
489
+
490
+ try {
491
+ return FW_Cache::get($cache_key);
492
+ } catch (FW_Cache_Not_Found_Exception $e) {
493
+ $extensions = array();
494
+
495
+ foreach (fw()->extensions->get_locations() as $location) {
496
+ // leave only used keys
497
+ $location = array(
498
+ 'path' => $location['path'],
499
+ 'is' => $location['is'],
500
+ );
501
+
502
+ $this->read_extensions($location, $extensions);
503
+ }
504
+
505
+ FW_Cache::set($cache_key, $extensions);
506
+
507
+ return $extensions;
508
+ }
509
+ }
510
+
511
+ /**
512
+ * used by $this->get_installed_extensions()
513
+ * @param string $location
514
+ * @param array $list
515
+ * @param null|string $parent_extension_name
516
+ */
517
+ private function read_extensions($location, &$list, $parent_extension_name = null)
518
+ {
519
+ $paths = glob($location['path'] .'/*', GLOB_ONLYDIR | GLOB_NOSORT);
520
+
521
+ if (empty($paths)) {
522
+ return;
523
+ }
524
+
525
+ foreach ($paths as $extension_path) {
526
+ $extension_name = basename($extension_path);
527
+
528
+ if (isset($list[$extension_name])) {
529
+ // extension already found
530
+ } elseif (file_exists($extension_path .'/manifest.php')) {
531
+ $vars = fw_get_variables_from_file($extension_path .'/manifest.php', array(
532
+ 'manifest' => array(),
533
+ ));
534
+
535
+ $list[$extension_name] = array(
536
+ 'path' => $extension_path,
537
+ 'manifest' => $vars['manifest'],
538
+ 'children' => array(),
539
+ 'active' => (bool)fw()->extensions->get($extension_name),
540
+ 'parent' => $parent_extension_name,
541
+ 'is' => $location['is'],
542
+ );
543
+
544
+ if ($parent_extension_name) {
545
+ $list[ $parent_extension_name ]['children'][$extension_name] = array();
546
+ }
547
+ } else {
548
+ // it's a directory with customizations for an extension
549
+ continue;
550
+ }
551
+
552
+ $sub_extension_location = $location;
553
+ $sub_extension_location['path'] .= '/'. $extension_name .'/extensions';
554
+
555
+ $this->read_extensions(
556
+ $sub_extension_location,
557
+ $list,
558
+ $extension_name
559
+ );
560
+ }
561
+ }
562
+
563
+ private function get_tmp_dir($append = '')
564
+ {
565
+ return apply_filters('fw_tmp_dir', fw_fix_path(WP_CONTENT_DIR) .'/tmp') . $append;
566
+ }
567
+
568
+ /**
569
+ * @internal
570
+ */
571
+ public function _action_fw_init()
572
+ {
573
+ $this->extension_settings_form = new FW_Form('fw_extension_settings', array(
574
+ 'render' => array($this, '_extension_settings_form_render'),
575
+ 'validate' => array($this, '_extension_settings_form_validate'),
576
+ 'save' => array($this, '_extension_settings_form_save'),
577
+ ));
578
+
579
+ if (is_admin() && $this->can_activate()) {
580
+ $db_wp_option_name = 'fw_extensions_activation';
581
+
582
+ if ($db_wp_option_value = get_option($db_wp_option_name, array())) {
583
+ $db_wp_option_value = array_merge(array(
584
+ 'activated' => array(),
585
+ 'deactivated' => array(),
586
+ ), $db_wp_option_value);
587
+
588
+ /**
589
+ * Fire the 'fw_extensions_after_activation' action
590
+ */
591
+ if ($db_wp_option_value['activated']) {
592
+ $succeeded_extensions = $failed_extensions = array();
593
+
594
+ foreach ($db_wp_option_value['activated'] as $extension_name => $not_used_var) {
595
+ if (fw_ext($extension_name)) {
596
+ $succeeded_extensions[$extension_name] = array();
597
+ } else {
598
+ $failed_extensions[$extension_name] = array();
599
+ }
600
+ }
601
+
602
+ if (!empty($succeeded_extensions)) {
603
+ do_action('fw_extensions_after_activation', $succeeded_extensions);
604
+ }
605
+ if (!empty($failed_extensions)) {
606
+ do_action('fw_extensions_activation_failed', $failed_extensions);
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Fire the 'fw_extensions_after_deactivation' action
612
+ */
613
+ if ($db_wp_option_value['deactivated']) {
614
+ $succeeded_extensions = $failed_extensions = array();
615
+
616
+ foreach ($db_wp_option_value['deactivated'] as $extension_name => $not_used_var) {
617
+ if (!fw_ext($extension_name)) {
618
+ $succeeded_extensions[$extension_name] = array();
619
+ } else {
620
+ $failed_extensions[$extension_name] = array();
621
+ }
622
+ }
623
+
624
+ if (!empty($succeeded_extensions)) {
625
+ do_action('fw_extensions_after_deactivation', $succeeded_extensions);
626
+ }
627
+ if (!empty($failed_extensions)) {
628
+ do_action('fw_extensions_deactivation_failed', $failed_extensions);
629
+ }
630
+ }
631
+
632
+ delete_option($db_wp_option_name);
633
+ }
634
+ }
635
+ }
636
+
637
+ /**
638
+ * Activate extensions with $manifest['display'] = false; $manifest['standalone'] = true;
639
+ * - First level extensions
640
+ * - Child extensions of the active extensions
641
+ */
642
+ private function activate_hidden_standalone_extensions()
643
+ {
644
+ if (!is_admin()) {
645
+ return;
646
+ }
647
+
648
+ if (!$this->can_activate()) {
649
+ return;
650
+ }
651
+
652
+ $activate_extensions = array();
653
+
654
+ foreach (
655
+ // all disabled extensions
656
+ array_diff_key($this->get_installed_extensions(), fw()->extensions->get_all())
657
+ as $ext_name => $ext_data
658
+ ) {
659
+ if ($ext_data['parent'] && !fw_ext($ext_data['parent'])) {
660
+ // child extensions of an inactive extension
661
+ continue;
662
+ }
663
+
664
+ if (false !== fw_akg(
665
+ 'display',
666
+ $ext_data['manifest'],
667
+ $this->manifest_default_values['display']
668
+ )) {
669
+ // is visible
670
+ continue;
671
+ }
672
+
673
+ if (true !== fw_akg(
674
+ 'standalone',
675
+ $ext_data['manifest'],
676
+ $this->manifest_default_values['standalone']
677
+ )) {
678
+ // not standalone
679
+ continue;
680
+ }
681
+
682
+ $collected = $this->get_extensions_for_activation($ext_name);
683
+
684
+ if (is_wp_error($collected)) {
685
+ if (defined('WP_DEBUG') && WP_DEBUG) {
686
+ if ($this->is_extensions_page()) {
687
+ // display this warning only on Unyson extensions page
688
+ FW_Flash_Messages::add('fw_ext_auto_activate_hidden_standalone',
689
+ sprintf(__('Cannot activate hidden standalone extension %s', 'fw'),
690
+ fw_akg('name', $ext_data['manifest'], fw_id_to_title($ext_name))
691
+ ),
692
+ 'error'
693
+ );
694
+ }
695
+ }
696
+ return;
697
+ }
698
+
699
+ $activate_extensions = array_merge($activate_extensions, $collected);
700
+ }
701
+
702
+ if (empty($activate_extensions)) {
703
+ return;
704
+ }
705
+
706
+ $option_name = fw()->extensions->_get_active_extensions_db_option_name();
707
+
708
+ $db_active_extensions = array_merge(get_option($option_name, array()), $activate_extensions);
709
+
710
+ update_option($option_name, $db_active_extensions);
711
+ }
712
+
713
+ /**
714
+ * @internal
715
+ */
716
+ public function _action_admin_menu()
717
+ {
718
+ $capability = $this->can_activate();
719
+
720
+ if (!$capability) {
721
+ return;
722
+ }
723
+
724
+ $data = array(
725
+ 'title' => fw()->manifest->get_name(),
726
+ 'capability' => $capability,
727
+ 'slug' => $this->get_page_slug(),
728
+ 'content_callback' => array($this, '_display_page'),
729
+ );
730
+
731
+ /**
732
+ * Collect $hookname that contains $data['slug'] before the action
733
+ * and skip them in verification after action
734
+ */
735
+ {
736
+ global $_registered_pages;
737
+
738
+ $found_hooknames = array();
739
+
740
+ if (!empty($_registered_pages)) {
741
+ foreach ( $_registered_pages as $hookname => $b ) {
742
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
743
+ $found_hooknames[$hookname] = true;
744
+ }
745
+ }
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Use this action if you what to add the extensions page in a custom place in menu
751
+ * Usage example http://pastebin.com/2iWVRPAU
752
+ */
753
+ do_action('fw_backend_add_custom_extensions_menu', $data);
754
+
755
+ /**
756
+ * Check if menu was added in the action above
757
+ */
758
+ {
759
+ $menu_exists = false;
760
+
761
+ if (!empty($_registered_pages)) {
762
+ foreach ( $_registered_pages as $hookname => $b ) {
763
+ if (isset($found_hooknames[$hookname])) {
764
+ continue;
765
+ }
766
+
767
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
768
+ $menu_exists = true;
769
+ break;
770
+ }
771
+ }
772
+ }
773
+ }
774
+
775
+ if ($menu_exists) {
776
+ // do nothing
777
+ } else {
778
+ add_menu_page(
779
+ $data['title'],
780
+ $data['title'],
781
+ $data['capability'],
782
+ $data['slug'],
783
+ $data['content_callback'],
784
+ 'none',
785
+ 3
786
+ );
787
+ }
788
+ }
789
+
790
+ /**
791
+ * If output already started, we cannot set the redirect header, do redirect from js
792
+ */
793
+ private function js_redirect()
794
+ {
795
+ echo
796
+ '<script type="text/javascript">'.
797
+ 'window.location.replace("'. esc_js($this->get_link()) .'");'.
798
+ '</script>';
799
+ }
800
+
801
+ /**
802
+ * @internal
803
+ */
804
+ public function _display_page()
805
+ {
806
+ $page = FW_Request::GET('sub-page');
807
+
808
+ switch ($page) {
809
+ case 'install':
810
+ $this->display_install_page();
811
+ break;
812
+ case 'delete':
813
+ $this->display_delete_page();
814
+ break;
815
+ case 'extension':
816
+ $this->display_extension_page();
817
+ break;
818
+ case 'activate':
819
+ $this->display_activate_page();
820
+ break;
821
+ case 'deactivate':
822
+ $this->display_deactivate_page();
823
+ break;
824
+ default:
825
+ $this->display_list_page();
826
+ }
827
+ }
828
+
829
+ private function display_list_page()
830
+ {
831
+ // note: static is enqueued in 'admin_enqueue_scripts' action
832
+
833
+ /** Prepare extensions list for view */
834
+ {
835
+ $lists = array(
836
+ 'active' => array(),
837
+ 'disabled' => array(),
838
+ 'installed' => array(),
839
+ 'available' => array(),
840
+ 'supported' => array(),
841
+ );
842
+
843
+ foreach ($this->get_installed_extensions() as $ext_name => $ext_data) {
844
+ $lists[ $ext_data['active'] ? 'active' : 'disabled' ][$ext_name] = $ext_data;
845
+ }
846
+
847
+ $lists['installed'] = $lists['active'] + $lists['disabled'];
848
+
849
+ unset($ext_data); // prevent change by reference
850
+
851
+ foreach ($this->get_available_extensions() as $ext_name => $ext_data) {
852
+ $lists['available'][$ext_name] = array(
853
+ 'name' => $ext_data['name'],
854
+ 'description' => $ext_data['description'],
855
+ 'thumbnail' => isset($ext_data['thumbnail'])
856
+ ? $ext_data['thumbnail']
857
+ : (isset($lists['installed'][$ext_name])
858
+ ? fw_akg('thumbnail', $lists['installed'][$ext_name]['manifest'], $this->default_thumbnail)
859
+ : $this->default_thumbnail),
860
+ 'display' => isset($ext_data['display'])
861
+ ? $ext_data['display']
862
+ : $this->manifest_default_values['display'],
863
+ 'theme' => isset($ext_data['theme']) && $ext_data['theme'],
864
+ 'download' => isset( $ext_data['download'] ) ? $ext_data['download'] : array()
865
+ );
866
+
867
+ if ($lists['available'][$ext_name]['theme']) {
868
+ $lists['supported'][$ext_name] = array(
869
+ 'name' => $lists['available'][$ext_name]['name'],
870
+ 'description' => $lists['available'][$ext_name]['description'],
871
+ );
872
+ }
873
+ }
874
+
875
+ foreach (fw()->theme->manifest->get('supported_extensions', array()) as $required_ext_name => $required_ext_data) {
876
+ if (isset($lists['installed'][ $required_ext_name ])) {
877
+ $lists['supported'][ $required_ext_name ] = array(
878
+ 'name' => fw_akg( 'name', $lists['installed'][ $required_ext_name ]['manifest'], fw_id_to_title( $required_ext_name ) ),
879
+ 'description' => fw_akg( 'description', $lists['installed'][ $required_ext_name ]['manifest'], '' ),
880
+ );
881
+ } elseif (isset($lists['available'][$required_ext_name])) {
882
+ $lists['supported'][ $required_ext_name ] = array(
883
+ 'name' => $lists['available'][ $required_ext_name ]['name'],
884
+ 'description' => $lists['available'][ $required_ext_name ]['description'],
885
+ );
886
+ } else {
887
+ $lists['supported'][ $required_ext_name ] = array(
888
+ 'name' => fw_id_to_title( $required_ext_name ),
889
+ 'description' => '',
890
+ );
891
+ }
892
+ }
893
+ }
894
+
895
+ echo '<div class="wrap">';
896
+
897
+ echo '<h2>'. sprintf(__('%s Extensions', 'fw'), fw()->manifest->get_name()) .'</h2><br/>';
898
+
899
+ echo '<div id="fw-extensions-list-wrapper">';
900
+
901
+ fw_render_view(dirname(__FILE__) .'/views/extensions-page.php', array(
902
+ 'lists' => &$lists,
903
+ 'link' => $this->get_link(),
904
+ 'display_default_value' => $this->manifest_default_values['display'],
905
+ 'default_thumbnail' => $this->default_thumbnail,
906
+ 'nonces' => array(
907
+ 'delete' => $this->get_nonce('delete'),
908
+ 'install' => $this->get_nonce('install'),
909
+ 'activate' => $this->get_nonce('activate'),
910
+ 'deactivate' => $this->get_nonce('deactivate'),
911
+ ),
912
+ 'can_install' => $this->can_install(),
913
+ ), false);
914
+
915
+ echo '</div>';
916
+
917
+ echo '</div>';
918
+ }
919
+
920
+ private function display_install_page()
921
+ {
922
+ $flash_id = 'fw_extensions_install';
923
+
924
+ if (!$this->can_install()) {
925
+ FW_Flash_Messages::add(
926
+ $flash_id,
927
+ __('You are not allowed to install extensions.', 'fw'),
928
+ 'error'
929
+ );
930
+ $this->js_redirect();
931
+ return;
932
+ }
933
+
934
+ if (array_key_exists('supported', $_GET)) {
935
+ $supported = true;
936
+ $extensions = array_fill_keys(
937
+ array_keys($this->get_supported_extensions_for_install()),
938
+ array()
939
+ );
940
+
941
+ if (empty($extensions)) {
942
+ FW_Flash_Messages::add(
943
+ $flash_id,
944
+ __('All supported extensions are already installed.', 'fw'),
945
+ 'info'
946
+ );
947
+ $this->js_redirect();
948
+ return;
949
+ }
950
+ } else {
951
+ $supported = false;
952
+
953
+ $extensions = array_fill_keys(
954
+ array_map( 'trim', explode( ',', FW_Request::GET( 'extension', '' ) )),
955
+ array()
956
+ );
957
+
958
+ // activate already installed extensions
959
+ $this->activate_extensions($extensions);
960
+ }
961
+
962
+ {
963
+ $skin = new _FW_Extensions_Install_Upgrader_Skin(array(
964
+ 'title' => $supported
965
+ ? _n('Install Compatible Extension', 'Install Compatible Extensions', count($extensions), 'fw')
966
+ : _n('Install Extension', 'Install Extensions', count($extensions), 'fw'),
967
+ ));
968
+ }
969
+
970
+ $skin->header();
971
+
972
+ do {
973
+ $nonce = $this->get_nonce('install');
974
+
975
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
976
+ if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
977
+ $skin->error(__('Invalid nonce.', 'fw'));
978
+ break;
979
+ }
980
+
981
+ if (!FW_WP_Filesystem::request_access(
982
+ fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
983
+ )) {
984
+ break;
985
+ }
986
+
987
+ $install_result = $this->install_extensions($extensions, array('verbose' => $skin));
988
+
989
+ if (is_wp_error($install_result)) {
990
+ $skin->error($install_result);
991
+ } elseif (is_array($install_result)) {
992
+ $error = array();
993
+
994
+ foreach ($install_result as $extension_name => $extension_result) {
995
+ if (is_wp_error($extension_result)) {
996
+ $error[] = $extension_result->get_error_message();
997
+ }
998
+ }
999
+
1000
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1001
+
1002
+ $skin->error($error);
1003
+ } elseif ($install_result === true) {
1004
+ $skin->set_result(true);
1005
+ }
1006
+
1007
+ /** @var WP_Filesystem_Base $wp_filesystem */
1008
+ global $wp_filesystem;
1009
+
1010
+ $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
1011
+
1012
+ if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
1013
+ if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
1014
+ $skin->error(
1015
+ sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
1016
+ );
1017
+ }
1018
+ }
1019
+
1020
+ $skin->after(array(
1021
+ 'extensions_page_link' => $this->get_link()
1022
+ ));
1023
+
1024
+ if ($supported && $install_result === true) {
1025
+ /**
1026
+ * @since 2.6.14
1027
+ * Fixes https://github.com/ThemeFuse/Unyson/issues/2330
1028
+ */
1029
+ do_action( 'fw_after_supported_extensions_install_success' );
1030
+ }
1031
+ } else {
1032
+ echo '<form method="post">';
1033
+
1034
+ wp_nonce_field($nonce['action'], $nonce['name']);
1035
+
1036
+ $extension_titles = array();
1037
+ foreach ($extensions as $extension_name => $not_used_var) {
1038
+ $extension_titles[$extension_name] = $this->get_extension_title($extension_name);
1039
+ }
1040
+
1041
+ fw_render_view(dirname(__FILE__) .'/views/install-form.php', array(
1042
+ 'extension_titles' => $extension_titles,
1043
+ 'list_page_link' => $this->get_link(),
1044
+ 'supported' => $supported
1045
+ ), false);
1046
+
1047
+ echo '</form>';
1048
+ }
1049
+ } while(false);
1050
+
1051
+ $skin->footer();
1052
+ }
1053
+
1054
+ /**
1055
+ * Download (and activate) extensions
1056
+ * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1057
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1058
+ * @param array $opts
1059
+ * @return WP_Error|bool|array
1060
+ * true: when all extensions succeeded
1061
+ * array: when some/all failed
1062
+ */
1063
+ public function install_extensions( array $extensions, $opts = array() ) {
1064
+ {
1065
+ $opts = array_merge( array(
1066
+ /**
1067
+ * @type bool
1068
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1069
+ * true: return first WP_Error or true on success
1070
+ */
1071
+ 'cancel_on_error' => false,
1072
+ /**
1073
+ * @type bool Activate installed extensions
1074
+ */
1075
+ 'activate' => true,
1076
+ /**
1077
+ * @type bool|WP_Upgrader_Skin
1078
+ */
1079
+ 'verbose' => false,
1080
+ ), $opts );
1081
+
1082
+ $cancel_on_error = $opts['cancel_on_error']; // fixme: remove successfully installed extensions before error?
1083
+ $activate = $opts['activate'];
1084
+ $verbose = $opts['verbose'];
1085
+
1086
+ unset( $opts );
1087
+ }
1088
+
1089
+ if ( ! $this->can_install() ) {
1090
+ return new WP_Error(
1091
+ 'access_denied',
1092
+ __( 'You have no permissions to install extensions', 'fw' )
1093
+ );
1094
+ }
1095
+
1096
+ if ( empty( $extensions ) ) {
1097
+ return new WP_Error(
1098
+ 'no_extensions',
1099
+ __( 'No extensions provided', 'fw' )
1100
+ );
1101
+ }
1102
+
1103
+ if ( ! FW_WP_Filesystem::is_ready() ) {
1104
+ return new WP_Error( 'fs_not_initialized', esc_html__( 'WP Filesystem is not initialized', 'fw' ) );
1105
+ }
1106
+
1107
+ $timeout = function_exists( 'ini_get' ) ? intval( ini_get( 'max_execution_time' ) ) : false;
1108
+ $available_extensions = $this->get_available_extensions();
1109
+ $installed_extensions = $this->get_installed_extensions();
1110
+ $result = $downloaded_extensions = array();
1111
+ $has_errors = false;
1112
+
1113
+ while ( ! empty( $extensions ) ) {
1114
+ reset( $extensions );
1115
+ $extension_name = key( $extensions );
1116
+ unset( $extensions[ $extension_name ] );
1117
+
1118
+ $extensions_before_install = array_keys( $installed_extensions );
1119
+
1120
+ if ( isset( $installed_extensions[ $extension_name ] ) ) {
1121
+ $result[ $extension_name ] = new WP_Error(
1122
+ 'extension_installed',
1123
+ sprintf( __( 'Extension "%s" is already installed.', 'fw' ), $this->get_extension_title( $extension_name ) )
1124
+ );
1125
+ $has_errors = true;
1126
+
1127
+ if ( $cancel_on_error ) {
1128
+ break;
1129
+ }
1130
+ }
1131
+
1132
+ if ( ! isset( $available_extensions[ $extension_name ] ) ) {
1133
+ $result[ $extension_name ] = new WP_Error(
1134
+ 'extension_not_available',
1135
+ sprintf(
1136
+ __( 'Extension "%s" is not available for install.', 'fw' ),
1137
+ $this->get_extension_title( $extension_name )
1138
+ )
1139
+ );
1140
+ $has_errors = true;
1141
+
1142
+ if ( $cancel_on_error ) {
1143
+ break;
1144
+ }
1145
+ }
1146
+
1147
+ /**
1148
+ * Find parent extensions
1149
+ * they will be installed if does not exist
1150
+ */
1151
+ {
1152
+ $parents = array( $extension_name );
1153
+
1154
+ $current_parent = $extension_name;
1155
+ while ( ! empty( $available_extensions[ $current_parent ]['parent'] ) ) {
1156
+ $current_parent = $available_extensions[ $current_parent ]['parent'];
1157
+
1158
+ if ( ! isset( $available_extensions[ $current_parent ] ) ) {
1159
+ $result[ $extension_name ] = new WP_Error(
1160
+ 'parent_extension_not_available',
1161
+ sprintf(
1162
+ __( 'Parent extension "%s" not available.', 'fw' ),
1163
+ $this->get_extension_title( $current_parent )
1164
+ )
1165
+ );
1166
+ $has_errors = true;
1167
+
1168
+ if ( $cancel_on_error ) {
1169
+ break 2;
1170
+ } else {
1171
+ continue 2;
1172
+ }
1173
+ }
1174
+
1175
+ $parents[] = $current_parent;
1176
+ }
1177
+
1178
+ $parents = array_reverse( $parents );
1179
+ }
1180
+
1181
+ /**
1182
+ * Install parent extensions and the extension
1183
+ */
1184
+ {
1185
+ $destination_path = array(
1186
+ 'framework' => fw_get_framework_directory(),
1187
+ 'theme' => fw_fix_path( get_template_directory() ) . fw_get_framework_customizations_dir_rel_path(),
1188
+ );
1189
+ $current_extension_path = '';
1190
+
1191
+ foreach ( $parents as $parent_extension_name ) {
1192
+ $current_extension_path .= '/extensions/' . $parent_extension_name;
1193
+ $set = $available_extensions[ $parent_extension_name ];
1194
+ $destination = isset( $set['theme'] ) && $set['theme'] ? 'theme' : 'framework';
1195
+
1196
+ if ( isset( $installed_extensions[ $parent_extension_name ] ) ) {
1197
+ continue; // skip already installed extensions
1198
+ }
1199
+
1200
+ if ( $verbose ) {
1201
+ $verbose_message = sprintf( esc_html__( 'Downloading the "%s" extension...', 'fw' ), $this->get_extension_title( $parent_extension_name ) );
1202
+
1203
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1204
+ $verbose->feedback( $verbose_message );
1205
+ } else {
1206
+ echo fw_html_tag( 'p', array(), $verbose_message );
1207
+ }
1208
+ }
1209
+
1210
+ // increase timeout
1211
+ if ( $timeout !== false && function_exists( 'set_time_limit' ) ) {
1212
+ $timeout += 30;
1213
+ set_time_limit( $timeout );
1214
+ }
1215
+
1216
+ // If is plugin will returned downloadable link zip.
1217
+ $wp_fw_downloaded_dir = $this->download( $parent_extension_name, $set );
1218
+
1219
+ if ( is_wp_error( $wp_fw_downloaded_dir ) ) {
1220
+ if ( $verbose ) {
1221
+ $verbose_message = $wp_fw_downloaded_dir->get_error_message();
1222
+
1223
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1224
+ $verbose->error( $verbose_message );
1225
+ } else {
1226
+ echo fw_html_tag( 'p', array(), $verbose_message );
1227
+ }
1228
+ }
1229
+
1230
+ $result[ $extension_name ] = $wp_fw_downloaded_dir;
1231
+ $has_errors = true;
1232
+
1233
+ if ( $cancel_on_error ) {
1234
+ break 2;
1235
+ } else {
1236
+ continue 2;
1237
+ }
1238
+ }
1239
+
1240
+ if ( $verbose ) {
1241
+ $verbose_message = sprintf( esc_html__( 'Installing the "%s" extension...', 'fw' ), $this->get_extension_title( $parent_extension_name ) );
1242
+
1243
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1244
+ $verbose->feedback( $verbose_message );
1245
+ } else {
1246
+ echo fw_html_tag( 'p', array(), $verbose_message );
1247
+ }
1248
+ }
1249
+
1250
+ // Merge directories only for extensions. If we have plugin it will installed via Plugin_Upgrader.
1251
+ if ( empty( $set['download']['opts']['plugin'] ) ) {
1252
+ $merge_result = $this->merge_extension(
1253
+ $wp_fw_downloaded_dir,
1254
+ FW_WP_Filesystem::real_path_to_filesystem_path( $destination_path[ $destination ] . $current_extension_path )
1255
+ );
1256
+
1257
+ if ( is_wp_error( $merge_result ) ) {
1258
+ if ( $verbose ) {
1259
+ $verbose_message = $merge_result->get_error_message();
1260
+
1261
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1262
+ $verbose->error( $verbose_message );
1263
+ } else {
1264
+ echo fw_html_tag( 'p', array(), $verbose_message );
1265
+ }
1266
+ }
1267
+
1268
+ $result[ $extension_name ] = $merge_result;
1269
+ $has_errors = true;
1270
+
1271
+ if ( $cancel_on_error ) {
1272
+ break 2;
1273
+ } else {
1274
+ continue 2;
1275
+ }
1276
+ }
1277
+ }
1278
+
1279
+ if ( $verbose ) {
1280
+ $verbose_message = sprintf( __( 'The %s extension has been successfully installed.', 'fw' ),
1281
+ $this->get_extension_title( $parent_extension_name )
1282
+ );
1283
+
1284
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1285
+ $verbose->feedback( $verbose_message );
1286
+ } else {
1287
+ echo fw_html_tag( 'p', array(), $verbose_message );
1288
+ }
1289
+ }
1290
+
1291
+ $downloaded_extensions[ $parent_extension_name ] = array();
1292
+
1293
+ /**
1294
+ * Read again all extensions
1295
+ * The downloaded extension may contain more sub extensions
1296
+ */
1297
+ {
1298
+ unset( $installed_extensions );
1299
+ $installed_extensions = $this->get_installed_extensions( true );
1300
+ }
1301
+ }
1302
+ }
1303
+
1304
+ $result[ $extension_name ] = true;
1305
+
1306
+ /**
1307
+ * Collect required extensions of the newly installed extensions
1308
+ */
1309
+ foreach (
1310
+ // new extensions
1311
+ array_diff(
1312
+ array_keys( $installed_extensions ),
1313
+ $extensions_before_install
1314
+ )
1315
+ as $new_extension_name
1316
+ ) {
1317
+ foreach (
1318
+ array_keys(
1319
+ fw_akg(
1320
+ 'requirements/extensions',
1321
+ $installed_extensions[ $new_extension_name ]['manifest'],
1322
+ array()
1323
+ )
1324
+ )
1325
+ as $required_extension_name
1326
+ ) {
1327
+ if ( isset( $installed_extensions[ $required_extension_name ] ) ) {
1328
+ // already installed
1329
+ continue;
1330
+ }
1331
+
1332
+ $extensions[ $required_extension_name ] = array();
1333
+ }
1334
+ }
1335
+ }
1336
+
1337
+ if ( $activate ) {
1338
+ $activate_extensions = array();
1339
+
1340
+ foreach ( $result as $extension_name => $extension_result ) {
1341
+ if ( ! is_wp_error( $extension_result ) ) {
1342
+ $activate_extensions[ $extension_name ] = array();
1343
+ }
1344
+ }
1345
+
1346
+ if ( ! empty( $activate_extensions ) ) {
1347
+ if ( $verbose ) {
1348
+ $verbose_message = _n(
1349
+ 'Activating extension...',
1350
+ 'Activating extensions...',
1351
+ count( $activate_extensions ),
1352
+ 'fw'
1353
+ );
1354
+
1355
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1356
+ $verbose->feedback( $verbose_message );
1357
+ } else {
1358
+ echo fw_html_tag( 'p', array(), $verbose_message );
1359
+ }
1360
+ }
1361
+
1362
+ $activation_result = $this->activate_extensions( $activate_extensions );
1363
+
1364
+ if ( $verbose ) {
1365
+ if ( is_wp_error( $activation_result ) ) {
1366
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1367
+ $verbose->error( $activation_result->get_error_message() );
1368
+ } else {
1369
+ echo fw_html_tag( 'p', array(), $activation_result->get_error_message() );
1370
+ }
1371
+ } elseif ( is_array( $activation_result ) ) {
1372
+ $verbose_message = array();
1373
+
1374
+ foreach ( $activation_result as $extension_name => $extension_result ) {
1375
+ if ( is_wp_error( $extension_result ) ) {
1376
+ $verbose_message[] = $extension_result->get_error_message();
1377
+ }
1378
+ }
1379
+
1380
+ $verbose_message = '<ul><li>' . implode( '</li><li>', $verbose_message ) . '</li></ul>';
1381
+
1382
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1383
+ $verbose->error( $verbose_message );
1384
+ } else {
1385
+ echo fw_html_tag( 'p', array(), $verbose_message );
1386
+ }
1387
+ } elseif ( $activation_result === true ) {
1388
+ $verbose_message = _n(
1389
+ 'Extension has been successfully activated.',
1390
+ 'Extensions has been successfully activated.',
1391
+ count( $activate_extensions ),
1392
+ 'fw'
1393
+ );
1394
+
1395
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1396
+ $verbose->feedback( $verbose_message );
1397
+ } else {
1398
+ echo fw_html_tag( 'p', array(), $verbose_message );
1399
+ }
1400
+ }
1401
+ }
1402
+ }
1403
+ }
1404
+
1405
+ do_action( 'fw_extensions_install', $result );
1406
+
1407
+ if ( $cancel_on_error && $has_errors ) {
1408
+ if ( ( $last_result = end( $result ) ) && is_wp_error( $last_result ) ) {
1409
+ return $last_result;
1410
+ } else {
1411
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1412
+ return new WP_Error(
1413
+ 'installation_failed',
1414
+ _n( 'Cannot install extension', 'Cannot install extensions', count( $extensions ), 'fw' )
1415
+ );
1416
+ }
1417
+ }
1418
+
1419
+ return $has_errors ? $result : true;
1420
+ }
1421
+
1422
+ private function display_delete_page()
1423
+ {
1424
+ $flash_id = 'fw_extensions_delete';
1425
+
1426
+ if (!$this->can_install()) {
1427
+ FW_Flash_Messages::add(
1428
+ $flash_id,
1429
+ __('You are not allowed to delete extensions.', 'fw'),
1430
+ 'error'
1431
+ );
1432
+ $this->js_redirect();
1433
+ return;
1434
+ }
1435
+
1436
+ $extensions = array_fill_keys(array_map('trim', explode(',', FW_Request::GET('extension', ''))), array());
1437
+
1438
+ {
1439
+ $skin = new _FW_Extensions_Delete_Upgrader_Skin(array(
1440
+ 'title' => _n('Delete Extension', 'Delete Extensions', count($extensions), 'fw'),
1441
+ ));
1442
+ }
1443
+
1444
+ $skin->header();
1445
+
1446
+ do {
1447
+ $nonce = $this->get_nonce('delete');
1448
+
1449
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1450
+ if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1451
+ $skin->error(__('Invalid nonce.', 'fw'));
1452
+ break;
1453
+ }
1454
+
1455
+ if (!FW_WP_Filesystem::request_access(
1456
+ fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
1457
+ )) {
1458
+ break;
1459
+ }
1460
+
1461
+ $uninstall_result = $this->uninstall_extensions($extensions, array('verbose' => $skin));
1462
+
1463
+ if (is_wp_error($uninstall_result)) {
1464
+ $skin->error($uninstall_result);
1465
+ } elseif (is_array($uninstall_result)) {
1466
+ $error = array();
1467
+
1468
+ foreach ($uninstall_result as $extension_name => $extension_result) {
1469
+ if (is_wp_error($extension_result)) {
1470
+ $error[] = $extension_result->get_error_message();
1471
+ }
1472
+ }
1473
+
1474
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1475
+
1476
+ $skin->error($error);
1477
+ } elseif ($uninstall_result === true) {
1478
+ $skin->set_result(true);
1479
+ }
1480
+
1481
+ $skin->after(array(
1482
+ 'extensions_page_link' => $this->get_link()
1483
+ ));
1484
+ } else {
1485
+ echo '<form method="post">';
1486
+
1487
+ wp_nonce_field($nonce['action'], $nonce['name']);
1488
+
1489
+ fw_render_view(dirname(__FILE__) .'/views/delete-form.php', array(
1490
+ 'extension_names' => array_keys($extensions),
1491
+ 'installed_extensions' => $this->get_installed_extensions(),
1492
+ 'list_page_link' => $this->get_link(),
1493
+ ), false);
1494
+
1495
+ echo '</form>';
1496
+ }
1497
+ } while(false);
1498
+
1499
+ $skin->footer();
1500
+ }
1501
+
1502
+ /**
1503
+ * Remove extensions
1504
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1505
+ * @param array $opts
1506
+ * @return WP_Error|bool|array
1507
+ * true: when all extensions succeeded
1508
+ * array: when some/all failed
1509
+ */
1510
+ public function uninstall_extensions(array $extensions, $opts = array())
1511
+ {
1512
+ {
1513
+ $opts = array_merge(array(
1514
+ /**
1515
+ * @type bool
1516
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1517
+ * true: return first WP_Error or true on success
1518
+ */
1519
+ 'cancel_on_error' => false,
1520
+ /**
1521
+ * @type bool|WP_Upgrader_Skin
1522
+ */
1523
+ 'verbose' => false,
1524
+ ), $opts);
1525
+
1526
+ $cancel_on_error = $opts['cancel_on_error']; // fixme: install back successfully removed extensions before error?
1527
+ $verbose = $opts['verbose'];
1528
+
1529
+ unset($opts);
1530
+ }
1531
+
1532
+ if (!$this->can_install()) {
1533
+ return new WP_Error(
1534
+ 'access_denied',
1535
+ __('You have no permissions to uninstall extensions', 'fw')
1536
+ );
1537
+ }
1538
+
1539
+ if (empty($extensions)) {
1540
+ return new WP_Error(
1541
+ 'no_extensions',
1542
+ __('No extensions provided', 'fw')
1543
+ );
1544
+ }
1545
+
1546
+ /** @var WP_Filesystem_Base $wp_filesystem */
1547
+ global $wp_filesystem;
1548
+
1549
+ if (!FW_WP_Filesystem::is_ready()) {
1550
+ return new WP_Error(
1551
+ 'fs_not_initialized',
1552
+ __('WP Filesystem is not initialized', 'fw')
1553
+ );
1554
+ }
1555
+
1556
+ $installed_extensions = $this->get_installed_extensions();
1557
+ $available_extensions = $this->get_available_extensions();
1558
+ $extensions_before_uninstall = array_fill_keys( array_keys( $installed_extensions ), array() );
1559
+
1560
+ $result = $uninstalled_extensions = array();
1561
+ $has_errors = false;
1562
+
1563
+ while ( ! empty( $extensions ) ) {
1564
+
1565
+ reset( $extensions );
1566
+ $extension_name = key( $extensions );
1567
+ unset( $extensions[ $extension_name ] );
1568
+
1569
+ if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
1570
+ $unistall = delete_plugins( (array) $available_extensions[ $extension_name ]['download']['opts']['plugin'] );
1571
+ $plugin_title = $available_extensions[ $extension_name ]['name'];
1572
+
1573
+ if ( $unistall ) {
1574
+ $this->verbose( sprintf( esc_html__( 'Extension "%s" has been deleted.', 'fw' ), $plugin_title ), $verbose );
1575
+ $result[ $extension_name ] = true;
1576
+ } else {
1577
+ if ( is_wp_error( $unistall ) ) {
1578
+ $msg_error = $unistall->get_error_message() . ' - ' . $plugin_title;
1579
+ } else {
1580
+ $msg_error = sprintf( esc_html__( 'Plugin %s is empty.' ), $plugin_title );
1581
+ }
1582
+
1583
+ $result[ $extension_name ] = new WP_Error( 'fw_delete_plugins', $msg_error, $plugin_title );
1584
+ $has_errors = true;
1585
+
1586
+ if ( $cancel_on_error ) {
1587
+ break;
1588
+ } else {
1589
+ continue;
1590
+ }
1591
+ }
1592
+
1593
+ continue;
1594
+ }
1595
+
1596
+ $extension_title = $this->get_extension_title($extension_name);
1597
+
1598
+ if (!isset($installed_extensions[ $extension_name ])) {
1599
+ // already deleted
1600
+ $result[$extension_name] = true;
1601
+ continue;
1602
+ }
1603
+
1604
+ if (
1605
+ !isset($installed_extensions[ $extension_name ]['path'])
1606
+ ||
1607
+ empty($installed_extensions[ $extension_name ]['path'])
1608
+ ) {
1609
+ /**
1610
+ * This happens sometimes, but I don't know why
1611
+ * If the script will continue, it will delete the root folder
1612
+ */
1613
+ fw_print(
1614
+ 'Please report this to https://github.com/ThemeFuse/Unyson/issues',
1615
+ $extension_name,
1616
+ $installed_extensions
1617
+ );
1618
+ die;
1619
+ }
1620
+
1621
+ $wp_fs_extension_path = FW_WP_Filesystem::real_path_to_filesystem_path(
1622
+ $installed_extensions[ $extension_name ]['path']
1623
+ );
1624
+
1625
+ if (!$wp_filesystem->exists($wp_fs_extension_path)) {
1626
+ // already deleted, maybe because it was a sub-extension of an deleted extension
1627
+ $result[$extension_name] = true;
1628
+ continue;
1629
+ }
1630
+
1631
+ $this->verbose( sprintf( esc_html__( 'Deleting the "%s" extension...', 'fw' ), $extension_title ), $verbose );
1632
+
1633
+ if (!$wp_filesystem->delete($wp_fs_extension_path, true, 'd')) {
1634
+ $result[$extension_name] = new WP_Error(
1635
+ 'cannot_delete_directory',
1636
+ sprintf(__('Cannot delete the "%s" extension.', 'fw'), $extension_title)
1637
+ );
1638
+ $has_errors = true;
1639
+
1640
+ if ($cancel_on_error) {
1641
+ break;
1642
+ } else {
1643
+ continue;
1644
+ }
1645
+ } else {
1646
+ $this->verbose( sprintf( esc_html__( 'The %s extension has been successfully deleted.', 'fw' ), $extension_title ), $verbose );
1647
+ $result[$extension_name] = true;
1648
+ }
1649
+
1650
+ /**
1651
+ * Read again all extensions
1652
+ * The delete extension may contain more sub extensions
1653
+ */
1654
+ {
1655
+ unset($installed_extensions);
1656
+ $installed_extensions = $this->get_installed_extensions(true);
1657
+ }
1658
+
1659
+ /**
1660
+ * Add for deletion not used extensions
1661
+ * For e.g. standalone=false extension that were required by the deleted extension
1662
+ * and now are not required by any other extension
1663
+ */
1664
+ {
1665
+ $not_used_extensions = array_fill_keys(
1666
+ array_keys(
1667
+ array_diff_key(
1668
+ $installed_extensions,
1669
+ $this->get_used_extensions($extensions, array_keys($installed_extensions))
1670
+ )
1671
+ ),
1672
+ array()
1673
+ );
1674
+
1675
+ $extensions = array_merge($extensions, $not_used_extensions);
1676
+ }
1677
+ }
1678
+
1679
+ do_action('fw_extensions_uninstall', $result);
1680
+
1681
+ if (
1682
+ $cancel_on_error
1683
+ &&
1684
+ $has_errors
1685
+ ) {
1686
+ if (
1687
+ ($last_result = end($result))
1688
+ &&
1689
+ is_wp_error($last_result)
1690
+ ) {
1691
+ return $last_result;
1692
+ } else {
1693
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1694
+ return new WP_Error(
1695
+ 'uninstall_failed',
1696
+ _n('Cannot uninstall extension', 'Cannot uninstall extensions', count($extensions), 'fw')
1697
+ );
1698
+ }
1699
+ }
1700
+
1701
+ // remove from active list the deleted extensions
1702
+ {
1703
+ update_option(
1704
+ fw()->extensions->_get_active_extensions_db_option_name(),
1705
+ array_diff_key(
1706
+ fw()->extensions->_get_db_active_extensions(),
1707
+ array_diff_key(
1708
+ $extensions_before_uninstall,
1709
+ $installed_extensions
1710
+ )
1711
+ )
1712
+ );
1713
+ }
1714
+
1715
+ if ($has_errors) {
1716
+ return $result;
1717
+ } else {
1718
+ return true;
1719
+ }
1720
+ }
1721
+
1722
+ public function verbose( $msg, &$verbose ) {
1723
+
1724
+ if ( ! $verbose ) {
1725
+ return;
1726
+ }
1727
+
1728
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1729
+ $verbose->feedback( $msg );
1730
+ } else {
1731
+ echo fw_html_tag( 'p', array(), $msg );
1732
+ }
1733
+ }
1734
+
1735
+ private function display_extension_page()
1736
+ {
1737
+ // note: static is enqueued in 'admin_enqueue_scripts' action
1738
+
1739
+ $extension_name = trim(FW_Request::GET('extension', ''));
1740
+
1741
+ $installed_extensions = $this->get_installed_extensions();
1742
+
1743
+ $flash_id = 'fw_extension_page';
1744
+
1745
+ {
1746
+ $error = '';
1747
+
1748
+ do {
1749
+ if (empty($extension_name)) {
1750
+ $error = __('Extension not specified.', 'fw');
1751
+ break;
1752
+ }
1753
+
1754
+ if (!isset($installed_extensions[$extension_name])) {
1755
+ $error = sprintf(__('Extension "%s" is not installed.', 'fw'), $this->get_extension_title($extension_name));
1756
+ break;
1757
+ }
1758
+ } while(false);
1759
+
1760
+ if ($error) {
1761
+ FW_Flash_Messages::add($flash_id, $error, 'error');
1762
+ $this->js_redirect();
1763
+ return;
1764
+ }
1765
+ }
1766
+
1767
+ {
1768
+ $tab = fw_akg('tab', $_GET, 'settings');
1769
+
1770
+ if (!in_array($tab, array('settings', 'docs'))) {
1771
+ $tab = 'settings';
1772
+ }
1773
+ }
1774
+
1775
+ $extension_title = $this->get_extension_title($extension_name);
1776
+ $link = $this->get_link();
1777
+
1778
+ echo '<div class="wrap" id="fw-extension-page">';
1779
+
1780
+ fw_render_view(dirname(__FILE__) .'/views/extension-page-header.php', array(
1781
+ 'extension_name' => $extension_name,
1782
+ 'extension_data' => $installed_extensions[$extension_name],
1783
+ 'link_delete' => $link .'&sub-page=delete',
1784
+ 'link_extension' => $link .'&sub-page=extension',
1785
+ 'extension_title' => $extension_title,
1786
+ 'tab' => $tab,
1787
+ 'is_supported' =>
1788
+ fw()->theme->manifest->get('supported_extensions/'. $extension_name, false) !== false
1789
+ ||
1790
+ $installed_extensions[$extension_name]['is']['theme']
1791
+ ), false);
1792
+
1793
+ unset($installed_extensions);
1794
+
1795
+ echo '<div id="fw-extension-tab-content">';
1796
+ {
1797
+ $method_data = array();
1798
+
1799
+ switch ($tab) {
1800
+ case 'settings':
1801
+ $error = $this->display_extension_settings_page($extension_name, $method_data);
1802
+ break;
1803
+ case 'docs':
1804
+ $error = $this->display_extension_docs_page($extension_name, $method_data);
1805
+ break;
1806
+ }
1807
+ }
1808
+ echo '</div>';
1809
+
1810
+ echo '</div>';
1811
+
1812
+ if ($error) {
1813
+ FW_Flash_Messages::add($flash_id, $error, 'error');
1814
+ $this->js_redirect();
1815
+ return;
1816
+ }
1817
+ }
1818
+
1819
+ private function display_extension_settings_page($extension_name, $data)
1820
+ {
1821
+ if (!fw()->extensions->get($extension_name)) {
1822
+ return sprintf(
1823
+ __('Extension "%s" does not exist or is not active.', 'fw'),
1824
+ fw_htmlspecialchars($extension_name)
1825
+ );
1826
+ }
1827
+
1828
+ $extension = fw()->extensions->get($extension_name);
1829
+
1830
+ if (!$extension->get_settings_options()) {
1831
+ return sprintf(
1832
+ __('%s extension does not have settings.', 'fw'),
1833
+ $extension->manifest->get_name()
1834
+ );
1835
+ }
1836
+
1837
+ echo '<div id="fw-extension-settings">';
1838
+
1839
+ echo $this->extension_settings_form->render(array(
1840
+ 'extension' => $extension,
1841
+ ));
1842
+
1843
+ echo '</div>';
1844
+ }
1845
+
1846
+ private function display_extension_docs_page($extension_name, $data)
1847
+ {
1848
+ $ext = fw_ext($extension_name);
1849
+ $docs = $ext->get_rendered_docs();
1850
+
1851
+ if (! $docs) {
1852
+ return __(
1853
+ 'Extension has no documentation. Maybe ask its developer to write some?',
1854
+ 'fw'
1855
+ );
1856
+ }
1857
+
1858
+ echo fw()->backend->render_box(
1859
+ 'fw-extension-docs',
1860
+ '',
1861
+ fw()->backend->render_options(array(
1862
+ 'docs' => array(
1863
+ 'label' => false,
1864
+ 'type' => 'html-full',
1865
+ 'html' => $docs
1866
+ ),
1867
+ ))
1868
+ );
1869
+ }
1870
+
1871
+ private function display_activate_page()
1872
+ {
1873
+ $error = '';
1874
+
1875
+ do {
1876
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
1877
+ $error = __('Invalid request method.', 'fw');
1878
+ break;
1879
+ }
1880
+
1881
+ $nonce = $this->get_nonce('activate');
1882
+
1883
+ if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
1884
+ $error = __('Invalid nonce.', 'fw');
1885
+ break;
1886
+ }
1887
+
1888
+ if (!isset($_GET['extension'])) {
1889
+ $error = __('No extension specified.', 'fw');
1890
+ break;
1891
+ }
1892
+
1893
+ $activation_result = $this->activate_extensions(
1894
+ array_fill_keys(explode(',', $_GET['extension']), array())
1895
+ );
1896
+
1897
+ if (is_wp_error($activation_result)) {
1898
+ $error = $activation_result->get_error_message();
1899
+ } elseif (is_array($activation_result)) {
1900
+ $error = array();
1901
+
1902
+ foreach ($activation_result as $extension_name => $extension_result) {
1903
+ if (is_wp_error($extension_result)) {
1904
+ $error[] = $extension_result->get_error_message();
1905
+ }
1906
+ }
1907
+
1908
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1909
+ }
1910
+ } while(false);
1911
+
1912
+ if ($error) {
1913
+ FW_Flash_Messages::add(
1914
+ 'fw_extensions_activate_page',
1915
+ $error,
1916
+ 'error'
1917
+ );
1918
+ $this->js_redirect();
1919
+ return;
1920
+ }
1921
+
1922
+ $this->js_redirect();
1923
+ }
1924
+
1925
+ /**
1926
+ * Add extensions to active extensions list in database
1927
+ * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1928
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1929
+ * @param bool $cancel_on_error
1930
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1931
+ * true: return first WP_Error or true on success
1932
+ * @return WP_Error|bool|array
1933
+ * true: when all extensions succeeded
1934
+ * array: when some/all failed
1935
+ */
1936
+ public function activate_extensions(array $extensions, $cancel_on_error = false)
1937
+ {
1938
+ if (!$this->can_activate()) {
1939
+ return new WP_Error(
1940
+ 'access_denied',
1941
+ __('You have no permissions to activate extensions', 'fw')
1942
+ );
1943
+ }
1944
+
1945
+ if (empty($extensions)) {
1946
+ return new WP_Error(
1947
+ 'no_extensions',
1948
+ __('No extensions provided', 'fw')
1949
+ );
1950
+ }
1951
+
1952
+ $installed_extensions = $this->get_installed_extensions();
1953
+ $available_extensions = $this->get_available_extensions();
1954
+
1955
+ $result = $extensions_for_activation = array();
1956
+ $has_errors = false;
1957
+
1958
+ foreach ($extensions as $extension_name => $not_used_var) {
1959
+
1960
+ if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
1961
+ activate_plugin( $available_extensions[ $extension_name ]['download']['opts']['plugin'] );
1962
+ continue;
1963
+ }
1964
+
1965
+ if (!isset($installed_extensions[$extension_name])) {
1966
+ $result[$extension_name] = new WP_Error(
1967
+ 'extension_not_installed',
1968
+ sprintf(__('Extension "%s" does not exist.', 'fw'), $this->get_extension_title($extension_name))
1969
+ );
1970
+ $has_errors = true;
1971
+
1972
+ if ($cancel_on_error) {
1973
+ break;
1974
+ } else {
1975
+ continue;
1976
+ }
1977
+ }
1978
+
1979
+ $collected = $this->get_extensions_for_activation($extension_name);
1980
+
1981
+ if (is_wp_error($collected)) {
1982
+ $result[$extension_name] = $collected;
1983
+ $has_errors = true;
1984
+
1985
+ if ($cancel_on_error) {
1986
+ break;
1987
+ } else {
1988
+ continue;
1989
+ }
1990
+ }
1991
+
1992
+ $extensions_for_activation = array_merge($extensions_for_activation, $collected);
1993
+
1994
+ $result[$extension_name] = true;
1995
+ }
1996
+
1997
+ if (
1998
+ $cancel_on_error
1999
+ &&
2000
+ $has_errors
2001
+ ) {
2002
+ if (
2003
+ ($last_result = end($result))
2004
+ &&
2005
+ is_wp_error($last_result)
2006
+ ) {
2007
+ return $last_result;
2008
+ } else {
2009
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
2010
+ return new WP_Error(
2011
+ 'activation_failed',
2012
+ _n('Cannot activate extension', 'Cannot activate extensions', count($extensions), 'fw')
2013
+ );
2014
+ }
2015
+ }
2016
+
2017
+ update_option(
2018
+ fw()->extensions->_get_active_extensions_db_option_name(),
2019
+ array_merge(fw()->extensions->_get_db_active_extensions(), $extensions_for_activation)
2020
+ );
2021
+
2022
+ // remove already active extensions
2023
+ foreach ($extensions_for_activation as $extension_name => $not_used_var) {
2024
+ if (fw_ext($extension_name)) {
2025
+ unset($extensions_for_activation[$extension_name]);
2026
+ }
2027
+ }
2028
+
2029
+ /**
2030
+ * Prepare db wp option used to fire the 'fw_extensions_after_activation' action on next refresh
2031
+ */
2032
+ {
2033
+ $db_wp_option_name = 'fw_extensions_activation';
2034
+ $db_wp_option_value = get_option($db_wp_option_name, array(
2035
+ 'activated' => array(),
2036
+ 'deactivated' => array(),
2037
+ ));
2038
+
2039
+ /**
2040
+ * Keep adding to the existing value instead of resetting it on each method call
2041
+ * in case the method will be called multiple times
2042
+ */
2043
+ $db_wp_option_value['activated'] = array_merge($db_wp_option_value['activated'], $extensions_for_activation);
2044
+
2045
+ /**
2046
+ * Remove activated extensions from deactivated
2047
+ */
2048
+ $db_wp_option_value['deactivated'] = array_diff_key($db_wp_option_value['deactivated'], $db_wp_option_value['activated']);
2049
+
2050
+ update_option($db_wp_option_name, $db_wp_option_value, false);
2051
+ }
2052
+
2053
+ do_action('fw_extensions_before_activation', $extensions_for_activation);
2054
+
2055
+ if ($has_errors) {
2056
+ return $result;
2057
+ } else {
2058
+ return true;
2059
+ }
2060
+ }
2061
+
2062
+ private function collect_sub_extensions($ext_name, &$installed_extensions)
2063
+ {
2064
+ $result = array();
2065
+
2066
+ foreach ($installed_extensions[$ext_name]['children'] as $child_ext_name => $child_ext_data) {
2067
+ $result[$child_ext_name] = array();
2068
+
2069
+ $result += $this->collect_sub_extensions($child_ext_name, $installed_extensions);
2070
+ }
2071
+
2072
+ return $result;
2073
+ }
2074
+
2075
+ private function collect_required_extensions($ext_name, &$installed_extensions, &$collected)
2076
+ {
2077
+ if (!isset($installed_extensions[$ext_name])) {
2078
+ return;
2079
+ }
2080
+
2081
+ foreach (fw_akg('requirements/extensions', $installed_extensions[$ext_name]['manifest'], array()) as $req_ext_name => $req_ext_data) {
2082
+ if (isset($collected[$req_ext_name])) {
2083
+ // prevent requirements recursion
2084
+ continue;
2085
+ }
2086
+
2087
+ $collected[$req_ext_name] = array();
2088
+
2089
+ $this->collect_required_extensions($req_ext_name, $installed_extensions, $collected);
2090
+ }
2091
+ }
2092
+
2093
+ private function display_deactivate_page()
2094
+ {
2095
+ $installed_extensions = $this->get_installed_extensions();
2096
+
2097
+ $error = '';
2098
+
2099
+ do {
2100
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
2101
+ $error = __('Invalid request method.', 'fw');
2102
+ break;
2103
+ }
2104
+
2105
+ $nonce = $this->get_nonce('deactivate');
2106
+
2107
+ if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
2108
+ $error = __('Invalid nonce.', 'fw');
2109
+ break;
2110
+ }
2111
+
2112
+ if (!isset($_GET['extension'])) {
2113
+ $error = __('No extension specified.', 'fw');
2114
+ break;
2115
+ }
2116
+
2117
+ $deactivation_result = $this->deactivate_extensions(
2118
+ array_fill_keys(explode(',', $_GET['extension']), array())
2119
+ );
2120
+
2121
+ if (is_wp_error($deactivation_result)) {
2122
+ $error = $deactivation_result->get_error_message();
2123
+ } elseif (is_array($deactivation_result)) {
2124
+ $error = array();
2125
+
2126
+ foreach ($deactivation_result as $extension_name => $extension_result) {
2127
+ if (is_wp_error($extension_result)) {
2128
+ $error[] = $extension_result->get_error_message();
2129
+ }
2130
+ }
2131
+
2132
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
2133
+ }
2134
+ } while(false);
2135
+
2136
+ if ($error) {
2137
+ FW_Flash_Messages::add(
2138
+ 'fw_extensions_activate_page',
2139
+ $error,
2140
+ 'error'
2141
+ );
2142
+ }
2143
+
2144
+ $this->js_redirect();
2145
+ }
2146
+
2147
+ /**
2148
+ * Remove extensions from active extensions list in database
2149
+ * After refresh they will be inactive
2150
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
2151
+ * @param bool $cancel_on_error
2152
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
2153
+ * true: return first WP_Error or true on success
2154
+ * @return WP_Error|bool|array
2155
+ * true: when all extensions succeeded
2156
+ * array: when some/all failed
2157
+ */
2158
+ public function deactivate_extensions(array $extensions, $cancel_on_error = false)
2159
+ {
2160
+ if (!$this->can_activate()) {
2161
+ return new WP_Error(
2162
+ 'access_denied',
2163
+ __('You have no permissions to deactivate extensions', 'fw')
2164
+ );
2165
+ }
2166
+
2167
+ if (empty($extensions)) {
2168
+ return new WP_Error(
2169
+ 'no_extensions',
2170
+ __('No extensions provided', 'fw')
2171
+ );
2172
+ }
2173
+
2174
+ $available_extensions = $this->get_available_extensions();
2175
+ $installed_extensions = $this->get_installed_extensions();
2176
+
2177
+ $result = $extensions_for_deactivation = array();
2178
+ $has_errors = false;
2179
+
2180
+ foreach ($extensions as $extension_name => $not_used_var) {
2181
+
2182
+ if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
2183
+ deactivate_plugins( plugin_basename( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) );
2184
+ continue;
2185
+ }
2186
+
2187
+
2188
+ if (!isset($installed_extensions[$extension_name])) {
2189
+ // anyway remove from the active list
2190
+ $extensions_for_deactivation[$extension_name] = array();
2191
+
2192
+ $result[$extension_name] = new WP_Error(
2193
+ 'extension_not_installed',
2194
+ sprintf(__( 'Extension "%s" does not exist.' , 'fw' ), $this->get_extension_title($extension_name))
2195
+ );
2196
+ $has_errors = true;
2197
+
2198
+ if ($cancel_on_error) {
2199
+ break;
2200
+ } else {
2201
+ continue;
2202
+ }
2203
+ }
2204
+
2205
+ $current_deactivating_extensions = array(
2206
+ $extension_name => array()
2207
+ );
2208
+
2209
+ // add sub-extensions for deactivation
2210
+ foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2211
+ $current_deactivating_extensions[ $sub_extension_name ] = array();
2212
+ }
2213
+
2214
+ // add extensions that requires deactivated extensions
2215
+ $this->collect_extensions_that_requires($current_deactivating_extensions, $current_deactivating_extensions);
2216
+
2217
+ $extensions_for_deactivation = array_merge(
2218
+ $extensions_for_deactivation,
2219
+ $current_deactivating_extensions
2220
+ );
2221
+
2222
+ unset($current_deactivating_extensions);
2223
+
2224
+ $result[$extension_name] = true;
2225
+ }
2226
+
2227
+ if (
2228
+ $cancel_on_error
2229
+ &&
2230
+ $has_errors
2231
+ ) {
2232
+ if (
2233
+ ($last_result = end($result))
2234
+ &&
2235
+ is_wp_error($last_result)
2236
+ ) {
2237
+ return $last_result;
2238
+ } else {
2239
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
2240
+ return new WP_Error(
2241
+ 'deactivation_failed',
2242
+ _n('Cannot deactivate extension', 'Cannot activate extensions', count($extensions), 'fw')
2243
+ );
2244
+ }
2245
+ }
2246
+
2247
+ // add not used extensions for deactivation
2248
+ $extensions_for_deactivation = array_merge($extensions_for_deactivation,
2249
+ array_fill_keys(
2250
+ array_keys(
2251
+ array_diff_key(
2252
+ $installed_extensions,
2253
+ $this->get_used_extensions($extensions_for_deactivation, array_keys(fw()->extensions->get_all()))
2254
+ )
2255
+ ),
2256
+ array()
2257
+ )
2258
+ );
2259
+
2260
+ update_option(
2261
+ fw()->extensions->_get_active_extensions_db_option_name(),
2262
+ array_diff_key(
2263
+ fw()->extensions->_get_db_active_extensions(),
2264
+ $extensions_for_deactivation
2265
+ )
2266
+ );
2267
+
2268
+ // remove already inactive extensions
2269
+ foreach ($extensions_for_deactivation as $extension_name => $not_used_var) {
2270
+ if (!fw_ext($extension_name)) {
2271
+ unset($extensions_for_deactivation[$extension_name]);
2272
+ }
2273
+ }
2274
+
2275
+ /**
2276
+ * Prepare db wp option used to fire the 'fw_extensions_after_deactivation' action on next refresh
2277
+ */
2278
+ {
2279
+ $db_wp_option_name = 'fw_extensions_activation';
2280
+ $db_wp_option_value = get_option($db_wp_option_name, array(
2281
+ 'activated' => array(),
2282
+ 'deactivated' => array(),
2283
+ ));
2284
+
2285
+ /**
2286
+ * Keep adding to the existing value instead of resetting it on each method call
2287
+ * in case the method will be called multiple times
2288
+ */
2289
+ $db_wp_option_value['deactivated'] = array_merge($db_wp_option_value['deactivated'], $extensions_for_deactivation);
2290
+
2291
+ /**
2292
+ * Remove deactivated extensions from activated
2293
+ */
2294
+ $db_wp_option_value['activated'] = array_diff_key($db_wp_option_value['activated'], $db_wp_option_value['deactivated']);
2295
+
2296
+ update_option($db_wp_option_name, $db_wp_option_value, false);
2297
+ }
2298
+
2299
+ do_action('fw_extensions_before_deactivation', $extensions_for_deactivation);
2300
+
2301
+ if ($has_errors) {
2302
+ return $result;
2303
+ } else {
2304
+ return true;
2305
+ }
2306
+ }
2307
+
2308
+ /**
2309
+ * @param array $data
2310
+ * @return array
2311
+ * @internal
2312
+ */
2313
+ public function _extension_settings_form_render($data)
2314
+ {
2315
+ /**
2316
+ * @var FW_Extension $extension
2317
+ */
2318
+ $extension = $data['data']['extension'];
2319
+
2320
+ do_action('fw_extension_settings_form_render:'. $extension->get_name());
2321
+
2322
+ echo fw_html_tag('input', array(
2323
+ 'type' => 'hidden',
2324
+ 'name' => 'fw_extension_name',
2325
+ 'value' => $extension->get_name(),
2326
+ ), true);
2327
+
2328
+ echo fw()->backend->render_options(
2329
+ $extension->get_settings_options(),
2330
+ fw_get_db_ext_settings_option($extension->get_name())
2331
+ );
2332
+
2333
+ $data['submit']['html'] = '';
2334
+
2335
+ echo '<p>';
2336
+ echo fw_html_tag('input', array(
2337
+ 'type' => 'submit',
2338
+ 'class' => 'button-primary',
2339
+ 'value' => __('Save', 'fw'),
2340
+ ));
2341
+ echo '&nbsp;&nbsp;&nbsp;&nbsp;';
2342
+ echo fw_html_tag('a', array(
2343
+ 'href' => $this->get_link(),
2344
+ ), __('Cancel', 'fw'));
2345
+ echo '</p>';
2346
+
2347
+ return $data;
2348
+ }
2349
+
2350
+ /**
2351
+ * @param array $errors
2352
+ * @return array
2353
+ * @internal
2354
+ */
2355
+ public function _extension_settings_form_validate($errors)
2356
+ {
2357
+ do {
2358
+ if (!current_user_can($this->can_activate())) {
2359
+ $errors[] = __('You are not allowed to save extensions settings.', 'fw');
2360
+ break;
2361
+ }
2362
+
2363
+ $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2364
+
2365
+ if (!$extension) {
2366
+ $errors[] = __('Invalid extension.', 'fw');
2367
+ break;
2368
+ }
2369
+
2370
+ if (!$extension->get_settings_options()) {
2371
+ $errors[] = __('Extension does not have settings options.', 'fw');
2372
+ break;
2373
+ }
2374
+ } while(false);
2375
+
2376
+ return $errors;
2377
+ }
2378
+
2379
+ /**
2380
+ * @param array $data
2381
+ * @return array
2382
+ * @internal
2383
+ */
2384
+ public function _extension_settings_form_save($data)
2385
+ {
2386
+ $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2387
+
2388
+ $options_before_save = (array)fw_get_db_ext_settings_option($extension->get_name());
2389
+
2390
+ fw_set_db_ext_settings_option(
2391
+ $extension->get_name(),
2392
+ null,
2393
+ array_merge(
2394
+ $options_before_save,
2395
+ fw_get_options_values_from_input(
2396
+ $extension->get_settings_options()
2397
+ )
2398
+ )
2399
+ );
2400
+
2401
+ FW_Flash_Messages::add(
2402
+ 'fw_extension_settings_saved',
2403
+ __('Extensions settings successfully saved.', 'fw'),
2404
+ 'success'
2405
+ );
2406
+
2407
+ $data['redirect'] = fw_current_url();
2408
+
2409
+ do_action('fw_extension_settings_form_saved:'. $extension->get_name(), $options_before_save);
2410
+
2411
+ return $data;
2412
+ }
2413
+
2414
+ /**
2415
+ * Download an extension
2416
+ *
2417
+ * global $wp_filesystem; must be initialized
2418
+ *
2419
+ * @param string $extension_name
2420
+ * @param array $data Extension data from the "available extensions" array
2421
+ * @return string|WP_Error WP Filesystem path to the downloaded directory
2422
+ */
2423
+ private function download( $extension_name, $data ) {
2424
+ global $wp_filesystem;
2425
+ $wp_error_id = 'fw_extension_download';
2426
+
2427
+ if ( empty( $data['download'] ) ) {
2428
+ return new WP_Error(
2429
+ $wp_error_id,
2430
+ sprintf( __( 'Extension "%s" has no download sources.', 'fw' ), $this->get_extension_title( $extension_name ) )
2431
+ );
2432
+ }
2433
+
2434
+ $opts = array_merge( array(
2435
+ 'item' => $extension_name,
2436
+ 'extension_name' => $extension_name,
2437
+ 'extension_title' => $this->get_extension_title( $extension_name )
2438
+ ), $data['download']['opts'] );
2439
+
2440
+ if ( isset( $opts['plugin'] ) && is_plugin_active( $opts['plugin'] ) ) {
2441
+ return '';
2442
+ }
2443
+
2444
+ if ( ( $download_source = $this->get_download_source( $data ) ) && is_wp_error( $download_source ) ) {
2445
+ return $download_source;
2446
+ }
2447
+
2448
+ if ( isset( $opts['plugin'] ) ) {
2449
+ return $download_source->download( $opts, '' );
2450
+ }
2451
+
2452
+ // create temporary directory
2453
+ $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path( $this->get_tmp_dir() );
2454
+
2455
+ if ( $wp_filesystem->exists( $wp_fs_tmp_dir ) ) {
2456
+ // just in case it already exists, clear everything, it may contain old files
2457
+ if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
2458
+ return new WP_Error(
2459
+ $wp_error_id,
2460
+ sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
2461
+ );
2462
+ }
2463
+ }
2464
+
2465
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $wp_fs_tmp_dir ) ) {
2466
+ return new WP_Error(
2467
+ $wp_error_id,
2468
+ sprintf( __( 'Cannot create temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
2469
+ );
2470
+ }
2471
+
2472
+ return $this->perform_zip_download( $download_source, $opts, $wp_fs_tmp_dir );
2473
+ }
2474
+
2475
+ private function perform_zip_download( FW_Ext_Download_Source $download_source, array $opts, $wp_fs_tmp_dir ) {
2476
+ $wp_error_id = 'fw_extension_download';
2477
+
2478
+ /** @var WP_Filesystem_Base $wp_filesystem */
2479
+ global $wp_filesystem;
2480
+
2481
+ $zip_path = $wp_fs_tmp_dir . '/temp.zip';
2482
+
2483
+ $download_result = $download_source->download( $opts, $zip_path );
2484
+
2485
+ /**
2486
+ * Pass further the error, if the service returned one.
2487
+ */
2488
+ if ( is_wp_error( $download_result ) ) {
2489
+ return $download_result;
2490
+ }
2491
+
2492
+ $extension_name = $opts['extension_name'];
2493
+
2494
+ $unzip_result = unzip_file(
2495
+ FW_WP_Filesystem::filesystem_path_to_real_path( $zip_path ),
2496
+ $wp_fs_tmp_dir
2497
+ );
2498
+
2499
+ if ( is_wp_error( $unzip_result ) ) {
2500
+ return $unzip_result;
2501
+ }
2502
+
2503
+ // remove zip file
2504
+ if ( ! $wp_filesystem->delete( $zip_path, false, 'f' ) ) {
2505
+ return new WP_Error(
2506
+ $wp_error_id,
2507
+ sprintf( __( 'Cannot remove the "%s" extension downloaded zip.', 'fw' ), $this->get_extension_title( $extension_name ) )
2508
+ );
2509
+ }
2510
+
2511
+ $unzipped_dir_files = $wp_filesystem->dirlist( $wp_fs_tmp_dir );
2512
+
2513
+ if ( ! $unzipped_dir_files ) {
2514
+ return new WP_Error(
2515
+ $wp_error_id,
2516
+ __( 'Cannot access the unzipped directory files.', 'fw' )
2517
+ );
2518
+ }
2519
+
2520
+ /**
2521
+ * get first found directory
2522
+ * (if everything worked well, there should be only one directory)
2523
+ */
2524
+ foreach ( $unzipped_dir_files as $file ) {
2525
+ if ( $file['type'] == 'd' ) {
2526
+ return $wp_fs_tmp_dir . '/' . $file['name'];
2527
+ }
2528
+ }
2529
+
2530
+ return new WP_Error(
2531
+ $wp_error_id,
2532
+ sprintf( __( 'The unzipped "%s" extension directory not found.', 'fw' ), $this->get_extension_title( $extension_name ) )
2533
+ );
2534
+ }
2535
+
2536
+ /**
2537
+ * @param $set
2538
+ *
2539
+ * @return FW_Ext_Download_Source|WP_Error
2540
+ */
2541
+ private function get_download_source( $set ) {
2542
+ require_once dirname( __FILE__ ) . '/includes/download-source/types/init.php';
2543
+
2544
+ $register = new _FW_Ext_Download_Source_Register( self::get_access_key()->get_key() );
2545
+
2546
+ /**
2547
+ * Register download sources for extensions.
2548
+ *
2549
+ * Usage:
2550
+ * $download_source = new FW_Ext_Download_Source();
2551
+ * $register->register($download_source);
2552
+ */
2553
+ do_action( 'fw_register_ext_download_sources', $register );
2554
+
2555
+ $download_source = $register->_get_type( self::get_access_key(), $set['download']['source'] );
2556
+
2557
+ if ( ! $download_source ) {
2558
+ $download_source = new WP_Error( 'invalid_dl_source', sprintf( esc_html__( 'Invalid download source: %s', 'fw' ), $set['download']['source'] ) );
2559
+ }
2560
+
2561
+ return $download_source;
2562
+ }
2563
+
2564
+ /**
2565
+ * Merge the downloaded extension directory with the existing directory
2566
+ *
2567
+ * @param string $source_wp_fs_dir Downloaded extension directory
2568
+ * @param string $destination_wp_fs_dir
2569
+ *
2570
+ * @return null|WP_Error
2571
+ */
2572
+ private function merge_extension( $source_wp_fs_dir, $destination_wp_fs_dir ) {
2573
+ /** @var WP_Filesystem_Base $wp_filesystem */
2574
+ global $wp_filesystem;
2575
+
2576
+ $wp_error_id = 'fw_extensions_merge';
2577
+
2578
+ // check source
2579
+ {
2580
+ $source_files = $wp_filesystem->dirlist( $source_wp_fs_dir );
2581
+
2582
+ if ( $source_files === false ) {
2583
+ return new WP_Error(
2584
+ $wp_error_id,
2585
+ sprintf( __( 'Cannot read directory "%s".', 'fw' ), $source_wp_fs_dir )
2586
+ );
2587
+ }
2588
+
2589
+ if ( empty( $source_files ) ) {
2590
+ return; // directory is empty, nothing to move
2591
+ }
2592
+ }
2593
+
2594
+ /**
2595
+ * Prepare destination directory
2596
+ * Remove everything except the extensions/ directory
2597
+ */
2598
+ if ( $wp_filesystem->exists( $destination_wp_fs_dir ) ) {
2599
+ $destination_files = $wp_filesystem->dirlist( $destination_wp_fs_dir );
2600
+
2601
+ if ( $destination_files === false ) {
2602
+ return new WP_Error(
2603
+ $wp_error_id,
2604
+ sprintf( __( 'Cannot read directory "%s".', 'fw' ), $destination_wp_fs_dir )
2605
+ );
2606
+ }
2607
+
2608
+ if ( ! empty( $destination_files ) ) {
2609
+ if (
2610
+ count( $source_files ) == 1
2611
+ &&
2612
+ ( $file = reset( $source_files ) )
2613
+ &&
2614
+ $file['name'] === 'extensions'
2615
+ &&
2616
+ $file['type'] === 'd'
2617
+ ) {
2618
+ /**
2619
+ * Source extension is empty
2620
+ * It happens when you merge a directory which contains child extensions
2621
+ * Do not delete current destination files, just go in the next child extensions level
2622
+ * Used by https://github.com/ThemeFuse/Unyson/issues/1874
2623
+ */
2624
+ } else {
2625
+ // the directory contains some files, delete everything
2626
+ foreach ( $destination_files as $file ) {
2627
+ if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
2628
+ // do not touch the extensions/ directory
2629
+ continue;
2630
+ }
2631
+
2632
+ if ( ! $wp_filesystem->delete(
2633
+ $destination_wp_fs_dir . '/' . $file['name'],
2634
+ true,
2635
+ $file['type']
2636
+ ) ) {
2637
+ return new WP_Error(
2638
+ $wp_error_id,
2639
+ sprintf(
2640
+ __( 'Cannot delete "%s".', 'fw' ),
2641
+ $destination_wp_fs_dir . '/' . $file['name']
2642
+ )
2643
+ );
2644
+ }
2645
+ }
2646
+ }
2647
+
2648
+ unset( $destination_files );
2649
+ }
2650
+ } else {
2651
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $destination_wp_fs_dir ) ) {
2652
+ return new WP_Error(
2653
+ $wp_error_id,
2654
+ sprintf( __( 'Cannot create the "%s" directory.', 'fw' ), $destination_wp_fs_dir )
2655
+ );
2656
+ }
2657
+ }
2658
+
2659
+ // Move files from source to destination
2660
+ {
2661
+ $has_sub_extensions = false;
2662
+
2663
+ foreach ( $source_files as $file ) {
2664
+ if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
2665
+ $has_sub_extensions = true; // do not touch the extensions/ directory
2666
+ continue;
2667
+ }
2668
+
2669
+ if ( ! $wp_filesystem->move( $source_wp_fs_dir . '/' . $file['name'], $destination_wp_fs_dir . '/' . $file['name'] ) ) {
2670
+ return new WP_Error(
2671
+ $wp_error_id,
2672
+ sprintf(
2673
+ __( 'Cannot move "%s" to "%s".', 'fw' ),
2674
+ $source_wp_fs_dir . '/' . $file['name'],
2675
+ $destination_wp_fs_dir . '/' . $file['name']
2676
+ )
2677
+ );
2678
+ }
2679
+ }
2680
+
2681
+ unset( $source_files );
2682
+ }
2683
+
2684
+ if ( ! $has_sub_extensions ) {
2685
+ return;
2686
+ }
2687
+
2688
+ $sub_extensions = $wp_filesystem->dirlist( $source_wp_fs_dir . '/extensions' );
2689
+
2690
+ if ( $sub_extensions === false ) {
2691
+ return new WP_Error(
2692
+ $wp_error_id,
2693
+ sprintf( __( 'Cannot read directory "%s".', 'fw' ), $source_wp_fs_dir . '/extensions' )
2694
+ );
2695
+ }
2696
+
2697
+ if ( empty( $sub_extensions ) ) {
2698
+ // directory is empty, nothing to remove
2699
+ return;
2700
+ }
2701
+
2702
+ foreach ( $sub_extensions as $file ) {
2703
+ if ( $file['type'] !== 'd' ) {
2704
+ // wrong, only directories must exist in the extensions/ directory
2705
+ continue;
2706
+ }
2707
+
2708
+ $merge_result = $this->merge_extension(
2709
+ $source_wp_fs_dir . '/extensions/' . $file['name'],
2710
+ $destination_wp_fs_dir . '/extensions/' . $file['name']
2711
+ );
2712
+
2713
+ if ( is_wp_error( $merge_result ) ) {
2714
+ return $merge_result;
2715
+ }
2716
+ }
2717
+ }
2718
+
2719
+ /**
2720
+ * @since 2.6.9
2721
+ */
2722
+ public function get_supported_extensions()
2723
+ {
2724
+ $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2725
+
2726
+ // Add Available Extensions registered by the theme
2727
+ foreach ($this->get_available_extensions() as $name => $extension) {
2728
+ if (isset($extension['theme']) && $extension['theme']) {
2729
+ $supported_extensions[$name] = array();
2730
+ }
2731
+ }
2732
+
2733
+ if (empty($supported_extensions)) {
2734
+ return array();
2735
+ }
2736
+
2737
+ // remove not available extensions
2738
+ $supported_extensions = array_intersect_key($supported_extensions, $this->get_available_extensions());
2739
+
2740
+ if (empty($supported_extensions)) {
2741
+ return array();
2742
+ }
2743
+
2744
+ if (empty($supported_extensions)) {
2745
+ return array();
2746
+ }
2747
+
2748
+ return $supported_extensions;
2749
+ }
2750
+
2751
+ /**
2752
+ * @since 2.6.9
2753
+ */
2754
+ public function get_supported_extensions_for_install()
2755
+ {
2756
+ // remove already installed extensions
2757
+ return array_diff_key(
2758
+ $this->get_supported_extensions(),
2759
+ $this->get_installed_extensions()
2760
+ );
2761
+ }
2762
+
2763
+ /**
2764
+ * @param $actions
2765
+ * @return array
2766
+ * @internal
2767
+ */
2768
+ public function _filter_plugin_action_list($actions)
2769
+ {
2770
+ return array_merge(
2771
+ array(
2772
+ 'fw-extensions' => fw_html_tag('a', array(
2773
+ 'href' => $this->get_link(),
2774
+ ), fw()->manifest->get_name()),
2775
+ ),
2776
+ $actions
2777
+ );
2778
+ }
2779
+
2780
+ /**
2781
+ * @return string Extensions page link
2782
+ */
2783
+ private function get_link()
2784
+ {
2785
+ static $cache_link = null;
2786
+
2787
+ if ($cache_link === null) {
2788
+ $cache_link = menu_page_url( $this->get_page_slug(), false );
2789
+
2790
+ // https://core.trac.wordpress.org/ticket/28226
2791
+ if (is_multisite() && is_network_admin()) {
2792
+ $cache_link = self_admin_url(
2793
+ // extract relative link
2794
+ preg_replace('/^'. preg_quote(admin_url(), '/') .'/', '', $cache_link)
2795
+ );
2796
+ }
2797
+ }
2798
+
2799
+ return $cache_link;
2800
+ }
2801
+
2802
+ /**
2803
+ * @param array $skip_extensions {'ext' => mixed}
2804
+ * @param array $check_for_deps ['ext', 'ext', ...] Extensions to check if has in dependencies the used extensions
2805
+ *
2806
+ * @return array
2807
+ */
2808
+ private function get_used_extensions($skip_extensions, $check_for_deps)
2809
+ {
2810
+ $used_extensions = array();
2811
+
2812
+ $installed_extensions = $this->get_installed_extensions();
2813
+
2814
+ foreach ($installed_extensions as $inst_ext_name => &$inst_ext_data) {
2815
+ if (isset($skip_extensions[ $inst_ext_name ])) {
2816
+ continue;
2817
+ }
2818
+
2819
+ if (isset($used_extensions[$inst_ext_name])) {
2820
+ // already marked as used
2821
+ continue;
2822
+ }
2823
+
2824
+ do {
2825
+ foreach ($check_for_deps as $deps_ext) {
2826
+ if (isset($skip_extensions[$deps_ext])) {
2827
+ continue;
2828
+ }
2829
+
2830
+ if (false !== fw_akg(
2831
+ 'requirements/extensions/'. $inst_ext_name,
2832
+ $installed_extensions[$deps_ext]['manifest'],
2833
+ false
2834
+ )) {
2835
+ // is required by an active extension
2836
+ break 2;
2837
+ }
2838
+ }
2839
+
2840
+ if ( true === fw_akg(
2841
+ 'standalone',
2842
+ $inst_ext_data['manifest'],
2843
+ $this->manifest_default_values['standalone']
2844
+ ) ) {
2845
+ // can exist alone
2846
+ break;
2847
+ }
2848
+
2849
+ // not used
2850
+ continue 2;
2851
+ } while(false);
2852
+
2853
+ $used_extensions[$inst_ext_name] = array();
2854
+
2855
+ // Set all sub-extensions as used
2856
+ foreach ($this->collect_sub_extensions($inst_ext_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2857
+ if (isset($skip_extensions[$sub_extension_name])) {
2858
+ continue;
2859
+ }
2860
+
2861
+ $used_extensions[ $sub_extension_name ] = array();
2862
+ }
2863
+
2864
+ // Set all parents as used
2865
+ {
2866
+ $current_parent = $inst_ext_name;
2867
+ while ($current_parent = $installed_extensions[$current_parent]['parent']) {
2868
+ $used_extensions[$current_parent] = array();
2869
+ }
2870
+ }
2871
+ }
2872
+ unset($inst_ext_data);
2873
+
2874
+ // remove all skipped extensions and sub-extension from used extensions
2875
+ foreach (array_keys($skip_extensions) as $skip_extension_name) {
2876
+ unset($used_extensions[$skip_extension_name]);
2877
+
2878
+ if (isset($installed_extensions[$skip_extension_name])) {
2879
+ foreach ($this->collect_sub_extensions($skip_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2880
+ unset($used_extensions[$sub_extension_name]);
2881
+ }
2882
+ }
2883
+ }
2884
+
2885
+ return $used_extensions;
2886
+ }
2887
+
2888
+ /**
2889
+ * @internal
2890
+ */
2891
+ public function _action_admin_footer()
2892
+ {
2893
+ $this->activate_hidden_standalone_extensions();
2894
+ }
2895
+
2896
+ public function get_extension_title($extension_name)
2897
+ {
2898
+ $installed_extensions = $this->get_installed_extensions();
2899
+
2900
+ if (isset($installed_extensions[$extension_name])) {
2901
+ return fw_akg('name', $installed_extensions[$extension_name]['manifest'], fw_id_to_title($extension_name));
2902
+ }
2903
+
2904
+ unset($installed_extensions);
2905
+
2906
+ $available_extensions = $this->get_available_extensions();
2907
+
2908
+ if (isset($available_extensions[$extension_name])) {
2909
+ return $available_extensions[$extension_name]['name'];
2910
+ }
2911
+
2912
+ return fw_id_to_title($extension_name);
2913
+ }
2914
+
2915
+ public function is_extensions_page()
2916
+ {
2917
+ $current_screen = get_current_screen();
2918
+
2919
+ if (empty($current_screen)) {
2920
+ return false;
2921
+ }
2922
+
2923
+ return (
2924
+ property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2925
+ &&
2926
+ property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2927
+ &&
2928
+ !isset($_GET['sub-page'])
2929
+ );
2930
+ }
2931
+
2932
+ public function is_extension_page()
2933
+ {
2934
+ $current_screen = get_current_screen();
2935
+
2936
+ if (empty($current_screen)) {
2937
+ return false;
2938
+ }
2939
+
2940
+ return (
2941
+ property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2942
+ &&
2943
+ property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2944
+ &&
2945
+ isset($_GET['sub-page']) && $_GET['sub-page'] === 'extension'
2946
+ );
2947
+ }
2948
+
2949
+ /**
2950
+ * @internal
2951
+ */
2952
+ public function _action_enqueue_scripts()
2953
+ {
2954
+ wp_enqueue_style(
2955
+ 'fw-extensions-menu-icon',
2956
+ $this->get_uri('/static/unyson-font-icon/style.css'),
2957
+ array(),
2958
+ fw()->manifest->get_version()
2959
+ );
2960
+
2961
+ /**
2962
+ * Enqueue only on Extensions List page
2963
+ */
2964
+ if ($this->is_extensions_page()) {
2965
+ wp_enqueue_style(
2966
+ 'fw-extensions-page',
2967
+ $this->get_uri('/static/extensions-page.css'),
2968
+ array(
2969
+ 'fw',
2970
+ 'fw-unycon', 'font-awesome', // in case some extension has font-icon thumbnail
2971
+ ),
2972
+ fw()->manifest->get_version()
2973
+ );
2974
+ wp_enqueue_script(
2975
+ 'fw-extensions-page',
2976
+ $this->get_uri('/static/extensions-page.js'),
2977
+ array('fw'),
2978
+ fw()->manifest->get_version(),
2979
+ true
2980
+ );
2981
+ wp_localize_script('fw-extensions-page', '_fw_extensions_script_data', array(
2982
+ 'link' => $this->get_link(),
2983
+ ));
2984
+
2985
+ /**
2986
+ * this is needed for fw.soleModal design
2987
+ * it is displayed when extension ajax install returns an error
2988
+ */
2989
+ wp_enqueue_media();
2990
+ }
2991
+
2992
+ if ($this->is_extension_page()) {
2993
+ wp_enqueue_style(
2994
+ 'fw-extension-page',
2995
+ $this->get_uri('/static/extension-page.css'),
2996
+ array('fw'),
2997
+ fw()->manifest->get_version()
2998
+ );
2999
+ wp_enqueue_script(
3000
+ 'fw-extension-page',
3001
+ $this->get_uri('/static/extension-page.js'),
3002
+ array('fw'),
3003
+ fw()->manifest->get_version(),
3004
+ true
3005
+ );
3006
+
3007
+ /**
3008
+ * Enqueue extension settings options static
3009
+ */
3010
+ if (
3011
+ isset($_GET['extension'])
3012
+ &&
3013
+ is_string($extension_name = $_GET['extension'])
3014
+ &&
3015
+ fw()->extensions->get($extension_name)
3016
+ &&
3017
+ ($extension_settings_options = fw()->extensions->get($extension_name)->get_settings_options())
3018
+ ) {
3019
+ fw()->backend->enqueue_options_static($extension_settings_options);
3020
+ }
3021
+ }
3022
+ }
3023
+
3024
+ private function activate_theme_extensions()
3025
+ {
3026
+ $db_active_extensions = fw()->extensions->_get_db_active_extensions();
3027
+
3028
+ foreach ($this->get_installed_extensions() as $extension_name => $extension) {
3029
+ if ($extension['is']['theme']) {
3030
+ $db_active_extensions[ $extension_name ] = array();
3031
+ }
3032
+ }
3033
+
3034
+ update_option(
3035
+ fw()->extensions->_get_active_extensions_db_option_name(),
3036
+ $db_active_extensions
3037
+ );
3038
+ }
3039
+
3040
+ /**
3041
+ * @internal
3042
+ */
3043
+ public function _action_theme_switch()
3044
+ {
3045
+ $this->activate_theme_extensions();
3046
+ $this->activate_extensions(
3047
+ array_fill_keys(
3048
+ array_keys(fw()->theme->manifest->get('supported_extensions', array())),
3049
+ array()
3050
+ )
3051
+ );
3052
+ }
3053
+
3054
+ /**
3055
+ * @param array $collected The found extensions {'extension_name' => array()}
3056
+ * @param array $extensions {'extension_name' => array()}
3057
+ * @param bool $check_all Check all extensions or only active extensions
3058
+ */
3059
+ private function collect_extensions_that_requires(&$collected, $extensions, $check_all = false)
3060
+ {
3061
+ if (empty($extensions)) {
3062
+ return;
3063
+ }
3064
+
3065
+ $found_extensions = array();
3066
+
3067
+ foreach ($this->get_installed_extensions() as $extension_name => $extension_data) {
3068
+ if (isset($collected[$extension_name])) {
3069
+ continue;
3070
+ }
3071
+
3072
+ if (!$check_all) {
3073
+ if (!fw_ext($extension_name)) {
3074
+ continue;
3075
+ }
3076
+ }
3077
+
3078
+ if (
3079
+ array_intersect_key(
3080
+ $extensions,
3081
+ fw_akg(
3082
+ 'requirements/extensions',
3083
+ $extension_data['manifest'],
3084
+ array()
3085
+ )
3086
+ )
3087
+ ) {
3088
+ $found_extensions[$extension_name] = $collected[$extension_name] = array();
3089
+ }
3090
+ }
3091
+
3092
+ $this->collect_extensions_that_requires($collected, $found_extensions, $check_all);
3093
+ }
3094
+
3095
+ /**
3096
+ * Get extension settings page link
3097
+ * @param string $extension_name
3098
+ * @return string
3099
+ */
3100
+ public function get_extension_link($extension_name)
3101
+ {
3102
+ return $this->get_link() .'&sub-page=extension&extension='. $extension_name;
3103
+ }
3104
+
3105
+ /**
3106
+ * @param string $extension_name
3107
+ * @return array|WP_Error Extensions to merge with db active extensions list
3108
+ */
3109
+ private function get_extensions_for_activation($extension_name)
3110
+ {
3111
+ $installed_extensions = $this->get_installed_extensions();
3112
+
3113
+ $wp_error_id = 'fw_ext_activation';
3114
+
3115
+ if (!isset($installed_extensions[$extension_name])) {
3116
+ return new WP_Error($wp_error_id,
3117
+ sprintf(
3118
+ __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3119
+ $this->get_extension_title($extension_name),
3120
+ fw_html_tag('a', array(
3121
+ 'href' => $this->get_link() .'&sub-page=install&extension='. $extension_name
3122
+ ), __('Install', 'fw'))
3123
+ )
3124
+ );
3125
+ }
3126
+
3127
+ {
3128
+ $extension_parents = array($extension_name);
3129
+
3130
+ $current_parent = $extension_name;
3131
+ while ($current_parent = $installed_extensions[$current_parent]['parent']) {
3132
+ $extension_parents[] = $current_parent;
3133
+ }
3134
+
3135
+ $extension_parents = array_reverse($extension_parents);
3136
+ }
3137
+
3138
+ $extensions = array();
3139
+
3140
+ foreach ($extension_parents as $parent_extension_name) {
3141
+ $extensions[ $parent_extension_name ] = array();
3142
+ }
3143
+
3144
+ // search sub-extensions
3145
+ foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3146
+ $extensions[ $sub_extension_name ] = array();
3147
+ }
3148
+
3149
+ // search required extensions
3150
+ {
3151
+ $pending_required_search = $extensions;
3152
+
3153
+ while ($pending_required_search) {
3154
+ foreach (array_keys($pending_required_search) as $pend_req_extension_name) {
3155
+ unset($pending_required_search[$pend_req_extension_name]);
3156
+
3157
+ unset($required_extensions); // reset reference
3158
+ $required_extensions = array();
3159
+ $this->collect_required_extensions($pend_req_extension_name, $installed_extensions, $required_extensions);
3160
+
3161
+ foreach ($required_extensions as $required_extension_name => $required_extension_data) {
3162
+ if (!isset($installed_extensions[$required_extension_name])) {
3163
+ return new WP_Error($wp_error_id,
3164
+ sprintf(
3165
+ __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3166
+ $this->get_extension_title($required_extension_name),
3167
+ fw_html_tag('a', array(
3168
+ 'href' => $this->get_link() .'&sub-page=install&extension='. $required_extension_name
3169
+ ), __('Install', 'fw'))
3170
+ )
3171
+ );
3172
+ }
3173
+
3174
+ $extensions[$required_extension_name] = array();
3175
+
3176
+ // search sub-extensions
3177
+ foreach ($this->collect_sub_extensions($required_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3178
+ if (isset($extensions[$sub_extension_name])) {
3179
+ continue;
3180
+ }
3181
+
3182
+ $extensions[$sub_extension_name] = array();
3183
+
3184
+ $pending_required_search[$sub_extension_name] = array();
3185
+ }
3186
+ }
3187
+ }
3188
+ }
3189
+ }
3190
+
3191
+ return $extensions;
3192
+ }
3193
+
3194
+ /**
3195
+ * @internal
3196
+ */
3197
+ public function _action_admin_notices() {
3198
+ $should_notify = apply_filters(
3199
+ 'fw_notify_about_missing_extensions',
3200
+ true
3201
+ );
3202
+
3203
+ /**
3204
+ * In v2.4.12 was done a terrible mistake https://github.com/ThemeFuse/Unyson-Extensions-Approval/issues/160
3205
+ * Show a warning with link to install theme supported extensions
3206
+ */
3207
+ if (
3208
+ $should_notify
3209
+ &&
3210
+ !isset($_GET['supported']) // already on 'Install Supported Extensions' page
3211
+ &&
3212
+ $this->can_install()
3213
+ &&
3214
+ (($installed_extensions = $this->get_installed_extensions()) || true)
3215
+ &&
3216
+ !isset($installed_extensions['page-builder'])
3217
+ &&
3218
+ $this->get_supported_extensions_for_install()
3219
+ ) {
3220
+ echo '<div class="error"> <p>'
3221
+ , fw_html_tag('a', array('href' => $this->get_link() .'&sub-page=install&supported'),
3222
+ __('Install theme compatible extensions', 'fw'))
3223
+ , '</p></div>';
3224
+ }
3225
+ }
3226
+
3227
+ /**
3228
+ * Copy Theme Available Extensions to a tmp directory
3229
+ * Used before theme update
3230
+ * @since 2.6.0
3231
+ * @return null|WP_Error
3232
+ */
3233
+ public function theme_available_extensions_copy() {
3234
+ /** @var WP_Filesystem_Base $wp_filesystem */
3235
+ global $wp_filesystem;
3236
+
3237
+ if (!FW_WP_Filesystem::is_ready()) {
3238
+ return new WP_Error(
3239
+ 'fs_not_initialized',
3240
+ __('WP Filesystem is not initialized', 'fw')
3241
+ );
3242
+ }
3243
+
3244
+ // Prepare temporary directory
3245
+ {
3246
+ $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3247
+ $this->get_tmp_dir('/theme-ext')
3248
+ );
3249
+
3250
+ if (
3251
+ $wp_filesystem->exists( $wpfs_tmp_dir )
3252
+ &&
3253
+ ! $wp_filesystem->rmdir( $wpfs_tmp_dir, true )
3254
+ ) {
3255
+ return new WP_Error(
3256
+ 'tmp_dir_rm_fail',
3257
+ sprintf(__('Temporary directory cannot be removed: %s', 'fw'), $wpfs_tmp_dir)
3258
+ );
3259
+ }
3260
+
3261
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $wpfs_tmp_dir ) ) {
3262
+ return new WP_Error(
3263
+ 'tmp_dir_rm_fail',
3264
+ sprintf(__('Temporary directory cannot be created: %s', 'fw'), $wpfs_tmp_dir)
3265
+ );
3266
+ }
3267
+ }
3268
+
3269
+ $available_extensions = $this->get_available_extensions();
3270
+ $installed_extensions = $this->get_installed_extensions(true);
3271
+ $base_dir = fw_get_template_customizations_directory('/extensions');
3272
+
3273
+ foreach ($installed_extensions as $name => $ext) {
3274
+ if ( ! (
3275
+ isset($available_extensions[$name])
3276
+ &&
3277
+ isset($available_extensions[$name]['theme'])
3278
+ &&
3279
+ $available_extensions[$name]['theme']
3280
+ ) ) {
3281
+ continue;
3282
+ }
3283
+
3284
+ if ( ($rel_path = preg_replace('/^'. preg_quote($base_dir, '/') .'/', '', $ext['path'])) === $base_dir ) {
3285
+ return new WP_Error(
3286
+ 'rel_path_failed',
3287
+ sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3288
+ );
3289
+ }
3290
+
3291
+ if ( ($wpfs_path = FW_WP_Filesystem::real_path_to_filesystem_path($ext['path'])) === false) {
3292
+ return new WP_Error(
3293
+ 'real_to_wpfs_filed',
3294
+ sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3295
+ );
3296
+ }
3297
+
3298
+ $wpfs_dest_dir = $wpfs_tmp_dir . $rel_path;
3299
+
3300
+ if ( ! FW_WP_Filesystem::mkdir_recursive($wpfs_dest_dir) ) {
3301
+ return new WP_Error(
3302
+ 'dest_dir_mk_fail',
3303
+ sprintf(__('Failed to create directory %s', 'fw'), $wpfs_dest_dir)
3304
+ );
3305
+ }
3306
+
3307
+ if ( is_wp_error( $copy_result = copy_dir($wpfs_path, $wpfs_dest_dir) ) ) {
3308
+ /** @var WP_Error $copy_result */
3309
+ return new WP_Error(
3310
+ 'ext_copy_failed',
3311
+ sprintf( __('Failed to copy extension to %s', 'fw'), $wpfs_dest_dir )
3312
+ );
3313
+ }
3314
+ }
3315
+ }
3316
+
3317
+ /**
3318
+ * Copy Theme Available Extensions from tmp directory to theme
3319
+ * Used after theme update
3320
+ * @since 2.6.0
3321
+ * @return null|WP_Error
3322
+ */
3323
+ public function theme_available_extensions_restore() {
3324
+ /** @var WP_Filesystem_Base $wp_filesystem */
3325
+ global $wp_filesystem;
3326
+
3327
+ if (!FW_WP_Filesystem::is_ready()) {
3328
+ return new WP_Error(
3329
+ 'fs_not_initialized',
3330
+ __('WP Filesystem is not initialized', 'fw')
3331
+ );
3332
+ }
3333
+
3334
+ if ( ! $wp_filesystem->exists(
3335
+ $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3336
+ $this->get_tmp_dir('/theme-ext')
3337
+ )
3338
+ ) ) {
3339
+ return new WP_Error(
3340
+ 'no_tmp_dir',
3341
+ sprintf(__('Temporary directory does not exist: %s', 'fw'), $wpfs_tmp_dir)
3342
+ );
3343
+ }
3344
+
3345
+ /**
3346
+ * Fixes the case when the theme path before update was
3347
+ * wp-content/themes/theme-name/theme-name-parent
3348
+ * but after update it became
3349
+ * wp-content/themes/theme-name-parent
3350
+ *
3351
+ * and at this point get_template_directory() returns old theme directory
3352
+ * so fw_get_template_customizations_directory() also returns old path
3353
+ */
3354
+ $theme_dir = wp_get_theme()->get_theme_root() .'/'. wp_get_theme()->get_template();
3355
+
3356
+ if ( ! ($wpfs_base_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3357
+ $base_dir = $theme_dir . fw_get_framework_customizations_dir_rel_path('/extensions')
3358
+ ) ) ) {
3359
+ return new WP_Error(
3360
+ 'base_dir_to_wpfs_fail',
3361
+ sprintf( __('Cannot obtain WP Filesystem dir for %s', 'fw'), $base_dir )
3362
+ );
3363
+ }
3364
+
3365
+ if ( ! ( $dirlist = $wp_filesystem->dirlist($wpfs_tmp_dir) ) ) {
3366
+ return;
3367
+ }
3368
+
3369
+ foreach ( $dirlist as $filename => $fileinfo ) {
3370
+ if ( 'd' !== $fileinfo['type'] ) {
3371
+ continue;
3372
+ }
3373
+
3374
+ if ( is_wp_error($merge_result = $this->merge_extension(
3375
+ $wpfs_tmp_dir .'/'. $filename,
3376
+ $wpfs_base_dir .'/'. $filename
3377
+ )) ) {
3378
+ return $merge_result;
3379
+ }
3380
+ }
3381
+
3382
+ $wp_filesystem->rmdir( $wpfs_tmp_dir, true );
3383
+ }
3384
+
3385
+ /**
3386
+ * Copy Theme Available Extensions to tmp dir
3387
+ * @param bool|WP_Error $result
3388
+ * @param array $data
3389
+ *
3390
+ * @return bool|WP_Error
3391
+ */
3392
+ public function _filter_theme_available_extensions_copy($result, $data) {
3393
+ if (
3394
+ !is_wp_error($result)
3395
+ &&
3396
+ is_array($data)
3397
+ &&
3398
+ isset($data['theme'])
3399
+ &&
3400
+ $data['theme'] === wp_get_theme()->get_template()
3401
+ ) {
3402
+ if ( is_wp_error( $copy_result = fw()->extensions->manager->theme_available_extensions_copy() ) ) {
3403
+ return $copy_result;
3404
+ }
3405
+ }
3406
+
3407
+ return $result;
3408
+ }
3409
+
3410
+ /**
3411
+ * Restore Theme Available Extensions from tmp dir
3412
+ * @param Theme_Upgrader $instance
3413
+ * @param array $data
3414
+ *
3415
+ * @return bool|WP_Error
3416
+ */
3417
+ public function _action_theme_available_extensions_restore($instance, $data) {
3418
+ if (
3419
+ !is_wp_error($instance->skin->result)
3420
+ &&
3421
+ is_array($data)
3422
+ &&
3423
+ isset($data['action']) && $data['action'] === 'update'
3424
+ &&
3425
+ isset($data['type']) && $data['type'] === 'theme'
3426
+ &&
3427
+ isset($data['themes'])
3428
+ &&
3429
+ ($template = wp_get_theme()->get_template())
3430
+ &&
3431
+ (
3432
+ in_array($template, $data['themes'])
3433
+ ||
3434
+ /**
3435
+ * Fixes the case when the theme path before update was
3436
+ * wp-content/themes/theme-name/theme-name-parent
3437
+ * but after update it became
3438
+ * wp-content/themes/theme-name-parent
3439
+ */
3440
+ ( preg_match($regex = '/\-parent$/', $template)
3441
+ ? in_array( preg_replace($regex, '', $template) .'/'. $template, $data['themes'] )
3442
+ : false )
3443
+ )
3444
+ ) {
3445
+ fw()->extensions->manager->theme_available_extensions_restore();
3446
+ }
3447
+ }
3448
+
3449
+ /**
3450
+ * Install compatible extensions on plugin install -> activate
3451
+ *
3452
+ * In order for this to work, int TGM config must be set: 'is_automatic' => true
3453
+ * http://tgmpluginactivation.com/configuration/
3454
+ *
3455
+ * @internal
3456
+ */
3457
+ public function _action_plugin_activate_install_compatible_extensions() {
3458
+ if (!FW_WP_Filesystem::is_ready()) {
3459
+ return;
3460
+ }
3461
+
3462
+ if ($compatible_extensions = $this->get_supported_extensions_for_install()) {
3463
+ $this->install_extensions($compatible_extensions);
3464
+ // the result is not used because we don't know here if we can print the errors or not
3465
+ }
3466
+ }
3467
+
3468
+ /**
3469
+ * @since 2.6.9
3470
+ */
3471
+ public function collect_extension_requirements($extension_name, $can_install = null) {
3472
+ $installed_extensions = $this->get_installed_extensions();
3473
+
3474
+ if (is_null($can_install)) {
3475
+ $can_install = $this->can_install();
3476
+ }
3477
+
3478
+ if (! isset($installed_extensions[$extension_name])) {
3479
+ return array();
3480
+ } else {
3481
+ $data = $installed_extensions[$extension_name];
3482
+ }
3483
+
3484
+ $result = array();
3485
+
3486
+ $manifest_requirements = fw_akg('requirements', $data['manifest'], array());
3487
+
3488
+ foreach ($manifest_requirements as $req_name => $req_data) {
3489
+ switch ($req_name) {
3490
+ case 'php':
3491
+ if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3492
+ break;
3493
+ }
3494
+
3495
+ if ( ! empty( $req_data['min_version'] ) ) {
3496
+ if (!version_compare($req_data['min_version'], phpversion(), '<=')) {
3497
+ $result[] = sprintf(
3498
+ __( 'PHP needs to be updated to %s', 'fw' ),
3499
+ $req_data['min_version']
3500
+ );
3501
+ }
3502
+ }
3503
+
3504
+ if ( ! empty( $req_data['max_version'] ) ) {
3505
+ if (!version_compare($req_data['max_version'], phpversion(), '>=')) {
3506
+ $result[] = sprintf(
3507
+ __('Maximum supported PHP version is %s', 'fw'),
3508
+ $req_data['max_version']
3509
+ );
3510
+ }
3511
+ }
3512
+
3513
+ break;
3514
+
3515
+ case 'wordpress':
3516
+ if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3517
+ break;
3518
+ }
3519
+
3520
+ global $wp_version;
3521
+
3522
+ if ( ! empty( $req_data['min_version'] ) ) {
3523
+ if (!version_compare($req_data['min_version'], $wp_version, '<=')) {
3524
+ if ($can_install) {
3525
+ $result[] = sprintf(
3526
+ __( 'You need to update WordPress to %s: %s', 'fw' ),
3527
+ $req_data['min_version'],
3528
+ fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ), __( 'Update WordPress', 'fw' ) )
3529
+ );
3530
+ } else {
3531
+ $result[] = sprintf(
3532
+ __( 'WordPress needs to be updated to %s', 'fw' ),
3533
+ $req_data['min_version']
3534
+ );
3535
+ }
3536
+ }
3537
+ }
3538
+
3539
+ if ( ! empty( $req_data['max_version'] ) ) {
3540
+ if (!version_compare($req_data['max_version'], $wp_version, '>=')) {
3541
+ $result[] = sprintf(
3542
+ __('Maximum supported WordPress version is %s', 'fw'),
3543
+ $req_data['max_version']
3544
+ );
3545
+ }
3546
+ }
3547
+
3548
+ break;
3549
+
3550
+ case 'framework':
3551
+ if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3552
+ break;
3553
+ }
3554
+
3555
+ if ( ! empty( $req_data['min_version'] ) ) {
3556
+ if (!version_compare($req_data['min_version'], fw()->manifest->get_version(), '<=')) {
3557
+ if ($can_install) {
3558
+ $result[] = sprintf(
3559
+ __( 'You need to update %s to %s: %s', 'fw' ),
3560
+ fw()->manifest->get_name(),
3561
+ $req_data['min_version'],
3562
+ fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ),
3563
+ sprintf( __( 'Update %s', 'fw' ), fw()->manifest->get_name() )
3564
+ )
3565
+ );
3566
+ } else {
3567
+ $result[] = sprintf(
3568
+ __( '%s needs to be updated to %s', 'fw' ),
3569
+ fw()->manifest->get_name(),
3570
+ $req_data['min_version']
3571
+ );
3572
+ }
3573
+ }
3574
+ }
3575
+
3576
+ if ( ! empty( $req_data['max_version'] ) ) {
3577
+ if (!version_compare($req_data['max_version'], fw()->manifest->get_version(), '>=')) {
3578
+ $result[] = sprintf(
3579
+ __( 'Maximum supported %s version is %s', 'fw' ),
3580
+ fw()->manifest->get_name(),
3581
+ $req_data['max_version']
3582
+ );
3583
+ }
3584
+ }
3585
+
3586
+ break;
3587
+
3588
+ case 'extensions':
3589
+ foreach ($req_data as $req_ext => $req_ext_data) {
3590
+ if ($ext = fw()->extensions->get($req_ext)) {
3591
+ if (empty($req_ext_data['min_version']) && empty($req_ext_data['max_version'])) {
3592
+ continue;
3593
+ }
3594
+
3595
+ if ( ! empty( $req_ext_data['min_version'] ) ) {
3596
+ if (!version_compare($req_ext_data['min_version'], $ext->manifest->get_version(), '<=')) {
3597
+ if ($can_install) {
3598
+ $result[] = sprintf(
3599
+ __('You need to update the %s extension to %s: %s', 'fw'),
3600
+ $ext->manifest->get_name(),
3601
+ $req_ext_data['min_version'],
3602
+ fw_html_tag('a', array('href' => self_admin_url('update-core.php')),
3603
+ sprintf(__('Update %s', 'fw'), $ext->manifest->get_name())
3604
+ )
3605
+ );
3606
+ } else {
3607
+ $result[] = sprintf(
3608
+ __('The %s extension needs to be updated to %s', 'fw'),
3609
+ $ext->manifest->get_name(),
3610
+ $req_ext_data['min_version']
3611
+ );
3612
+ }
3613
+ }
3614
+ }
3615
+
3616
+ if ( ! empty( $req_ext_data['max_version'] ) ) {
3617
+ if (!version_compare($req_ext_data['max_version'], $ext->manifest->get_version(), '>=')) {
3618
+ $result[] = sprintf(
3619
+ __( 'Maximum supported %s extension version is %s', 'fw' ),
3620
+ $ext->manifest->get_name(),
3621
+ $req_ext_data['max_version']
3622
+ );
3623
+ }
3624
+ }
3625
+ } else {
3626
+ $ext_title = fw_id_to_title($req_ext);
3627
+
3628
+ if (isset($lists['installed'][$req_ext])) {
3629
+ $ext_title = fw_akg('name', $lists['installed'][$req_ext]['manifest'], $ext_title);
3630
+
3631
+ ob_start(); ?>
3632
+ <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($req_ext) ?>" method="post" style="display: inline;">
3633
+ <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
3634
+ <?php echo sprintf(__( 'The %s extension is disabled', 'fw' ), $ext_title); ?>:
3635
+ <a href="#" onclick="jQuery(this).closest('form').submit(); return false;"><?php echo sprintf(__('Activate %s', 'fw'), $ext_title); ?></a>
3636
+ </form>
3637
+ <?php
3638
+ $result[] = ob_get_clean();
3639
+ } else {
3640
+ if ($can_install && isset($lists['available'][$req_ext])) {
3641
+ $ext_title = $lists['available'][ $req_ext ]['name'];
3642
+
3643
+ $result[] = sprintf(
3644
+ __( 'The %s extension is not installed: %s', 'fw' ),
3645
+ $ext_title,
3646
+ fw_html_tag( 'a', array( 'href' => $link . '&sub-page=install&extension=' . $req_ext ),
3647
+ sprintf( __( 'Install %s', 'fw' ), $ext_title )
3648
+ )
3649
+ );
3650
+ } else {
3651
+ $result[] = sprintf(
3652
+ __( 'The %s extension is not installed', 'fw' ),
3653
+ $ext_title
3654
+ );
3655
+ }
3656
+ }
3657
+ }
3658
+ }
3659
+
3660
+ break;
3661
+
3662
+ default:
3663
+ trigger_error('Invalid requirement: '. $req_name, E_USER_WARNING);
3664
+ continue;
3665
+ }
3666
+ }
3667
+
3668
+ return $result;
3669
+ }
3670
+ }
 
framework/core/components/extensions/manager/includes/available-ext/class--fw-available-extensions-register.php CHANGED
@@ -1,7 +1,7 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- class _FW_Available_Extensions_Register extends FW_Type_Register {
4
- protected function validate_type( FW_Type $type ) {
5
- return $type instanceof FW_Available_Extension;
6
- }
7
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ class _FW_Available_Extensions_Register extends FW_Type_Register {
4
+ protected function validate_type( FW_Type $type ) {
5
+ return $type instanceof FW_Available_Extension;
6
+ }
7
+ }
framework/core/components/extensions/manager/includes/available-ext/class-fw-available-extension.php CHANGED
@@ -1,131 +1,131 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Used to define extension in framework Available Extensions list
5
- * @since 2.5.12
6
- */
7
- class FW_Available_Extension extends FW_Type {
8
- /**
9
- * Extension (directory) name
10
- */
11
- private $name;
12
-
13
- /**
14
- * @var null|string Parent extension name
15
- */
16
- private $parent = null;
17
-
18
- /**
19
- * @var bool If visible in extensions list
20
- */
21
- private $display = true;
22
-
23
- /**
24
- * @var string
25
- */
26
- private $title;
27
-
28
- /**
29
- * @var string
30
- */
31
- private $description;
32
-
33
- /**
34
- * @var string Image url
35
- */
36
- private $thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
37
-
38
- /**
39
- * @var array {source: id, opts: {...}}
40
- * @see FW_Ext_Download_Source::get_type() is id
41
- * @see FW_Ext_Download_Source
42
- */
43
- private $download_source = array();
44
-
45
- /**
46
- * @return bool
47
- * @since 2.6.0
48
- */
49
- public function is_valid() {
50
- return (
51
- !empty($this->name) && is_string($this->name)
52
- &&
53
- !empty($this->title) && is_string($this->title)
54
- &&
55
- !empty($this->description) && is_string($this->description)
56
- &&
57
- !empty($this->download_source)
58
- &&
59
- is_bool($this->display)
60
- &&
61
- (is_null($this->parent) || is_string($this->parent))
62
- );
63
- }
64
-
65
- /**
66
- * @return string
67
- * @internal
68
- */
69
- final public function get_type() {
70
- return $this->get_name();
71
- }
72
-
73
- public function get_name() {
74
- return $this->name;
75
- }
76
-
77
- public function set_name($name) {
78
- $this->name = $name;
79
- }
80
-
81
- public function get_parent() {
82
- return $this->parent;
83
- }
84
-
85
- public function set_parent($parent) {
86
- $this->parent = $parent;
87
- }
88
-
89
- public function get_display() {
90
- return $this->display;
91
- }
92
-
93
- public function set_display($display) {
94
- $this->display = $display;
95
- }
96
-
97
- public function get_title() {
98
- return $this->title;
99
- }
100
-
101
- public function set_title($title) {
102
- $this->title = $title;
103
- }
104
-
105
- public function get_description() {
106
- return $this->description;
107
- }
108
-
109
- public function set_description($description) {
110
- $this->description = $description;
111
- }
112
-
113
- public function get_thumbnail() {
114
- return $this->thumbnail;
115
- }
116
-
117
- public function set_thumbnail($thumbnail) {
118
- $this->thumbnail = $thumbnail;
119
- }
120
-
121
- public function get_download_source() {
122
- return $this->download_source;
123
- }
124
-
125
- public function set_download_source($id, $data) {
126
- $this->download_source = array(
127
- 'source' => $id,
128
- 'opts' => $data
129
- );
130
- }
131
- }
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Used to define extension in framework Available Extensions list
5
+ * @since 2.5.12
6
+ */
7
+ class FW_Available_Extension extends FW_Type {
8
+ /**
9
+ * Extension (directory) name
10
+ */
11
+ private $name;
12
+
13
+ /**
14
+ * @var null|string Parent extension name
15
+ */
16
+ private $parent = null;
17
+
18
+ /**
19
+ * @var bool If visible in extensions list
20
+ */
21
+ private $display = true;
22
+
23
+ /**
24
+ * @var string
25
+ */
26
+ private $title;
27
+
28
+ /**
29
+ * @var string
30
+ */
31
+ private $description;
32
+
33
+ /**
34
+ * @var string Image url
35
+ */
36
+ private $thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2PUsHf9DwAC8AGtfm5YCAAAAABJRU5ErkJgggAA';
37
+
38
+ /**
39
+ * @var array {source: id, opts: {...}}
40
+ * @see FW_Ext_Download_Source::get_type() is id
41
+ * @see FW_Ext_Download_Source
42
+ */
43
+ private $download_source = array();
44
+
45
+ /**
46
+ * @return bool
47
+ * @since 2.6.0
48
+ */
49
+ public function is_valid() {
50
+ return (
51
+ !empty($this->name) && is_string($this->name)
52
+ &&
53
+ !empty($this->title) && is_string($this->title)
54
+ &&
55
+ !empty($this->description) && is_string($this->description)
56
+ &&
57
+ !empty($this->download_source)
58
+ &&
59
+ is_bool($this->display)
60
+ &&
61
+ (is_null($this->parent) || is_string($this->parent))
62
+ );
63
+ }
64
+
65
+ /**
66
+ * @return string
67
+ * @internal
68
+ */
69
+ final public function get_type() {
70
+ return $this->get_name();
71
+ }
72
+
73
+ public function get_name() {
74
+ return $this->name;
75
+ }
76
+
77
+ public function set_name($name) {
78
+ $this->name = $name;
79
+ }
80
+
81
+ public function get_parent() {
82
+ return $this->parent;
83
+ }
84
+
85
+ public function set_parent($parent) {
86
+ $this->parent = $parent;
87
+ }
88
+
89
+ public function get_display() {
90
+ return $this->display;
91
+ }
92
+
93
+ public function set_display($display) {
94
+ $this->display = $display;
95
+ }
96
+
97
+ public function get_title() {
98
+ return $this->title;
99
+ }
100
+
101
+ public function set_title($title) {
102
+ $this->title = $title;
103
+ }
104
+
105
+ public function get_description() {
106
+ return $this->description;
107
+ }
108
+
109
+ public function set_description($description) {
110
+ $this->description = $description;
111
+ }
112
+
113
+ public function get_thumbnail() {
114
+ return $this->thumbnail;
115
+ }
116
+
117
+ public function set_thumbnail($thumbnail) {
118
+ $this->thumbnail = $thumbnail;
119
+ }
120
+
121
+ public function get_download_source() {
122
+ return $this->download_source;
123
+ }
124
+
125
+ public function set_download_source($id, $data) {
126
+ $this->download_source = array(
127
+ 'source' => $id,
128
+ 'opts' => $data
129
+ );
130
+ }
131
+ }
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.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.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.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.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/download-source/class--fw-ext-download-source-register.php CHANGED
@@ -1,9 +1,9 @@
1
- <?php if (! defined('FW')) { die('Forbidden'); }
2
-
3
- class _FW_Ext_Download_Source_Register extends FW_Type_Register
4
- {
5
- protected function validate_type( FW_Type $type ) {
6
- return $type instanceof FW_Ext_Download_Source;
7
- }
8
- }
9
-
1
+ <?php if (! defined('FW')) { die('Forbidden'); }
2
+
3
+ class _FW_Ext_Download_Source_Register extends FW_Type_Register
4
+ {
5
+ protected function validate_type( FW_Type $type ) {
6
+ return $type instanceof FW_Ext_Download_Source;
7
+ }
8
+ }
9
+
framework/core/components/extensions/manager/includes/download-source/class--fw-ext-download-source.php CHANGED
@@ -1,20 +1,21 @@
1
- <?php if (! defined('FW')) { die('Forbidden'); }
2
-
3
- /**
4
- * User to specify multiple download sources for an extension.
5
- * @since 2.5.12
6
- */
7
- abstract class FW_Ext_Download_Source extends FW_Type
8
- {
9
- /**
10
- * Perform the actual download.
11
- * It should download, by convention, a zip file which absolute path
12
- * is $path.
13
- *
14
- * @param array $opts {extension_name: '...', extension_title: '...', ...}
15
- * @param string $zip_path Absolute file of the future ZIP file
16
- * @return null|WP_Error
17
- */
18
- abstract public function download(array $opts, $zip_path);
19
- }
20
-
 
1
+ <?php if (! defined('FW')) { die('Forbidden'); }
2
+
3
+ /**
4
+ * User to specify multiple download sources for an extension.
5
+ * @since 2.5.12
6
+ */
7
+ abstract class FW_Ext_Download_Source extends FW_Type
8
+ {
9
+ /**
10
+ * Perform the actual download.
11
+ * It should download, by convention, a zip file which absolute path
12
+ * is $path.
13
+ *
14
+ * @param array $set {extension_name: '...', extension_title: '...', ...}
15
+ * @param string $zip_path Absolute file of the future ZIP file
16
+ *
17
+ * @return null|WP_Error
18
+ */
19
+ abstract public function download(array $set, $zip_path);
20
+ }
21
+
framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-custom.php ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'FW' ) or die();
2
+
3
+ class FW_Ext_Download_Source_Custom extends FW_Ext_Download_Source {
4
+ private $download_timeout = 300;
5
+ // Used in filter http_request_args when extension is as plugin we use api worpdress Plugin_Upgrader.
6
+ private $set = array();
7
+
8
+ public function get_type() {
9
+ return 'custom';
10
+ }
11
+
12
+ /**
13
+ * @param array $set
14
+ * @param string $zip_path
15
+ *
16
+ * @return WP_Error|bool
17
+ */
18
+ public function download( array $set, $zip_path ) {
19
+ /** @var WP_Filesystem_Base $wp_filesystem */
20
+ global $wp_filesystem;
21
+
22
+ $wp_error_id = 'fw_ext_custom_download_source';
23
+ $transient_name = 'fw_ext_mngr_gh_dl';
24
+ $requirements = fw()->theme->manifest->get( 'requirements/extensions' );
25
+ $set['type'] = 'extension';
26
+
27
+ if ( isset( $requirements[ $set['extension_name'] ] ) && isset( $requirements[ $set['extension_name'] ]['max_version'] ) ) {
28
+ $set['tag'] = $requirements[ $set['extension_name'] ]['max_version'];
29
+ } else {
30
+ $set['tag'] = $this->get_version( $set );
31
+
32
+ if ( is_wp_error( $set['tag'] ) ) {
33
+ return $set['tag'];
34
+ }
35
+ }
36
+
37
+ $cache = ( $c = get_site_transient( $transient_name ) ) && $c !== false ? $c : array();
38
+ $cache[ $set['item'] ] = array( 'tag_name' => $set['tag'] );
39
+ set_site_transient( $transient_name, $cache, HOUR_IN_SECONDS );
40
+
41
+ if ( $set['plugin'] ) {
42
+ return $this->install_plugin( $set, $set['remote'] );
43
+ }
44
+
45
+ $request = wp_remote_post(
46
+ $set['remote'],
47
+ array(
48
+ 'timeout' => $this->download_timeout,
49
+ 'body' => json_encode( array_merge( $set, array( 'pull' => 'zip' ) ) )
50
+ )
51
+ );
52
+
53
+ if ( is_wp_error( $request ) ) {
54
+ return $request;
55
+ }
56
+
57
+ if ( ! ( $body = wp_remote_retrieve_body( $request ) ) || is_wp_error( $body ) ) {
58
+ return ! $body ? new WP_Error( $wp_error_id, sprintf( esc_html__( 'Empty zip body for extension: %s', 'fw' ), $set['extension_title'] ) ) : $body;
59
+ }
60
+
61
+ // Try to extract error if server returned json with key error, if not then is an archive zip.
62
+ if ( ( $error = json_decode( $body, true ) ) && isset( $error['error'] ) ) {
63
+ return new WP_Error( $wp_error_id, $error['error'] );
64
+ }
65
+
66
+ // save zip to file
67
+ if ( ! $wp_filesystem->put_contents( $zip_path, $body ) ) {
68
+ return new WP_Error( $wp_error_id, sprintf( __( 'Cannot save the "%s" extension zip.', 'fw' ), $set['name'] ) );
69
+ }
70
+
71
+ return '';
72
+ }
73
+
74
+ public function get_version( $set ) {
75
+
76
+ if ( $this->is_wp_org( $set['remote'] ) ) {
77
+
78
+ include ABSPATH . 'wp-admin/includes/plugin-install.php';
79
+
80
+ $wp_org = plugins_api(
81
+ 'plugin_information',
82
+ array(
83
+ 'slug' => 'translatepress-multilingual',
84
+ 'fields' => array(
85
+ 'downloaded' => false,
86
+ 'versions' => false,
87
+ 'reviews' => false,
88
+ 'banners' => false,
89
+ 'icons' => false,
90
+ 'rating' => false,
91
+ 'active_installs' => false,
92
+ 'group' => false,
93
+ 'contributors' => false,
94
+ 'description' => false,
95
+ 'short_description' => false,
96
+ 'donate_link' => false,
97
+ 'tags' => false,
98
+ 'sections' => false,
99
+ 'homepage' => false,
100
+ 'added' => false,
101
+ 'last_updated' => false,
102
+ 'compatibility' => false,
103
+ 'tested' => false,
104
+ 'requires' => false,
105
+ 'downloadlink' => true,
106
+ )
107
+ )
108
+ );
109
+
110
+ if ( is_wp_error( $wp_org ) ) {
111
+ return new WP_Error( sprintf( __( 'Cannot get latest versions for extension: %s', 'fw' ), $set['extension_title'] ) );
112
+ }
113
+
114
+ return $wp_org->version;
115
+ }
116
+
117
+ $request = wp_remote_post(
118
+ $set['remote'],
119
+ array(
120
+ 'timeout' => $this->download_timeout,
121
+ 'body' => json_encode( array_merge( $set, array( 'pull' => 'version' ) ) )
122
+ )
123
+ );
124
+
125
+ if ( is_wp_error( $request ) ) {
126
+ return $request;
127
+ }
128
+
129
+ if ( ! ( $version = wp_remote_retrieve_body( $request ) ) || is_wp_error( $version ) ) {
130
+ return ! $version ? new WP_Error( sprintf( esc_html__( 'Empty version for extension: %s', 'fw' ), $set['extension_title'] ) ) : $version;
131
+ }
132
+
133
+ return $version;
134
+ }
135
+
136
+ public function install_plugin( $set, $source ) {
137
+
138
+ if ( ! function_exists( 'is_plugin_active' ) ) {
139
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
140
+ }
141
+
142
+ if ( is_plugin_active( $set['plugin'] ) ) {
143
+ return '';
144
+ }
145
+
146
+ if ( ! ( $installed = get_plugins() ) || ! isset( $installed[ $set['plugin'] ] ) ) {
147
+ if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
148
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
149
+ }
150
+
151
+ if ( $this->is_wp_org( $set['remote'] ) ) {
152
+ $source = esc_url( "{$source}.{$set['tag']}.zip" );
153
+ }
154
+
155
+ $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() );
156
+ // To easy access download settings in function http_request_args.
157
+ $this->set = $set;
158
+ add_filter( 'http_request_args', array( $this, 'http_request_args' ) );
159
+
160
+ $install = $upgrader->install( $source );
161
+
162
+ remove_filter( 'http_request_args', array( $this, 'http_request_args' ) );
163
+
164
+ if ( ! $install || is_wp_error( $install ) ) {
165
+ return new WP_Error( sprintf( __( 'Cannot install plugin: %s', 'fw' ), $set['extension_title'] ) );
166
+ }
167
+
168
+ if ( ! ( $installed = get_plugins() ) || ! isset( $installed[ $set['plugin'] ] ) ) {
169
+ return new WP_Error( sprintf( __( 'Cannot find plugin: %s', 'fw' ), $set['extension_title'] ) );
170
+ }
171
+
172
+ $cache_plugins = ( $c = wp_cache_get( 'plugins', 'plugins' ) ) && ! empty( $c ) ? $c : array();
173
+ $cache_plugins[''][ $set['plugin'] ] = $installed[ $set['plugin'] ];
174
+ wp_cache_set( 'plugins', $cache_plugins, 'plugins' );
175
+ }
176
+
177
+ return activate_plugin( $set['plugin'] );
178
+ }
179
+
180
+ public function http_request_args( $r ) {
181
+ $r['body'] = json_encode( array_merge( $this->set, array( 'type' => 'extension' ) ) );
182
+ return $r;
183
+ }
184
+
185
+ public function is_wp_org( $url ) {
186
+ return strpos( $url, 'downloads.wordpress.org' ) !== false;
187
+ }
188
+ }
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-github.php CHANGED
@@ -1,187 +1,188 @@
1
- <?php if (! defined('FW')) { die('Forbidden'); }
2
-
3
- class FW_Ext_Download_Source_Github extends FW_Ext_Download_Source
4
- {
5
- private $download_timeout = 300;
6
-
7
- public function get_type() {
8
- return 'github';
9
- }
10
-
11
- /**
12
- * @param array $opts {user_repo: 'ThemeFuse/Unyson'}
13
- * @param string $zip_path
14
- *
15
- * @return WP_Error
16
- */
17
- public function download(array $opts, $zip_path) {
18
- $wp_error_id = 'fw_ext_github_download_source';
19
- $theme_ext_requirements = fw()->theme->manifest->get('requirements/extensions');
20
-
21
- /** @var WP_Filesystem_Base $wp_filesystem */
22
- global $wp_filesystem;
23
-
24
- $extension_name = $opts['extension_name'];
25
- $extension_title = $opts['extension_title'];
26
-
27
- if (empty($opts['user_repo'])) {
28
- return new WP_Error(
29
- $wp_error_id,
30
- sprintf(__('"%s" extension github source "user_repo" parameter is required', 'fw'), $extension_title)
31
- );
32
- }
33
-
34
- {
35
- $transient_name = 'fw_ext_mngr_gh_dl';
36
- $transient_ttl = HOUR_IN_SECONDS;
37
-
38
- $cache = get_site_transient($transient_name);
39
-
40
- if ($cache === false) {
41
- $cache = array();
42
- }
43
- }
44
-
45
- if (isset($cache[ $opts['user_repo'] ])) {
46
- $download_link = $cache[ $opts['user_repo'] ]['zipball_url'];
47
- } else {
48
- $http = new WP_Http();
49
-
50
- if (
51
- isset($theme_ext_requirements[$extension_name])
52
- &&
53
- isset($theme_ext_requirements[$extension_name]['max_version'])
54
- ) {
55
- $tag = 'tags/v'. $theme_ext_requirements[$extension_name]['max_version'];
56
- } else {
57
- $tag = 'latest';
58
- }
59
-
60
- $response = $http->get(
61
- apply_filters('fw_github_api_url', 'https://api.github.com')
62
- . '/repos/'. $opts['user_repo'] .'/releases/'. $tag
63
- );
64
-
65
- unset($http);
66
-
67
- $response_code = intval(wp_remote_retrieve_response_code($response));
68
-
69
- if ($response_code !== 200) {
70
- if ($response_code === 403) {
71
- if ($json_response = json_decode($response['body'], true)) {
72
- return new WP_Error(
73
- $wp_error_id,
74
- __('Github error:', 'fw') .' '. $json_response['message']
75
- );
76
- } else {
77
- return new WP_Error(
78
- $wp_error_id,
79
- sprintf(
80
- __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
81
- $opts['user_repo'], $response_code
82
- )
83
- );
84
- }
85
- } elseif ($response_code) {
86
- return new WP_Error(
87
- $wp_error_id,
88
- sprintf(
89
- __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
90
- $opts['user_repo'], $response_code
91
- )
92
- );
93
- } elseif (is_wp_error($response)) {
94
- return new WP_Error(
95
- $wp_error_id,
96
- sprintf(
97
- __( 'Failed to access Github repository "%s" releases. (%s)', 'fw' ),
98
- $opts['user_repo'], $response->get_error_message()
99
- )
100
- );
101
- } else {
102
- return new WP_Error(
103
- $wp_error_id,
104
- sprintf(
105
- __( 'Failed to access Github repository "%s" releases.', 'fw' ),
106
- $opts['user_repo']
107
- )
108
- );
109
- }
110
- }
111
-
112
- $release = json_decode($response['body'], true);
113
-
114
- unset($response);
115
-
116
- if (empty($release)) {
117
- return new WP_Error(
118
- $wp_error_id,
119
- sprintf(
120
- __('"%s" extension github repository "%s" has no releases.', 'fw'),
121
- $extension_title, $opts['user_repo']
122
- )
123
- );
124
- }
125
-
126
- {
127
- $cache[ $opts['user_repo'] ] = array(
128
- 'zipball_url' => 'https://github.com/'. $opts['user_repo'] .'/archive/'. $release['tag_name'] .'.zip',
129
- 'tag_name' => $release['tag_name']
130
- );
131
-
132
- set_site_transient($transient_name, $cache, $transient_ttl);
133
- }
134
-
135
- $download_link = $cache[ $opts['user_repo'] ]['zipball_url'];
136
-
137
-
138
- unset($release);
139
- }
140
-
141
- {
142
- $http = new WP_Http();
143
-
144
- $response = $http->request($download_link, array(
145
- 'timeout' => $this->download_timeout,
146
- ));
147
-
148
- unset($http);
149
-
150
- if (($response_code = intval(wp_remote_retrieve_response_code($response))) !== 200) {
151
- if ($response_code) {
152
- return new WP_Error(
153
- $wp_error_id,
154
- sprintf( __( 'Cannot download the "%s" extension zip. (Response code: %d)', 'fw' ),
155
- $extension_title, $response_code
156
- )
157
- );
158
- } elseif (is_wp_error($response)) {
159
- return new WP_Error(
160
- $wp_error_id,
161
- sprintf( __( 'Cannot download the "%s" extension zip. %s', 'fw' ),
162
- $extension_title,
163
- $response->get_error_message()
164
- )
165
- );
166
- } else {
167
- return new WP_Error(
168
- $wp_error_id,
169
- sprintf( __( 'Cannot download the "%s" extension zip.', 'fw' ),
170
- $extension_title
171
- )
172
- );
173
- }
174
- }
175
-
176
- // save zip to file
177
- if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
178
- return new WP_Error(
179
- $wp_error_id,
180
- sprintf(__('Cannot save the "%s" extension zip.', 'fw'), $extension_title)
181
- );
182
- }
183
-
184
- unset($response);
185
- }
186
- }
187
- }
 
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ class FW_Ext_Download_Source_Github extends FW_Ext_Download_Source {
6
+ private $download_timeout = 300;
7
+
8
+ public function get_type() {
9
+ return 'github';
10
+ }
11
+
12
+ /**
13
+ * @param array $set {user_repo: 'ThemeFuse/Unyson'}
14
+ * @param string $zip_path
15
+ *
16
+ * @return WP_Error
17
+ */
18
+ public function download( array $set, $zip_path ) {
19
+ $wp_error_id = 'fw_ext_github_download_source';
20
+ $theme_ext_requirements = fw()->theme->manifest->get( 'requirements/extensions' );
21
+
22
+ /** @var WP_Filesystem_Base $wp_filesystem */
23
+ global $wp_filesystem;
24
+
25
+ $extension_name = $set['extension_name'];
26
+ $extension_title = $set['extension_title'];
27
+
28
+ if ( empty( $set['user_repo'] ) ) {
29
+ return new WP_Error(
30
+ $wp_error_id,
31
+ sprintf( __( '"%s" extension github source "user_repo" parameter is required', 'fw' ), $extension_title )
32
+ );
33
+ }
34
+
35
+ {
36
+ $transient_name = 'fw_ext_mngr_gh_dl';
37
+ $transient_ttl = HOUR_IN_SECONDS;
38
+
39
+ $cache = get_site_transient( $transient_name );
40
+
41
+ if ( $cache === false ) {
42
+ $cache = array();
43
+ }
44
+ }
45
+
46
+ if ( isset( $cache[ $set['user_repo'] ] ) ) {
47
+ $download_link = $cache[ $set['user_repo'] ]['zipball_url'];
48
+ } else {
49
+ $http = new WP_Http();
50
+
51
+ if (
52
+ isset( $theme_ext_requirements[ $extension_name ] )
53
+ &&
54
+ isset( $theme_ext_requirements[ $extension_name ]['max_version'] )
55
+ ) {
56
+ $tag = 'tags/v' . $theme_ext_requirements[ $extension_name ]['max_version'];
57
+ } else {
58
+ $tag = 'latest';
59
+ }
60
+
61
+ $response = $http->get(
62
+ apply_filters( 'fw_github_api_url', 'https://api.github.com' )
63
+ . '/repos/' . $set['user_repo'] . '/releases/' . $tag
64
+ );
65
+
66
+ unset( $http );
67
+
68
+ $response_code = intval( wp_remote_retrieve_response_code( $response ) );
69
+
70
+ if ( $response_code !== 200 ) {
71
+ if ( $response_code === 403 ) {
72
+ if ( $json_response = json_decode( $response['body'], true ) ) {
73
+ return new WP_Error(
74
+ $wp_error_id,
75
+ __( 'Github error:', 'fw' ) . ' ' . $json_response['message']
76
+ );
77
+ } else {
78
+ return new WP_Error(
79
+ $wp_error_id,
80
+ sprintf(
81
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
82
+ $set['user_repo'], $response_code
83
+ )
84
+ );
85
+ }
86
+ } elseif ( $response_code ) {
87
+ return new WP_Error(
88
+ $wp_error_id,
89
+ sprintf(
90
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
91
+ $set['user_repo'], $response_code
92
+ )
93
+ );
94
+ } elseif ( is_wp_error( $response ) ) {
95
+ return new WP_Error(
96
+ $wp_error_id,
97
+ sprintf(
98
+ __( 'Failed to access Github repository "%s" releases. (%s)', 'fw' ),
99
+ $set['user_repo'], $response->get_error_message()
100
+ )
101
+ );
102
+ } else {
103
+ return new WP_Error(
104
+ $wp_error_id,
105
+ sprintf(
106
+ __( 'Failed to access Github repository "%s" releases.', 'fw' ),
107
+ $set['user_repo']
108
+ )
109
+ );
110
+ }
111
+ }
112
+
113
+ $release = json_decode( $response['body'], true );
114
+
115
+ unset( $response );
116
+
117
+ if ( empty( $release ) ) {
118
+ return new WP_Error(
119
+ $wp_error_id,
120
+ sprintf(
121
+ __( '"%s" extension github repository "%s" has no releases.', 'fw' ),
122
+ $extension_title, $set['user_repo']
123
+ )
124
+ );
125
+ }
126
+
127
+ {
128
+ $cache[ $set['user_repo'] ] = array(
129
+ 'zipball_url' => 'https://github.com/' . $set['user_repo'] . '/archive/' . $release['tag_name'] . '.zip',
130
+ 'tag_name' => $release['tag_name']
131
+ );
132
+
133
+ set_site_transient( $transient_name, $cache, $transient_ttl );
134
+ }
135
+
136
+ $download_link = $cache[ $set['user_repo'] ]['zipball_url'];
137
+
138
+
139
+ unset( $release );
140
+ }
141
+
142
+ {
143
+ $http = new WP_Http();
144
+
145
+ $response = $http->request( $download_link, array(
146
+ 'timeout' => $this->download_timeout,
147
+ ) );
148
+
149
+ unset( $http );
150
+
151
+ if ( ( $response_code = intval( wp_remote_retrieve_response_code( $response ) ) ) !== 200 ) {
152
+ if ( $response_code ) {
153
+ return new WP_Error(
154
+ $wp_error_id,
155
+ sprintf( __( 'Cannot download the "%s" extension zip. (Response code: %d)', 'fw' ),
156
+ $extension_title, $response_code
157
+ )
158
+ );
159
+ } elseif ( is_wp_error( $response ) ) {
160
+ return new WP_Error(
161
+ $wp_error_id,
162
+ sprintf( __( 'Cannot download the "%s" extension zip. %s', 'fw' ),
163
+ $extension_title,
164
+ $response->get_error_message()
165
+ )
166
+ );
167
+ } else {
168
+ return new WP_Error(
169
+ $wp_error_id,
170
+ sprintf( __( 'Cannot download the "%s" extension zip.', 'fw' ),
171
+ $extension_title
172
+ )
173
+ );
174
+ }
175
+ }
176
+
177
+ // save zip to file
178
+ if ( ! $wp_filesystem->put_contents( $zip_path, $response['body'] ) ) {
179
+ return new WP_Error(
180
+ $wp_error_id,
181
+ sprintf( __( 'Cannot save the "%s" extension zip.', 'fw' ), $extension_title )
182
+ );
183
+ }
184
+
185
+ unset( $response );
186
+ }
187
+ }
188
+ }
framework/core/components/extensions/manager/includes/download-source/types/init.php CHANGED
@@ -1,12 +1,10 @@
1
- <?php if (! defined('FW')) { die('Forbidden'); }
2
-
3
- if ( ! function_exists( '_action_fw_register_ext_download_sources' ) ) {
4
- function _action_fw_register_ext_download_sources(_FW_Ext_Download_Source_Register $download_sources) {
5
- $download_sources->register(new FW_Ext_Download_Source_Github());
6
- }
7
- }
8
-
9
- add_action(
10
- 'fw_register_ext_download_sources',
11
- '_action_fw_register_ext_download_sources'
12
- );
1
+ <?php defined( 'FW' ) or die();
2
+
3
+ if ( ! function_exists( '_action_fw_register_ext_download_sources' ) ) {
4
+ function _action_fw_register_ext_download_sources( _FW_Ext_Download_Source_Register $download_sources ) {
5
+ $download_sources->register( new FW_Ext_Download_Source_Github() );
6
+ $download_sources->register( new FW_Ext_Download_Source_Custom() );
7
+ }
8
+ }
9
+
10
+ add_action( 'fw_register_ext_download_sources', '_action_fw_register_ext_download_sources' );
 
 
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 self(); // static doesn't work in php 5.2
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 self(); // static doesn't work in php 5.2
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,261 +1,261 @@
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):not(:last-child) {
138
- padding-left: 20px;
139
- }
140
- body.rtl .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):not(:last-child) {
141
- padding-left: 0;
142
- padding-right: 20px;
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 > p:last-child {
146
- margin-bottom: 0;
147
- }
148
-
149
- .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 {
150
- display: block;
151
- position: absolute;
152
- top: 20px;
153
- right: 20px;
154
- width: auto;
155
- }
156
- body.rtl .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 {
157
- right: auto;
158
- left: 20px;
159
- }
160
-
161
- .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 {
162
- display: inline-block;
163
- }
164
-
165
- .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 {
166
- padding: 0 0 4px 15px;
167
- vertical-align: bottom;
168
- }
169
- body.rtl .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 {
170
- padding: 0 13px 4px 0;
171
- }
172
-
173
- .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 {
174
- font-size: 16px;
175
- }
176
- }
177
-
178
-
179
- /* disabled style */
180
-
181
- .fw-extensions-list .fw-extensions-list-item .fw-extension-disabled {
182
- display: none;
183
- background: rgba(255, 255, 255, 0.5) url("img/disabled-bg.png");
184
- border: 1px solid #ffffff;
185
- position: absolute;
186
- top: 0;
187
- left: 0;
188
- width: 100%;
189
- height: 100%;
190
- }
191
-
192
- .fw-extensions-list .fw-extensions-list-item.disabled .fw-extension-disabled {
193
- display: block;
194
- }
195
-
196
- .fw-extensions-list .fw-extensions-list-item .fw-extension-disabled .fw-extension-disabled-panel {
197
- position: absolute;
198
- left: 0;
199
- bottom: 0;
200
- width: 100%;
201
- background: #ffffff;
202
- padding: 20px;
203
- line-height: 28px;
204
- }
205
-
206
- @media (max-width: 782px) {
207
- .fw-extensions-list .fw-extensions-list-item.disabled > .inner {
208
- min-height: 320px;
209
- }
210
- }
211
-
212
- /* end: disabled style */
213
-
214
-
215
- /* tip content */
216
-
217
- .fw-extension-tip-content {
218
- padding: 15px;
219
- max-width: 380px;
220
- }
221
-
222
- .fw-extension-tip-content ul.fw-extension-requirements {
223
- margin: 0;
224
- }
225
-
226
- .fw-extension-tip-content ul.fw-extension-requirements li:last-child {
227
- margin-bottom: 0;
228
- }
229
-
230
- /* end: tip content */
231
-
232
-
233
- /* form ajax loading */
234
-
235
- .fw-extensions-list .fw-extensions-list-item .ajax-form-loading {
236
- display: inline-block;
237
- margin: -2px 0;
238
- padding-left: 7px;
239
- }
240
-
241
- .fw-extensions-list .fw-extensions-list-item .ajax-form-loading img {
242
- vertical-align: middle;
243
- display: inline-block;
244
- margin-top: -2px;
245
- }
246
-
247
- /* end: form ajax loading */
248
-
249
-
250
- /* anchor */
251
-
252
- .fw-extensions-list .fw-extensions-list-item a.fw-ext-anchor {
253
- position: absolute;
254
- top: -15px;
255
- }
256
-
257
- body.admin-bar .fw-extensions-list .fw-extensions-list-item a.fw-ext-anchor {
258
- top: -50px;
259
- }
260
-
261
  /* end: anchor */
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):not(:last-child) {
138
+ padding-left: 20px;
139
+ }
140
+ body.rtl .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):not(:last-child) {
141
+ padding-left: 0;
142
+ padding-right: 20px;
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 > p:last-child {
146
+ margin-bottom: 0;
147
+ }
148
+
149
+ .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 {
150
+ display: block;
151
+ position: absolute;
152
+ top: 20px;
153
+ right: 20px;
154
+ width: auto;
155
+ }
156
+ body.rtl .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 {
157
+ right: auto;
158
+ left: 20px;
159
+ }
160
+
161
+ .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 {
162
+ display: inline-block;
163
+ }
164
+
165
+ .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 {
166
+ padding: 0 0 4px 15px;
167
+ vertical-align: bottom;
168
+ }
169
+ body.rtl .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 {
170
+ padding: 0 13px 4px 0;
171
+ }
172
+
173
+ .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 {
174
+ font-size: 16px;
175
+ }
176
+ }
177
+
178
+
179
+ /* disabled style */
180
+
181
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-disabled {
182
+ display: none;
183
+ background: rgba(255, 255, 255, 0.5) url("img/disabled-bg.png");
184
+ border: 1px solid #ffffff;
185
+ position: absolute;
186
+ top: 0;
187
+ left: 0;
188
+ width: 100%;
189
+ height: 100%;
190
+ }
191
+
192
+ .fw-extensions-list .fw-extensions-list-item.disabled .fw-extension-disabled {
193
+ display: block;
194
+ }
195
+
196
+ .fw-extensions-list .fw-extensions-list-item .fw-extension-disabled .fw-extension-disabled-panel {
197
+ position: absolute;
198
+ left: 0;
199
+ bottom: 0;
200
+ width: 100%;
201
+ background: #ffffff;
202
+ padding: 20px;
203
+ line-height: 28px;
204
+ }
205
+
206
+ @media (max-width: 782px) {
207
+ .fw-extensions-list .fw-extensions-list-item.disabled > .inner {
208
+ min-height: 320px;
209
+ }
210
+ }
211
+
212
+ /* end: disabled style */
213
+
214
+
215
+ /* tip content */
216
+
217
+ .fw-extension-tip-content {
218
+ padding: 15px;
219
+ max-width: 380px;
220
+ }
221
+
222
+ .fw-extension-tip-content ul.fw-extension-requirements {
223
+ margin: 0;
224
+ }
225
+
226
+ .fw-extension-tip-content ul.fw-extension-requirements li:last-child {
227
+ margin-bottom: 0;
228
+ }
229
+
230
+ /* end: tip content */
231
+
232
+
233
+ /* form ajax loading */
234
+
235
+ .fw-extensions-list .fw-extensions-list-item .ajax-form-loading {
236
+ display: inline-block;
237
+ margin: -2px 0;
238
+ padding-left: 7px;
239
+ }
240
+
241
+ .fw-extensions-list .fw-extensions-list-item .ajax-form-loading img {
242
+ vertical-align: middle;
243
+ display: inline-block;
244
+ margin-top: -2px;
245
+ }
246
+
247
+ /* end: form ajax loading */
248
+
249
+
250
+ /* anchor */
251
+
252
+ .fw-extensions-list .fw-extensions-list-item a.fw-ext-anchor {
253
+ position: absolute;
254
+ top: -15px;
255
+ }
256
+
257
+ body.admin-bar .fw-extensions-list .fw-extensions-list-item a.fw-ext-anchor {
258
+ top: -50px;
259
+ }
260
+
261
  /* end: anchor */
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,229 +1,246 @@
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
- if (isset($lists['available'][$name])) {
30
- $thumbnail = $lists['available'][$name]['thumbnail'];
31
- } else {
32
- $thumbnail = $default_thumbnail;
33
- }
34
-
35
- if (isset($lists['installed'][$name])) {
36
- $thumbnail = fw_akg('thumbnail', $lists['installed'][$name]['manifest'], $thumbnail);
37
-
38
- // local image
39
- if (
40
- substr($thumbnail, 0, 11) !== 'data:image/'
41
- &&
42
- !filter_var($thumbnail, FILTER_VALIDATE_URL)
43
- &&
44
- file_exists($thumbnail_path = $lists['installed'][$name]['path'] .'/'. $thumbnail)
45
- ) {
46
- $thumbnail = fw_get_path_url($thumbnail_path);
47
- }
48
- }
49
- }
50
-
51
- $is_compatible =
52
- isset($lists['supported'][$name]) // is listed in the supported extensions list in theme manifest
53
- ||
54
- ($installed_data && $installed_data['is']['theme']); // is located in the theme
55
-
56
- $wrapper_class = 'fw-col-xs-12 fw-col-lg-6 fw-extensions-list-item';
57
-
58
- if ($installed_data && !$is_active) {
59
- $wrapper_class .= ' disabled';
60
- }
61
-
62
- if (!$installed_data && !$is_compatible) {
63
- $wrapper_class .= ' not-compatible';
64
- }
65
- ?>
66
- <div class="<?php echo esc_attr($wrapper_class) ?>" id="fw-ext-<?php echo esc_attr($name) ?>">
67
- <a class="fw-ext-anchor" name="ext-<?php echo esc_attr($name) ?>"></a>
68
- <div class="inner">
69
- <div class="fw-extension-list-item-table">
70
- <div class="fw-extension-list-item-table-row">
71
- <div class="fw-extension-list-item-table-cell cell-1">
72
- <div class="fw-extensions-list-item-thumbnail-wrapper">
73
- <?php echo fw_string_to_icon_html($thumbnail, array('class' => 'fw-extensions-list-item-thumbnail')); ?>
74
- </div>
75
- </div>
76
- <div class="fw-extension-list-item-table-cell cell-2">
77
- <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
78
- if ($is_active && ($extension_link = fw()->extensions->get($name)->_get_link())) {
79
- echo fw_html_tag('a', array('href' => $extension_link), $title);
80
- } else {
81
- echo $title;
82
- }
83
- ?></h3>
84
-
85
- <?php if ($description): ?>
86
- <p class="fw-extensions-list-item-desc"><?php echo esc_html($description); ?></p>
87
- <?php endif; ?>
88
-
89
- <?php
90
- if ($installed_data) {
91
- $_links = array();
92
-
93
- if ( $is_active && fw()->extensions->get( $name )->get_settings_options() ) {
94
- $_links[] = '<a href="' . esc_attr( $link ) . '&sub-page=extension&extension=' . esc_attr( $name ) . '">' . __( 'Settings', 'fw' ) . '</a>';
95
- }
96
-
97
- if ( $is_active && file_exists( $installed_data['path'] . '/readme.md.php' ) ) {
98
- if ( isset($lists['supported'][$name]) ) {
99
- // no sense to teach how to install the extension if theme is already configured and the is extension marked as compatible
100
- } else {
101
- $_links[] = '<a href="' . esc_attr( $link ) . '&sub-page=extension&extension=' . esc_attr( $name ) . '&tab=docs">' . __( 'Install Instructions', 'fw' ) . '</a>';
102
- }
103
- }
104
-
105
- if ( ! empty( $_links ) ):
106
- ?><p
107
- class="fw-extensions-list-item-links"><?php echo implode( ' <span class="fw-text-muted">|</span> ', $_links ); ?></p><?php
108
- endif;
109
-
110
- unset( $_links );
111
- }
112
- ?>
113
- <?php if ($is_compatible): ?>
114
- <p><em><strong><span class="dashicons dashicons-yes"></span> <?php _e('Compatible', 'fw') ?></strong> <?php _e('with your current theme', 'fw') ?></em></p>
115
- <?php endif; ?>
116
- </div>
117
- <div class="fw-extension-list-item-table-cell cell-3">
118
- <?php if ($is_active): ?>
119
- <form action="<?php echo esc_attr($link) ?>&sub-page=deactivate&extension=<?php echo esc_attr($name) ?>" method="post">
120
- <?php wp_nonce_field($nonces['deactivate']['action'], $nonces['deactivate']['name']); ?>
121
- <input class="button" type="submit" value="<?php esc_attr_e('Deactivate', 'fw'); ?>"/>
122
- </form>
123
- <?php elseif ($installed_data): ?>
124
- <div class="fw-text-center">
125
- <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($name) ?>"
126
- method="post"
127
- class="extension-activate-form"
128
- >
129
- <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
130
- <input class="button" type="submit" value="<?php esc_attr_e('Activate', 'fw'); ?>"/>
131
- </form>
132
- <?php
133
- /**
134
- * Do not show the "Delete extension" button if the extension is not in the available list.
135
- * If you delete such extension you will not be able to install it back.
136
- * Most often these will be extensions located in the theme.
137
- */
138
- if ($can_install && $available_data):
139
- ?>
140
- <form action="<?php echo esc_attr($link) ?>&sub-page=delete&extension=<?php echo esc_attr($name) ?>"
141
- method="post"
142
- class="fw-extension-ajax-form extension-delete-form"
143
- data-confirm-message="<?php esc_attr_e('Are you sure you want to remove this extension?', 'fw') ?>"
144
- data-extension-name="<?php echo esc_attr($name) ?>"
145
- data-extension-action="uninstall"
146
- >
147
- <?php wp_nonce_field($nonces['delete']['action'], $nonces['delete']['name']); ?>
148
- <p class="fw-visible-xs-block"></p>
149
- <a href="#"
150
- onclick="jQuery(this).closest('form').submit(); return false;"
151
- data-remove-extension="<?php echo esc_attr($name) ?>"
152
- title="<?php echo esc_attr_e('Remove', 'fw'); ?>"
153
- ><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>
154
- </form>
155
- <?php endif; ?>
156
- </div>
157
- <?php elseif ($can_install && $available_data): ?>
158
- <form action="<?php echo esc_attr($link) ?>&sub-page=install&extension=<?php echo esc_attr($name) ?>"
159
- method="post"
160
- class="fw-extension-ajax-form"
161
- data-extension-name="<?php echo esc_attr($name) ?>"
162
- data-extension-action="install"
163
- >
164
- <?php wp_nonce_field($nonces['install']['action'], $nonces['install']['name']); ?>
165
- <input type="submit" class="button" value="<?php esc_attr_e('Download', 'fw') ?>">
166
- </form>
167
- <?php endif; ?>
168
- </div>
169
- </div>
170
- </div>
171
- <?php if ($installed_data): ?>
172
- <?php if (!$is_active): ?>
173
- <?php if (!fw()->extensions->_get_db_active_extensions($name)): ?>
174
- <span><!-- Is not set as active in db --></span>
175
- <?php elseif ($installed_data['parent'] && !fw()->extensions->get($installed_data['parent'])): ?>
176
- <?php
177
- $parent_extension_name = $installed_data['parent'];
178
- $parent_extension_title = fw_id_to_title($parent_extension_name);
179
-
180
- if (isset($lists['installed'][$parent_extension_name])) {
181
- $parent_extension_title = fw_akg('name', $lists['installed'][$parent_extension_name]['manifest'], $parent_extension_title);
182
- } elseif (isset($lists['available'][$parent_extension_name])) {
183
- $parent_extension_title = $lists['available'][$parent_extension_name]['name'];
184
- }
185
- ?>
186
- <p class="fw-text-muted"><?php echo sprintf(__('Parent extension "%s" is disabled', 'fw'), $parent_extension_title); ?></p>
187
- <?php else: ?>
188
- <div class="fw-extension-disabled fw-border-box-sizing">
189
- <div class="fw-extension-disabled-panel fw-border-box-sizing">
190
- <div class="fw-row">
191
- <div class="fw-col-xs-12 fw-col-sm-3">
192
- <span class="fw-text-danger">!&nbsp;<?php _e('Disabled', 'fw'); ?></span>
193
- </div>
194
- <div class="fw-col-xs-12 fw-col-sm-9 fw-text-right">
195
- <?php
196
-
197
- $requirements = fw()
198
- ->extensions
199
- ->manager->collect_extension_requirements(
200
- $name
201
- );
202
-
203
- ?>
204
- <a onclick="return false;" href="#" class="fw-extension-tip" title="<?php
205
- echo fw_htmlspecialchars(
206
- '<div class="fw-extension-tip-content">'.
207
- '<ul class="fw-extension-requirements"><li>- '. implode('</li><li>- ', $requirements) .'</li></ul>'.
208
- '</div>'
209
- );
210
- unset($requirements);
211
- ?>"><?php _e('View Requirements', 'fw') ?></a>
212
- &nbsp; <p class="fw-visible-xs-block"></p><?php
213
- if ($can_install):
214
- ?><a href="<?php echo esc_attr($link) ?>&sub-page=delete&extension=<?php echo esc_attr($name) ?>" class="button" ><?php _e('Remove', 'fw'); ?></a><?php
215
- endif;
216
- ?>
217
- </div>
218
- </div>
219
- </div>
220
- </div>
221
- <?php endif; ?>
222
- <?php endif; ?>
223
- <?php elseif ($available_data): ?>
224
- <!-- -->
225
- <?php else: ?>
226
- <!-- -->
227
- <?php endif; ?>
228
- </div>
229
- </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
+ * @var bool $is_active
13
+ */
14
+
15
+ $ext = fw_ext( $name );
16
+ $is_active = isset( $lists['active'][ $name ] ) ? true : false;
17
+ $version = $ext ? $ext->manifest->get_version() : '';
18
+ $ext_page = $ext ? $ext->_get_link() : '';
19
+ $url_set = '';
20
+
21
+ if ( $ext && $ext->get_settings_options() ) {
22
+ $url_set = "{$link}&sub-page=extension&extension={$name}";
23
+ } else {
24
+ if ( ! empty( $lists['available'][ $name ]['download']['url_set'] ) ) {
25
+ $url_set = admin_url( $lists['available'][ $name ]['download']['url_set'] );
26
+ }
27
+ }
28
+
29
+ if (isset($lists['installed'][$name])) {
30
+ $installed_data = &$lists['installed'][$name];
31
+ } else {
32
+ $installed_data = false;
33
+ }
34
+
35
+ if (isset($lists['available'][$name])) {
36
+ $available_data = &$lists['available'][$name];
37
+ } else {
38
+ $available_data = false;
39
+ }
40
+
41
+ {
42
+ if (isset($lists['available'][$name])) {
43
+ $thumbnail = $lists['available'][$name]['thumbnail'];
44
+ } else {
45
+ $thumbnail = $default_thumbnail;
46
+ }
47
+
48
+ if ( isset( $lists['installed'][ $name ] ) ) {
49
+
50
+ $manifest = ! empty( $lists['installed'][ $name ]['thumbnail'] ) ? $lists['installed'][ $name ]['thumbnail'] : $lists['installed'][ $name ]['manifest'];
51
+ $thumbnail = fw_akg( 'thumbnail', $manifest, $thumbnail );
52
+
53
+ // local image
54
+ if (
55
+ substr( $thumbnail, 0, 11 ) !== 'data:image/'
56
+ &&
57
+ ! filter_var( $thumbnail, FILTER_VALIDATE_URL )
58
+ &&
59
+ file_exists( $thumbnail_path = $lists['installed'][ $name ]['path'] . '/' . $thumbnail )
60
+ ) {
61
+ $thumbnail = fw_get_path_url( $thumbnail_path );
62
+ }
63
+ }
64
+ }
65
+
66
+ $is_compatible =
67
+ isset($lists['supported'][$name]) // is listed in the supported extensions list in theme manifest
68
+ ||
69
+ ($installed_data && ! empty( $installed_data['is']['theme'] ) ); // is located in the theme
70
+
71
+ $wrapper_class = 'fw-col-xs-12 fw-col-lg-6 fw-extensions-list-item';
72
+
73
+ if ($installed_data && !$is_active) {
74
+ $wrapper_class .= ' disabled';
75
+ }
76
+
77
+ if (!$installed_data && !$is_compatible) {
78
+ $wrapper_class .= ' not-compatible';
79
+ }
80
+ ?>
81
+ <div class="<?php echo esc_attr($wrapper_class) ?>" id="fw-ext-<?php echo esc_attr($name) ?>">
82
+ <a class="fw-ext-anchor" name="ext-<?php echo esc_attr($name) ?>"></a>
83
+ <div class="inner">
84
+ <div class="fw-extension-list-item-table">
85
+ <div class="fw-extension-list-item-table-row">
86
+ <div class="fw-extension-list-item-table-cell cell-1">
87
+ <div class="fw-extensions-list-item-thumbnail-wrapper">
88
+ <?php echo fw_string_to_icon_html($thumbnail, array('class' => 'fw-extensions-list-item-thumbnail')); ?>
89
+ </div>
90
+ </div>
91
+ <div class="fw-extension-list-item-table-cell cell-2">
92
+
93
+ <h3 class="fw-extensions-list-item-title"<?php echo( $is_active && $version ? ' title="v' . esc_attr( $version ) . '"' : '' ); ?>>
94
+ <?php
95
+ if ( $is_active && $ext_page ) {
96
+ echo fw_html_tag( 'a', array( 'href' => $ext_page ), $title );
97
+ } else {
98
+ echo $title;
99
+ }
100
+ ?>
101
+ </h3>
102
+
103
+ <?php if ($description): ?>
104
+ <p class="fw-extensions-list-item-desc"><?php echo esc_html($description); ?></p>
105
+ <?php endif; ?>
106
+
107
+ <?php
108
+ if ( $installed_data ) {
109
+ $_links = array();
110
+
111
+ if ( $is_active && $url_set ) {
112
+ $_links[] = '<a href="' . esc_url( $url_set ) . '">' . __( 'Settings', 'fw' ) . '</a>';
113
+ }
114
+
115
+ if ( $is_active && isset( $installed_data['path'] ) && file_exists( $installed_data['path'] . '/readme.md.php' ) ) {
116
+ if ( isset($lists['supported'][$name]) ) {
117
+ // no sense to teach how to install the extension if theme is already configured and the is extension marked as compatible
118
+ } else {
119
+ $_links[] = '<a href="' . esc_attr( $link ) . '&sub-page=extension&extension=' . esc_attr( $name ) . '&tab=docs">' . __( 'Install Instructions', 'fw' ) . '</a>';
120
+ }
121
+ }
122
+
123
+ if ( ! empty( $_links ) ):
124
+ ?><p class="fw-extensions-list-item-links"><?php echo implode( ' <span class="fw-text-muted">|</span> ', $_links ); ?></p><?php
125
+ endif;
126
+
127
+ unset( $_links );
128
+ }
129
+ ?>
130
+ <?php if ($is_compatible): ?>
131
+ <p><em><strong><span class="dashicons dashicons-yes"></span> <?php _e('Compatible', 'fw') ?></strong> <?php _e('with your current theme', 'fw') ?></em></p>
132
+ <?php endif; ?>
133
+ </div>
134
+ <div class="fw-extension-list-item-table-cell cell-3">
135
+ <?php if ($is_active): ?>
136
+ <form action="<?php echo esc_attr($link) ?>&sub-page=deactivate&extension=<?php echo esc_attr( $name ) ?>" method="post">
137
+ <?php wp_nonce_field($nonces['deactivate']['action'], $nonces['deactivate']['name']); ?>
138
+ <input class="button" type="submit" value="<?php esc_attr_e('Deactivate', 'fw'); ?>"/>
139
+ </form>
140
+ <?php elseif ($installed_data): ?>
141
+ <div class="fw-text-center">
142
+ <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($name) ?>"
143
+ method="post"
144
+ class="extension-activate-form"
145
+ >
146
+ <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
147
+ <input class="button" type="submit" value="<?php esc_attr_e('Activate', 'fw'); ?>"/>
148
+ </form>
149
+ <?php
150
+ /**
151
+ * Do not show the "Delete extension" button if the extension is not in the available list.
152
+ * If you delete such extension you will not be able to install it back.
153
+ * Most often these will be extensions located in the theme.
154
+ */
155
+ if ($can_install && $available_data):
156
+ ?>
157
+ <form action="<?php echo esc_attr($link) ?>&sub-page=delete&extension=<?php echo esc_attr($name) ?>"
158
+ method="post"
159
+ class="fw-extension-ajax-form extension-delete-form"
160
+ data-confirm-message="<?php esc_attr_e('Are you sure you want to remove this extension?', 'fw') ?>"
161
+ data-extension-name="<?php echo esc_attr($name) ?>"
162
+ data-extension-action="uninstall"
163
+ >
164
+ <?php wp_nonce_field($nonces['delete']['action'], $nonces['delete']['name']); ?>
165
+ <p class="fw-visible-xs-block"></p>
166
+ <a href="#"
167
+ onclick="jQuery(this).closest('form').submit(); return false;"
168
+ data-remove-extension="<?php echo esc_attr($name) ?>"
169
+ title="<?php esc_attr_e('Remove', 'fw'); ?>"
170
+ ><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>
171
+ </form>
172
+ <?php endif; ?>
173
+ </div>
174
+ <?php elseif ($can_install && $available_data): ?>
175
+ <form action="<?php echo esc_attr($link) ?>&sub-page=install&extension=<?php echo esc_attr($name) ?>"
176
+ method="post"
177
+ class="fw-extension-ajax-form"
178
+ data-extension-name="<?php echo esc_attr($name) ?>"
179
+ data-extension-action="install"
180
+ >
181
+ <?php wp_nonce_field($nonces['install']['action'], $nonces['install']['name']); ?>
182
+ <input type="submit" class="button" value="<?php esc_attr_e('Download', 'fw') ?>">
183
+ </form>
184
+ <?php endif; ?>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ <?php if ($installed_data): ?>
189
+ <?php if (!$is_active): ?>
190
+ <?php if (!fw()->extensions->_get_db_active_extensions($name)): ?>
191
+ <span><!-- Is not set as active in db --></span>
192
+ <?php elseif ($installed_data['parent'] && !fw()->extensions->get($installed_data['parent'])): ?>
193
+ <?php
194
+ $parent_extension_name = $installed_data['parent'];
195
+ $parent_extension_title = fw_id_to_title($parent_extension_name);
196
+
197
+ if (isset($lists['installed'][$parent_extension_name])) {
198
+ $parent_extension_title = fw_akg('name', $lists['installed'][$parent_extension_name]['manifest'], $parent_extension_title);
199
+ } elseif (isset($lists['available'][$parent_extension_name])) {
200
+ $parent_extension_title = $lists['available'][$parent_extension_name]['name'];
201
+ }
202
+ ?>
203
+ <p class="fw-text-muted"><?php echo sprintf(__('Parent extension "%s" is disabled', 'fw'), $parent_extension_title); ?></p>
204
+ <?php else: ?>
205
+ <div class="fw-extension-disabled fw-border-box-sizing">
206
+ <div class="fw-extension-disabled-panel fw-border-box-sizing">
207
+ <div class="fw-row">
208
+ <div class="fw-col-xs-12 fw-col-sm-3">
209
+ <span class="fw-text-danger">!&nbsp;<?php _e('Disabled', 'fw'); ?></span>
210
+ </div>
211
+ <div class="fw-col-xs-12 fw-col-sm-9 fw-text-right">
212
+ <?php
213
+
214
+ $requirements = fw()
215
+ ->extensions
216
+ ->manager->collect_extension_requirements(
217
+ $name
218
+ );
219
+
220
+ ?>
221
+ <a onclick="return false;" href="#" class="fw-extension-tip" title="<?php
222
+ echo fw_htmlspecialchars(
223
+ '<div class="fw-extension-tip-content">'.
224
+ '<ul class="fw-extension-requirements"><li>- '. implode('</li><li>- ', $requirements) .'</li></ul>'.
225
+ '</div>'
226
+ );
227
+ unset($requirements);
228
+ ?>"><?php _e('View Requirements', 'fw') ?></a>
229
+ &nbsp; <p class="fw-visible-xs-block"></p><?php
230
+ if ($can_install):
231
+ ?><a href="<?php echo esc_attr($link) ?>&sub-page=delete&extension=<?php echo esc_attr($name) ?>" class="button" ><?php _e('Remove', 'fw'); ?></a><?php
232
+ endif;
233
+ ?>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ <?php endif; ?>
239
+ <?php endif; ?>
240
+ <?php elseif ($available_data): ?>
241
+ <!-- -->
242
+ <?php else: ?>
243
+ <!-- -->
244
+ <?php endif; ?>
245
+ </div>
246
+ </div>
framework/core/components/extensions/manager/views/extensions-page.php CHANGED
@@ -1,242 +1,267 @@
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
- // Set extensions order same as in available extensions list
12
- {
13
- $ordered = array(
14
- 'active' => array(),
15
- 'installed' => array(),
16
- );
17
-
18
- foreach ($lists['available'] as $name => &$_ext) {
19
- foreach ($ordered as $type => &$_exts) {
20
- if (isset($lists[$type][$name])) {
21
- $ordered[$type][$name] = $lists[$type][$name];
22
- }
23
- }
24
- }
25
-
26
- foreach ($ordered as $type => &$_exts) {
27
- if (!empty($ordered[$type])) {
28
- $lists[$type] = array_merge($ordered[$type], $lists[$type]);
29
- }
30
- }
31
-
32
- unset($ordered, $name, $_ext, $_exts, $type);
33
- }
34
-
35
- $dir = dirname(__FILE__);
36
- $extension_view_path = $dir .'/extension.php';
37
-
38
- $displayed = array();
39
- ?>
40
-
41
- <h3><?php _e('Active Extensions', 'fw') ?></h3>
42
- <?php
43
- $display_active_extensions = array();
44
-
45
- foreach ($lists['active'] as $name => &$data) {
46
- if (true !== fw_akg('display', $data['manifest'], $display_default_value)) {
47
- continue;
48
- }
49
-
50
- $display_active_extensions[$name] = &$data;
51
- }
52
- unset($data);
53
- ?>
54
- <?php if (empty($display_active_extensions)): ?>
55
- <div class="fw-extensions-no-active">
56
- <div class="fw-text-center fw-extensions-title-icon"><span class="dashicons dashicons-screenoptions"></span></div>
57
- <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>
58
- </div>
59
- <?php else: ?>
60
- <div class="fw-row fw-extensions-list">
61
- <?php
62
- foreach ($display_active_extensions as $name => &$data) {
63
- fw_render_view($extension_view_path, array(
64
- 'name' => $name,
65
- 'title' => fw_ext($name)->manifest->get_name(),
66
- 'description' => fw_ext($name)->manifest->get('description'),
67
- 'link' => $link,
68
- 'lists' => &$lists,
69
- 'nonces' => $nonces,
70
- 'default_thumbnail' => $default_thumbnail,
71
- 'can_install' => $can_install,
72
- ), false);
73
-
74
- $displayed[$name] = true;
75
- }
76
- unset($data);
77
- ?>
78
- </div>
79
- <?php endif; ?>
80
-
81
- <div id="fw-extensions-list-available">
82
- <hr class="fw-extensions-lists-separator"/>
83
- <h3><?php _e('Available Extensions', 'fw') ?></h3><!-- This "available" differs from technical "available" -->
84
- <div class="fw-row fw-extensions-list">
85
- <?php $something_displayed = false; ?>
86
- <?php
87
- {
88
- $theme_extensions = array();
89
-
90
- foreach ($lists['disabled'] as $name => &$data) {
91
- if (!$data['is']['theme']) {
92
- continue;
93
- }
94
-
95
- $theme_extensions[$name] = array(
96
- 'name' => fw_akg('name', $data['manifest'], fw_id_to_title($name)),
97
- 'description' => fw_akg('description', $data['manifest'], '')
98
- );
99
- }
100
- unset($data);
101
-
102
- foreach ($theme_extensions + $lists['supported'] as $name => $data) {
103
- if (isset($displayed[$name])) {
104
- continue;
105
- } elseif (isset($lists['installed'][$name])) {
106
- if (true !== fw_akg('display', $lists['installed'][$name]['manifest'], $display_default_value)) {
107
- continue;
108
- }
109
- } else {
110
- if (isset($lists['available'][$name])) {
111
- if (!$can_install) {
112
- continue;
113
- }
114
- } else {
115
- //trigger_error(sprintf(__('Supported extension "%s" is not available.', 'fw'), $name));
116
- continue;
117
- }
118
- }
119
-
120
- fw_render_view($extension_view_path, array(
121
- 'name' => $name,
122
- 'title' => $data['name'],
123
- 'description' => $data['description'],
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
-
134
- unset($theme_extensions);
135
- }
136
-
137
- foreach ($lists['disabled'] as $name => &$data) {
138
- if (isset($displayed[$name])) {
139
- continue;
140
- } elseif (true !== fw_akg('display', $data['manifest'], $display_default_value)) {
141
- continue;
142
- }
143
-
144
- fw_render_view($extension_view_path, array(
145
- 'name' => $name,
146
- 'title' => fw_akg('name', $data['manifest'], fw_id_to_title($name)),
147
- 'description' => fw_akg('description', $data['manifest'], ''),
148
- 'link' => $link,
149
- 'lists' => &$lists,
150
- 'nonces' => $nonces,
151
- 'default_thumbnail' => $default_thumbnail,
152
- 'can_install' => $can_install,
153
- ), false);
154
-
155
- $displayed[$name] = $something_displayed = true;
156
- }
157
- unset($data);
158
-
159
- if ($can_install) {
160
- foreach ( $lists['available'] as $name => &$data ) {
161
- if ( isset( $displayed[ $name ] ) ) {
162
- continue;
163
- } elseif ( isset( $lists['installed'][ $name ] ) ) {
164
- continue;
165
- } elseif ( $data['display'] !== true ) {
166
- continue;
167
- }
168
-
169
- /**
170
- * fixme: remove this in the future when this extensions will look good on any theme
171
- */
172
- if ( in_array( $name, array( 'styling', 'megamenu' ) ) ) {
173
- if ( isset( $lists['supported'][ $name ] ) || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
174
- } else {
175
- continue;
176
- }
177
- }
178
-
179
- fw_render_view( $extension_view_path, array(
180
- 'name' => $name,
181
- 'title' => $data['name'],
182
- 'description' => $data['description'],
183
- 'link' => $link,
184
- 'lists' => &$lists,
185
- 'nonces' => $nonces,
186
- 'default_thumbnail' => $default_thumbnail,
187
- 'can_install' => $can_install,
188
- ), false );
189
-
190
- $something_displayed = true;
191
- }
192
- unset($data);
193
- }
194
- ?>
195
- </div>
196
-
197
- <?php if ($something_displayed && apply_filters('fw_extensions_page_show_other_extensions', true)): ?>
198
- <!-- show/hide not compatible extensions -->
199
- <p class="fw-text-center toggle-not-compat-ext-btn-wrapper"><?php
200
- echo fw_html_tag(
201
- 'a',
202
- array(
203
- 'href' => '#',
204
- 'onclick' => 'return false;',
205
- 'class' => 'button toggle-not-compat-ext-btn',
206
- 'style' => 'box-shadow:none;'
207
- ),
208
- '<span class="the-show-text">'. __('Show other extensions', 'fw') .'</span>'.
209
- '<span class="the-hide-text fw-hidden">'. __('Hide other extensions', 'fw') .'</span>'
210
- );
211
- ?></p>
212
- <script type="text/javascript">
213
- jQuery(function($){
214
- if (
215
- !$('.fw-extensions-list .fw-extensions-list-item.not-compatible').length
216
- ||
217
- <?php echo empty($lists['supported']) ? 'true' : 'false' ?>
218
- ) {
219
- // disable the show/hide feature
220
- $('#fw-extensions-list-wrapper .toggle-not-compat-ext-btn-wrapper').addClass('fw-hidden');
221
- } else {
222
- $('#fw-extensions-list-wrapper .fw-extensions-list .fw-extensions-list-item.not-compatible').fadeOut('fast');
223
-
224
- $('#fw-extensions-list-wrapper .toggle-not-compat-ext-btn-wrapper').on('click', function(){
225
- $('#fw-extensions-list-wrapper .fw-extensions-list .fw-extensions-list-item.not-compatible')[
226
- $(this).find('.the-hide-text').hasClass('fw-hidden') ? 'fadeIn' : 'fadeOut'
227
- ]();
228
-
229
- $(this).find('.the-show-text, .the-hide-text').toggleClass('fw-hidden');
230
- });
231
- }
232
- });
233
- </script>
234
- <!-- end: show/hide not compatible extensions -->
235
- <?php else: ?>
236
- <script type="text/javascript">
237
- jQuery(function($){
238
- $('#fw-extensions-list-available').remove();
239
- });
240
- </script>
241
- <?php endif; ?>
242
- </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
+ $installed_plugins = get_plugins();
12
+
13
+ foreach ( $lists['available'] as $name => &$_ext ) {
14
+ if ( empty( $_ext['download']['opts']['plugin'] ) ) {
15
+ continue;
16
+ }
17
+
18
+ $slug = $_ext['download']['opts']['plugin'];
19
+
20
+ if ( is_plugin_active( $slug ) ) {
21
+ $lists['active'][ $name ] = $_ext;
22
+ $lists['installed'][ $name ] = $_ext;
23
+ } else {
24
+ if ( isset( $installed_plugins[ $slug ] ) ) {
25
+ $lists['installed'][ $name ] = $_ext;
26
+ $lists['disabled'][ $name ] = $_ext;
27
+ }
28
+ }
29
+
30
+ $lists['supported'][ $name ] = $_ext;
31
+ }
32
+
33
+ // Set extensions order same as in available extensions list
34
+ {
35
+ $ordered = array(
36
+ 'active' => array(),
37
+ 'installed' => array(),
38
+ );
39
+
40
+ foreach ( $lists['available'] as $name => &$_ext ) {
41
+ foreach ( $ordered as $type => &$_exts ) {
42
+ if ( isset( $lists[ $type ][ $name ] ) ) {
43
+ $ordered[ $type ][ $name ] = $lists[ $type ][ $name ];
44
+ }
45
+ }
46
+ }
47
+
48
+ foreach ( $ordered as $type => &$_exts ) {
49
+ if ( ! empty( $ordered[ $type ] ) ) {
50
+ $lists[ $type ] = array_merge( $ordered[ $type ], $lists[ $type ] );
51
+ }
52
+ }
53
+
54
+ unset( $ordered, $name, $_ext, $_exts, $type );
55
+ }
56
+
57
+ $extension_view_path = dirname( __FILE__ ) . '/extension.php';
58
+
59
+ $displayed = array();
60
+ ?>
61
+
62
+ <h3><?php _e('Active Extensions', 'fw') ?></h3>
63
+ <?php
64
+ $display_active_extensions = array();
65
+
66
+ foreach ( $lists['active'] as $name => &$data ) {
67
+ if ( ! empty( $data['display'] ) || true === fw_akg( 'display', $data['manifest'], $display_default_value ) ) {
68
+ $display_active_extensions[ $name ] = &$data;
69
+ }
70
+ }
71
+
72
+ unset($data);
73
+ ?>
74
+ <?php if (empty($display_active_extensions)): ?>
75
+ <div class="fw-extensions-no-active">
76
+ <div class="fw-text-center fw-extensions-title-icon"><span class="dashicons dashicons-screenoptions"></span></div>
77
+ <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>
78
+ </div>
79
+ <?php else: ?>
80
+ <div class="fw-row fw-extensions-list">
81
+ <?php
82
+ foreach ( $display_active_extensions as $name => &$data ) {
83
+
84
+ $ext = fw_ext( $name );
85
+
86
+ fw_render_view( $extension_view_path, array(
87
+ 'name' => $name,
88
+ 'title' => $ext ? $ext->manifest->get_name() : $data['name'],
89
+ 'description' => $ext ? $ext->manifest->get( 'description' ) : ( isset( $data['description'] ) ? $data['description'] : '' ),
90
+ 'link' => $link,
91
+ 'lists' => &$lists,
92
+ 'nonces' => $nonces,
93
+ 'default_thumbnail' => $default_thumbnail,
94
+ 'can_install' => $can_install,
95
+ ), false );
96
+
97
+ $displayed[ $name ] = true;
98
+ }
99
+ unset($data);
100
+ ?>
101
+ </div>
102
+ <?php endif; ?>
103
+
104
+ <div id="fw-extensions-list-available">
105
+ <hr class="fw-extensions-lists-separator"/>
106
+ <h3><?php _e('Available Extensions', 'fw') ?></h3><!-- This "available" differs from technical "available" -->
107
+ <div class="fw-row fw-extensions-list">
108
+ <?php $something_displayed = false; ?>
109
+ <?php
110
+ {
111
+ $theme_extensions = array();
112
+
113
+ foreach ( $lists['disabled'] as $name => &$data ) {
114
+ if ( empty( $data['is']['theme'] ) ) {
115
+ continue;
116
+ }
117
+
118
+ $theme_extensions[ $name ] = array(
119
+ 'name' => fw_akg( 'name', $data['manifest'], fw_id_to_title( $name ) ),
120
+ 'description' => fw_akg( 'description', $data['manifest'], '' )
121
+ );
122
+ }
123
+ unset($data);
124
+
125
+ foreach ($theme_extensions + $lists['supported'] as $name => $data) {
126
+ if (isset($displayed[$name])) {
127
+ continue;
128
+ } elseif ( isset( $lists['installed'][ $name ] ) && ! empty( $lists['installed'][$name]['manifest'] ) ) {
129
+ if (true !== fw_akg('display', $lists['installed'][$name]['manifest'], $display_default_value)) {
130
+ continue;
131
+ }
132
+ } else {
133
+ if (isset($lists['available'][$name])) {
134
+ if (!$can_install) {
135
+ continue;
136
+ }
137
+ } else {
138
+ //trigger_error(sprintf(__('Supported extension "%s" is not available.', 'fw'), $name));
139
+ continue;
140
+ }
141
+ }
142
+
143
+ fw_render_view($extension_view_path, array(
144
+ 'name' => $name,
145
+ 'title' => $data['name'],
146
+ 'description' => $data['description'],
147
+ 'link' => $link,
148
+ 'lists' => &$lists,
149
+ 'nonces' => $nonces,
150
+ 'default_thumbnail' => $default_thumbnail,
151
+ 'can_install' => $can_install,
152
+ ), false);
153
+
154
+ $displayed[$name] = $something_displayed = true;
155
+ }
156
+
157
+ unset($theme_extensions);
158
+ }
159
+
160
+ foreach ( $lists['disabled'] as $name => &$data ) {
161
+ if ( isset( $displayed[ $name ] ) ) {
162
+ continue;
163
+ } elseif ( isset( $data['display'] ) && true !== $data['display'] ) {
164
+ continue;
165
+ } elseif ( isset( $data['manifest'] ) && true !== fw_akg( 'display', $data['manifest'], $display_default_value ) ) {
166
+ continue;
167
+ }
168
+
169
+ fw_render_view( $extension_view_path, array(
170
+ 'name' => $name,
171
+ 'title' => ! empty( $data['manifest']['name'] ) ? $data['manifest']['name'] : ( ! empty( $data['name'] ) ? $data['name'] : 'No name' ),
172
+ 'description' => ! empty( $data['manifest']['description'] ) ? $data['manifest']['description'] : ( ! empty( $data['description'] ) ? $data['description'] : '' ),
173
+ 'link' => $link,
174
+ 'lists' => &$lists,
175
+ 'nonces' => $nonces,
176
+ 'default_thumbnail' => $default_thumbnail,
177
+ 'can_install' => $can_install,
178
+ ), false );
179
+
180
+ $displayed[$name] = $something_displayed = true;
181
+ }
182
+ unset($data);
183
+
184
+ if ($can_install) {
185
+ foreach ( $lists['available'] as $name => &$data ) {
186
+ if ( isset( $displayed[ $name ] ) ) {
187
+ continue;
188
+ } elseif ( isset( $lists['installed'][ $name ] ) ) {
189
+ continue;
190
+ } elseif ( $data['display'] !== true ) {
191
+ continue;
192
+ }
193
+
194
+ /**
195
+ * fixme: remove this in the future when this extensions will look good on any theme
196
+ */
197
+ if ( in_array( $name, array( 'styling', 'megamenu' ) ) ) {
198
+ if ( isset( $lists['supported'][ $name ] ) || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
199
+ } else {
200
+ continue;
201
+ }
202
+ }
203
+
204
+ fw_render_view( $extension_view_path, array(
205
+ 'name' => $name,
206
+ 'title' => $data['name'],
207
+ 'description' => $data['description'],
208
+ 'link' => $link,
209
+ 'lists' => &$lists,
210
+ 'nonces' => $nonces,
211
+ 'default_thumbnail' => $default_thumbnail,
212
+ 'can_install' => $can_install,
213
+ ), false );
214
+
215
+ $something_displayed = true;
216
+ }
217
+ unset($data);
218
+ }
219
+ ?>
220
+ </div>
221
+
222
+ <?php if ($something_displayed && apply_filters('fw_extensions_page_show_other_extensions', true)): ?>
223
+ <!-- show/hide not compatible extensions -->
224
+ <p class="fw-text-center toggle-not-compat-ext-btn-wrapper"><?php
225
+ echo fw_html_tag(
226
+ 'a',
227
+ array(
228
+ 'href' => '#',
229
+ 'onclick' => 'return false;',
230
+ 'class' => 'button toggle-not-compat-ext-btn',
231
+ 'style' => 'box-shadow:none;'
232
+ ),
233
+ '<span class="the-show-text">'. __('Show other extensions', 'fw') .'</span>'.
234
+ '<span class="the-hide-text fw-hidden">'. __('Hide other extensions', 'fw') .'</span>'
235
+ );
236
+ ?></p>
237
+ <script type="text/javascript">
238
+ jQuery(function($){
239
+ if (
240
+ !$('.fw-extensions-list .fw-extensions-list-item.not-compatible').length
241
+ ||
242
+ <?php echo empty($lists['supported']) ? 'true' : 'false' ?>
243
+ ) {
244
+ // disable the show/hide feature
245
+ $('#fw-extensions-list-wrapper .toggle-not-compat-ext-btn-wrapper').addClass('fw-hidden');
246
+ } else {
247
+ $('#fw-extensions-list-wrapper .fw-extensions-list .fw-extensions-list-item.not-compatible').fadeOut('fast');
248
+
249
+ $('#fw-extensions-list-wrapper .toggle-not-compat-ext-btn-wrapper').on('click', function(){
250
+ $('#fw-extensions-list-wrapper .fw-extensions-list .fw-extensions-list-item.not-compatible')[
251
+ $(this).find('.the-hide-text').hasClass('fw-hidden') ? 'fadeIn' : 'fadeOut'
252
+ ]();
253
+
254
+ $(this).find('.the-show-text, .the-hide-text').toggleClass('fw-hidden');
255
+ });
256
+ }
257
+ });
258
+ </script>
259
+ <!-- end: show/hide not compatible extensions -->
260
+ <?php else: ?>
261
+ <script type="text/javascript">
262
+ jQuery(function($){
263
+ $('#fw-extensions-list-available').remove();
264
+ });
265
+ </script>
266
+ <?php endif; ?>
267
+ </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,203 +1,203 @@
1
- <?php defined( 'FW' ) or die();
2
-
3
- /**
4
- * Theme Component
5
- * Works with framework customizations / theme directory
6
- */
7
- final class _FW_Component_Theme {
8
- private static $cache_key = 'fw_theme';
9
-
10
- /**
11
- * @var FW_Theme_Manifest
12
- */
13
- public $manifest;
14
-
15
- public function __construct() {
16
- $manifest = array();
17
-
18
- if ( ( $manifest_file = fw_get_template_customizations_directory( '/theme/manifest.php' ) ) && is_file( $manifest_file ) ) {
19
- @include $manifest_file;
20
- }
21
-
22
- $this->manifest = new FW_Theme_Manifest( $manifest );
23
- }
24
-
25
- /**
26
- * @internal
27
- */
28
- public function _init() {
29
- add_action( 'admin_notices', array( $this, '_action_admin_notices' ) );
30
- }
31
-
32
- /**
33
- * @internal
34
- */
35
- public function _after_components_init() {
36
- }
37
-
38
- /**
39
- * Search relative path in: child theme -> parent "theme" directory and return full path
40
- *
41
- * @param string $rel_path
42
- *
43
- * @return false|string
44
- */
45
- public function locate_path( $rel_path ) {
46
- if ( is_child_theme() && file_exists( fw_get_stylesheet_customizations_directory( '/theme' . $rel_path ) ) ) {
47
- return fw_get_stylesheet_customizations_directory( '/theme' . $rel_path );
48
- } elseif ( file_exists( fw_get_template_customizations_directory( '/theme' . $rel_path ) ) ) {
49
- return fw_get_template_customizations_directory( '/theme' . $rel_path );
50
- } else {
51
- return false;
52
- }
53
- }
54
-
55
- /**
56
- * Return array with options from specified name/path
57
- *
58
- * @param string $name '{theme}/framework-customizations/theme/options/{$name}.php'
59
- * @param array $variables These will be available in options file (like variables for view)
60
- *
61
- * @return array
62
- */
63
- public function get_options( $name, array $variables = array() ) {
64
- $path = $this->locate_path( '/options/' . $name . '.php' );
65
-
66
- if ( ! $path ) {
67
- return array();
68
- }
69
-
70
- $variables = fw_get_variables_from_file( $path, array( 'options' => array() ), $variables );
71
-
72
- return $variables['options'];
73
- }
74
-
75
- public function get_settings_options() {
76
- $cache_key = self::$cache_key . '/options/settings';
77
-
78
- try {
79
- return FW_Cache::get( $cache_key );
80
- } catch ( FW_Cache_Not_Found_Exception $e ) {
81
- $options = apply_filters( 'fw_settings_options', $this->get_options( 'settings' ) );
82
-
83
- FW_Cache::set( $cache_key, $options );
84
-
85
- return $options;
86
- }
87
- }
88
-
89
- public function get_customizer_options() {
90
- $cache_key = self::$cache_key . '/options/customizer';
91
-
92
- try {
93
- return FW_Cache::get( $cache_key );
94
- } catch ( FW_Cache_Not_Found_Exception $e ) {
95
- $options = apply_filters( 'fw_customizer_options', $this->get_options( 'customizer' ) );
96
-
97
- FW_Cache::set( $cache_key, $options );
98
-
99
- return $options;
100
- }
101
- }
102
-
103
- public function get_post_options( $post_type ) {
104
- $cache_key = self::$cache_key . '/options/posts/' . $post_type;
105
-
106
- try {
107
- return FW_Cache::get( $cache_key );
108
- } catch ( FW_Cache_Not_Found_Exception $e ) {
109
- $options = apply_filters(
110
- 'fw_post_options',
111
- apply_filters( "fw_post_options:$post_type", $this->get_options( 'posts/' . $post_type ) ),
112
- $post_type
113
- );
114
-
115
- FW_Cache::set( $cache_key, $options );
116
-
117
- return $options;
118
- }
119
- }
120
-
121
- public function get_taxonomy_options( $taxonomy ) {
122
- $cache_key = self::$cache_key . '/options/taxonomies/' . $taxonomy;
123
-
124
- try {
125
- return FW_Cache::get( $cache_key );
126
- } catch ( FW_Cache_Not_Found_Exception $e ) {
127
- $options = apply_filters(
128
- 'fw_taxonomy_options',
129
- apply_filters( "fw_taxonomy_options:$taxonomy", $this->get_options( 'taxonomies/' . $taxonomy ) ),
130
- $taxonomy
131
- );
132
-
133
- FW_Cache::set( $cache_key, $options );
134
-
135
- return $options;
136
- }
137
- }
138
-
139
- /**
140
- * Return config key value, or entire config array
141
- * Config array is merged from child configs
142
- *
143
- * @param string|null $key Multi key format accepted: 'a/b/c'
144
- * @param mixed $default_value
145
- *
146
- * @return mixed|null
147
- */
148
- final public function get_config( $key = null, $default_value = null ) {
149
- $cache_key = self::$cache_key . '/config';
150
-
151
- try {
152
- $config = FW_Cache::get( $cache_key );
153
- } catch ( FW_Cache_Not_Found_Exception $e ) {
154
- // default values
155
- $config = array(
156
- /** Toggle Theme Settings form ajax submit */
157
- 'settings_form_ajax_submit' => true,
158
- /** Toggle Theme Settings side tabs */
159
- 'settings_form_side_tabs' => false,
160
- /** Toggle Tabs rendered all at once, or initialized only on open/display */
161
- 'lazy_tabs' => true,
162
- );
163
-
164
- if ( file_exists( fw_get_template_customizations_directory( '/theme/config.php' ) ) ) {
165
- $variables = fw_get_variables_from_file( fw_get_template_customizations_directory( '/theme/config.php' ), array( 'cfg' => null ) );
166
-
167
- if ( ! empty( $variables['cfg'] ) ) {
168
- $config = array_merge( $config, $variables['cfg'] );
169
- unset( $variables );
170
- }
171
- }
172
-
173
- if ( is_child_theme() && file_exists( fw_get_stylesheet_customizations_directory( '/theme/config.php' ) ) ) {
174
- $variables = fw_get_variables_from_file( fw_get_stylesheet_customizations_directory( '/theme/config.php' ), array( 'cfg' => null ) );
175
-
176
- if ( ! empty( $variables['cfg'] ) ) {
177
- $config = array_merge( $config, $variables['cfg'] );
178
- unset( $variables );
179
- }
180
- }
181
-
182
- unset( $path );
183
-
184
- FW_Cache::set( $cache_key, $config );
185
- }
186
-
187
- return $key === null ? $config : fw_akg( $key, $config, $default_value );
188
- }
189
-
190
- /**
191
- * @internal
192
- */
193
- public function _action_admin_notices() {
194
- if ( is_admin() && ! fw()->theme->manifest->check_requirements() && current_user_can( 'manage_options' ) ) {
195
- echo
196
- '<div class="notice notice-warning">
197
- <p>' .
198
- __( 'Theme requirements not met:', 'fw' ) . ' ' . fw()->theme->manifest->get_not_met_requirement_text() .
199
- '</p>
200
- </div>';
201
- }
202
- }
203
- }
1
+ <?php defined( 'FW' ) or die();
2
+
3
+ /**
4
+ * Theme Component
5
+ * Works with framework customizations / theme directory
6
+ */
7
+ final class _FW_Component_Theme {
8
+ private static $cache_key = 'fw_theme';
9
+
10
+ /**
11
+ * @var FW_Theme_Manifest
12
+ */
13
+ public $manifest;
14
+
15
+ public function __construct() {
16
+ $manifest = array();
17
+
18
+ if ( ( $manifest_file = fw_get_template_customizations_directory( '/theme/manifest.php' ) ) && is_file( $manifest_file ) ) {
19
+ @include $manifest_file;
20
+ }
21
+
22
+ $this->manifest = new FW_Theme_Manifest( $manifest );
23
+ }
24
+
25
+ /**
26
+ * @internal
27
+ */
28
+ public function _init() {
29
+ add_action( 'admin_notices', array( $this, '_action_admin_notices' ) );
30
+ }
31
+
32
+ /**
33
+ * @internal
34
+ */
35
+ public function _after_components_init() {
36
+ }
37
+
38
+ /**
39
+ * Search relative path in: child theme -> parent "theme" directory and return full path
40
+ *
41
+ * @param string $rel_path
42
+ *
43
+ * @return false|string
44
+ */
45
+ public function locate_path( $rel_path ) {
46
+ if ( is_child_theme() && file_exists( fw_get_stylesheet_customizations_directory( '/theme' . $rel_path ) ) ) {
47
+ return fw_get_stylesheet_customizations_directory( '/theme' . $rel_path );
48
+ } elseif ( file_exists( fw_get_template_customizations_directory( '/theme' . $rel_path ) ) ) {
49
+ return fw_get_template_customizations_directory( '/theme' . $rel_path );
50
+ } else {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Return array with options from specified name/path
57
+ *
58
+ * @param string $name '{theme}/framework-customizations/theme/options/{$name}.php'
59
+ * @param array $variables These will be available in options file (like variables for view)
60
+ *
61
+ * @return array
62
+ */
63
+ public function get_options( $name, array $variables = array() ) {
64
+ $path = $this->locate_path( '/options/' . $name . '.php' );
65
+
66
+ if ( ! $path ) {
67
+ return array();
68
+ }
69
+
70
+ $variables = fw_get_variables_from_file( $path, array( 'options' => array() ), $variables );
71
+
72
+ return $variables['options'];
73
+ }
74
+
75
+ public function get_settings_options() {
76
+ $cache_key = self::$cache_key . '/options/settings';
77
+
78
+ try {
79
+ return FW_Cache::get( $cache_key );
80
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
81
+ $options = apply_filters( 'fw_settings_options', $this->get_options( 'settings' ) );
82
+
83
+ FW_Cache::set( $cache_key, $options );
84
+
85
+ return $options;
86
+ }
87
+ }
88
+
89
+ public function get_customizer_options() {
90
+ $cache_key = self::$cache_key . '/options/customizer';
91
+
92
+ try {
93
+ return FW_Cache::get( $cache_key );
94
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
95
+ $options = apply_filters( 'fw_customizer_options', $this->get_options( 'customizer' ) );
96
+
97
+ FW_Cache::set( $cache_key, $options );
98
+
99
+ return $options;
100
+ }
101
+ }
102
+
103
+ public function get_post_options( $post_type ) {
104
+ $cache_key = self::$cache_key . '/options/posts/' . $post_type;
105
+
106
+ try {
107
+ return FW_Cache::get( $cache_key );
108
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
109
+ $options = apply_filters(
110
+ 'fw_post_options',
111
+ apply_filters( "fw_post_options:$post_type", $this->get_options( 'posts/' . $post_type ) ),
112
+ $post_type
113
+ );
114
+
115
+ FW_Cache::set( $cache_key, $options );
116
+
117
+ return $options;
118
+ }
119
+ }
120
+
121
+ public function get_taxonomy_options( $taxonomy ) {
122
+ $cache_key = self::$cache_key . '/options/taxonomies/' . $taxonomy;
123
+
124
+ try {
125
+ return FW_Cache::get( $cache_key );
126
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
127
+ $options = apply_filters(
128
+ 'fw_taxonomy_options',
129
+ apply_filters( "fw_taxonomy_options:$taxonomy", $this->get_options( 'taxonomies/' . $taxonomy ) ),
130
+ $taxonomy
131
+ );
132
+
133
+ FW_Cache::set( $cache_key, $options );
134
+
135
+ return $options;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Return config key value, or entire config array
141
+ * Config array is merged from child configs
142
+ *
143
+ * @param string|null $key Multi key format accepted: 'a/b/c'
144
+ * @param mixed $default_value
145
+ *
146
+ * @return mixed|null
147
+ */
148
+ final public function get_config( $key = null, $default_value = null ) {
149
+ $cache_key = self::$cache_key . '/config';
150
+
151
+ try {
152
+ $config = FW_Cache::get( $cache_key );
153
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
154
+ // default values
155
+ $config = array(
156
+ /** Toggle Theme Settings form ajax submit */
157
+ 'settings_form_ajax_submit' => true,
158
+ /** Toggle Theme Settings side tabs */
159
+ 'settings_form_side_tabs' => false,
160
+ /** Toggle Tabs rendered all at once, or initialized only on open/display */
161
+ 'lazy_tabs' => true,
162
+ );
163
+
164
+ if ( file_exists( fw_get_template_customizations_directory( '/theme/config.php' ) ) ) {
165
+ $variables = fw_get_variables_from_file( fw_get_template_customizations_directory( '/theme/config.php' ), array( 'cfg' => null ) );
166
+
167
+ if ( ! empty( $variables['cfg'] ) ) {
168
+ $config = array_merge( $config, $variables['cfg'] );
169
+ unset( $variables );
170
+ }
171
+ }
172
+
173
+ if ( is_child_theme() && file_exists( fw_get_stylesheet_customizations_directory( '/theme/config.php' ) ) ) {
174
+ $variables = fw_get_variables_from_file( fw_get_stylesheet_customizations_directory( '/theme/config.php' ), array( 'cfg' => null ) );
175
+
176
+ if ( ! empty( $variables['cfg'] ) ) {
177
+ $config = array_merge( $config, $variables['cfg'] );
178
+ unset( $variables );
179
+ }
180
+ }
181
+
182
+ unset( $path );
183
+
184
+ FW_Cache::set( $cache_key, $config );
185
+ }
186
+
187
+ return $key === null ? $config : fw_akg( $key, $config, $default_value );
188
+ }
189
+
190
+ /**
191
+ * @internal
192
+ */
193
+ public function _action_admin_notices() {
194
+ if ( is_admin() && ! fw()->theme->manifest->check_requirements() && current_user_can( 'manage_options' ) ) {
195
+ echo
196
+ '<div class="notice notice-warning">
197
+ <p>' .
198
+ __( 'Theme requirements not met:', 'fw' ) . ' ' . fw()->theme->manifest->get_not_met_requirement_text() .
199
+ '</p>
200
+ </div>';
201
+ }
202
+ }
203
+ }
framework/core/exceptions/class-fw-option-type-exception.php CHANGED
@@ -1,40 +1,40 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
-
6
- /**
7
- * Class FW_Option_Type_Exception
8
- *
9
- * @since 2.6.11
10
- */
11
- class FW_Option_Type_Exception extends Exception {
12
-
13
- }
14
-
15
- /**
16
- * Class FW_Option_Type_Exception_Not_Found
17
- *
18
- * @since 2.6.11
19
- */
20
- class FW_Option_Type_Exception_Not_Found extends FW_Option_Type_Exception {
21
-
22
- }
23
-
24
- /**
25
- * Class FW_Option_Type_Exception_Invalid_Class
26
- *
27
- * @since 2.6.11
28
- */
29
- class FW_Option_Type_Exception_Invalid_Class extends FW_Option_Type_Exception {
30
-
31
- }
32
-
33
- /**
34
- * Class FW_Option_Type_Exception_Already_Registered
35
- *
36
- * @since 2.6.11
37
- */
38
- class FW_Option_Type_Exception_Already_Registered extends FW_Option_Type_Exception {
39
-
40
  }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+
6
+ /**
7
+ * Class FW_Option_Type_Exception
8
+ *
9
+ * @since 2.6.11
10
+ */
11
+ class FW_Option_Type_Exception extends Exception {
12
+
13
+ }
14
+
15
+ /**
16
+ * Class FW_Option_Type_Exception_Not_Found
17
+ *
18
+ * @since 2.6.11
19
+ */
20
+ class FW_Option_Type_Exception_Not_Found extends FW_Option_Type_Exception {
21
+
22
+ }
23
+
24
+ /**
25
+ * Class FW_Option_Type_Exception_Invalid_Class
26
+ *
27
+ * @since 2.6.11
28
+ */
29
+ class FW_Option_Type_Exception_Invalid_Class extends FW_Option_Type_Exception {
30
+
31
+ }
32
+
33
+ /**
34
+ * Class FW_Option_Type_Exception_Already_Registered
35
+ *
36
+ * @since 2.6.11
37
+ */
38
+ class FW_Option_Type_Exception_Already_Registered extends FW_Option_Type_Exception {
39
+
40
  }
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()->backend->get_options_id_attr_prefix(), // attribute id prefix
104
- 'name_prefix' => fw()->backend->get_options_name_attr_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()->backend->get_options_id_attr_prefix(), // attribute id prefix
104
+ 'name_prefix' => fw()->backend->get_options_name_attr_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,514 +1,514 @@
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
- $this->manifest = _FW_Component_Extensions::_get_manifest($this->get_name(), self::$access_key);
81
- }
82
-
83
- /**
84
- * Cache key for this extension
85
- *
86
- * Usage:
87
- * FW_Cache::get( $this->get_cache_key('/some/key') )
88
- *
89
- * @param string $sub_key
90
- * @return string
91
- */
92
- final public function get_cache_key($sub_key = '')
93
- {
94
- return self::$cache_key .'/'. $this->get_name() . $sub_key;
95
- }
96
-
97
- /**
98
- * @param string $name View file name (without .php) from <extension>/views directory
99
- * @param array $view_variables Keys will be variables names within view
100
- * @param bool $return In some cases, for memory saving reasons, you can disable the use of output buffering
101
- * @return string HTML
102
- */
103
- final protected function render_view($name, $view_variables = array(), $return = true)
104
- {
105
- $full_path = $this->locate_path('/views/'. $name .'.php');
106
-
107
- if (!$full_path) {
108
- trigger_error('Extension view not found: '. $name, E_USER_WARNING);
109
- return;
110
- }
111
-
112
- return fw_render_view($full_path, $view_variables, $return);
113
- }
114
-
115
- /**
116
- * @internal
117
- * @param FW_Access_Key $access_key
118
- * @return mixed
119
- */
120
- final public function _call_init($access_key)
121
- {
122
- if ($access_key->get_key() !== 'fw_extensions') {
123
- trigger_error(__METHOD__ .' denied', E_USER_ERROR);
124
- }
125
-
126
- return $this->_init();
127
- }
128
-
129
- /**
130
- * Tree array with all sub extensions
131
- * @return array
132
- */
133
- final public function get_tree()
134
- {
135
- return fw()->extensions->_get_extension_tree(self::$access_key, $this->get_name());
136
- }
137
-
138
- /**
139
- * @param string $rel_path '/views/test.php'
140
- * @return false|string '/var/www/.../extensions/<extension>/views/test.php'
141
- */
142
- final public function locate_path($rel_path)
143
- {
144
- $locations = $this->customizations_locations;
145
- $locations[$this->get_path()] = $this->get_uri();
146
-
147
- foreach ($locations as $path => $uri) {
148
- if (file_exists($path . $rel_path)) {
149
- return $path . $rel_path;
150
- }
151
- }
152
-
153
- return false;
154
- }
155
-
156
- /**
157
- * @param string $rel_path E.g. '/static/js/scripts.js'
158
- * @return string URI E.g. 'http: //wordpress.com/.../extensions/<extension>/static/js/scripts.js'
159
- */
160
- final public function locate_URI($rel_path)
161
- {
162
- $locations = $this->customizations_locations;
163
- $locations[$this->get_path()] = $this->get_uri();
164
-
165
- foreach ($locations as $path => $uri) {
166
- if (file_exists($path . $rel_path)) {
167
- return $uri . $rel_path;
168
- }
169
- }
170
-
171
- return false;
172
- }
173
-
174
- /**
175
- * @return FW_Extension|null if has no parent extension
176
- */
177
- final public function get_parent()
178
- {
179
- return $this->parent;
180
- }
181
-
182
- /**
183
- * @return string
184
- */
185
- final public function get_name()
186
- {
187
- if ($this->name === null) {
188
- $this->name = basename($this->path);
189
- }
190
-
191
- return $this->name;
192
- }
193
-
194
- /**
195
- * @return string
196
- * @deprecated
197
- */
198
- final public function get_declared_source()
199
- {
200
- return 'deprecated';
201
- }
202
-
203
- /**
204
- * @param string $append_rel_path E.g. '/includes/something.php'
205
- * @return string
206
- * @deprecated
207
- */
208
- final public function get_declared_path($append_rel_path = '')
209
- {
210
- return $this->get_path($append_rel_path);
211
- }
212
-
213
- final public function get_path($append_rel_path = '')
214
- {
215
- return $this->path . $append_rel_path;
216
- }
217
-
218
- /**
219
- * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
220
- * @return string
221
- * @deprecated
222
- */
223
- final public function get_declared_URI($append_rel_path = '')
224
- {
225
- return $this->get_uri($append_rel_path);
226
- }
227
-
228
- /**
229
- * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
230
- * @return string
231
- */
232
- final public function get_uri($append_rel_path = '')
233
- {
234
- return $this->uri . $append_rel_path;
235
- }
236
-
237
- /**
238
- * @param string $child_extension_name
239
- * @return FW_Extension|null
240
- */
241
- final public function get_child($child_extension_name)
242
- {
243
- $active_tree = $this->get_tree();
244
-
245
- if (isset($active_tree[$child_extension_name])) {
246
- return fw()->extensions->get($child_extension_name);
247
- } else {
248
- return null;
249
- }
250
- }
251
-
252
- /**
253
- * Return all child extensions
254
- * Only one level, not all sub levels
255
- * @return FW_Extension[]
256
- */
257
- final public function get_children()
258
- {
259
- $active_tree = $this->get_tree();
260
-
261
- $result = array();
262
-
263
- foreach ($active_tree as $extension_name => &$sub_extensions) {
264
- $result[$extension_name] = fw()->extensions->get($extension_name);
265
- }
266
- unset($sub_extensions);
267
-
268
- return $result;
269
- }
270
-
271
- /**
272
- * Return config key value, or entire config array
273
- * Config array is merged from child configs
274
- * @param string|null $key Multi key format accepted: 'a/b/c'
275
- * @return mixed|null
276
- */
277
- final public function get_config($key = null)
278
- {
279
- $cache_key = $this->get_cache_key() .'/config';
280
-
281
- try {
282
- $config = FW_Cache::get($cache_key);
283
- } catch (FW_Cache_Not_Found_Exception $e) {
284
- $config = array();
285
-
286
- $locations = $this->customizations_locations;
287
- $locations[$this->get_path()] = $this->get_uri();
288
-
289
- foreach (array_reverse($locations) as $path => $uri) {
290
- $config_path = $path .'/config.php';
291
-
292
- if (file_exists($config_path)) {
293
- $variables = fw_get_variables_from_file($config_path, array('cfg' => null));
294
-
295
- if (!empty($variables['cfg'])) {
296
- $config = array_merge($config, $variables['cfg']);
297
- unset($variables);
298
- }
299
- }
300
- }
301
-
302
- FW_Cache::set($cache_key, $config);
303
- }
304
-
305
- return $key === null ? $config : fw_call( fw_akg( $key, $config ) );
306
- }
307
-
308
- /**
309
- * Return array with options from specified name/path
310
- * @param string $name Examples: 'framework', 'posts/portfolio'
311
- * @param array $variables These will be available in options file (like variables for view)
312
- * @return array
313
- */
314
- final public function get_options($name, array $variables = array())
315
- {
316
- try {
317
- return FW_Cache::get($cache_key = $this->get_cache_key('/options/'. $name));
318
- } catch (FW_Cache_Not_Found_Exception $e) {
319
- if ($path = $this->locate_path('/options/'. $name .'.php')) {
320
- $variables = fw_get_variables_from_file($path, array('options' => array()), $variables);
321
- } else {
322
- $variables = array('options' => array());
323
- }
324
-
325
- FW_Cache::set($cache_key, $variables['options']);
326
-
327
- return $variables['options'];
328
- }
329
- }
330
-
331
- final public function get_settings_options()
332
- {
333
- try {
334
- return FW_Cache::get($cache_key = $this->get_cache_key('/settings_options'));
335
- } catch (FW_Cache_Not_Found_Exception $e) {
336
- if (file_exists($path = $this->get_path('/settings-options.php'))) {
337
- $variables = fw_get_variables_from_file($path, array('options' => array()));
338
- } else {
339
- $variables = array('options' => array());
340
- }
341
-
342
- FW_Cache::set($cache_key, $variables['options']);
343
-
344
- return $variables['options'];
345
- }
346
- }
347
-
348
- /**
349
- * @since 2.6.9
350
- */
351
- final public function get_rendered_docs() {
352
- $docs_path = $this->get_path('/readme.md.php');
353
-
354
- if (! file_exists($docs_path)) {
355
- return false;
356
- }
357
-
358
- return fw()->backend->get_markdown_parser()->text(
359
- /**
360
- * TODO: Perhaps send here some values in order to make extension docs
361
- * more dynamic???
362
- */
363
- fw_render_view($docs_path, array())
364
- );
365
- }
366
-
367
- /**
368
- * Get extension's settings option value from the database
369
- *
370
- * @param string|null $option_id
371
- * @param null|mixed $default_value If no option found in the database, this value will be returned
372
- * @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
373
- *
374
- * @return mixed|null
375
- */
376
- final public function get_db_settings_option( $option_id = null, $default_value = null, $get_original_value = null ) {
377
- return fw_get_db_ext_settings_option( $this->get_name(), $option_id, $default_value, $get_original_value );
378
- }
379
-
380
- /**
381
- * Set extension's setting option value in database
382
- *
383
- * @param string|null $option_id
384
- * @param mixed $value
385
- */
386
- final public function set_db_settings_option( $option_id = null, $value ) {
387
- fw_set_db_ext_settings_option( $this->get_name(), $option_id, $value );
388
- }
389
-
390
- /**
391
- * Get extension's data from the database
392
- *
393
- * @param string|null $multi_key The key of the data you want to get. null - all data
394
- * @param null|mixed $default_value If no option found in the database, this value will be returned
395
- * @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
396
- *
397
- * @return mixed|null
398
- */
399
- final public function get_db_data( $multi_key = null, $default_value = null, $get_original_value = null ) {
400
- return fw_get_db_extension_data( $this->get_name(), $multi_key, $default_value, $get_original_value );
401
- }
402
-
403
- /**
404
- * Set some extension's data in database
405
- *
406
- * @param string|null $multi_key The key of the data you want to set. null - all data
407
- * @param mixed $value
408
- */
409
- final public function set_db_data( $multi_key = null, $value ) {
410
- fw_set_db_extension_data( $this->get_name(), $multi_key, $value );
411
- }
412
-
413
- /**
414
- * Get extension's data from user meta
415
- *
416
- * @param int $user_id
417
- * @param string|null $keys
418
- *
419
- * @return mixed|null
420
- */
421
- final public function get_user_data( $user_id, $keys = null ) {
422
- return fw_get_db_extension_user_data($user_id, $this->get_name(), $keys);
423
- }
424
-
425
- /**
426
- * et some extension's data in user meta
427
- *
428
- * @param int $user_id
429
- * @param mixed $value
430
- * @param string|null $keys
431
- *
432
- * @return bool|int
433
- */
434
- final public function set_user_data( $user_id, $value, $keys = null ) {
435
- return fw_set_db_extension_user_data($user_id, $this->get_name(), $value, $keys);
436
- }
437
-
438
- final public function get_post_options($post_type)
439
- {
440
- return $this->get_options('posts/'. $post_type);
441
- }
442
-
443
- final public function get_taxonomy_options($taxonomy)
444
- {
445
- return $this->get_options('taxonomies/'. $taxonomy);
446
- }
447
-
448
- /**
449
- * @param string $name File name without extension, located in <extension>/static/js/$name.js
450
- * @return string URI
451
- */
452
- final public function locate_js_URI($name)
453
- {
454
- return $this->locate_URI('/static/js/'. $name .'.js');
455
- }
456
-
457
- /**
458
- * @param string $name File name without extension, located in <extension>/static/js/$name.js
459
- * @return string URI
460
- */
461
- final public function locate_css_URI($name)
462
- {
463
- return $this->locate_URI('/static/css/'. $name .'.css');
464
- }
465
-
466
- /**
467
- * @param string $name File name without extension, located in <extension>/views/$name.php
468
- * @return false|string
469
- */
470
- final public function locate_view_path($name)
471
- {
472
- return $this->locate_path('/views/'. $name .'.php');
473
- }
474
-
475
- final public function get_depth()
476
- {
477
- return $this->depth;
478
- }
479
-
480
- final public function get_customizations_locations()
481
- {
482
- return $this->customizations_locations;
483
- }
484
-
485
- final public function get_rel_path()
486
- {
487
- return $this->rel_path;
488
- }
489
-
490
- /**
491
- * Check if child extension is valid
492
- *
493
- * Used for special cases when an extension requires its child extensions to extend some special class
494
- *
495
- * @param FW_Extension $child_extension_instance
496
- * @return bool
497
- * @internal
498
- */
499
- public function _child_extension_is_valid($child_extension_instance)
500
- {
501
- return is_subclass_of($child_extension_instance, 'FW_Extension');
502
- }
503
-
504
- /**
505
- * Get link to the page created by this extension in dashboard
506
- * (Used on the extensions page)
507
- * @internal
508
- * @return string
509
- */
510
- public function _get_link()
511
- {
512
- return false;
513
- }
514
- }
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
+ $this->manifest = _FW_Component_Extensions::_get_manifest($this->get_name(), self::$access_key);
81
+ }
82
+
83
+ /**
84
+ * Cache key for this extension
85
+ *
86
+ * Usage:
87
+ * FW_Cache::get( $this->get_cache_key('/some/key') )
88
+ *
89
+ * @param string $sub_key
90
+ * @return string
91
+ */
92
+ final public function get_cache_key($sub_key = '')
93
+ {
94
+ return self::$cache_key .'/'. $this->get_name() . $sub_key;
95
+ }
96
+
97
+ /**
98
+ * @param string $name View file name (without .php) from <extension>/views directory
99
+ * @param array $view_variables Keys will be variables names within view
100
+ * @param bool $return In some cases, for memory saving reasons, you can disable the use of output buffering
101
+ * @return string HTML
102
+ */
103
+ final protected function render_view($name, $view_variables = array(), $return = true)
104
+ {
105
+ $full_path = $this->locate_path('/views/'. $name .'.php');
106
+
107
+ if (!$full_path) {
108
+ trigger_error('Extension view not found: '. $name, E_USER_WARNING);
109
+ return;
110
+ }
111
+
112
+ return fw_render_view($full_path, $view_variables, $return);
113
+ }
114
+
115
+ /**
116
+ * @internal
117
+ * @param FW_Access_Key $access_key
118
+ * @return mixed
119
+ */
120
+ final public function _call_init($access_key)
121
+ {
122
+ if ($access_key->get_key() !== 'fw_extensions') {
123
+ trigger_error(__METHOD__ .' denied', E_USER_ERROR);
124
+ }
125
+
126
+ return $this->_init();
127
+ }
128
+
129
+ /**
130
+ * Tree array with all sub extensions
131
+ * @return array
132
+ */
133
+ final public function get_tree()
134
+ {
135
+ return fw()->extensions->_get_extension_tree(self::$access_key, $this->get_name());
136
+ }
137
+
138
+ /**
139
+ * @param string $rel_path '/views/test.php'
140
+ * @return false|string '/var/www/.../extensions/<extension>/views/test.php'
141
+ */
142
+ final public function locate_path($rel_path)
143
+ {
144
+ $locations = $this->customizations_locations;
145
+ $locations[$this->get_path()] = $this->get_uri();
146
+
147
+ foreach ($locations as $path => $uri) {
148
+ if (file_exists($path . $rel_path)) {
149
+ return $path . $rel_path;
150
+ }
151
+ }
152
+
153
+ return false;
154
+ }
155
+
156
+ /**
157
+ * @param string $rel_path E.g. '/static/js/scripts.js'
158
+ * @return string URI E.g. 'http: //wordpress.com/.../extensions/<extension>/static/js/scripts.js'
159
+ */
160
+ final public function locate_URI($rel_path)
161
+ {
162
+ $locations = $this->customizations_locations;
163
+ $locations[$this->get_path()] = $this->get_uri();
164
+
165
+ foreach ($locations as $path => $uri) {
166
+ if (file_exists($path . $rel_path)) {
167
+ return $uri . $rel_path;
168
+ }
169
+ }
170
+
171
+ return false;
172
+ }
173
+
174
+ /**
175
+ * @return FW_Extension|null if has no parent extension
176
+ */
177
+ final public function get_parent()
178
+ {
179
+ return $this->parent;
180
+ }
181
+
182
+ /**
183
+ * @return string
184
+ */
185
+ final public function get_name()
186
+ {
187
+ if ($this->name === null) {
188
+ $this->name = basename($this->path);
189
+ }
190
+
191
+ return $this->name;
192
+ }
193
+
194
+ /**
195
+ * @return string
196
+ * @deprecated
197
+ */
198
+ final public function get_declared_source()
199
+ {
200
+ return 'deprecated';
201
+ }
202
+
203
+ /**
204
+ * @param string $append_rel_path E.g. '/includes/something.php'
205
+ * @return string
206
+ * @deprecated
207
+ */
208
+ final public function get_declared_path($append_rel_path = '')
209
+ {
210
+ return $this->get_path($append_rel_path);
211
+ }
212
+
213
+ final public function get_path($append_rel_path = '')
214
+ {
215
+ return $this->path . $append_rel_path;
216
+ }
217
+
218
+ /**
219
+ * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
220
+ * @return string
221
+ * @deprecated
222
+ */
223
+ final public function get_declared_URI($append_rel_path = '')
224
+ {
225
+ return $this->get_uri($append_rel_path);
226
+ }
227
+
228
+ /**
229
+ * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
230
+ * @return string
231
+ */
232
+ final public function get_uri($append_rel_path = '')
233
+ {
234
+ return $this->uri . $append_rel_path;
235
+ }
236
+
237
+ /**
238
+ * @param string $child_extension_name
239
+ * @return FW_Extension|null
240
+ */
241
+ final public function get_child($child_extension_name)
242
+ {
243
+ $active_tree = $this->get_tree();
244
+
245
+ if (isset($active_tree[$child_extension_name])) {
246
+ return fw()->extensions->get($child_extension_name);
247
+ } else {
248
+ return null;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Return all child extensions
254
+ * Only one level, not all sub levels
255
+ * @return FW_Extension[]
256
+ */
257
+ final public function get_children()
258
+ {
259
+ $active_tree = $this->get_tree();
260
+
261
+ $result = array();
262
+
263
+ foreach ($active_tree as $extension_name => &$sub_extensions) {
264
+ $result[$extension_name] = fw()->extensions->get($extension_name);
265
+ }
266
+ unset($sub_extensions);
267
+
268
+ return $result;
269
+ }
270
+
271
+ /**
272
+ * Return config key value, or entire config array
273
+ * Config array is merged from child configs
274
+ * @param string|null $key Multi key format accepted: 'a/b/c'
275
+ * @return mixed|null
276
+ */
277
+ final public function get_config($key = null)
278
+ {
279
+ $cache_key = $this->get_cache_key() .'/config';
280
+
281
+ try {
282
+ $config = FW_Cache::get($cache_key);
283
+ } catch (FW_Cache_Not_Found_Exception $e) {
284
+ $config = array();
285
+
286
+ $locations = $this->customizations_locations;
287
+ $locations[$this->get_path()] = $this->get_uri();
288
+
289
+ foreach (array_reverse($locations) as $path => $uri) {
290
+ $config_path = $path .'/config.php';
291
+
292
+ if (file_exists($config_path)) {
293
+ $variables = fw_get_variables_from_file($config_path, array('cfg' => null));
294
+
295
+ if (!empty($variables['cfg'])) {
296
+ $config = array_merge($config, $variables['cfg']);
297
+ unset($variables);
298
+ }
299
+ }
300
+ }
301
+
302
+ FW_Cache::set($cache_key, $config);
303
+ }
304
+
305
+ return $key === null ? $config : fw_call( fw_akg( $key, $config ) );
306
+ }
307
+
308
+ /**
309
+ * Return array with options from specified name/path
310
+ * @param string $name Examples: 'framework', 'posts/portfolio'
311
+ * @param array $variables These will be available in options file (like variables for view)
312
+ * @return array
313
+ */
314
+ final public function get_options($name, array $variables = array())
315
+ {
316
+ try {
317
+ return FW_Cache::get($cache_key = $this->get_cache_key('/options/'. $name));
318
+ } catch (FW_Cache_Not_Found_Exception $e) {
319
+ if ($path = $this->locate_path('/options/'. $name .'.php')) {
320
+ $variables = fw_get_variables_from_file($path, array('options' => array()), $variables);
321
+ } else {
322
+ $variables = array('options' => array());
323
+ }
324
+
325
+ FW_Cache::set($cache_key, $variables['options']);
326
+
327
+ return $variables['options'];
328
+ }
329
+ }
330
+
331
+ final public function get_settings_options()
332
+ {
333
+ try {
334
+ return FW_Cache::get($cache_key = $this->get_cache_key('/settings_options'));
335
+ } catch (FW_Cache_Not_Found_Exception $e) {
336
+ if (file_exists($path = $this->get_path('/settings-options.php'))) {
337
+ $variables = fw_get_variables_from_file($path, array('options' => array()));
338
+ } else {
339
+ $variables = array('options' => array());
340
+ }
341
+
342
+ FW_Cache::set($cache_key, $variables['options']);
343
+
344
+ return $variables['options'];
345
+ }
346
+ }
347
+
348
+ /**
349
+ * @since 2.6.9
350
+ */
351
+ final public function get_rendered_docs() {
352
+ $docs_path = $this->get_path('/readme.md.php');
353
+
354
+ if (! file_exists($docs_path)) {
355
+ return false;
356
+ }
357
+
358
+ return fw()->backend->get_markdown_parser()->text(
359
+ /**
360
+ * TODO: Perhaps send here some values in order to make extension docs
361
+ * more dynamic???
362
+ */
363
+ fw_render_view($docs_path, array())
364
+ );
365
+ }
366
+
367
+ /**
368
+ * Get extension's settings option value from the database
369
+ *
370
+ * @param string|null $option_id
371
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
372
+ * @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
373
+ *
374
+ * @return mixed|null
375
+ */
376
+ final public function get_db_settings_option( $option_id = null, $default_value = null, $get_original_value = null ) {
377
+ return fw_get_db_ext_settings_option( $this->get_name(), $option_id, $default_value, $get_original_value );
378
+ }
379
+
380
+ /**
381
+ * Set extension's setting option value in database
382
+ *
383
+ * @param string|null $option_id
384
+ * @param mixed $value
385
+ */
386
+ final public function set_db_settings_option( $option_id = null, $value ) {
387
+ fw_set_db_ext_settings_option( $this->get_name(), $option_id, $value );
388
+ }
389
+
390
+ /**
391
+ * Get extension's data from the database
392
+ *
393
+ * @param string|null $multi_key The key of the data you want to get. null - all data
394
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
395
+ * @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
396
+ *
397
+ * @return mixed|null
398
+ */
399
+ final public function get_db_data( $multi_key = null, $default_value = null, $get_original_value = null ) {
400
+ return fw_get_db_extension_data( $this->get_name(), $multi_key, $default_value, $get_original_value );
401
+ }
402
+
403
+ /**
404
+ * Set some extension's data in database
405
+ *
406
+ * @param string|null $multi_key The key of the data you want to set. null - all data
407
+ * @param mixed $value
408
+ */
409
+ final public function set_db_data( $multi_key = null, $value ) {
410
+ fw_set_db_extension_data( $this->get_name(), $multi_key, $value );
411
+ }
412
+
413
+ /**
414
+ * Get extension's data from user meta
415
+ *
416
+ * @param int $user_id
417
+ * @param string|null $keys
418
+ *
419
+ * @return mixed|null
420
+ */
421
+ final public function get_user_data( $user_id, $keys = null ) {
422
+ return fw_get_db_extension_user_data($user_id, $this->get_name(), $keys);
423
+ }
424
+
425
+ /**
426
+ * et some extension's data in user meta
427
+ *
428
+ * @param int $user_id
429
+ * @param mixed $value
430
+ * @param string|null $keys
431
+ *
432
+ * @return bool|int
433
+ */
434
+ final public function set_user_data( $user_id, $value, $keys = null ) {
435
+ return fw_set_db_extension_user_data($user_id, $this->get_name(), $value, $keys);
436
+ }
437
+
438
+ final public function get_post_options($post_type)
439
+ {
440
+ return $this->get_options('posts/'. $post_type);
441
+ }
442
+
443
+ final public function get_taxonomy_options($taxonomy)
444
+ {
445
+ return $this->get_options('taxonomies/'. $taxonomy);
446
+ }
447
+
448
+ /**
449
+ * @param string $name File name without extension, located in <extension>/static/js/$name.js
450
+ * @return string URI
451
+ */
452
+ final public function locate_js_URI($name)
453
+ {
454
+ return $this->locate_URI('/static/js/'. $name .'.js');
455
+ }
456
+
457
+ /**
458
+ * @param string $name File name without extension, located in <extension>/static/js/$name.js
459
+ * @return string URI
460
+ */
461
+ final public function locate_css_URI($name)
462
+ {
463
+ return $this->locate_URI('/static/css/'. $name .'.css');
464
+ }
465
+
466
+ /**
467
+ * @param string $name File name without extension, located in <extension>/views/$name.php
468
+ * @return false|string
469
+ */
470
+ final public function locate_view_path($name)
471
+ {
472
+ return $this->locate_path('/views/'. $name .'.php');
473
+ }
474
+
475
+ final public function get_depth()
476
+ {
477
+ return $this->depth;
478
+ }
479
+
480
+ final public function get_customizations_locations()
481
+ {
482
+ return $this->customizations_locations;
483
+ }
484
+
485
+ final public function get_rel_path()
486
+ {
487
+ return $this->rel_path;
488
+ }
489
+
490
+ /**
491
+ * Check if child extension is valid
492
+ *
493
+ * Used for special cases when an extension requires its child extensions to extend some special class
494
+ *
495
+ * @param FW_Extension $child_extension_instance
496
+ * @return bool
497
+ * @internal
498
+ */
499
+ public function _child_extension_is_valid($child_extension_instance)
500
+ {
501
+ return is_subclass_of($child_extension_instance, 'FW_Extension');
502
+ }
503
+
504
+ /**
505
+ * Get link to the page created by this extension in dashboard
506
+ * (Used on the extensions page)
507
+ * @internal
508
+ * @return string
509
+ */
510
+ public function _get_link()
511
+ {
512
+ return false;
513
+ }
514
+ }
framework/core/extends/class-fw-option-type.php CHANGED
@@ -1,453 +1,465 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Backend option
5
- */
6
- abstract class FW_Option_Type
7
- {
8
- /**
9
- * @var FW_Access_Key
10
- */
11
- private static $access_key;
12
-
13
- /**
14
- * Option's unique type, used in option array in 'type' key
15
- * @return string
16
- */
17
- abstract public function get_type();
18
-
19
- /**
20
- * Overwrite this method to enqueue scripts and styles
21
- *
22
- * This method would be abstract but was added after the framework release,
23
- * and to prevent fatal errors from new option types created by users we can't make it abstract.
24
- *
25
- * @param string $id
26
- * @param array $option
27
- * @param array $data
28
- * @param bool Return true to call this method again on the next enqueue,
29
- * if you have some functionality in it that depends on option parameters.
30
- * By default this method is called only once for performance reasons.
31
- */
32
- protected function _enqueue_static($id, $option, $data) {}
33
-
34
- /**
35
- * Generate html
36
- * @param string $id
37
- * @param array $option Option array merged with _get_defaults()
38
- * @param array $data {value => _get_value_from_input(), id_prefix => ..., name_prefix => ...}
39
- * @return string HTML
40
- * @internal
41
- */
42
- abstract protected function _render($id, $option, $data);
43
-
44
- /**
45
- * Extract correct value that will be stored in db or $option['value'] from raw form input value
46
- * If input value is empty, will be returned $option['value']
47
- * This method should be named get_db_value($form_input_value, $option)
48
- * @param array $option Option array merged with _get_defaults()
49
- * @param array|string|null $input_value
50
- * @return string|array|int|bool Correct value
51
- * @internal
52
- */
53
- abstract protected function _get_value_from_input($option, $input_value);
54
-
55
- /**
56
- * Default option array
57
- *
58
- * This makes possible an option array to have required only one parameter: array('type' => '...')
59
- * Other parameters are merged with the array returned by this method.
60
- *
61
- * @return array
62
- *
63
- * array(
64
- * 'value' => '',
65
- * ...
66
- * )
67
- * @internal
68
- */
69
- abstract protected function _get_defaults();
70
-
71
- /**
72
- * Put data for to be accessed in JavaScript for each option type instance
73
- */
74
- protected function _get_data_for_js($id, $option, $data = array()) {
75
- return array(
76
- 'option' => $option
77
- );
78
- }
79
-
80
- /**
81
- * Prevent execute enqueue multiple times
82
- * @var bool
83
- */
84
- private $static_enqueued = false;
85
-
86
- /**
87
- * Used as prefix for attribute id="{prefix}{option-id}"
88
- * @return string
89
- */
90
- final public static function get_default_id_prefix()
91
- {
92
- return fw()->backend->get_options_id_attr_prefix();
93
- }
94
-
95
- /**
96
- * Used as default prefix for attribute name="prefix[name]"
97
- * Cannot contain [], it is used for $_POST[ self::get_default_name_prefix() ]
98
- * @return string
99
- */
100
- final public static function get_default_name_prefix()
101
- {
102
- return fw()->backend->get_options_name_attr_prefix();
103
- }
104
-
105
- /**
106
- * @param FW_Access_Key $access_key
107
- * @internal
108
- * This must be called right after an instance of option type has been created
109
- * and was added to the registered array, so it is available through
110
- * fw()->backend->option_type($this->get_type())
111
- */
112
- final public function _call_init($access_key)
113
- {
114
- if ($access_key->get_key() !== 'fw_backend') {
115
- trigger_error('Method call not allowed', E_USER_ERROR);
116
- }
117
-
118
- if (method_exists($this, '_init')) {
119
- $this->_init();
120
- }
121
- }
122
-
123
- public function __construct() {
124
-
125
- }
126
-
127
- /**
128
- * Fixes and prepare defaults
129
- *
130
- * @param string $id
131
- * @param array $option
132
- * @param array $data
133
- * @return array
134
- *
135
- * @since 2.5.10
136
- */
137
- public function prepare(&$id, &$option, &$data)
138
- {
139
- $data = array_merge(
140
- array(
141
- 'id_prefix' => self::get_default_id_prefix(), // attribute id prefix
142
- 'name_prefix' => self::get_default_name_prefix(), // attribute name prefix
143
- ),
144
- $data
145
- );
146
-
147
- $defaults = $this->get_defaults();
148
- $merge_attr = !empty($option['attr']) && !empty($defaults['attr']);
149
-
150
- $option = array_merge($defaults, $option, array(
151
- 'type' => $this->get_type()
152
- ));
153
-
154
- if ($merge_attr) {
155
- $option['attr'] = array_merge($defaults['attr'], $option['attr']);
156
- }
157
-
158
- if (!isset($data['value'])) {
159
- // if no input value, use default
160
- $data['value'] = $option['value'];
161
- }
162
-
163
- if (!isset($option['attr'])) {
164
- $option['attr'] = array();
165
- }
166
-
167
- $option['attr']['name'] = $data['name_prefix'] .'['. $id .']';
168
- $option['attr']['id'] = $data['id_prefix'] . $id;
169
- $option['attr']['class'] = 'fw-option fw-option-type-'. $option['type'] .(
170
- isset($option['attr']['class'])
171
- ? ' '. $option['attr']['class']
172
- : ''
173
- );
174
- $option['attr']['value'] = is_array($option['value']) ? '' : $option['value'];
175
-
176
- /**
177
- * Remove some blacklisted attributes
178
- * They should be added only by the render method
179
- */
180
- {
181
- unset($option['attr']['type']);
182
- unset($option['attr']['checked']);
183
- unset($option['attr']['selected']);
184
- }
185
- }
186
-
187
- /**
188
- * Generate option's html from option array
189
- * @param string $id
190
- * @param array $option
191
- * @param array $data {value => $this->get_value_from_input()}
192
- * @return string HTML
193
- */
194
- final public function render($id, $option, $data = array())
195
- {
196
- $this->prepare($id, $option, $data);
197
-
198
- $this->enqueue_static($id, $option, $data);
199
-
200
- $html_attributes = array(
201
- 'class' => 'fw-backend-option-descriptor',
202
- 'data-fw-option-id' => $id,
203
- 'data-fw-option-type' => $option['type']
204
- );
205
-
206
- $data_for_js = $this->_get_data_for_js($id, $option, $data);
207
-
208
- if ($data_for_js) {
209
- $html_attributes['data-fw-for-js'] = json_encode($data_for_js);
210
- }
211
-
212
- return fw_html_tag(
213
- 'div',
214
- $html_attributes,
215
- $this->_render( $id, $this->load_callbacks( $option ), $data )
216
- );
217
- }
218
-
219
- /**
220
- * Enqueue option type scripts and styles
221
- *
222
- * All parameters are optional and will be populated with defaults
223
- * @param string $id
224
- * @param array $option
225
- * @param array $data
226
- * @return bool
227
- */
228
- final public function enqueue_static($id = '', $option = array(), $data = array())
229
- {
230
- if ($this->static_enqueued) {
231
- return false;
232
- }
233
-
234
- if (
235
- !doing_action('admin_enqueue_scripts')
236
- &&
237
- !did_action('admin_enqueue_scripts')
238
- ) {
239
- /**
240
- * Do not wp_enqueue/register_...() because at this point not all handles has been registered
241
- * and maybe they are used in dependencies in handles that are going to be enqueued.
242
- * So as a result some handles will not be equeued because of not registered dependecies.
243
- */
244
- return;
245
- }
246
-
247
- {
248
- static $option_types_static_enqueued = false;
249
-
250
- if (!$option_types_static_enqueued) {
251
- wp_enqueue_style(
252
- 'fw-option-types',
253
- fw_get_framework_directory_uri('/static/css/option-types.css'),
254
- array('fw', 'qtip'),
255
- fw()->manifest->get_version()
256
- );
257
- wp_enqueue_script(
258
- 'fw-option-types',
259
- fw_get_framework_directory_uri('/static/js/option-types.js'),
260
- array('fw-events', 'qtip', 'fw-reactive-options'),
261
- fw()->manifest->get_version(),
262
- true
263
- );
264
-
265
- $option_types_static_enqueued = true;
266
- }
267
- }
268
-
269
- $this->prepare($id, $option, $data);
270
-
271
- $call_next_time = $this->_enqueue_static($id, $option, $data);
272
-
273
- $this->static_enqueued = !$call_next_time;
274
-
275
- return $call_next_time;
276
- }
277
-
278
- /**
279
- * Extract correct value that will be stored in db or $option['value'] from raw form input value
280
- * If input value is empty, will be returned $option['value']
281
- * This method should be named get_db_value($form_input_value, $option)
282
- * @param array $option
283
- * @param mixed|null $input_value Option's value from $_POST or elsewhere. If is null, it means it does not exists
284
- * @return array|string
285
- */
286
- final public function get_value_from_input($option, $input_value)
287
- {
288
- $option = array_merge(
289
- $this->get_defaults(),
290
- $option,
291
- array(
292
- 'type' => $this->get_type()
293
- )
294
- );
295
-
296
- return $this->_get_value_from_input( $this->load_callbacks( $option ), $input_value);
297
- }
298
-
299
- /**
300
- * Default option array
301
- *
302
- * This makes possible an option array to have required only one parameter: array('type' => '...')
303
- * Other parameters are merged with array returned from this method
304
- *
305
- * @param string Multikey. Since 2.6.9
306
- * @return array
307
- */
308
- final public function get_defaults($key = null)
309
- {
310
- $option = $this->_get_defaults();
311
-
312
- $option['type'] = $this->get_type();
313
-
314
- if (!array_key_exists('value', $option)) {
315
- FW_Flash_Messages::add(
316
- 'fw-option-type-no-default-value',
317
- sprintf(__('Option type %s has no default value', 'fw'), $this->get_type()),
318
- 'warning'
319
- );
320
-
321
- $option['value'] = array();
322
- }
323
-
324
- return is_string($key) ? fw_akg($key, $option) : $option;
325
- }
326
-
327
- /**
328
- * Exist 3 types of options widths:
329
- * - auto (float left real width of the option (minimal) )
330
- * - fixed (inputs, select, textarea, and others - they have same width)
331
- * - full (100% . eg. html option should expand to maximum width)
332
- * Options can override this method to return another value
333
- * @return bool
334
- * @internal
335
- */
336
- public function _get_backend_width_type()
337
- {
338
- return 'fixed';
339
- }
340
-
341
- /**
342
- * a general purpose 'label' => false | true from options.php
343
- * @return bool | string
344
- *
345
- * @since 2.7.1
346
- */
347
- public function _default_label($id, $option) {
348
- return fw_id_to_title($id);
349
- }
350
-
351
- /**
352
- * Use this method to register a new option type
353
- *
354
- * @param string|FW_Option_Type $option_type_class
355
- */
356
- final public static function register( $option_type_class, $type = null ) {
357
- fw()->backend->_register_option_type( self::get_access_key(), $option_type_class, $type );
358
- }
359
-
360
- /**
361
- * If the option is composed of more options (added by user) which values are stored in database
362
- * the option must call fw_db_option_storage_load() for each sub-option
363
- * because some of them may have configured the save to be done in separate place (post meta, wp option, etc.)
364
- * @param string $id
365
- * @param array $option
366
- * @param mixed $value
367
- * @param array $params
368
- * @return mixed
369
- * @since 2.5.0
370
- */
371
- final public function storage_load($id, array $option, $value, array $params = array()) {
372
- if ( // do not check !empty($option['fw-storage']) because this param can be set in option defaults
373
- $this->get_type() === $option['type']
374
- &&
375
- ($option = array_merge($this->get_defaults(), $option))
376
- ) {
377
- if (is_null($value)) {
378
- $value = fw()->backend->option_type($option['type'])->get_value_from_input($option, $value);
379
- }
380
-
381
- return $this->_storage_load($id, $option, $value, $params);
382
- } else {
383
- return $value;
384
- }
385
- }
386
-
387
- /**
388
- * @see storage_load()
389
- * @param string $id
390
- * @param array $option
391
- * @param mixed $value
392
- * @param array $params
393
- * @return mixed
394
- * @since 2.5.0
395
- * @internal
396
- */
397
- protected function _storage_load($id, array $option, $value, array $params) {
398
- return fw_db_option_storage_load($id, $option, $value, $params);
399
- }
400
-
401
- /**
402
- * If the option is composed of more options (added by user) which values are stored in database
403
- * the option must call fw_db_option_storage_save() for each sub-option
404
- * because some of them may have configured the save to be done in separate place (post meta, wp option, etc.)
405
- * @param string $id
406
- * @param array $option
407
- * @param mixed $value
408
- * @param array $params
409
- * @return mixed
410
- * @since 2.5.0
411
- */
412
- final public function storage_save($id, array $option, $value, array $params = array()) {
413
- if ( // do not check !empty($option['fw-storage']) because this param can be set in option defaults
414
- $this->get_type() === $option['type']
415
- &&
416
- ($option = array_merge($this->get_defaults(), $option))
417
- ) {
418
- return $this->_storage_save($id, $option, $value, $params);
419
- } else {
420
- return $value;
421
- }
422
- }
423
-
424
- /**
425
- * @see storage_save()
426
- * @param string $id
427
- * @param array $option
428
- * @param mixed $value
429
- * @param array $params
430
- * @return mixed
431
- * @since 2.5.0
432
- * @internal
433
- */
434
- protected function _storage_save($id, array $option, $value, array $params) {
435
- return fw_db_option_storage_save($id, $option, $value, $params);
436
- }
437
-
438
- private static function get_access_key() {
439
- if ( self::$access_key === null ) {
440
- self::$access_key = new FW_Access_Key( 'fw_option_type' );
441
- }
442
-
443
- return self::$access_key;
444
- }
445
-
446
- protected function load_callbacks( $data ) {
447
- if ( ! is_array( $data ) ) {
448
- return $data;
449
- }
450
-
451
- return array_map( 'fw_call', $data );
452
- }
453
- }
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Backend option
5
+ */
6
+ abstract class FW_Option_Type
7
+ {
8
+ /**
9
+ * @var FW_Access_Key
10
+ */
11
+ private static $access_key;
12
+
13
+ /**
14
+ * Option's unique type, used in option array in 'type' key
15
+ * @return string
16
+ */
17
+ abstract public function get_type();
18
+
19
+ /**
20
+ * Overwrite this method to enqueue scripts and styles
21
+ *
22
+ * This method would be abstract but was added after the framework release,
23
+ * and to prevent fatal errors from new option types created by users we can't make it abstract.
24
+ *
25
+ * @param string $id
26
+ * @param array $option
27
+ * @param array $data
28
+ * @param bool Return true to call this method again on the next enqueue,
29
+ * if you have some functionality in it that depends on option parameters.
30
+ * By default this method is called only once for performance reasons.
31
+ */
32
+ protected function _enqueue_static($id, $option, $data) {}
33
+
34
+ /**
35
+ * Generate html
36
+ * @param string $id
37
+ * @param array $option Option array merged with _get_defaults()
38
+ * @param array $data {value => _get_value_from_input(), id_prefix => ..., name_prefix => ...}
39
+ * @return string HTML
40
+ * @internal
41
+ */
42
+ abstract protected function _render($id, $option, $data);
43
+
44
+ /**
45
+ * Extract correct value that will be stored in db or $option['value'] from raw form input value
46
+ * If input value is empty, will be returned $option['value']
47
+ * This method should be named get_db_value($form_input_value, $option)
48
+ * @param array $option Option array merged with _get_defaults()
49
+ * @param array|string|null $input_value
50
+ * @return string|array|int|bool Correct value
51
+ * @internal
52
+ */
53
+ abstract protected function _get_value_from_input($option, $input_value);
54
+
55
+ /**
56
+ * Default option array
57
+ *
58
+ * This makes possible an option array to have required only one parameter: array('type' => '...')
59
+ * Other parameters are merged with the array returned by this method.
60
+ *
61
+ * @return array
62
+ *
63
+ * array(
64
+ * 'value' => '',
65
+ * ...
66
+ * )
67
+ * @internal
68
+ */
69
+ abstract protected function _get_defaults();
70
+
71
+ /**
72
+ * Put data for to be accessed in JavaScript for each option type instance
73
+ */
74
+ protected function _get_data_for_js($id, $option, $data = array()) {
75
+ return array(
76
+ 'option' => $option
77
+ );
78
+ }
79
+
80
+ /**
81
+ * An option type can decide which design to use by default when rendering
82
+ * itself.
83
+ *
84
+ * @return
85
+ * null - will use whatever is passed based on the context
86
+ * string - will use that particular design
87
+ */
88
+ public function get_forced_render_design() {
89
+ return null;
90
+ }
91
+
92
+ /**
93
+ * Prevent execute enqueue multiple times
94
+ * @var bool
95
+ */
96
+ private $static_enqueued = false;
97
+
98
+ /**
99
+ * Used as prefix for attribute id="{prefix}{option-id}"
100
+ * @return string
101
+ */
102
+ final public static function get_default_id_prefix()
103
+ {
104
+ return fw()->backend->get_options_id_attr_prefix();
105
+ }
106
+
107
+ /**
108
+ * Used as default prefix for attribute name="prefix[name]"
109
+ * Cannot contain [], it is used for $_POST[ self::get_default_name_prefix() ]
110
+ * @return string
111
+ */
112
+ final public static function get_default_name_prefix()
113
+ {
114
+ return fw()->backend->get_options_name_attr_prefix();
115
+ }
116
+
117
+ /**
118
+ * @param FW_Access_Key $access_key
119
+ * @internal
120
+ * This must be called right after an instance of option type has been created
121
+ * and was added to the registered array, so it is available through
122
+ * fw()->backend->option_type($this->get_type())
123
+ */
124
+ final public function _call_init($access_key)
125
+ {
126
+ if ($access_key->get_key() !== 'fw_backend') {
127
+ trigger_error('Method call not allowed', E_USER_ERROR);
128
+ }
129
+
130
+ if (method_exists($this, '_init')) {
131
+ $this->_init();
132
+ }
133
+ }
134
+
135
+ public function __construct() {
136
+
137
+ }
138
+
139
+ /**
140
+ * Fixes and prepare defaults
141
+ *
142
+ * @param string $id
143
+ * @param array $option
144
+ * @param array $data
145
+ * @return array
146
+ *
147
+ * @since 2.5.10
148
+ */
149
+ public function prepare(&$id, &$option, &$data)
150
+ {
151
+ $data = array_merge(
152
+ array(
153
+ 'id_prefix' => self::get_default_id_prefix(), // attribute id prefix
154
+ 'name_prefix' => self::get_default_name_prefix(), // attribute name prefix
155
+ ),
156
+ $data
157
+ );
158
+
159
+ $defaults = $this->get_defaults();
160
+ $merge_attr = !empty($option['attr']) && !empty($defaults['attr']);
161
+
162
+ $option = array_merge($defaults, $option, array(
163
+ 'type' => $this->get_type()
164
+ ));
165
+
166
+ if ($merge_attr) {
167
+ $option['attr'] = array_merge($defaults['attr'], $option['attr']);
168
+ }
169
+
170
+ if (!isset($data['value'])) {
171
+ // if no input value, use default
172
+ $data['value'] = $option['value'];
173
+ }
174
+
175
+ if (!isset($option['attr'])) {
176
+ $option['attr'] = array();
177
+ }
178
+
179
+ $option['attr']['name'] = $data['name_prefix'] .'['. $id .']';
180
+ $option['attr']['id'] = $data['id_prefix'] . $id;
181
+ $option['attr']['class'] = 'fw-option fw-option-type-'. $option['type'] .(
182
+ isset($option['attr']['class'])
183
+ ? ' '. $option['attr']['class']
184
+ : ''
185
+ );
186
+ $option['attr']['value'] = is_array($option['value']) ? '' : $option['value'];
187
+
188
+ /**
189
+ * Remove some blacklisted attributes
190
+ * They should be added only by the render method
191
+ */
192
+ {
193
+ unset($option['attr']['type']);
194
+ unset($option['attr']['checked']);
195
+ unset($option['attr']['selected']);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Generate option's html from option array
201
+ * @param string $id
202
+ * @param array $option
203
+ * @param array $data {value => $this->get_value_from_input()}
204
+ * @return string HTML
205
+ */
206
+ final public function render($id, $option, $data = array())
207
+ {
208
+ $this->prepare($id, $option, $data);
209
+
210
+ $this->enqueue_static($id, $option, $data);
211
+
212
+ $html_attributes = array(
213
+ 'class' => 'fw-backend-option-descriptor',
214
+ 'data-fw-option-id' => $id,
215
+ 'data-fw-option-type' => $option['type']
216
+ );
217
+
218
+ $data_for_js = $this->_get_data_for_js($id, $option, $data);
219
+
220
+ if ($data_for_js) {
221
+ $html_attributes['data-fw-for-js'] = json_encode($data_for_js);
222
+ }
223
+
224
+ return fw_html_tag(
225
+ 'div',
226
+ $html_attributes,
227
+ $this->_render( $id, $this->load_callbacks( $option ), $data )
228
+ );
229
+ }
230
+
231
+ /**
232
+ * Enqueue option type scripts and styles
233
+ *
234
+ * All parameters are optional and will be populated with defaults
235
+ * @param string $id
236
+ * @param array $option
237
+ * @param array $data
238
+ * @return bool
239
+ */
240
+ final public function enqueue_static($id = '', $option = array(), $data = array())
241
+ {
242
+ if ($this->static_enqueued) {
243
+ return false;
244
+ }
245
+
246
+ if (
247
+ !doing_action('admin_enqueue_scripts')
248
+ &&
249
+ !did_action('admin_enqueue_scripts')
250
+ ) {
251
+ /**
252
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
253
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
254
+ * So as a result some handles will not be equeued because of not registered dependecies.
255
+ */
256
+ return;
257
+ }
258
+
259
+ {
260
+ static $option_types_static_enqueued = false;
261
+
262
+ if (!$option_types_static_enqueued) {
263
+ wp_enqueue_style(
264
+ 'fw-option-types',
265
+ fw_get_framework_directory_uri('/static/css/option-types.css'),
266
+ array('fw', 'qtip'),
267
+ fw()->manifest->get_version()
268
+ );
269
+ wp_enqueue_script(
270
+ 'fw-option-types',
271
+ fw_get_framework_directory_uri('/static/js/option-types.js'),
272
+ array('fw-events', 'qtip', 'fw-reactive-options'),
273
+ fw()->manifest->get_version(),
274
+ true
275
+ );
276
+
277
+ $option_types_static_enqueued = true;
278
+ }
279
+ }
280
+
281
+ $this->prepare($id, $option, $data);
282
+
283
+ $call_next_time = $this->_enqueue_static($id, $option, $data);
284
+
285
+ $this->static_enqueued = !$call_next_time;
286
+
287
+ return $call_next_time;
288
+ }
289
+
290
+ /**
291
+ * Extract correct value that will be stored in db or $option['value'] from raw form input value
292
+ * If input value is empty, will be returned $option['value']
293
+ * This method should be named get_db_value($form_input_value, $option)
294
+ * @param array $option
295
+ * @param mixed|null $input_value Option's value from $_POST or elsewhere. If is null, it means it does not exists
296
+ * @return array|string
297
+ */
298
+ final public function get_value_from_input($option, $input_value)
299
+ {
300
+ $option = array_merge(
301
+ $this->get_defaults(),
302
+ $option,
303
+ array(
304
+ 'type' => $this->get_type()
305
+ )
306
+ );
307
+
308
+ return $this->_get_value_from_input( $this->load_callbacks( $option ), $input_value);
309
+ }
310
+
311
+ /**
312
+ * Default option array
313
+ *
314
+ * This makes possible an option array to have required only one parameter: array('type' => '...')
315
+ * Other parameters are merged with array returned from this method
316
+ *
317
+ * @param string Multikey. Since 2.6.9
318
+ * @return array
319
+ */
320
+ final public function get_defaults($key = null)
321
+ {
322
+ $option = $this->_get_defaults();
323
+
324
+ $option['type'] = $this->get_type();
325
+
326
+ if (!array_key_exists('value', $option)) {
327
+ FW_Flash_Messages::add(
328
+ 'fw-option-type-no-default-value',
329
+ sprintf(__('Option type %s has no default value', 'fw'), $this->get_type()),
330
+ 'warning'
331
+ );
332
+
333
+ $option['value'] = array();
334
+ }
335
+
336
+ return is_string($key) ? fw_akg($key, $option) : $option;
337
+ }
338
+
339
+ /**
340
+ * Exist 3 types of options widths:
341
+ * - auto (float left real width of the option (minimal) )
342
+ * - fixed (inputs, select, textarea, and others - they have same width)
343
+ * - full (100% . eg. html option should expand to maximum width)
344
+ * Options can override this method to return another value
345
+ * @return bool
346
+ * @internal
347
+ */
348
+ public function _get_backend_width_type()
349
+ {
350
+ return 'fixed';
351
+ }
352
+
353
+ /**
354
+ * a general purpose 'label' => false | true from options.php
355
+ * @return bool | string
356
+ *
357
+ * @since 2.7.1
358
+ */
359
+ public function _default_label($id, $option) {
360
+ return fw_id_to_title($id);
361
+ }
362
+
363
+ /**
364
+ * Use this method to register a new option type
365
+ *
366
+ * @param string|FW_Option_Type $option_type_class
367
+ */
368
+ final public static function register( $option_type_class, $type = null ) {
369
+ fw()->backend->_register_option_type( self::get_access_key(), $option_type_class, $type );
370
+ }
371
+
372
+ /**
373
+ * If the option is composed of more options (added by user) which values are stored in database
374
+ * the option must call fw_db_option_storage_load() for each sub-option
375
+ * because some of them may have configured the save to be done in separate place (post meta, wp option, etc.)
376
+ * @param string $id
377
+ * @param array $option
378
+ * @param mixed $value
379
+ * @param array $params
380
+ * @return mixed
381
+ * @since 2.5.0
382
+ */
383
+ final public function storage_load($id, array $option, $value, array $params = array()) {
384
+ if ( // do not check !empty($option['fw-storage']) because this param can be set in option defaults
385
+ $this->get_type() === $option['type']
386
+ &&
387
+ ($option = array_merge($this->get_defaults(), $option))
388
+ ) {
389
+ if (is_null($value)) {
390
+ $value = fw()->backend->option_type($option['type'])->get_value_from_input($option, $value);
391
+ }
392
+
393
+ return $this->_storage_load($id, $option, $value, $params);
394
+ } else {
395
+ return $value;
396
+ }
397
+ }
398
+
399
+ /**
400
+ * @see storage_load()
401
+ * @param string $id
402
+ * @param array $option
403
+ * @param mixed $value
404
+ * @param array $params
405
+ * @return mixed
406
+ * @since 2.5.0
407
+ * @internal
408
+ */
409
+ protected function _storage_load($id, array $option, $value, array $params) {
410
+ return fw_db_option_storage_load($id, $option, $value, $params);
411
+ }
412
+
413
+ /**
414
+ * If the option is composed of more options (added by user) which values are stored in database
415
+ * the option must call fw_db_option_storage_save() for each sub-option
416
+ * because some of them may have configured the save to be done in separate place (post meta, wp option, etc.)
417
+ * @param string $id
418
+ * @param array $option
419
+ * @param mixed $value
420
+ * @param array $params
421
+ * @return mixed
422
+ * @since 2.5.0
423
+ */
424
+ final public function storage_save($id, array $option, $value, array $params = array()) {
425
+ if ( // do not check !empty($option['fw-storage']) because this param can be set in option defaults
426
+ $this->get_type() === $option['type']
427
+ &&
428
+ ($option = array_merge($this->get_defaults(), $option))
429
+ ) {
430
+ return $this->_storage_save($id, $option, $value, $params);
431
+ } else {
432
+ return $value;
433
+ }
434
+ }
435
+
436
+ /**
437
+ * @see storage_save()
438
+ * @param string $id
439
+ * @param array $option
440
+ * @param mixed $value
441
+ * @param array $params
442
+ * @return mixed
443
+ * @since 2.5.0
444
+ * @internal
445
+ */
446
+ protected function _storage_save($id, array $option, $value, array $params) {
447
+ return fw_db_option_storage_save($id, $option, $value, $params);
448
+ }
449
+
450
+ private static function get_access_key() {
451
+ if ( self::$access_key === null ) {
452
+ self::$access_key = new FW_Access_Key( 'fw_option_type' );
453
+ }
454
+
455
+ return self::$access_key;
456
+ }
457
+
458
+ protected function load_callbacks( $data ) {
459
+ if ( ! is_array( $data ) ) {
460
+ return $data;
461
+ }
462
+
463
+ return array_map( 'fw_call', $data );
464
+ }
465
+ }
framework/core/extends/interface-fw-option-handler.php CHANGED
@@ -1,13 +1,13 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * @deprecated since 2.5.0
5
- * Will be removed soon https://github.com/ThemeFuse/Unyson/issues/1937
6
- */
7
- interface FW_Option_Handler
8
- {
9
- function get_option_value($option_id, $option, $data = array());
10
-
11
- function save_option_value($option_id, $option, $value, $data = array());
12
- }
13
-
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * @deprecated since 2.5.0
5
+ * Will be removed soon https://github.com/ThemeFuse/Unyson/issues/1937
6
+ */
7
+ interface FW_Option_Handler
8
+ {
9
+ function get_option_value($option_id, $option, $data = array());
10
+
11
+ function save_option_value($option_id, $option, $value, $data = array());
12
+ }
13
+
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.2';
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.2';
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,963 +1,1005 @@
1
- <?php defined( 'FW' ) or 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
- * {@inheritdoc}
8
- */
9
- public function _child_extension_is_valid( $child_extension_instance ) {
10
- return is_subclass_of( $child_extension_instance, 'FW_Ext_Update_Service' );
11
- }
12
-
13
- /**
14
- * File names to skip (do not delete or change) during the update process
15
- * @var array
16
- */
17
- private $skip_file_names = array( '.git' );
18
-
19
- /**
20
- * @internal
21
- */
22
- protected function _init() {
23
-
24
- if ( ! current_user_can( 'update_plugins' ) ) {
25
- return false;
26
- }
27
-
28
- $this->add_actions();
29
- $this->add_filters();
30
-
31
- return true;
32
- }
33
-
34
- private function add_actions() {
35
-
36
- add_action( 'admin_notices', array( $this, '_action_admin_notices' ) );
37
- add_action( 'network_admin_notices', array( $this, '_action_admin_notices' ) );
38
-
39
- add_action( 'core_upgrade_preamble', array( $this, '_action_updates_page_footer' ) );
40
- add_action( 'update-core-custom_' . 'fw-update-framework', array( $this, '_action_update_framework' ) );
41
- add_action( 'update-core-custom_' . 'fw-update-theme', array( $this, '_action_update_theme' ) );
42
- add_action( 'update-core-custom_' . 'fw-update-extensions', array( $this, '_action_update_extensions' ) );
43
- }
44
-
45
- private function add_filters() {
46
- add_filter( 'wp_get_update_data', array( $this, '_filter_update_data' ), 10, 2 );
47
- }
48
-
49
- private function get_fixed_version( $version ) {
50
- // remove from the beginning everything that is not a number: 'v1.2.3' -> '1.2.3', 'ver1.0.0' -> '1.0.0'
51
- return preg_replace( '/^[^0-9]+/i', '', $version );;
52
- }
53
-
54
- private function get_wp_fs_tmp_dir() {
55
- return FW_WP_Filesystem::real_path_to_filesystem_path(
56
- apply_filters( 'fw_tmp_dir', fw_fix_path( WP_CONTENT_DIR ) . '/tmp' )
57
- );
58
- }
59
-
60
- /**
61
- * @internal
62
- */
63
- public function _action_updates_page_footer() {
64
- echo $this->render_view( 'updates-list', array(
65
- 'updates' => $this->get_updates( ! empty( $_GET['force-check'] ) )
66
- ) );
67
- }
68
-
69
- /**
70
- * @internal
71
- */
72
- public function _filter_update_data( $data, $titles ) {
73
- $updates = $this->get_updates( ! empty( $_GET['force-check'] ) );
74
-
75
- if ( $updates['framework'] && ! is_wp_error( $updates['framework'] ) ) {
76
- ++ $data['counts']['total'];
77
- }
78
-
79
- if ( $updates['theme'] && ! is_wp_error( $updates['theme'] ) ) {
80
- ++ $data['counts']['total'];
81
- }
82
-
83
- if ( ! empty( $updates['extensions'] ) ) {
84
- foreach ( $updates['extensions'] as $ext_name => $ext_update ) {
85
- if ( is_wp_error( $ext_update ) ) {
86
- continue;
87
- }
88
-
89
- ++ $data['counts']['total'];
90
-
91
- if ( $this->get_config( 'extensions_as_one_update' ) ) {
92
- // no matter how many extensions, display as one update
93
- break;
94
- }
95
- }
96
-
97
- $title = esc_html__( 'Available extensions updates.', 'fw' );
98
-
99
- $data['title'] = empty( $data['title'] ) ? $title : $data['title'] . ', ' . $title;
100
- }
101
-
102
- return $data;
103
- }
104
-
105
- private function get_updates( $force_check = false ) {
106
- $cache_key = 'fw_ext_update/updates';
107
-
108
- // use cache because this method may be called multiple times (to prevent useless requests to update servers)
109
- try {
110
- return FW_Cache::get( $cache_key );
111
- } catch ( FW_Cache_Not_Found_Exception $e ) {
112
- $updates = array(
113
- 'framework' => $this->get_framework_update( $force_check ),
114
- 'theme' => $this->get_theme_update( $force_check ),
115
- 'extensions' => $this->get_extensions_with_updates( $force_check )
116
- );
117
-
118
- FW_Cache::set( $cache_key, $updates );
119
-
120
- return $updates;
121
- }
122
- }
123
-
124
- /**
125
- * Collect extensions that has new versions available
126
- *
127
- * @param bool $force_check
128
- *
129
- * @return array {ext_name => update_data}
130
- */
131
- private function get_extensions_with_updates( $force_check = false ) {
132
- $updates = array();
133
- $services = $this->get_children();
134
- $theme_ext_requirements = fw()->theme->manifest->get( 'requirements/extensions' );
135
-
136
- foreach ( fw()->extensions->get_all() as $ext_name => $extension ) {
137
- /** @var FW_Extension $extension */
138
-
139
- /**
140
- * Ask each service if it knows how to update the extension
141
- */
142
- foreach ( $services as $service_name => $service ) {
143
- /** @var $service FW_Ext_Update_Service */
144
-
145
- $latest_version = $service->_get_extension_latest_version( $extension, $force_check );
146
-
147
- if ( $latest_version === false ) {
148
- // It said that it doesn't know how to update it
149
- continue;
150
- }
151
-
152
- if ( is_wp_error( $latest_version ) ) {
153
- $updates[ $ext_name ] = $latest_version;
154
- break;
155
- }
156
-
157
- $fixed_latest_version = $this->get_fixed_version( $latest_version );
158
-
159
- if ( ! version_compare( $fixed_latest_version, $extension->manifest->get_version(), '>' ) ) {
160
- // we already have latest version
161
- continue;
162
- }
163
-
164
- if (
165
- isset( $theme_ext_requirements[ $ext_name ] )
166
- &&
167
- isset( $theme_ext_requirements[ $ext_name ]['max_version'] )
168
- &&
169
- version_compare( $fixed_latest_version, $theme_ext_requirements[ $ext_name ]['max_version'], '>' )
170
- ) {
171
- continue; // do not allow update if it exceeds max_version
172
- }
173
-
174
- $updates[ $ext_name ] = array(
175
- 'service' => $service_name,
176
- 'latest_version' => $latest_version,
177
- 'fixed_latest_version' => $fixed_latest_version
178
- );
179
-
180
- break;
181
- }
182
- }
183
-
184
- return $updates;
185
- }
186
-
187
- /**
188
- * @param bool $force_check
189
- *
190
- * @return array|false|WP_Error
191
- */
192
- private function get_framework_update( $force_check = false ) {
193
- /**
194
- * Ask each service if it knows how to update the framework
195
- */
196
- foreach ( $this->get_children() as $service_name => $service ) {
197
- /** @var $service FW_Ext_Update_Service */
198
-
199
- $latest_version = $service->_get_framework_latest_version( $force_check );
200
-
201
- if ( $latest_version === false ) {
202
- // It said that it doesn't know how to update it
203
- continue;
204
- }
205
-
206
- if ( is_wp_error( $latest_version ) ) {
207
- return $latest_version;
208
- }
209
-
210
- $fixed_latest_version = $this->get_fixed_version( $latest_version );
211
-
212
- if ( ! version_compare( $fixed_latest_version, fw()->manifest->get_version(), '>' ) ) {
213
- // we already have latest version
214
- continue;
215
- }
216
-
217
- return array(
218
- 'service' => $service_name,
219
- 'latest_version' => $latest_version,
220
- 'fixed_latest_version' => $fixed_latest_version
221
- );
222
- }
223
-
224
- return false;
225
- }
226
-
227
- /**
228
- * @param bool $force_check
229
- *
230
- * @return array|false|WP_Error
231
- */
232
- private function get_theme_update( $force_check = false ) {
233
- /**
234
- * Ask each service if it knows how to update the theme
235
- */
236
- foreach ( $this->get_children() as $service_name => $service ) {
237
- /** @var $service FW_Ext_Update_Service */
238
-
239
- $latest_version = $service->_get_theme_latest_version( $force_check );
240
-
241
- if ( $latest_version === false ) {
242
- // It said that it doesn't know how to update it
243
- continue;
244
- }
245
-
246
- if ( is_wp_error( $latest_version ) ) {
247
- return $latest_version;
248
- }
249
-
250
- $fixed_latest_version = $this->get_fixed_version( $latest_version );
251
-
252
- if ( ! version_compare( $fixed_latest_version, fw()->theme->manifest->get_version(), '>' ) ) {
253
- // we already have latest version
254
- continue;
255
- }
256
-
257
- return array(
258
- 'service' => $service_name,
259
- 'latest_version' => $latest_version,
260
- 'fixed_latest_version' => $fixed_latest_version
261
- );
262
- }
263
-
264
- return false;
265
- }
266
-
267
- /**
268
- * Turn on/off the maintenance mode
269
- *
270
- * @param bool $enable
271
- */
272
- private function maintenance_mode( $enable = false ) {
273
- /** @var WP_Filesystem_Base $wp_filesystem */
274
- global $wp_filesystem;
275
-
276
- if ( ! $wp_filesystem || ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) ) {
277
- return;
278
- }
279
-
280
- $file_path = $wp_filesystem->abspath() . '.maintenance';
281
-
282
- if ( $wp_filesystem->exists( $file_path ) ) {
283
- if ( ! $wp_filesystem->delete( $file_path ) ) {
284
- trigger_error( __( 'Cannot delete: ', 'fw' ) . $file_path, E_USER_WARNING );
285
- }
286
- }
287
-
288
- if ( $enable ) {
289
- // Create maintenance file to signal that we are upgrading
290
- if ( ! $wp_filesystem->put_contents( $file_path, '<?php $upgrading = ' . time() . '; ?>', FS_CHMOD_FILE ) ) {
291
- trigger_error( __( 'Cannot create: ', 'fw' ) . $file_path, E_USER_WARNING );
292
- }
293
- }
294
- }
295
-
296
- /**
297
- * Download and install new version files
298
- *
299
- * global $wp_filesystem; must be initialized before calling this method
300
- *
301
- * @param array $data
302
- * @param bool $merge_extensions The extensions/ directory will not be replaced entirely,
303
- * only extensions that comes with the update will be replaced
304
- *
305
- * @return null|WP_Error
306
- */
307
- private function update( $data, $merge_extensions = false ) {
308
- $required_data_keys = array(
309
- 'wp_fs_destination_dir' => true,
310
- 'download_callback' => true,
311
- 'download_callback_args' => true,
312
- 'skin' => true,
313
- 'title' => true,
314
- );
315
-
316
- if ( count( $required_data_keys ) > count( array_intersect_key( $required_data_keys, $data ) ) ) {
317
- trigger_error( 'Some required keys are not present', E_USER_ERROR );
318
- }
319
-
320
- // move manually every key to variable, so IDE will understand better them
321
- {
322
- /**
323
- * Replace all files in this directory with downloaded
324
- * @var string $wp_fs_destination_dir
325
- */
326
- $wp_fs_destination_dir = $data['wp_fs_destination_dir'];
327
-
328
- /**
329
- * Called to download new version files to $this->get_wp_fs_tmp_dir()
330
- * @var callable $download_callback
331
- */
332
- $download_callback = $data['download_callback'];
333
-
334
- /**
335
- * @var array
336
- */
337
- $download_callback_args = $data['download_callback_args'];
338
-
339
- /**
340
- * @var WP_Upgrader_Skin $skin
341
- */
342
- $skin = $data['skin'];
343
-
344
- /**
345
- * Used in text messages
346
- * @var string $title
347
- */
348
- $title = $data['title'];
349
-
350
- unset( $data );
351
- }
352
-
353
- /**
354
- * @var string|WP_Error
355
- */
356
- $error = false;
357
-
358
- $tmp_download_dir = $this->get_wp_fs_tmp_dir();
359
-
360
- do {
361
- /** @var WP_Filesystem_Base $wp_filesystem */
362
- global $wp_filesystem;
363
-
364
- // create temporary directory
365
- {
366
- if ( $wp_filesystem->exists( $tmp_download_dir ) ) {
367
- // just in case it already exists, clear everything, it may contain old files
368
- if ( ! $wp_filesystem->rmdir( $tmp_download_dir, true ) ) {
369
- $error = __( 'Cannot remove old temporary directory: ', 'fw' ) . $tmp_download_dir;
370
- break;
371
- }
372
- }
373
-
374
- if ( ! FW_WP_Filesystem::mkdir_recursive( $tmp_download_dir ) ) {
375
- $error = __( 'Cannot create directory: ', 'fw' ) . $tmp_download_dir;
376
- break;
377
- }
378
- }
379
-
380
- $skin->feedback( sprintf( __( 'Downloading the %s...', 'fw' ), $title ) );
381
- {
382
- $downloaded_dir = call_user_func_array( $download_callback, $download_callback_args );
383
-
384
- if ( ! $downloaded_dir ) {
385
- $error = sprintf( __( 'Cannot download the %s.', 'fw' ), $title );
386
- break;
387
- } elseif ( is_wp_error( $downloaded_dir ) ) {
388
- $error = $downloaded_dir;
389
- break;
390
- }
391
- }
392
-
393
- $this->maintenance_mode( true );
394
-
395
- $skin->feedback( sprintf( __( 'Installing the %s...', 'fw' ), $title ) );
396
- {
397
- // remove all files from destination directory
398
- {
399
- $dir_files = $wp_filesystem->dirlist( $wp_fs_destination_dir, true );
400
- if ( $dir_files === false ) {
401
- $error = __( 'Cannot access directory: ', 'fw' ) . $wp_fs_destination_dir;
402
- break;
403
- }
404
-
405
- foreach ( $dir_files as $file ) {
406
- if ( in_array( $file['name'], $this->skip_file_names ) ) {
407
- continue;
408
- }
409
-
410
- if ( $merge_extensions ) {
411
- if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
412
- // do not remove extensions, will be merged later
413
- continue;
414
- }
415
- }
416
-
417
- $file_path = $wp_fs_destination_dir . '/' . $file['name'];
418
-
419
- if ( ! $wp_filesystem->delete( $file_path, true, $file['type'] ) ) {
420
- $error = __( 'Cannot remove: ', 'fw' ) . $file_path;
421
- break 2;
422
- }
423
- }
424
- }
425
-
426
- // move all files from the temporary directory to the destination directory
427
- {
428
- $dir_files = $wp_filesystem->dirlist( $downloaded_dir, true );
429
- if ( $dir_files === false ) {
430
- $error = __( 'Cannot access directory: ', 'fw' ) . $downloaded_dir;
431
- break;
432
- }
433
-
434
- foreach ( $dir_files as $file ) {
435
- if ( in_array( $file['name'], $this->skip_file_names ) ) {
436
- continue;
437
- }
438
-
439
- $downloaded_file_path = $downloaded_dir . '/' . $file['name'];
440
- $destination_file_path = $wp_fs_destination_dir . '/' . $file['name'];
441
-
442
- if ( $merge_extensions ) {
443
- if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
444
- // merge extensions/ after all other files was moved
445
- $merge_extensions_data = array(
446
- 'source' => $downloaded_file_path,
447
- 'destination' => $destination_file_path,
448
- );
449
- continue;
450
- }
451
- }
452
-
453
- if ( ! $wp_filesystem->move( $downloaded_file_path, $destination_file_path ) ) {
454
- $error = sprintf(
455
- __( 'Cannot move "%s" to "%s"', 'fw' ),
456
- $downloaded_file_path, $destination_file_path
457
- );
458
- break 2;
459
- }
460
- }
461
-
462
- if ( $merge_extensions ) {
463
- if ( ! empty( $merge_extensions_data ) ) {
464
- $merge_result = $this->merge_extensions(
465
- $merge_extensions_data['source'],
466
- $merge_extensions_data['destination']
467
- );
468
-
469
- if ( $merge_result === false ) {
470
- $error = sprintf(
471
- __( 'Cannot merge "%s" with "%s"', 'fw' ),
472
- $downloaded_file_path, $destination_file_path
473
- );
474
- break;
475
- } elseif ( is_wp_error( $merge_result ) ) {
476
- $error = $merge_result;
477
- break;
478
- }
479
- }
480
- }
481
- }
482
- }
483
-
484
- $skin->feedback( sprintf( __( 'The %s has been successfully updated.', 'fw' ), $title ) );
485
- } while ( false );
486
-
487
- $this->maintenance_mode( false );
488
-
489
- if ( $wp_filesystem->exists( $tmp_download_dir ) ) {
490
- if ( ! $wp_filesystem->delete( $tmp_download_dir, true, 'd' ) ) {
491
- $error = sprintf( __( 'Cannot remove temporary directory "%s".', 'fw' ), $tmp_download_dir );
492
- }
493
- }
494
-
495
- if ( $error ) {
496
- if ( ! is_wp_error( $error ) ) {
497
- $error = new WP_Error( 'fw_ext_update_failed', (string) $error );
498
- }
499
-
500
- return $error;
501
- }
502
- }
503
-
504
- /**
505
- * Merge two extensions/ directories
506
- *
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
- *
510
- * @return bool|WP_Error
511
- */
512
- private function merge_extensions( $source_dir, $destination_dir ) {
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
-
526
- return true;
527
- }
528
-
529
- $source_ext_dirs = $wp_filesystem->dirlist( $source_dir, true );
530
- if ( $source_ext_dirs === false ) {
531
- return new WP_Error( $wp_error_id,
532
- __( 'Cannot access directory: ', 'fw' ) . $source_dir
533
- );
534
- }
535
-
536
- foreach ( $source_ext_dirs as $ext_dir ) {
537
- if ( in_array( $ext_dir['name'], $this->skip_file_names ) ) {
538
- continue;
539
- }
540
-
541
- if ( $ext_dir['type'] !== 'd' ) {
542
- // process only directories from the extensions/ directory
543
- continue;
544
- }
545
-
546
- $source_extension_dir = $source_dir . '/' . $ext_dir['name'];
547
- $destination_extension_dir = $destination_dir . '/' . $ext_dir['name'];
548
-
549
- {
550
- $source_ext_files = $wp_filesystem->dirlist( $source_extension_dir, true );
551
- if ( $source_ext_files === false ) {
552
- return new WP_Error( $wp_error_id,
553
- __( 'Cannot access directory: ', 'fw' ) . $source_extension_dir
554
- );
555
- }
556
-
557
- if ( empty( $source_ext_files ) ) {
558
- /**
559
- * Source extension directory is empty, do nothing.
560
- * This happens when the extension is a git submodule in repository
561
- * but in zip it comes as an empty directory.
562
- */
563
- continue;
564
- }
565
- }
566
-
567
- // prepare destination
568
- {
569
- // create if not exists
570
- if ( ! $wp_filesystem->exists( $destination_extension_dir ) ) {
571
- if ( ! FW_WP_Filesystem::mkdir_recursive( $destination_extension_dir ) ) {
572
- return new WP_Error( $wp_error_id,
573
- __( 'Cannot create directory: ', 'fw' ) . $destination_extension_dir
574
- );
575
- }
576
- }
577
-
578
- // remove everything except the extensions/ dir
579
- {
580
- $dest_ext_files = $wp_filesystem->dirlist( $destination_extension_dir, true );
581
- if ( $dest_ext_files === false ) {
582
- return new WP_Error( $wp_error_id,
583
- __( 'Cannot access directory: ', 'fw' ) . $destination_extension_dir
584
- );
585
- }
586
-
587
- $destination_has_extensions_dir = false;
588
-
589
- foreach ( $dest_ext_files as $dest_ext_file ) {
590
- if ( in_array( $dest_ext_file['name'], $this->skip_file_names ) ) {
591
- continue;
592
- }
593
-
594
- if ( $dest_ext_file['name'] === 'extensions' && $dest_ext_file['type'] === 'd' ) {
595
- $destination_has_extensions_dir = true;
596
- continue;
597
- }
598
-
599
- $dest_ext_file_path = $destination_extension_dir . '/' . $dest_ext_file['name'];
600
-
601
- if ( ! $wp_filesystem->delete( $dest_ext_file_path, true, $dest_ext_file['type'] ) ) {
602
- return new WP_Error( $wp_error_id,
603
- __( 'Cannot delete: ', 'fw' ) . $dest_ext_file_path
604
- );
605
- }
606
- }
607
- }
608
- }
609
-
610
- // move files from source to destination extension directory
611
- {
612
- $source_has_extensions_dir = false;
613
-
614
- foreach ( $source_ext_files as $source_ext_file ) {
615
- if ( in_array( $source_ext_file['name'], $this->skip_file_names ) ) {
616
- continue;
617
- }
618
-
619
- if ( $source_ext_file['name'] === 'extensions' && $source_ext_file['type'] === 'd' ) {
620
- $source_has_extensions_dir = true;
621
- continue;
622
- }
623
-
624
- $source_ext_file_path = $source_extension_dir . '/' . $source_ext_file['name'];
625
- $dest_ext_file_path = $destination_extension_dir . '/' . $source_ext_file['name'];
626
-
627
- if ( ! $wp_filesystem->move( $source_ext_file_path, $dest_ext_file_path ) ) {
628
- return new WP_Error( $wp_error_id,
629
- sprintf( __( 'Cannot move "%s" to "%s"', 'fw' ),
630
- $source_ext_file_path, $dest_ext_file_path
631
- )
632
- );
633
- }
634
- }
635
- }
636
-
637
- if ( $source_has_extensions_dir ) {
638
- if ( $destination_has_extensions_dir ) {
639
- $merge_result = $this->merge_extensions(
640
- $source_extension_dir . '/extensions',
641
- $destination_extension_dir . '/extensions'
642
- );
643
-
644
- if ( $merge_result !== true ) {
645
- return $merge_result;
646
- }
647
- } else {
648
- if ( ! $wp_filesystem->move(
649
- $source_extension_dir . '/extensions',
650
- $destination_extension_dir . '/extensions'
651
- ) ) {
652
- return new WP_Error( $wp_error_id,
653
- sprintf( __( 'Cannot move "%s" to "%s"', 'fw' ),
654
- $source_extension_dir . '/extensions',
655
- $destination_extension_dir . '/extensions'
656
- )
657
- );
658
- }
659
- }
660
- }
661
- }
662
-
663
- return true;
664
- }
665
-
666
- /**
667
- * @internal
668
- */
669
- public function _action_update_framework() {
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
- $nonce_name = '_nonce_fw_ext_update_theme';
738
- if ( ! isset( $_POST[ $nonce_name ] ) || ! wp_verify_nonce( $_POST[ $nonce_name ] ) ) {
739
- wp_die( __( 'Invalid nonce.', 'fw' ) );
740
- }
741
-
742
- {
743
- if ( ! class_exists( '_FW_Ext_Update_Theme_Upgrader_Skin' ) ) {
744
- fw_include_file_isolated(
745
- $this->get_declared_path( '/includes/classes/class--fw-ext-update-theme-upgrader-skin.php' )
746
- );
747
- }
748
-
749
- $skin = new _FW_Ext_Update_Theme_Upgrader_Skin( array(
750
- 'title' => __( 'Theme Update', 'fw' ),
751
- ) );
752
- }
753
-
754
- require_once( ABSPATH . 'wp-admin/admin-header.php' );
755
-
756
- $skin->header();
757
-
758
- do {
759
- if ( ! FW_WP_Filesystem::request_access( get_template_directory(), fw_current_url(), array( $nonce_name ) ) ) {
760
- break;
761
- }
762
-
763
- $update = $this->get_theme_update();
764
-
765
- if ( $update === false ) {
766
- $skin->error( __( 'Failed to get theme latest version.', 'fw' ) );
767
- break;
768
- } elseif ( is_wp_error( $update ) ) {
769
- $skin->error( $update );
770
- break;
771
- }
772
-
773
- /** @var FW_Ext_Update_Service $service */
774
- $service = $this->get_child( $update['service'] );
775
-
776
- $update_result = $this->update( array(
777
- 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
778
- get_template_directory()
779
- ),
780
- 'download_callback' => array( $service, '_download_theme' ),
781
- 'download_callback_args' => array( $update['latest_version'], $this->get_wp_fs_tmp_dir() ),
782
- 'skin' => $skin,
783
- 'title' => __( 'Theme', 'fw' ),
784
- ) );
785
-
786
- if ( is_wp_error( $update_result ) ) {
787
- $skin->error( $update_result );
788
- break;
789
- }
790
-
791
- $skin->set_result( true );
792
- $skin->after();
793
- } while ( false );
794
-
795
- $skin->footer();
796
-
797
- require_once( ABSPATH . 'wp-admin/admin-footer.php' );
798
- }
799
-
800
- /**
801
- * @internal
802
- */
803
- public function _action_update_extensions() {
804
- $nonce_name = '_nonce_fw_ext_update_extensions';
805
- if ( ! isset( $_POST[ $nonce_name ] ) || ! wp_verify_nonce( $_POST[ $nonce_name ] ) ) {
806
- wp_die( __( 'Invalid nonce.', 'fw' ) );
807
- }
808
-
809
- $form_input_name = 'extensions';
810
- $extensions_list = FW_Request::POST( $form_input_name );
811
-
812
- if ( empty( $extensions_list ) ) {
813
- FW_Flash_Messages::add(
814
- 'fw_ext_update',
815
- __( 'Please check the extensions you want to update.', 'fw' ),
816
- 'warning'
817
- );
818
- wp_redirect( self_admin_url( 'update-core.php' ) );
819
- exit;
820
- }
821
-
822
- // handle changes by the hack below
823
- {
824
- if ( is_string( $extensions_list ) ) {
825
- $extensions_list = json_decode( $extensions_list );
826
- } else {
827
- $extensions_list = array_keys( $extensions_list );
828
- }
829
- }
830
-
831
- {
832
- if ( ! class_exists( '_FW_Ext_Update_Extensions_Upgrader_Skin' ) ) {
833
- fw_include_file_isolated(
834
- $this->get_declared_path( '/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php' )
835
- );
836
- }
837
-
838
- $skin = new _FW_Ext_Update_Extensions_Upgrader_Skin( array(
839
- 'title' => __( 'Extensions Update', 'fw' ),
840
- ) );
841
- }
842
-
843
- require_once( ABSPATH . 'wp-admin/admin-header.php' );
844
-
845
- $skin->header();
846
-
847
- do {
848
- /**
849
- * Hack for the ftp credentials template that does not support array post values
850
- * https://github.com/WordPress/WordPress/blob/3949a8b6cc50a021ed93798287b4ef9ea8a560d9/wp-admin/includes/file.php#L1144
851
- */
852
- $original_post_value = $_POST[ $form_input_name ];
853
- $_POST[ $form_input_name ] = wp_slash( json_encode( $extensions_list ) );
854
-
855
- if ( ! FW_WP_Filesystem::request_access( fw_get_framework_directory( '/extensions' ), fw_current_url(), array( $nonce_name, $form_input_name ) ) ) {
856
- // revert hack changes
857
- $_POST[ $form_input_name ] = $original_post_value;
858
- unset( $original_post_value );
859
-
860
- break;
861
- }
862
-
863
- // revert hack changes
864
- $_POST[ $form_input_name ] = $original_post_value;
865
- unset( $original_post_value );
866
-
867
- $updates = $this->get_extensions_with_updates();
868
-
869
- if ( empty( $updates ) ) {
870
- $skin->error( __( 'No extensions updates found.', 'fw' ) );
871
- break;
872
- }
873
-
874
- foreach ( $extensions_list as $extension_name ) {
875
- if ( ! ( $extension = fw()->extensions->get( $extension_name ) ) ) {
876
- $skin->error( sprintf( __( 'Extension "%s" does not exist or is disabled.', 'fw' ), $extension_name ) );
877
- continue;
878
- }
879
-
880
- if ( ! isset( $updates[ $extension_name ] ) ) {
881
- $skin->error( sprintf( __( 'No update found for the "%s" extension.', 'fw' ), $extension->manifest->get_name() ) );
882
- continue;
883
- }
884
-
885
- $update = $updates[ $extension_name ];
886
-
887
- if ( is_wp_error( $update ) ) {
888
- $skin->error( $update );
889
- continue;
890
- }
891
-
892
- /** @var FW_Ext_Update_Service $service */
893
- $service = $this->get_child( $update['service'] );
894
-
895
- $update_result = $this->update( array(
896
- 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
897
- $extension->get_declared_path()
898
- ),
899
- 'download_callback' => array( $service, '_download_extension' ),
900
- 'download_callback_args' => array(
901
- $extension,
902
- $update['latest_version'],
903
- $this->get_wp_fs_tmp_dir()
904
- ),
905
- 'skin' => $skin,
906
- 'title' => sprintf( __( '%s extension', 'fw' ), $extension->manifest->get_name() ),
907
- ), true );
908
-
909
- if ( is_wp_error( $update_result ) ) {
910
- $skin->error( $update_result );
911
- continue;
912
- }
913
-
914
- $skin->set_result( true );
915
-
916
- if ( ! $this->get_config( 'extensions_as_one_update' ) ) {
917
- $skin->decrement_extension_update_count( $extension_name );
918
- }
919
- }
920
-
921
- if ( $this->get_config( 'extensions_as_one_update' ) ) {
922
- $skin->decrement_extension_update_count( $extension_name );
923
- }
924
-
925
- $skin->after();
926
- } while ( false );
927
-
928
- $skin->footer();
929
-
930
- require_once( ABSPATH . 'wp-admin/admin-footer.php' );
931
- }
932
-
933
- public function _action_admin_notices() {
934
-
935
- $updates = $this->get_updates();
936
-
937
- if ( empty( $updates['extensions'] ) || strpos( get_current_screen()->id, 'update-core' ) !== false ) {
938
- return;
939
- }
940
-
941
- foreach ( $updates['extensions'] as $ext_name => $ext_update ) {
942
- if ( is_wp_error( $ext_update ) ) {
943
- return;
944
- }
945
-
946
- break;
947
- }
948
-
949
- echo
950
- '<div class="notice notice-warning">
951
- <p>' .
952
- sprintf(
953
- esc_html__( 'New extensions updates available. %s', 'fw' ),
954
- fw_html_tag(
955
- 'a',
956
- array( 'href' => self_admin_url( 'update-core.php' ) . '#fw-ext-update-extensions' ),
957
- esc_html__( 'Go to Updates page', 'fw' )
958
- )
959
- ) .
960
- '</p>
961
- </div>';
962
- }
963
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'FW' ) or 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
+ * {@inheritdoc}
8
+ */
9
+ public function _child_extension_is_valid( $child_extension_instance ) {
10
+ return is_subclass_of( $child_extension_instance, 'FW_Ext_Update_Service' );
11
+ }
12
+
13
+ /**
14
+ * File names to skip (do not delete or change) during the update process
15
+ * @var array
16
+ */
17
+ private $skip_file_names = array( '.git' );
18
+
19
+ /**
20
+ * @internal
21
+ */
22
+ protected function _init() {
23
+
24
+ if ( ! current_user_can( 'update_plugins' ) ) {
25
+ return false;
26
+ }
27
+
28
+ $this->add_actions();
29
+ $this->add_filters();
30
+
31
+ return true;
32
+ }
33
+
34
+ private function add_actions() {
35
+
36
+ add_action( 'admin_menu', array( $this, '_action_admin_menu' ) );
37
+ add_action( 'admin_notices', array( $this, '_action_admin_notices' ) );
38
+ add_action( 'network_admin_notices', array( $this, '_action_admin_notices' ) );
39
+
40
+ add_action( 'core_upgrade_preamble', array( $this, '_action_updates_page_footer' ) );
41
+ add_action( 'update-core-custom_' . 'fw-update-framework', array( $this, '_action_update_framework' ) );
42
+ add_action( 'update-core-custom_' . 'fw-update-theme', array( $this, '_action_update_theme' ) );
43
+ add_action( 'update-core-custom_' . 'fw-update-extensions', array( $this, '_action_update_extensions' ) );
44
+ }
45
+
46
+ private function add_filters() {
47
+ add_filter( 'wp_get_update_data', array( $this, '_filter_update_data' ), 10, 2 );
48
+ }
49
+
50
+ private function get_fixed_version( $version ) {
51
+ // remove from the beginning everything that is not a number: 'v1.2.3' -> '1.2.3', 'ver1.0.0' -> '1.0.0'
52
+ return preg_replace( '/^[^0-9]+/i', '', $version );
53
+ }
54
+
55
+ private function get_wp_fs_tmp_dir() {
56
+ return FW_WP_Filesystem::real_path_to_filesystem_path(
57
+ apply_filters( 'fw_tmp_dir', fw_fix_path( WP_CONTENT_DIR ) . '/tmp' )
58
+ );
59
+ }
60
+
61
+ public function _action_admin_menu() {
62
+
63
+ if ( ! is_multisite() || is_plugin_active_for_network( 'unyson/unyson.php' ) ) {
64
+ return;
65
+ }
66
+
67
+ add_submenu_page( 'admin.php', esc_html__( 'Unyson updates', 'fw' ), '', 'update_plugins', 'fw-update', array( $this, '_multisite_updates_page' ) );
68
+ }
69
+
70
+ public function _multisite_updates_page() {
71
+ if ( isset( $_GET['action'] ) && ! empty( $_POST ) ) {
72
+ if ( $_GET['action'] === 'fw-update-extensions' ) {
73
+ $this->_action_update_extensions();
74
+ } else {
75
+ $this->_action_update_theme();
76
+ }
77
+ }
78
+
79
+ $this->_action_updates_page_footer();
80
+ }
81
+
82
+ /**
83
+ * @internal
84
+ */
85
+ public function _action_updates_page_footer() {
86
+
87
+ $url = current_filter() == 'core_upgrade_preamble' ? 'update-core.php' : '?page=fw-update';
88
+
89
+ echo $this->render_view( 'updates-list', array(
90
+ 'updates' => $this->get_updates( ! empty( $_GET['force-check'] ) ),
91
+ 'form_action' => self_admin_url( $url )
92
+ ) );
93
+ }
94
+
95
+ /**
96
+ * @internal
97
+ */
98
+ public function _filter_update_data( $data, $titles ) {
99
+ $updates = $this->get_updates( ! empty( $_GET['force-check'] ) );
100
+
101
+ if ( $updates['framework'] && ! is_wp_error( $updates['framework'] ) ) {
102
+ ++ $data['counts']['total'];
103
+ }
104
+
105
+ if ( $updates['theme'] && ! is_wp_error( $updates['theme'] ) ) {
106
+ ++ $data['counts']['total'];
107
+ }
108
+
109
+ if ( ! empty( $updates['extensions'] ) ) {
110
+ foreach ( $updates['extensions'] as $ext_name => $ext_update ) {
111
+ if ( is_wp_error( $ext_update ) ) {
112
+ continue;
113
+ }
114
+
115
+ ++ $data['counts']['total'];
116
+
117
+ if ( $this->get_config( 'extensions_as_one_update' ) ) {
118
+ // no matter how many extensions, display as one update
119
+ break;
120
+ }
121
+ }
122
+
123
+ $title = esc_html__( 'Available extensions updates.', 'fw' );
124
+
125
+ $data['title'] = empty( $data['title'] ) ? $title : $data['title'] . ', ' . $title;
126
+ }
127
+
128
+ return $data;
129
+ }
130
+
131
+ private function get_updates( $force_check = false ) {
132
+ $cache_key = 'fw_ext_update/updates';
133
+
134
+ // use cache because this method may be called multiple times (to prevent useless requests to update servers)
135
+ try {
136
+ return FW_Cache::get( $cache_key );
137
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
138
+ $updates = array(
139
+ 'framework' => $this->get_framework_update( $force_check ),
140
+ 'theme' => $this->get_theme_update( $force_check ),
141
+ 'extensions' => $this->get_extensions_with_updates( $force_check )
142
+ );
143
+
144
+ FW_Cache::set( $cache_key, $updates );
145
+
146
+ return $updates;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Collect extensions that has new versions available
152
+ *
153
+ * @param bool $force_check
154
+ *
155
+ * @return array {ext_name => update_data}
156
+ */
157
+ private function get_extensions_with_updates( $force_check = false ) {
158
+ $updates = array();
159
+ $services = $this->get_children();
160
+ $theme_ext_requirements = fw()->theme->manifest->get( 'requirements/extensions' );
161
+
162
+ foreach ( fw()->extensions->get_all() as $ext_name => $extension ) {
163
+ /** @var FW_Extension $extension */
164
+
165
+ /**
166
+ * Ask each service if it knows how to update the extension
167
+ */
168
+ foreach ( $services as $service_name => $service ) {
169
+ /** @var $service FW_Ext_Update_Service */
170
+
171
+ $latest_version = $service->_get_extension_latest_version( $extension, $force_check );
172
+
173
+ if ( $latest_version === false ) {
174
+ // It said that it doesn't know how to update it
175
+ continue;
176
+ }
177
+
178
+ if ( is_wp_error( $latest_version ) ) {
179
+ $updates[ $ext_name ] = $latest_version;
180
+ break;
181
+ }
182
+
183
+ $fixed_latest_version = $this->get_fixed_version( $latest_version );
184
+
185
+ if ( ! version_compare( $fixed_latest_version, $extension->manifest->get_version(), '>' ) ) {
186
+ // we already have latest version
187
+ continue;
188
+ }
189
+
190
+ if (
191
+ isset( $theme_ext_requirements[ $ext_name ] )
192
+ &&
193
+ isset( $theme_ext_requirements[ $ext_name ]['max_version'] )
194
+ &&
195
+ version_compare( $fixed_latest_version, $theme_ext_requirements[ $ext_name ]['max_version'], '>' )
196
+ ) {
197
+ continue; // do not allow update if it exceeds max_version
198
+ }
199
+
200
+ $updates[ $ext_name ] = array(
201
+ 'service' => $service_name,
202
+ 'latest_version' => $latest_version,
203
+ 'fixed_latest_version' => $fixed_latest_version
204
+ );
205
+
206
+ break;
207
+ }
208
+ }
209
+
210
+ return $updates;
211
+ }
212
+
213
+ /**
214
+ * @param bool $force_check
215
+ *
216
+ * @return array|false|WP_Error
217
+ */
218
+ private function get_framework_update( $force_check = false ) {
219
+ /**
220
+ * Ask each service if it knows how to update the framework
221
+ */
222
+ foreach ( $this->get_children() as $service_name => $service ) {
223
+ /** @var $service FW_Ext_Update_Service */
224
+
225
+ $latest_version = $service->_get_framework_latest_version( $force_check );
226
+
227
+ if ( $latest_version === false ) {
228
+ // It said that it doesn't know how to update it
229
+ continue;
230
+ }
231
+
232
+ if ( is_wp_error( $latest_version ) ) {
233
+ return $latest_version;
234
+ }
235
+
236
+ $fixed_latest_version = $this->get_fixed_version( $latest_version );
237
+
238
+ if ( ! version_compare( $fixed_latest_version, fw()->manifest->get_version(), '>' ) ) {
239
+ // we already have latest version
240
+ continue;
241
+ }
242
+
243
+ return array(
244
+ 'service' => $service_name,
245
+ 'latest_version' => $latest_version,
246
+ 'fixed_latest_version' => $fixed_latest_version
247
+ );
248
+ }
249
+
250
+ return false;
251
+ }
252
+
253
+ /**
254
+ * @param bool $force_check
255
+ *
256
+ * @return array|false|WP_Error
257
+ */
258
+ private function get_theme_update( $force_check = false ) {
259
+ /**
260
+ * Ask each service if it knows how to update the theme
261
+ */
262
+ foreach ( $this->get_children() as $service_name => $service ) {
263
+ /** @var $service FW_Ext_Update_Service */
264
+
265
+ $latest_version = $service->_get_theme_latest_version( $force_check );
266
+
267
+ if ( $latest_version === false ) {
268
+ // It said that it doesn't know how to update it
269
+ continue;
270
+ }
271
+
272
+ if ( is_wp_error( $latest_version ) ) {
273
+ return $latest_version;
274
+ }
275
+
276
+ $fixed_latest_version = $this->get_fixed_version( $latest_version );
277
+
278
+ if ( ! version_compare( $fixed_latest_version, fw()->theme->manifest->get_version(), '>' ) ) {
279
+ // we already have latest version
280
+ continue;
281
+ }
282
+
283
+ return array(
284
+ 'service' => $service_name,
285
+ 'latest_version' => $latest_version,
286
+ 'fixed_latest_version' => $fixed_latest_version
287
+ );
288
+ }
289
+
290
+ return false;
291
+ }
292
+
293
+ /**
294
+ * Turn on/off the maintenance mode
295
+ *
296
+ * @param bool $enable
297
+ */
298
+ private function maintenance_mode( $enable = false ) {
299
+ /** @var WP_Filesystem_Base $wp_filesystem */
300
+ global $wp_filesystem;
301
+
302
+ if ( ! $wp_filesystem || ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) ) {
303
+ return;
304
+ }
305
+
306
+ $file_path = $wp_filesystem->abspath() . '.maintenance';
307
+
308
+ if ( $wp_filesystem->exists( $file_path ) ) {
309
+ if ( ! $wp_filesystem->delete( $file_path ) ) {
310
+ trigger_error( __( 'Cannot delete: ', 'fw' ) . $file_path, E_USER_WARNING );
311
+ }
312
+ }
313
+
314
+ if ( $enable ) {
315
+ // Create maintenance file to signal that we are upgrading
316
+ if ( ! $wp_filesystem->put_contents( $file_path, '<?php $upgrading = ' . time() . '; ?>', FS_CHMOD_FILE ) ) {
317
+ trigger_error( __( 'Cannot create: ', 'fw' ) . $file_path, E_USER_WARNING );
318
+ }
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Download and install new version files
324
+ *
325
+ * global $wp_filesystem; must be initialized before calling this method
326
+ *
327
+ * @param array $data
328
+ * @param bool $merge_extensions The extensions/ directory will not be replaced entirely,
329
+ * only extensions that comes with the update will be replaced
330
+ *
331
+ * @return null|WP_Error
332
+ */
333
+ private function update( $data, $merge_extensions = false ) {
334
+ $required_data_keys = array(
335
+ 'wp_fs_destination_dir' => true,
336
+ 'download_callback' => true,
337
+ 'download_callback_args' => true,
338
+ 'skin' => true,
339
+ 'title' => true,
340
+ );
341
+
342
+ if ( count( $required_data_keys ) > count( array_intersect_key( $required_data_keys, $data ) ) ) {
343
+ trigger_error( 'Some required keys are not present', E_USER_ERROR );
344
+ }
345
+
346
+ // move manually every key to variable, so IDE will understand better them
347
+ {
348
+ /**
349
+ * Replace all files in this directory with downloaded
350
+ * @var string $wp_fs_destination_dir
351
+ */
352
+ $wp_fs_destination_dir = $data['wp_fs_destination_dir'];
353
+
354
+ /**
355
+ * Called to download new version files to $this->get_wp_fs_tmp_dir()
356
+ * @var callable $download_callback
357
+ */
358
+ $download_callback = $data['download_callback'];
359
+
360
+ /**
361
+ * @var array
362
+ */
363
+ $download_callback_args = $data['download_callback_args'];
364
+
365
+ /**
366
+ * @var WP_Upgrader_Skin $skin
367
+ */
368
+ $skin = $data['skin'];
369
+
370
+ /**
371
+ * Used in text messages
372
+ * @var string $title
373
+ */
374
+ $title = $data['title'];
375
+
376
+ unset( $data );
377
+ }
378
+
379
+ /**
380
+ * @var string|WP_Error
381
+ */
382
+ $error = false;
383
+
384
+ $tmp_download_dir = $this->get_wp_fs_tmp_dir();
385
+
386
+ do {
387
+ /** @var WP_Filesystem_Base $wp_filesystem */
388
+ global $wp_filesystem;
389
+
390
+ // create temporary directory
391
+ {
392
+ if ( $wp_filesystem->exists( $tmp_download_dir ) ) {
393
+ // just in case it already exists, clear everything, it may contain old files
394
+ if ( ! $wp_filesystem->rmdir( $tmp_download_dir, true ) ) {
395
+ $error = __( 'Cannot remove old temporary directory: ', 'fw' ) . $tmp_download_dir;
396
+ break;
397
+ }
398
+ }
399
+
400
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $tmp_download_dir ) ) {
401
+ $error = __( 'Cannot create directory: ', 'fw' ) . $tmp_download_dir;
402
+ break;
403
+ }
404
+ }
405
+
406
+ $skin->feedback( sprintf( __( 'Downloading the %s...', 'fw' ), $title ) );
407
+ {
408
+ $downloaded_dir = call_user_func_array( $download_callback, $download_callback_args );
409
+
410
+ if ( ! $downloaded_dir ) {
411
+ $error = sprintf( __( 'Cannot download the %s.', 'fw' ), $title );
412
+ break;
413
+ } elseif ( is_wp_error( $downloaded_dir ) ) {
414
+ $error = $downloaded_dir;
415
+ break;
416
+ }
417
+ }
418
+
419
+ $this->maintenance_mode( true );
420
+
421
+ $skin->feedback( sprintf( __( 'Installing the %s...', 'fw' ), $title ) );
422
+ {
423
+ // remove all files from destination directory
424
+ {
425
+ $dir_files = $wp_filesystem->dirlist( $wp_fs_destination_dir, true );
426
+ if ( $dir_files === false ) {
427
+ $error = __( 'Cannot access directory: ', 'fw' ) . $wp_fs_destination_dir;
428
+ break;
429
+ }
430
+
431
+ foreach ( $dir_files as $file ) {
432
+ if ( in_array( $file['name'], $this->skip_file_names ) ) {
433
+ continue;
434
+ }
435
+
436
+ if ( $merge_extensions ) {
437
+ if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
438
+ // do not remove extensions, will be merged later
439
+ continue;
440
+ }
441
+ }
442
+
443
+ $file_path = $wp_fs_destination_dir . '/' . $file['name'];
444
+
445
+ if ( ! $wp_filesystem->delete( $file_path, true, $file['type'] ) ) {
446
+ $error = __( 'Cannot remove: ', 'fw' ) . $file_path;
447
+ break 2;
448
+ }
449
+ }
450
+ }
451
+
452
+ // move all files from the temporary directory to the destination directory
453
+ {
454
+ $dir_files = $wp_filesystem->dirlist( $downloaded_dir, true );
455
+ if ( $dir_files === false ) {
456
+ $error = __( 'Cannot access directory: ', 'fw' ) . $downloaded_dir;
457
+ break;
458
+ }
459
+
460
+ foreach ( $dir_files as $file ) {
461
+ if ( in_array( $file['name'], $this->skip_file_names ) ) {
462
+ continue;
463
+ }
464
+
465
+ $downloaded_file_path = $downloaded_dir . '/' . $file['name'];
466
+ $destination_file_path = $wp_fs_destination_dir . '/' . $file['name'];
467
+
468
+ if ( $merge_extensions ) {
469
+ if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
470
+ // merge extensions/ after all other files was moved
471
+ $merge_extensions_data = array(
472
+ 'source' => $downloaded_file_path,
473
+ 'destination' => $destination_file_path,
474
+ );
475
+ continue;
476
+ }
477
+ }
478
+
479
+ if ( ! $wp_filesystem->move( $downloaded_file_path, $destination_file_path ) ) {
480
+ $error = sprintf(
481
+ __( 'Cannot move "%s" to "%s"', 'fw' ),
482
+ $downloaded_file_path, $destination_file_path
483
+ );
484
+ break 2;
485
+ }
486
+ }
487
+
488
+ if ( $merge_extensions ) {
489
+ if ( ! empty( $merge_extensions_data ) ) {
490
+ $merge_result = $this->merge_extensions(
491
+ $merge_extensions_data['source'],
492
+ $merge_extensions_data['destination']
493
+ );
494
+
495
+ if ( $merge_result === false ) {
496
+ $error = sprintf(
497
+ __( 'Cannot merge "%s" with "%s"', 'fw' ),
498
+ $downloaded_file_path, $destination_file_path
499
+ );
500
+ break;
501
+ } elseif ( is_wp_error( $merge_result ) ) {
502
+ $error = $merge_result;
503
+ break;
504
+ }
505
+ }
506
+ }
507
+ }
508
+ }
509
+
510
+ $skin->feedback( sprintf( __( 'The %s has been successfully updated.', 'fw' ), $title ) );
511
+ } while ( false );
512
+
513
+ $this->maintenance_mode( false );
514
+
515
+ if ( $wp_filesystem->exists( $tmp_download_dir ) ) {
516
+ if ( ! $wp_filesystem->delete( $tmp_download_dir, true, 'd' ) ) {
517
+ $error = sprintf( __( 'Cannot remove temporary directory "%s".', 'fw' ), $tmp_download_dir );
518
+ }
519
+ }
520
+
521
+ if ( $error ) {
522
+ if ( ! is_wp_error( $error ) ) {
523
+ $error = new WP_Error( 'fw_ext_update_failed', (string) $error );
524
+ }
525
+
526
+ return $error;
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Merge two extensions/ directories
532
+ *
533
+ * @param string $source_dir WP_Filesystem dir '/a/b/c/extensions'
534
+ * @param string $destination_dir WP_Filesystem dir '/a/b/d/extensions'
535
+ *
536
+ * @return bool|WP_Error
537
+ */
538
+ private function merge_extensions( $source_dir, $destination_dir ) {
539
+ /** @var WP_Filesystem_Base $wp_filesystem */
540
+ global $wp_filesystem;
541
+
542
+ $wp_error_id = 'fw_ext_update_merge_extensions';
543
+
544
+ if ( ! $wp_filesystem->exists( $destination_dir ) ) {
545
+ // do a simple move if destination does not exist
546
+ if ( ! $wp_filesystem->move( $source_dir, $destination_dir ) ) {
547
+ return new WP_Error( $wp_error_id,
548
+ sprintf( __( 'Cannot move "%s" to "%s"', 'fw' ), $source_dir, $destination_dir )
549
+ );
550
+ }
551
+
552
+ return true;
553
+ }
554
+
555
+ $source_ext_dirs = $wp_filesystem->dirlist( $source_dir, true );
556
+ if ( $source_ext_dirs === false ) {
557
+ return new WP_Error( $wp_error_id,
558
+ __( 'Cannot access directory: ', 'fw' ) . $source_dir
559
+ );
560
+ }
561
+
562
+ foreach ( $source_ext_dirs as $ext_dir ) {
563
+ if ( in_array( $ext_dir['name'], $this->skip_file_names ) ) {
564
+ continue;
565
+ }
566
+
567
+ if ( $ext_dir['type'] !== 'd' ) {
568
+ // process only directories from the extensions/ directory
569
+ continue;
570
+ }
571
+
572
+ $source_extension_dir = $source_dir . '/' . $ext_dir['name'];
573
+ $destination_extension_dir = $destination_dir . '/' . $ext_dir['name'];
574
+
575
+ {
576
+ $source_ext_files = $wp_filesystem->dirlist( $source_extension_dir, true );
577
+ if ( $source_ext_files === false ) {
578
+ return new WP_Error( $wp_error_id,
579
+ __( 'Cannot access directory: ', 'fw' ) . $source_extension_dir
580
+ );
581
+ }
582
+
583
+ if ( empty( $source_ext_files ) ) {
584
+ /**
585
+ * Source extension directory is empty, do nothing.
586
+ * This happens when the extension is a git submodule in repository
587
+ * but in zip it comes as an empty directory.
588
+ */
589
+ continue;
590
+ }
591
+ }
592
+
593
+ // prepare destination
594
+ {
595
+ // create if not exists
596
+ if ( ! $wp_filesystem->exists( $destination_extension_dir ) ) {
597
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $destination_extension_dir ) ) {
598
+ return new WP_Error( $wp_error_id,
599
+ __( 'Cannot create directory: ', 'fw' ) . $destination_extension_dir
600
+ );
601
+ }
602
+ }
603
+
604
+ // remove everything except the extensions/ dir
605
+ {
606
+ $dest_ext_files = $wp_filesystem->dirlist( $destination_extension_dir, true );
607
+ if ( $dest_ext_files === false ) {
608
+ return new WP_Error( $wp_error_id,
609
+ __( 'Cannot access directory: ', 'fw' ) . $destination_extension_dir
610
+ );
611
+ }
612
+
613
+ $destination_has_extensions_dir = false;
614
+
615
+ foreach ( $dest_ext_files as $dest_ext_file ) {
616
+ if ( in_array( $dest_ext_file['name'], $this->skip_file_names ) ) {
617
+ continue;
618
+ }
619
+
620
+ if ( $dest_ext_file['name'] === 'extensions' && $dest_ext_file['type'] === 'd' ) {
621
+ $destination_has_extensions_dir = true;
622
+ continue;
623
+ }
624
+
625
+ $dest_ext_file_path = $destination_extension_dir . '/' . $dest_ext_file['name'];
626
+
627
+ if ( ! $wp_filesystem->delete( $dest_ext_file_path, true, $dest_ext_file['type'] ) ) {
628
+ return new WP_Error( $wp_error_id,
629
+ __( 'Cannot delete: ', 'fw' ) . $dest_ext_file_path
630
+ );
631
+ }
632
+ }
633
+ }
634
+ }
635
+
636
+ // move files from source to destination extension directory
637
+ {
638
+ $source_has_extensions_dir = false;
639
+
640
+ foreach ( $source_ext_files as $source_ext_file ) {
641
+ if ( in_array( $source_ext_file['name'], $this->skip_file_names ) ) {
642
+ continue;
643
+ }
644
+
645
+ if ( $source_ext_file['name'] === 'extensions' && $source_ext_file['type'] === 'd' ) {
646
+ $source_has_extensions_dir = true;
647
+ continue;
648
+ }
649
+
650
+ $source_ext_file_path = $source_extension_dir . '/' . $source_ext_file['name'];
651
+ $dest_ext_file_path = $destination_extension_dir . '/' . $source_ext_file['name'];
652
+
653
+ if ( ! $wp_filesystem->move( $source_ext_file_path, $dest_ext_file_path ) ) {
654
+ return new WP_Error( $wp_error_id,
655
+ sprintf( __( 'Cannot move "%s" to "%s"', 'fw' ),
656
+ $source_ext_file_path, $dest_ext_file_path
657
+ )
658
+ );
659
+ }
660
+ }
661
+ }
662
+
663
+ if ( $source_has_extensions_dir ) {
664
+ if ( $destination_has_extensions_dir ) {
665
+ $merge_result = $this->merge_extensions(
666
+ $source_extension_dir . '/extensions',
667
+ $destination_extension_dir . '/extensions'
668
+ );
669
+
670
+ if ( $merge_result !== true ) {
671
+ return $merge_result;
672
+ }
673
+ } else {
674
+ if ( ! $wp_filesystem->move(
675
+ $source_extension_dir . '/extensions',
676
+ $destination_extension_dir . '/extensions'
677
+ ) ) {
678
+ return new WP_Error( $wp_error_id,
679
+ sprintf( __( 'Cannot move "%s" to "%s"', 'fw' ),
680
+ $source_extension_dir . '/extensions',
681
+ $destination_extension_dir . '/extensions'
682
+ )
683
+ );
684
+ }
685
+ }
686
+ }
687
+ }
688
+
689
+ return true;
690
+ }
691
+
692
+ /**
693
+ * @internal
694
+ */
695
+ public function _action_update_framework() {
696
+ $nonce_name = '_nonce_fw_ext_update_framework';
697
+ if ( ! isset( $_POST[ $nonce_name ] ) || ! wp_verify_nonce( $_POST[ $nonce_name ] ) ) {
698
+ wp_die( __( 'Invalid nonce.', 'fw' ) );
699
+ }
700
+
701
+ {
702
+ if ( ! class_exists( '_FW_Ext_Update_Framework_Upgrader_Skin' ) ) {
703
+ fw_include_file_isolated(
704
+ $this->get_declared_path( '/includes/classes/class--fw-ext-update-framework-upgrader-skin.php' )
705
+ );
706
+ }
707
+
708
+ $skin = new _FW_Ext_Update_Framework_Upgrader_Skin( array(
709
+ 'title' => __( 'Framework Update', 'fw' ),
710
+ ) );
711
+ }
712
+
713
+ require_once ABSPATH . 'wp-admin/admin-header.php';
714
+
715
+ $skin->header();
716
+
717
+ do {
718
+ if ( ! FW_WP_Filesystem::request_access( fw_get_framework_directory(), fw_current_url(), array( $nonce_name ) ) ) {
719
+ break;
720
+ }
721
+
722
+ $update = $this->get_framework_update();
723
+
724
+ if ( $update === false ) {
725
+ $skin->error( __( 'Failed to get framework latest version.', 'fw' ) );
726
+ break;
727
+ } elseif ( is_wp_error( $update ) ) {
728
+ $skin->error( $update );
729
+ break;
730
+ }
731
+
732
+ /** @var FW_Ext_Update_Service $service */
733
+ $service = $this->get_child( $update['service'] );
734
+
735
+ $update_result = $this->update( array(
736
+ 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
737
+ fw_get_framework_directory()
738
+ ),
739
+ 'download_callback' => array( $service, '_download_framework' ),
740
+ 'download_callback_args' => array( $update['latest_version'], $this->get_wp_fs_tmp_dir() ),
741
+ 'skin' => $skin,
742
+ 'title' => __( 'Framework', 'fw' ),
743
+ ) );
744
+
745
+ if ( is_wp_error( $update_result ) ) {
746
+ $skin->error( $update_result );
747
+ break;
748
+ }
749
+
750
+ $skin->set_result( true );
751
+ $skin->after();
752
+ } while ( false );
753
+
754
+ $skin->footer();
755
+
756
+ require_once( ABSPATH . 'wp-admin/admin-footer.php' );
757
+ }
758
+
759
+ /**
760
+ * @internal
761
+ */
762
+ public function _action_update_theme() {
763
+ $nonce_name = '_nonce_fw_ext_update_theme';
764
+ if ( ! isset( $_POST[ $nonce_name ] ) || ! wp_verify_nonce( $_POST[ $nonce_name ] ) ) {
765
+ wp_die( __( 'Invalid nonce.', 'fw' ) );
766
+ }
767
+
768
+ {
769
+ if ( ! class_exists( '_FW_Ext_Update_Theme_Upgrader_Skin' ) ) {
770
+ fw_include_file_isolated(
771
+ $this->get_declared_path( '/includes/classes/class--fw-ext-update-theme-upgrader-skin.php' )
772
+ );
773
+ }
774
+
775
+ $skin = new _FW_Ext_Update_Theme_Upgrader_Skin( array(
776
+ 'title' => __( 'Theme Update', 'fw' ),
777
+ ) );
778
+ }
779
+
780
+ require_once( ABSPATH . 'wp-admin/admin-header.php' );
781
+
782
+ $skin->header();
783
+
784
+ do {
785
+ if ( ! FW_WP_Filesystem::request_access( get_template_directory(), fw_current_url(), array( $nonce_name ) ) ) {
786
+ break;
787
+ }
788
+
789
+ $update = $this->get_theme_update();
790
+
791
+ if ( $update === false ) {
792
+ $skin->error( __( 'Failed to get theme latest version.', 'fw' ) );
793
+ break;
794
+ } elseif ( is_wp_error( $update ) ) {
795
+ $skin->error( $update );
796
+ break;
797
+ }
798
+
799
+ /** @var FW_Ext_Update_Service $service */
800
+ $service = $this->get_child( $update['service'] );
801
+
802
+ $update_result = $this->update( array(
803
+ 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
804
+ get_template_directory()
805
+ ),
806
+ 'download_callback' => array( $service, '_download_theme' ),
807
+ 'download_callback_args' => array( $update['latest_version'], $this->get_wp_fs_tmp_dir() ),
808
+ 'skin' => $skin,
809
+ 'title' => __( 'Theme', 'fw' ),
810
+ ) );
811
+
812
+ if ( is_wp_error( $update_result ) ) {
813
+ $skin->error( $update_result );
814
+ break;
815
+ }
816
+
817
+ $skin->set_result( true );
818
+ $skin->after();
819
+ } while ( false );
820
+
821
+ $skin->footer();
822
+
823
+ require_once( ABSPATH . 'wp-admin/admin-footer.php' );
824
+ }
825
+
826
+ /**
827
+ * @internal
828
+ */
829
+ public function _action_update_extensions() {
830
+ $nonce_name = '_nonce_fw_ext_update_extensions';
831
+ if ( ! isset( $_POST[ $nonce_name ] ) || ! wp_verify_nonce( $_POST[ $nonce_name ] ) ) {
832
+ wp_die( __( 'Invalid nonce.', 'fw' ) );
833
+ }
834
+
835
+ $form_input_name = 'extensions';
836
+ $extensions_list = FW_Request::POST( $form_input_name );
837
+
838
+ if ( empty( $extensions_list ) ) {
839
+ FW_Flash_Messages::add(
840
+ 'fw_ext_update',
841
+ __( 'Please check the extensions you want to update.', 'fw' ),
842
+ 'warning'
843
+ );
844
+ wp_redirect( self_admin_url( 'update-core.php' ) );
845
+ exit;
846
+ }
847
+
848
+ // handle changes by the hack below
849
+ {
850
+ if ( is_string( $extensions_list ) ) {
851
+ $extensions_list = json_decode( $extensions_list );
852
+ } else {
853
+ $extensions_list = array_keys( $extensions_list );
854
+ }
855
+ }
856
+
857
+ {
858
+ if ( ! class_exists( '_FW_Ext_Update_Extensions_Upgrader_Skin' ) ) {
859
+ fw_include_file_isolated(
860
+ $this->get_declared_path( '/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php' )
861
+ );
862
+ }
863
+
864
+ $skin = new _FW_Ext_Update_Extensions_Upgrader_Skin( array(
865
+ 'title' => __( 'Extensions Update', 'fw' ),
866
+ ) );
867
+ }
868
+
869
+ require_once( ABSPATH . 'wp-admin/admin-header.php' );
870
+
871
+ $skin->header();
872
+
873
+ do {
874
+ /**
875
+ * Hack for the ftp credentials template that does not support array post values
876
+ * https://github.com/WordPress/WordPress/blob/3949a8b6cc50a021ed93798287b4ef9ea8a560d9/wp-admin/includes/file.php#L1144
877
+ */
878
+ $original_post_value = $_POST[ $form_input_name ];
879
+ $_POST[ $form_input_name ] = wp_slash( json_encode( $extensions_list ) );
880
+
881
+ if ( ! FW_WP_Filesystem::request_access( fw_get_framework_directory( '/extensions' ), fw_current_url(), array( $nonce_name, $form_input_name ) ) ) {
882
+ // revert hack changes
883
+ $_POST[ $form_input_name ] = $original_post_value;
884
+ unset( $original_post_value );
885
+
886
+ break;
887
+ }
888
+
889
+ // revert hack changes
890
+ $_POST[ $form_input_name ] = $original_post_value;
891
+ unset( $original_post_value );
892
+
893
+ $updates = $this->get_extensions_with_updates();
894
+
895
+ if ( empty( $updates ) ) {
896
+ $skin->error( __( 'No extensions updates found.', 'fw' ) );
897
+ break;
898
+ }
899
+
900
+ foreach ( $extensions_list as $extension_name ) {
901
+ if ( ! ( $extension = fw()->extensions->get( $extension_name ) ) ) {
902
+ $skin->error( sprintf( __( 'Extension "%s" does not exist or is disabled.', 'fw' ), $extension_name ) );
903
+ continue;
904
+ }
905
+
906
+ if ( ! isset( $updates[ $extension_name ] ) ) {
907
+ $skin->error( sprintf( __( 'No update found for the "%s" extension.', 'fw' ), $extension->manifest->get_name() ) );
908
+ continue;
909
+ }
910
+
911
+ $update = $updates[ $extension_name ];
912
+
913
+ if ( is_wp_error( $update ) ) {
914
+ $skin->error( $update );
915
+ continue;
916
+ }
917
+
918
+ /** @var FW_Ext_Update_Service $service */
919
+ $service = $this->get_child( $update['service'] );
920
+
921
+ $update_result = $this->update( array(
922
+ 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
923
+ $extension->get_declared_path()
924
+ ),
925
+ 'download_callback' => array( $service, '_download_extension' ),
926
+ 'download_callback_args' => array(
927
+ $extension,
928
+ $update['latest_version'],
929
+ $this->get_wp_fs_tmp_dir()
930
+ ),
931
+ 'skin' => $skin,
932
+ 'title' => sprintf( __( '%s extension', 'fw' ), $extension->manifest->get_name() ),
933
+ ), true );
934
+
935
+ if ( is_wp_error( $update_result ) ) {
936
+ $skin->error( $update_result );
937
+ continue;
938
+ }
939
+
940
+ $skin->set_result( true );
941
+
942
+ if ( ! $this->get_config( 'extensions_as_one_update' ) ) {
943
+ $skin->decrement_extension_update_count( $extension_name );
944
+ }
945
+ }
946
+
947
+ if ( $this->get_config( 'extensions_as_one_update' ) ) {
948
+ $skin->decrement_extension_update_count( $extension_name );
949
+ }
950
+
951
+ $skin->after();
952
+ } while ( false );
953
+
954
+ $skin->footer();
955
+
956
+ require_once( ABSPATH . 'wp-admin/admin-footer.php' );
957
+ }
958
+
959
+ public function _action_admin_notices() {
960
+
961
+ $updates = $this->get_updates();
962
+
963
+ if ( ( empty( $updates['extensions'] ) && empty( $updates['theme'] ) )
964
+ ||
965
+ strpos( get_current_screen()->id, 'update-core' ) !== false
966
+ ||
967
+ ( isset( $_GET['page'] ) && $_GET['page'] === 'fw-update' ) ) {
968
+
969
+ return;
970
+ }
971
+
972
+ foreach ( $updates['extensions'] as $ext_name => $ext_update ) {
973
+ if ( is_wp_error( $ext_update ) ) {
974
+ return;
975
+ }
976
+
977
+ break;
978
+ }
979
+
980
+ $slug = is_multisite() && ! is_plugin_active_for_network( 'unyson/unyson.php' ) ? '?page=fw-update' : 'update-core.php#fw-ext-update-extensions';
981
+
982
+ if ( ! empty( $updates['extensions'] ) && empty( $updates['theme'] ) ) {
983
+ $text = 'extensions updates';
984
+ } elseif ( empty( $updates['extensions'] ) && ! empty( $updates['theme'] ) ) {
985
+ $text = 'theme update';
986
+ } else {
987
+ $text = 'extensions/theme updates';
988
+ }
989
+
990
+ echo
991
+ '<div class="notice notice-warning">
992
+ <p>' .
993
+ sprintf(
994
+ esc_html__( 'New %s available. %s', 'fw' ),
995
+ $text,
996
+ fw_html_tag(
997
+ 'a',
998
+ array( 'href' => self_admin_url( $slug ) ),
999
+ esc_html__( 'Go to Updates page', 'fw' )
1000
+ )
1001
+ ) .
1002
+ '</p>
1003
+ </div>';
1004
+ }
1005
+ }
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/custom-update/class-fw-extension-custom-update.php ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'FW' ) or die();
2
+
3
+ /**
4
+ * Custom server Update
5
+ *
6
+ * Add {'remote' => 'your_url'} to your manifest and this extension will handle it
7
+ */
8
+ class FW_Extension_Custom_Update extends FW_Ext_Update_Service {
9
+
10
+ /**
11
+ * How long to cache server responses
12
+ * @var int seconds
13
+ */
14
+ private $transient_expiration = DAY_IN_SECONDS;
15
+
16
+ private $download_timeout = 300;
17
+
18
+ /**
19
+ * Used when there is internet connection problems
20
+ * To prevent site being blocked on every refresh, this fake version will be cached in the transient
21
+ * @var string
22
+ */
23
+ private $fake_latest_version = '0.0.0';
24
+
25
+ /**
26
+ * @internal
27
+ */
28
+ protected function _init() {}
29
+
30
+ /**
31
+ * @param $force_check
32
+ * @param $set - manifest settings.
33
+ *
34
+ * @return mixed|string|WP_Error
35
+ */
36
+ private function get_latest_version( $force_check, $set ) {
37
+
38
+ $transient_name = 'fw_ext_upd_gh_fw';
39
+
40
+ if ( $force_check ) {
41
+ delete_site_transient( $transient_name );
42
+
43
+ $cache = array();
44
+ } else {
45
+ $cache = ( $c = get_site_transient( $transient_name ) ) && $c !== false ? $c : array();
46
+
47
+ if ( isset( $cache[ $set['item'] ] ) ) {
48
+ return $cache[ $set['item'] ];
49
+ }
50
+ }
51
+
52
+ $version = $this->fetch_latest_version( $set );
53
+
54
+ if ( is_wp_error( $version ) ) {
55
+ // Cache fake version to prevent requests to yourserver on every refresh.
56
+ $cache[ $set['item'] ] = $this->fake_latest_version;
57
+
58
+ // Show the error to the user because it is not visible elsewhere.
59
+ FW_Flash_Messages::add( 'fw_ext_custom_update_error', $version->get_error_message(), 'error' );
60
+
61
+ } else {
62
+ $cache[ $set['item'] ] = $version;
63
+ }
64
+
65
+ set_site_transient( $transient_name, $cache, $this->transient_expiration );
66
+
67
+ return $version;
68
+ }
69
+
70
+ /**
71
+ * @param $set
72
+ *
73
+ * @return array|string|WP_Error
74
+ */
75
+ private function fetch_latest_version( $set ) {
76
+ /**
77
+ * If at least one request failed, do not do any other requests, to prevent site being blocked on every refresh.
78
+ * This may happen on localhost when develop your theme and you have no internet connection.
79
+ * Then this method will return a fake '0.0.0' version, it will be cached by the transient
80
+ * and will not bother you until the transient will expire, then a new request will be made.
81
+ * @var bool
82
+ */
83
+ static $no_internet_connection = false;
84
+
85
+ if ( $no_internet_connection ) {
86
+ return $this->fake_latest_version;
87
+ }
88
+
89
+ $request = wp_remote_post(
90
+ apply_filters( 'fw_custom_url_version', $set['remote'], $set ),
91
+ array(
92
+ 'timeout' => $this->download_timeout,
93
+ 'body' => json_encode( array_merge( $set, array( 'pull' => 'version' ) ) )
94
+ )
95
+ );
96
+
97
+ if ( is_wp_error( $request ) ) {
98
+ if ( $request->get_error_code() === 'http_request_failed' ) {
99
+ $no_internet_connection = true;
100
+ }
101
+
102
+ return $request;
103
+ }
104
+
105
+ if ( ! ( $version = wp_remote_retrieve_body( $request ) ) || is_wp_error( $version ) ) {
106
+ return ! $version ? new WP_Error( sprintf( __( 'Empty version for item: %s', 'fw' ), $set['item'] ) ) : $version;
107
+ }
108
+
109
+ return $version;
110
+ }
111
+
112
+ /**
113
+ * @param array $set - manifest keys.
114
+ * @param string $version Requested version to download
115
+ * @param string $wp_filesystem_download_directory Allocated temporary empty directory
116
+ * @param string $title Used in messages
117
+ *
118
+ * @return string|WP_Error Path to the downloaded directory
119
+ */
120
+ private function download( $set, $version, $wp_filesystem_download_directory, $title ) {
121
+ /** @var WP_Filesystem_Base $wp_filesystem */
122
+ global $wp_filesystem;
123
+
124
+ $error_id = 'fw_ext_update_custom_download_zip';
125
+ $request = wp_remote_post(
126
+ $set['remote'],
127
+ array(
128
+ 'timeout' => $this->download_timeout,
129
+ 'body' => json_encode( array_merge( $set, array( 'pull' => 'zip' ) ) )
130
+ )
131
+ );
132
+
133
+ if ( is_wp_error( $request ) ) {
134
+ return $request;
135
+ }
136
+
137
+ if ( ! ( $body = wp_remote_retrieve_body( $request ) ) || is_wp_error( $body ) ) {
138
+ return ! $body ? new WP_Error( $error_id, sprintf( esc_html__( 'Empty zip body for item: %s', 'fw' ), $title ) ) : $body;
139
+ }
140
+
141
+ // Try to extract error if server returned json with key error. If not then is an archive zip.
142
+ if ( ( $error = json_decode( $body, true ) ) && isset( $error['error'] ) ) {
143
+ return new WP_Error( $error_id, $error['error'] );
144
+ }
145
+
146
+ $zip_path = $wp_filesystem_download_directory . '/temp.zip';
147
+
148
+ // save zip to file
149
+ if ( ! $wp_filesystem->put_contents( $zip_path, $body ) ) {
150
+ return new WP_Error( $error_id, sprintf( esc_html__( 'Cannot save %s zip.', 'fw' ), $title ) );
151
+ }
152
+
153
+ $unzip_result = unzip_file( FW_WP_Filesystem::filesystem_path_to_real_path( $zip_path ), $wp_filesystem_download_directory );
154
+
155
+ if ( is_wp_error( $unzip_result ) ) {
156
+ return $unzip_result;
157
+ }
158
+
159
+ // remove zip file
160
+ if ( ! $wp_filesystem->delete( $zip_path, false, 'f' ) ) {
161
+ return new WP_Error( $error_id, sprintf( esc_html__( 'Cannot remove %s zip.', 'fw' ), $title ) );
162
+ }
163
+
164
+ $unzipped_dir_files = $wp_filesystem->dirlist( $wp_filesystem_download_directory );
165
+
166
+ if ( ! $unzipped_dir_files ) {
167
+ return new WP_Error( $error_id, esc_html__( 'Cannot access the unzipped directory files.', 'fw' ) );
168
+ }
169
+
170
+ /**
171
+ * get first found directory
172
+ * (if everything worked well, there should be only one directory)
173
+ */
174
+ foreach ( $unzipped_dir_files as $file ) {
175
+ if ( $file['type'] == 'd' ) {
176
+ return $wp_filesystem_download_directory . '/' . $file['name'];
177
+ }
178
+ }
179
+
180
+ return new WP_Error( $error_id, sprintf( esc_html__( 'The unzipped %s directory not found.', 'fw' ), $title ) );
181
+ }
182
+
183
+ /**
184
+ * {@inheritdoc}
185
+ * @internal
186
+ */
187
+ public function _get_framework_latest_version( $force_check ) {
188
+ return false;
189
+ }
190
+
191
+ /**
192
+ * {@inheritdoc}
193
+ * @internal
194
+ */
195
+ public function _get_theme_latest_version( $force_check ) {
196
+
197
+ $manifest = $this->get_clean_theme_manifest();
198
+
199
+ if ( empty( $manifest['remote'] ) ) {
200
+ return false;
201
+ }
202
+
203
+ return $this->get_latest_version( $force_check, $manifest );
204
+ }
205
+
206
+ /**
207
+ * {@inheritdoc}
208
+ * @internal
209
+ */
210
+ public function _download_theme( $version, $wp_filesystem_download_directory ) {
211
+ return $this->download( $this->get_clean_theme_manifest(), $version, $wp_filesystem_download_directory, esc_html__( 'Theme', 'fw' ) );
212
+ }
213
+
214
+ /**
215
+ * {@inheritdoc}
216
+ * @internal
217
+ */
218
+ public function _get_extension_latest_version( FW_Extension $extension, $force_check ) {
219
+
220
+ if ( ! $extension->manifest->get( 'remote' ) ) {
221
+ return false;
222
+ }
223
+
224
+ return $this->get_latest_version( $force_check, $this->data_manifest( $extension->manifest->get_manifest(), 'extension', $extension->get_name() ) );
225
+ }
226
+
227
+ /**
228
+ * {@inheritdoc}
229
+ * @internal
230
+ */
231
+ public function _download_extension( FW_Extension $extension, $version, $wp_filesystem_download_directory ) {
232
+ return $this->download(
233
+ $this->data_manifest( $extension->manifest->get_manifest(), 'extension', $extension->get_name() ),
234
+ $version,
235
+ $wp_filesystem_download_directory,
236
+ sprintf( esc_html__( '%s extension', 'fw' ), $extension->manifest->get_name() )
237
+ );
238
+ }
239
+
240
+ public function data_manifest( $manifest, $type, $id ) {
241
+ return array_merge( $manifest, array( 'type' => $type, 'item' => $id ) );
242
+ }
243
+
244
+ public function get_clean_theme_manifest() {
245
+
246
+ if ( ! ( $manifest_file = fw_get_template_customizations_directory( '/theme/manifest.php' ) ) || ! is_file( $manifest_file ) ) {
247
+ return array();
248
+ }
249
+
250
+ include $manifest_file;
251
+
252
+ return isset( $manifest ) ? $this->data_manifest( $manifest, 'theme', $manifest['id'] ) : array();
253
+ }
254
+ }
framework/extensions/update/extensions/custom-update/manifest.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php defined( 'FW' ) or die();
2
+
3
+ $manifest = array();
4
+
5
+ $manifest['standalone'] = true;
framework/extensions/update/extensions/github-update/class-fw-extension-github-update.php CHANGED
@@ -1,443 +1,433 @@
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' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ /**
6
+ * Github Update
7
+ *
8
+ * Add {'github_update' => 'user/repo'} to your manifest and this extension will handle it
9
+ */
10
+ class FW_Extension_Github_Update extends FW_Ext_Update_Service {
11
+ /**
12
+ * Handle framework, theme and extensions that has this key in manifest
13
+ * @var string
14
+ */
15
+ private $manifest_key = 'github_update';
16
+
17
+ /**
18
+ * Check if manifest key format is correct 'user/repo'
19
+ * @var string
20
+ */
21
+ private $manifest_key_regex = '/^([^\s\/]+)\/([^\s\/]+)$/';
22
+
23
+ /**
24
+ * How long to cache server responses
25
+ * @var int seconds
26
+ */
27
+ private $transient_expiration = DAY_IN_SECONDS;
28
+
29
+ private $download_timeout = 300;
30
+
31
+ /**
32
+ * Used when there is internet connection problems
33
+ * To prevent site being blocked on every refresh, this fake version will be cached in the transient
34
+ * @var string
35
+ */
36
+ private $fake_latest_version = '0.0.0';
37
+
38
+ /**
39
+ * @internal
40
+ */
41
+ protected function _init() {}
42
+
43
+ /**
44
+ * @param string $append '/foo/bar'
45
+ *
46
+ * @return string
47
+ */
48
+ private function get_github_api_url( $append ) {
49
+ return apply_filters( 'fw_github_api_url', 'https://api.github.com' ) . $append;
50
+ }
51
+
52
+ private function fetch_latest_version( $user_slash_repo ) {
53
+ /**
54
+ * If at least one request failed, do not do any other requests, to prevent site being blocked on every refresh.
55
+ * This may happen on localhost when develop your theme and you have no internet connection.
56
+ * Then this method will return a fake '0.0.0' version, it will be cached by the transient
57
+ * and will not bother you until the transient will expire, then a new request will be made.
58
+ * @var bool
59
+ */
60
+ static $no_internet_connection = false;
61
+
62
+ if ( $no_internet_connection ) {
63
+ return $this->fake_latest_version;
64
+ }
65
+
66
+ $http = new WP_Http();
67
+
68
+ $response = $http->get(
69
+ $this->get_github_api_url( '/repos/' . $user_slash_repo . '/releases/latest' )
70
+ );
71
+
72
+ unset( $http );
73
+
74
+ if ( is_wp_error( $response ) ) {
75
+ if ( $response->get_error_code() === 'http_request_failed' ) {
76
+ $no_internet_connection = true;
77
+ }
78
+
79
+ return $response;
80
+ }
81
+
82
+ if ( ( $response_code = intval( wp_remote_retrieve_response_code( $response ) ) ) !== 200 ) {
83
+ if ( $response_code === 403 ) {
84
+ $json_response = json_decode( $response['body'], true );
85
+
86
+ if ( $json_response ) {
87
+ return new WP_Error(
88
+ 'fw_ext_update_github_fetch_releases_failed',
89
+ __( 'Github error:', 'fw' ) . ' ' . $json_response['message']
90
+ );
91
+ }
92
+ }
93
+
94
+ if ( $response_code ) {
95
+ return new WP_Error(
96
+ 'fw_ext_update_github_fetch_releases_failed',
97
+ sprintf(
98
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
99
+ $user_slash_repo, $response_code
100
+ )
101
+ );
102
+ } else {
103
+ return new WP_Error(
104
+ 'fw_ext_update_github_fetch_releases_failed',
105
+ sprintf(
106
+ __( 'Failed to access Github repository "%s" releases.', 'fw' ),
107
+ $user_slash_repo
108
+ )
109
+ );
110
+ }
111
+ }
112
+
113
+ $release = json_decode( $response['body'], true );
114
+
115
+ unset( $response );
116
+
117
+ if ( empty( $release ) ) {
118
+ return new WP_Error(
119
+ 'fw_ext_update_github_fetch_no_releases',
120
+ sprintf( __( 'No releases found in repository "%s".', 'fw' ), $user_slash_repo )
121
+ );
122
+ }
123
+
124
+ return $release['tag_name'];
125
+ }
126
+
127
+ /**
128
+ * Get repository latest release version
129
+ *
130
+ * @param string $user_slash_repo Github 'user/repo'
131
+ * @param bool $force_check Bypass cache
132
+ * @param string $title Used in messages
133
+ *
134
+ * @return string|WP_Error
135
+ */
136
+ private function get_latest_version( $user_slash_repo, $force_check, $title ) {
137
+ if ( ! preg_match( $this->manifest_key_regex, $user_slash_repo ) ) {
138
+ return new WP_Error( 'fw_ext_update_github_manifest_invalid',
139
+ sprintf(
140
+ __( '%s manifest has invalid "github_update" parameter. Please use "user/repo" format.', 'fw' ),
141
+ $title
142
+ )
143
+ );
144
+ }
145
+
146
+ $transient_id = 'fw_ext_upd_gh_fw'; // the length must be 45 characters or less
147
+
148
+ if ( $force_check ) {
149
+ delete_site_transient( $transient_id );
150
+
151
+ $cache = array();
152
+ } else {
153
+ $cache = get_site_transient( $transient_id );
154
+
155
+ if ( $cache === false ) {
156
+ $cache = array();
157
+ } elseif ( isset( $cache[ $user_slash_repo ] ) ) {
158
+ return $cache[ $user_slash_repo ];
159
+ }
160
+ }
161
+
162
+ $latest_version = $this->fetch_latest_version( $user_slash_repo );
163
+
164
+ if ( empty( $latest_version ) ) {
165
+ return new WP_Error(
166
+ 'fw_ext_update_github_failed_fetch_latest_version',
167
+ sprintf(
168
+ __( 'Failed to fetch %s latest version from github "%s".', 'fw' ),
169
+ $title, $user_slash_repo
170
+ )
171
+ );
172
+ }
173
+
174
+ if ( is_wp_error( $latest_version ) ) {
175
+ /**
176
+ * Internet connection problems or Github API requests limit reached.
177
+ * Cache fake version to prevent requests to Github API on every refresh.
178
+ */
179
+ $cache = array_merge( $cache, array( $user_slash_repo => $this->fake_latest_version ) );
180
+
181
+ /**
182
+ * Show the error to the user because it is not visible elsewhere
183
+ */
184
+ FW_Flash_Messages::add(
185
+ 'fw_ext_github_update_error',
186
+ $latest_version->get_error_message(),
187
+ 'error'
188
+ );
189
+ } else {
190
+ $cache = array_merge( $cache, array( $user_slash_repo => $latest_version ) );
191
+ }
192
+
193
+ set_site_transient(
194
+ $transient_id,
195
+ $cache,
196
+ $this->transient_expiration
197
+ );
198
+
199
+ return $latest_version;
200
+ }
201
+
202
+ /**
203
+ * @param string $user_slash_repo Github 'user/repo'
204
+ * @param string $version Requested version to download
205
+ * @param string $wp_filesystem_download_directory Allocated temporary empty directory
206
+ * @param string $title Used in messages
207
+ *
208
+ * @return string|WP_Error Path to the downloaded directory
209
+ */
210
+ private function download( $user_slash_repo, $version, $wp_filesystem_download_directory, $title ) {
211
+ $http = new WP_Http();
212
+
213
+ $response = $http->get(
214
+ $this->get_github_api_url( '/repos/' . $user_slash_repo . '/releases/tags/' . $version )
215
+ );
216
+
217
+ unset( $http );
218
+
219
+ $response_code = intval( wp_remote_retrieve_response_code( $response ) );
220
+
221
+ if ( $response_code !== 200 ) {
222
+ if ( $response_code === 403 ) {
223
+ $json_response = json_decode( $response['body'], true );
224
+
225
+ if ( $json_response ) {
226
+ return new WP_Error(
227
+ 'fw_ext_update_github_download_releases_failed',
228
+ __( 'Github error:', 'fw' ) . ' ' . $json_response['message']
229
+ );
230
+ }
231
+ }
232
+
233
+ if ( $response_code ) {
234
+ return new WP_Error(
235
+ 'fw_ext_update_github_download_releases_failed',
236
+ sprintf(
237
+ __( 'Failed to access Github repository "%s" releases. (Response code: %d)', 'fw' ),
238
+ $user_slash_repo, $response_code
239
+ )
240
+ );
241
+ } else {
242
+ return new WP_Error(
243
+ 'fw_ext_update_github_download_releases_failed',
244
+ sprintf(
245
+ __( 'Failed to access Github repository "%s" releases.', 'fw' ),
246
+ $user_slash_repo
247
+ )
248
+ );
249
+ }
250
+ }
251
+
252
+ $release = json_decode( $response['body'], true );
253
+
254
+ unset( $response );
255
+
256
+ if ( empty( $release ) ) {
257
+ return new WP_Error(
258
+ 'fw_ext_update_github_download_no_release',
259
+ sprintf(
260
+ __( '%s github repository "%s" does not have the "%s" release.', 'fw' ),
261
+ $title, $user_slash_repo, $version
262
+ )
263
+ );
264
+ }
265
+
266
+ $http = new WP_Http();
267
+
268
+ $response = $http->request(
269
+ 'https://github.com/' . $user_slash_repo . '/archive/' . $release['tag_name'] . '.zip',
270
+ array(
271
+ 'timeout' => $this->download_timeout,
272
+ )
273
+ );
274
+
275
+ unset( $http );
276
+
277
+ if ( intval( wp_remote_retrieve_response_code( $response ) ) !== 200 ) {
278
+ return new WP_Error(
279
+ 'fw_ext_update_github_download_failed',
280
+ sprintf( __( 'Cannot download %s zip.', 'fw' ), $title )
281
+ );
282
+ }
283
+
284
+ /** @var WP_Filesystem_Base $wp_filesystem */
285
+ global $wp_filesystem;
286
+
287
+ $zip_path = $wp_filesystem_download_directory . '/temp.zip';
288
+
289
+ // save zip to file
290
+ if ( ! $wp_filesystem->put_contents( $zip_path, $response['body'] ) ) {
291
+ return new WP_Error(
292
+ 'fw_ext_update_github_save_download_failed',
293
+ sprintf( __( 'Cannot save %s zip.', 'fw' ), $title )
294
+ );
295
+ }
296
+
297
+ unset( $response );
298
+
299
+ $unzip_result = unzip_file(
300
+ FW_WP_Filesystem::filesystem_path_to_real_path( $zip_path ),
301
+ $wp_filesystem_download_directory
302
+ );
303
+
304
+ if ( is_wp_error( $unzip_result ) ) {
305
+ return $unzip_result;
306
+ }
307
+
308
+ // remove zip file
309
+ if ( ! $wp_filesystem->delete( $zip_path, false, 'f' ) ) {
310
+ return new WP_Error(
311
+ 'fw_ext_update_github_remove_downloaded_zip_failed',
312
+ sprintf( __( 'Cannot remove %s zip.', 'fw' ), $title )
313
+ );
314
+ }
315
+
316
+ $unzipped_dir_files = $wp_filesystem->dirlist( $wp_filesystem_download_directory );
317
+
318
+ if ( ! $unzipped_dir_files ) {
319
+ return new WP_Error(
320
+ 'fw_ext_update_github_unzipped_dir_fail',
321
+ __( 'Cannot access the unzipped directory files.', 'fw' )
322
+ );
323
+ }
324
+
325
+ /**
326
+ * get first found directory
327
+ * (if everything worked well, there should be only one directory)
328
+ */
329
+ foreach ( $unzipped_dir_files as $file ) {
330
+ if ( $file['type'] == 'd' ) {
331
+ return $wp_filesystem_download_directory . '/' . $file['name'];
332
+ }
333
+ }
334
+
335
+ return new WP_Error(
336
+ 'fw_ext_update_github_unzipped_dir_not_found',
337
+ sprintf( __( 'The unzipped %s directory not found.', 'fw' ), $title )
338
+ );
339
+ }
340
+
341
+ /**
342
+ * {@inheritdoc}
343
+ * @internal
344
+ */
345
+ public function _get_framework_latest_version( $force_check ) {
346
+ $user_slash_repo = fw()->manifest->get( $this->manifest_key );
347
+
348
+ if ( empty( $user_slash_repo ) ) {
349
+ return false;
350
+ }
351
+
352
+ return $this->get_latest_version(
353
+ $user_slash_repo,
354
+ $force_check,
355
+ __( 'Framework', 'fw' )
356
+ );
357
+ }
358
+
359
+ /**
360
+ * {@inheritdoc}
361
+ * @internal
362
+ */
363
+ public function _download_framework( $version, $wp_filesystem_download_directory ) {
364
+ return $this->download(
365
+ fw()->manifest->get( $this->manifest_key ),
366
+ $version,
367
+ $wp_filesystem_download_directory,
368
+ __( 'Framework', 'fw' )
369
+ );
370
+ }
371
+
372
+ /**
373
+ * {@inheritdoc}
374
+ * @internal
375
+ */
376
+ public function _get_theme_latest_version( $force_check ) {
377
+ $user_slash_repo = fw()->theme->manifest->get( $this->manifest_key );
378
+
379
+ if ( empty( $user_slash_repo ) ) {
380
+ return false;
381
+ }
382
+
383
+ return $this->get_latest_version(
384
+ $user_slash_repo,
385
+ $force_check,
386
+ __( 'Theme', 'fw' )
387
+ );
388
+ }
389
+
390
+ /**
391
+ * {@inheritdoc}
392
+ * @internal
393
+ */
394
+ public function _download_theme( $version, $wp_filesystem_download_directory ) {
395
+ return $this->download(
396
+ fw()->theme->manifest->get( $this->manifest_key ),
397
+ $version,
398
+ $wp_filesystem_download_directory,
399
+ __( 'Theme', 'fw' )
400
+ );
401
+ }
402
+
403
+ /**
404
+ * {@inheritdoc}
405
+ * @internal
406
+ */
407
+ public function _get_extension_latest_version( FW_Extension $extension, $force_check ) {
408
+ $user_slash_repo = $extension->manifest->get( $this->manifest_key );
409
+
410
+ if ( empty( $user_slash_repo ) ) {
411
+ return false;
412
+ }
413
+
414
+ return $this->get_latest_version(
415
+ $user_slash_repo,
416
+ $force_check,
417
+ sprintf( __( '%s extension', 'fw' ), $extension->manifest->get_name() )
418
+ );
419
+ }
420
+
421
+ /**
422
+ * {@inheritdoc}
423
+ * @internal
424
+ */
425
+ public function _download_extension( FW_Extension $extension, $version, $wp_filesystem_download_directory ) {
426
+ return $this->download(
427
+ $extension->manifest->get( $this->manifest_key ),
428
+ $version,
429
+ $wp_filesystem_download_directory,
430
+ sprintf( __( '%s extension', 'fw' ), $extension->manifest->get_name() )
431
+ );
432
+ }
433
+ }
 
 
 
 
 
 
 
 
 
 
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.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.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.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.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.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.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.12';
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.12';
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,118 @@
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', (is_child_theme() ? $theme->parent()->get('Name') : $theme->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 defined( 'FW' ) or die(); ?>
2
+
3
+ <?php if ( $updates['framework'] !== false ): ?>
4
+ <div id="fw-ext-update-framework">
5
+ <a name="fw-framework"></a>
6
+ <h3><?php _e( 'Framework', 'fw' ) ?></h3>
7
+ <?php if ( empty( $updates['framework'] ) ): ?>
8
+ <p><?php echo sprintf( __( 'You have the latest version of %s.', 'fw' ), fw()->manifest->get_name() ) ?></p>
9
+ <?php else: ?>
10
+ <?php if ( is_wp_error( $updates['framework'] ) ): ?>
11
+ <p class="wp-ui-text-notification"><?php echo $updates['framework']->get_error_message() ?></p>
12
+ <?php else: ?>
13
+ <form id="fw-ext-update-framework" method="post" action="update-core.php?action=fw-update-framework">
14
+ <p>
15
+ <?php
16
+ _e( sprintf( 'You have version %s installed. Update to %s.',
17
+ fw()->manifest->get_version(),
18
+ $updates['framework']['fixed_latest_version']
19
+ ), 'fw' )
20
+ ?>
21
+ </p>
22
+ <?php wp_nonce_field( - 1, '_nonce_fw_ext_update_framework' ); ?>
23
+ <p>
24
+ <input class="button" type="submit" value="<?php echo esc_attr( __( 'Update Framework', 'fw' ) ); ?>" name="update">
25
+ </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();
36
+ _e( sprintf( '%s Theme', ( is_child_theme() ? $theme->parent()->get( 'Name' ) : $theme->get( 'Name' ) ) ), 'fw' ) ?></h3>
37
+ <?php if ( empty( $updates['theme'] ) ): ?>
38
+ <p><?php _e( 'Your theme is up to date.', 'fw' ) ?></p>
39
+ <?php else: ?>
40
+ <?php if ( is_wp_error( $updates['theme'] ) ): ?>
41
+ <p class="wp-ui-text-notification"><?php echo $updates['theme']->get_error_message() ?></p>
42
+ <?php else: ?>
43
+ <form id="fw-ext-update-theme" method="post" action="<?php echo esc_url( add_query_arg( 'action', 'fw-update-theme', $form_action ) ); ?>">
44
+ <p>
45
+ <?php
46
+ _e( sprintf( 'You have version %s installed. Update to %s.',
47
+ fw()->theme->manifest->get_version(),
48
+ $updates['theme']['fixed_latest_version']
49
+ ), 'fw' )
50
+ ?>
51
+ </p>
52
+ <?php wp_nonce_field( - 1, '_nonce_fw_ext_update_theme' ); ?>
53
+ <p>
54
+ <input class="button" type="submit" value="<?php echo esc_attr( __( 'Update Theme', 'fw' ) ) ?>" name="update">
55
+ </p>
56
+ </form>
57
+ <?php endif; ?>
58
+ <?php endif; ?>
59
+ </div>
60
+ <?php endif; ?>
61
+
62
+ <div id="fw-ext-update-extensions">
63
+ <a name="fw-extensions"></a>
64
+ <h3><?php echo sprintf( __( '%s Extensions', 'fw' ), fw()->manifest->get_name() ); ?></h3>
65
+ <?php if ( empty( $updates['extensions'] ) ): ?>
66
+ <p><?php echo sprintf( __( 'You have the latest version of %s Extensions.', 'fw' ), fw()->manifest->get_name() ); ?></p>
67
+ <?php else: ?>
68
+ <?php
69
+ $one_update_mode = fw()->extensions->get( 'update' )->get_config( 'extensions_as_one_update' );
70
+
71
+ foreach ( $updates['extensions'] as $extension ) {
72
+ if ( is_wp_error( $extension ) ) {
73
+ /**
74
+ * Cancel the "One update mode" and display all extensions list table with details
75
+ * if at least one extension has an error that needs to be visible
76
+ */
77
+ $one_update_mode = false;
78
+ break;
79
+ }
80
+ }
81
+ ?>
82
+ <form id="fw-ext-update-extensions" method="post" action="<?php echo esc_url( add_query_arg( 'action', 'fw-update-extensions', $form_action ) ); ?>">
83
+ <div class="fw-ext-update-extensions-form-detailed"<?php echo( $one_update_mode ? ' style="display: none;"' : '' ); ?>>
84
+ <p>
85
+ <input class="button" type="submit" value="<?php echo esc_attr( __( 'Update Extensions', 'fw' ) ) ?>" name="update">
86
+ </p>
87
+ <?php
88
+ if ( ! class_exists( '_FW_Ext_Update_Extensions_List_Table' ) ) {
89
+ fw_include_file_isolated(
90
+ fw()->extensions->get( 'update' )->get_declared_path( '/includes/classes/class--fw-ext-update-extensions-list-table.php' )
91
+ );
92
+ }
93
+
94
+ $list_table = new _FW_Ext_Update_Extensions_List_Table( array( 'extensions' => $updates['extensions'] ) );
95
+ $list_table->display();
96
+ ?>
97
+ <?php wp_nonce_field( - 1, '_nonce_fw_ext_update_extensions' ); ?>
98
+ <p>
99
+ <input class="button" type="submit" value="<?php echo esc_attr( esc_html__( 'Update Extensions', 'fw' ) ); ?>" name="update">
100
+ </p>
101
+ </div>
102
+ <?php if ( $one_update_mode ) : ?>
103
+ <div class="fw-ext-update-extensions-form-simple">
104
+ <p style="color:#d54e21;"><?php _e( 'New extensions updates available.', 'fw' ); ?></p>
105
+ <p><input class="button" type="submit"
106
+ value="<?php echo esc_attr( __( 'Update Extensions', 'fw' ) ) ?>" name="update"></p>
107
+ <script type="text/javascript">
108
+ jQuery( function ( $ ) {
109
+ $( 'form#fw-ext-update-extensions' ).on( 'submit', function () {
110
+ $( this ).find( '.check-column input[type="checkbox"]' ).prop( 'checked', true );
111
+ } );
112
+ } );
113
+ </script>
114
+ </div>
115
+ <?php endif; ?>
116
+ </form>
117
+ <?php endif; ?>
118
+ </div>
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,309 +1,309 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Memory Cache
5
- *
6
- * Recommended usage example:
7
- * try {
8
- * $value = FW_Cache::get('some/key');
9
- * } catch(FW_Cache_Not_Found_Exception $e) {
10
- * $value = get_value_from_somewhere();
11
- *
12
- * FW_Cache::set('some/key', $value);
13
- *
14
- * // (!) after set, do not do this:
15
- * $value = FW_Cache::get('some/key');
16
- * // because there is no guaranty that FW_Cache::set('some/key', $value); succeeded
17
- * // trust only your $value, cache can do clean-up right after set() and remove the value you tried to set
18
- * }
19
- *
20
- * // use $value ...
21
- */
22
- class FW_Cache
23
- {
24
- /**
25
- * The actual cache
26
- * @var array
27
- */
28
- protected static $cache = array();
29
-
30
- /**
31
- * If the PHP will have less that this memory, the cache will try to delete parts from its array to free memory
32
- *
33
- * (1024 * 1024 = 1048576 = 1 Mb) * 10
34
- */
35
- protected static $min_free_memory = 10485760;
36
-
37
- /**
38
- * A special value that is used to detect if value was found in cache
39
- * We can't use null|false because these can be values set by user and we can't treat them as not existing values
40
- */
41
- protected static $not_found_value;
42
-
43
- /**
44
- * The amount of times the data was already stored in the cache.
45
- * @var int
46
- * @since 2.4.17
47
- */
48
- protected static $hits = 0;
49
-
50
- /**
51
- * Amount of times the cache did not have the value in cache.
52
- * @var int
53
- * @since 2.4.17
54
- */
55
- protected static $misses = 0;
56
-
57
- /**
58
- * Amount of times the cache free was called.
59
- * @var int
60
- * @since 2.4.17
61
- */
62
- protected static $freed = 0;
63
-
64
- protected static function get_memory_limit()
65
- {
66
- $memory_limit = ini_get('memory_limit');
67
-
68
- if ($memory_limit === '-1') { // This happens in WP CLI
69
- return 256 * 1024 * 1024;
70
- }
71
-
72
- switch (substr($memory_limit, -1)) {
73
- case 'M': return intval($memory_limit) * 1024 * 1024;
74
- case 'K': return intval($memory_limit) * 1024;
75
- case 'G': return intval($memory_limit) * 1024 * 1024 * 1024;
76
- default: return intval($memory_limit) * 1024 * 1024;
77
- }
78
- }
79
-
80
- protected static function memory_exceeded()
81
- {
82
- return memory_get_usage(false) >= self::get_memory_limit() - self::$min_free_memory;
83
-
84
- // about memory_get_usage(false) http://stackoverflow.com/a/16239377/1794248
85
- }
86
-
87
- /**
88
- * @internal
89
- */
90
- public static function _init()
91
- {
92
- self::$not_found_value = new FW_Cache_Not_Found_Exception();
93
-
94
- /**
95
- * Listen often triggered hooks to clear the memory
96
- * instead of tick function https://github.com/ThemeFuse/Unyson/issues/1197
97
- * @since 2.4.17
98
- */
99
- foreach (array(
100
- 'query' => true,
101
- 'plugins_loaded' => true,
102
- 'wp_get_object_terms' => true,
103
- 'created_term' => true,
104
- 'wp_upgrade' => true,
105
- 'added_option' => true,
106
- 'updated_option' => true,
107
- 'deleted_option' => true,
108
- 'wp_after_admin_bar_render' => true,
109
- 'http_response' => true,
110
- 'oembed_result' => true,
111
- 'customize_post_value_set' => true,
112
- 'customize_save_after' => true,
113
- 'customize_render_panel' => true,
114
- 'customize_render_control' => true,
115
- 'customize_render_section' => true,
116
- 'role_has_cap' => true,
117
- 'user_has_cap' => true,
118
- 'theme_page_templates' => true,
119
- 'pre_get_users' => true,
120
- 'request' => true,
121
- 'send_headers' => true,
122
- 'updated_usermeta' => true,
123
- 'added_usermeta' => true,
124
- 'image_memory_limit' => true,
125
- 'upload_dir' => true,
126
- 'wp_head' => true,
127
- 'wp_footer' => true,
128
- 'wp' => true,
129
- 'wp_init' => true,
130
- 'fw_init' => true,
131
- 'init' => true,
132
- 'updated_postmeta' => true,
133
- 'deleted_postmeta' => true,
134
- 'setted_transient' => true,
135
- 'registered_post_type' => true,
136
- 'wp_count_posts' => true,
137
- 'wp_count_attachments' => true,
138
- 'after_delete_post' => true,
139
- 'post_updated' => true,
140
- 'wp_insert_post' => true,
141
- 'deleted_post' => true,
142
- 'clean_post_cache' => true,
143
- 'wp_restore_post_revision' => true,
144
- 'wp_delete_post_revision' => true,
145
- 'get_term' => true,
146
- 'edited_term_taxonomies' => true,
147
- 'deleted_term_taxonomy' => true,
148
- 'edited_terms' => true,
149
- 'created_term' => true,
150
- 'clean_term_cache' => true,
151
- 'edited_term_taxonomy' => true,
152
- 'switch_theme' => true,
153
- 'wp_get_update_data' => true,
154
- 'clean_user_cache' => true,
155
- 'process_text_diff_html' => true,
156
- ) as $hook => $tmp) {
157
- add_filter($hook, array(__CLASS__, 'free_memory'), 1);
158
- }
159
-
160
- /**
161
- * Flush the cache when something major is changed (files or db values)
162
- */
163
- foreach (array(
164
- 'switch_blog' => true,
165
- 'upgrader_post_install' => true,
166
- 'upgrader_process_complete' => true,
167
- 'switch_theme' => true,
168
- ) as $hook => $tmp) {
169
- add_filter($hook, array(__CLASS__, 'clear'), 1);
170
- }
171
- }
172
-
173
- /**
174
- * This method does nothing @since 2.4.17
175
- * but we can't delete it because it's public and maybe somebody is calling it
176
- * @return bool
177
- */
178
- public static function is_enabled()
179
- {
180
- return true;
181
- }
182
-
183
- /**
184
- * @param mixed $dummy
185
- * @return mixed
186
- */
187
- public static function free_memory($dummy = null)
188
- {
189
- while (self::memory_exceeded() && !empty(self::$cache)) {
190
- reset(self::$cache);
191
-
192
- $key = key(self::$cache);
193
-
194
- unset(self::$cache[$key]);
195
- }
196
-
197
- ++self::$freed;
198
-
199
- /**
200
- * This method is used in add_filter() so to not break anything return filter value
201
- */
202
- return $dummy;
203
- }
204
-
205
- /**
206
- * @param $keys
207
- * @param $value
208
- * @param $keys_delimiter
209
- */
210
- public static function set($keys, $value, $keys_delimiter = '/')
211
- {
212
- if (!self::is_enabled()) {
213
- return;
214
- }
215
-
216
- self::free_memory();
217
-
218
- fw_aks($keys, $value, self::$cache, $keys_delimiter);
219
-
220
- self::free_memory();
221
- }
222
-
223
- /**
224
- * Unset key from cache
225
- * @param $keys
226
- * @param $keys_delimiter
227
- */
228
- public static function del($keys, $keys_delimiter = '/')
229
- {
230
- fw_aku($keys, self::$cache, $keys_delimiter);
231
-
232
- self::free_memory();
233
- }
234
-
235
- /**
236
- * @param $keys
237
- * @param $keys_delimiter
238
- * @return mixed
239
- * @throws FW_Cache_Not_Found_Exception
240
- */
241
- public static function get($keys, $keys_delimiter = '/')
242
- {
243
- $keys = (string)$keys;
244
- $keys_arr = explode($keys_delimiter, $keys);
245
-
246
- $key = $keys_arr;
247
- $key = array_shift($key);
248
-
249
- if ($key === '' || $key === null) {
250
- trigger_error('First key must not be empty', E_USER_ERROR);
251
- }
252
-
253
- self::free_memory();
254
-
255
- $value = fw_akg($keys, self::$cache, self::$not_found_value, $keys_delimiter);
256
-
257
- self::free_memory();
258
-
259
- if ($value === self::$not_found_value) {
260
- ++self::$misses;
261
-
262
- throw new FW_Cache_Not_Found_Exception();
263
- } else {
264
- ++self::$hits;
265
-
266
- return $value;
267
- }
268
- }
269
-
270
- /**
271
- * Empty the cache
272
- * @param mixed $dummy When method is used in add_filter()
273
- * @return mixed
274
- */
275
- public static function clear($dummy = null)
276
- {
277
- self::$cache = array();
278
-
279
- /**
280
- * This method is used in add_filter() so to not break anything return filter value
281
- */
282
- return $dummy;
283
- }
284
-
285
- /**
286
- * Debug information
287
- * <?php add_action('admin_footer', function(){ FW_Cache::stats(); });
288
- * @since 2.4.17
289
- */
290
- public static function stats() {
291
- echo '<div style="z-index: 10000; position: relative; background: #fff; padding: 15px;">';
292
- echo '<p>';
293
- echo '<strong>Cache Hits:</strong> '. self::$hits .'<br />';
294
- echo '<strong>Cache Misses:</strong> '. self::$misses .'<br />';
295
- echo '<strong>Cache Freed:</strong> '. self::$freed .'<br />';
296
- echo '<strong>PHP Memory Peak Usage:</strong> '. fw_human_bytes(memory_get_peak_usage(false)) .'<br />';
297
- echo '</p>';
298
- echo '<ul>';
299
- foreach (self::$cache as $group => $cache) {
300
- echo "<li><strong>Group:</strong> $group - ( " . number_format( strlen( serialize( $cache ) ) / KB_IN_BYTES, 2 ) . 'k )</li>';
301
- }
302
- echo '</ul>';
303
- echo '</div>';
304
- }
305
- }
306
-
307
- class FW_Cache_Not_Found_Exception extends Exception {}
308
-
309
  FW_Cache::_init();
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Memory Cache
5
+ *
6
+ * Recommended usage example:
7
+ * try {
8
+ * $value = FW_Cache::get('some/key');
9
+ * } catch(FW_Cache_Not_Found_Exception $e) {
10
+ * $value = get_value_from_somewhere();
11
+ *
12
+ * FW_Cache::set('some/key', $value);
13
+ *
14
+ * // (!) after set, do not do this:
15
+ * $value = FW_Cache::get('some/key');
16
+ * // because there is no guaranty that FW_Cache::set('some/key', $value); succeeded
17
+ * // trust only your $value, cache can do clean-up right after set() and remove the value you tried to set
18
+ * }
19
+ *
20
+ * // use $value ...
21
+ */
22
+ class FW_Cache
23
+ {
24
+ /**
25
+ * The actual cache
26
+ * @var array
27
+ */
28
+ protected static $cache = array();
29
+
30
+ /**
31
+ * If the PHP will have less that this memory, the cache will try to delete parts from its array to free memory
32
+ *
33
+ * (1024 * 1024 = 1048576 = 1 Mb) * 10
34
+ */
35
+ protected static $min_free_memory = 10485760;
36
+
37
+ /**
38
+ * A special value that is used to detect if value was found in cache
39
+ * We can't use null|false because these can be values set by user and we can't treat them as not existing values
40
+ */
41
+ protected static $not_found_value;
42
+
43
+ /**
44
+ * The amount of times the data was already stored in the cache.
45
+ * @var int
46
+ * @since 2.4.17
47
+ */
48
+ protected static $hits = 0;
49
+
50
+ /**
51
+ * Amount of times the cache did not have the value in cache.
52
+ * @var int
53
+ * @since 2.4.17
54
+ */
55
+ protected static $misses = 0;
56
+
57
+ /**
58
+ * Amount of times the cache free was called.
59
+ * @var int
60
+ * @since 2.4.17
61
+ */
62
+ protected static $freed = 0;
63
+
64
+ protected static function get_memory_limit()
65
+ {
66
+ $memory_limit = ini_get('memory_limit');
67
+
68
+ if ($memory_limit === '-1') { // This happens in WP CLI
69
+ return 256 * 1024 * 1024;
70
+ }
71
+
72
+ switch (substr($memory_limit, -1)) {
73
+ case 'M': return intval($memory_limit) * 1024 * 1024;
74
+ case 'K': return intval($memory_limit) * 1024;
75
+ case 'G': return intval($memory_limit) * 1024 * 1024 * 1024;
76
+ default: return intval($memory_limit) * 1024 * 1024;
77
+ }
78
+ }
79
+
80
+ protected static function memory_exceeded()
81
+ {
82
+ return memory_get_usage(false) >= self::get_memory_limit() - self::$min_free_memory;
83
+
84
+ // about memory_get_usage(false) http://stackoverflow.com/a/16239377/1794248
85
+ }
86
+
87
+ /**
88
+ * @internal
89
+ */
90
+ public static function _init()
91
+ {
92
+ self::$not_found_value = new FW_Cache_Not_Found_Exception();
93
+
94
+ /**
95
+ * Listen often triggered hooks to clear the memory
96
+ * instead of tick function https://github.com/ThemeFuse/Unyson/issues/1197
97
+ * @since 2.4.17
98
+ */
99
+ foreach (array(
100
+ 'query' => true,
101
+ 'plugins_loaded' => true,
102
+ 'wp_get_object_terms' => true,
103
+ 'created_term' => true,
104
+ 'wp_upgrade' => true,
105
+ 'added_option' => true,
106
+ 'updated_option' => true,
107
+ 'deleted_option' => true,
108
+ 'wp_after_admin_bar_render' => true,
109
+ 'http_response' => true,
110
+ 'oembed_result' => true,
111
+ 'customize_post_value_set' => true,
112
+ 'customize_save_after' => true,
113
+ 'customize_render_panel' => true,
114
+ 'customize_render_control' => true,
115
+ 'customize_render_section' => true,
116
+ 'role_has_cap' => true,
117
+ 'user_has_cap' => true,
118
+ 'theme_page_templates' => true,
119
+ 'pre_get_users' => true,
120
+ 'request' => true,
121
+ 'send_headers' => true,
122
+ 'updated_usermeta' => true,
123
+ 'added_usermeta' => true,
124
+ 'image_memory_limit' => true,
125
+ 'upload_dir' => true,
126
+ 'wp_head' => true,
127
+ 'wp_footer' => true,
128
+ 'wp' => true,
129
+ 'wp_init' => true,
130
+ 'fw_init' => true,
131
+ 'init' => true,
132
+ 'updated_postmeta' => true,
133
+ 'deleted_postmeta' => true,
134
+ 'setted_transient' => true,
135
+ 'registered_post_type' => true,
136
+ 'wp_count_posts' => true,
137
+ 'wp_count_attachments' => true,
138
+ 'after_delete_post' => true,
139
+ 'post_updated' => true,
140
+ 'wp_insert_post' => true,
141
+ 'deleted_post' => true,
142
+ 'clean_post_cache' => true,
143
+ 'wp_restore_post_revision' => true,
144
+ 'wp_delete_post_revision' => true,
145
+ 'get_term' => true,
146
+ 'edited_term_taxonomies' => true,
147
+ 'deleted_term_taxonomy' => true,
148
+ 'edited_terms' => true,
149
+ 'created_term' => true,
150
+ 'clean_term_cache' => true,
151
+ 'edited_term_taxonomy' => true,
152
+ 'switch_theme' => true,
153
+ 'wp_get_update_data' => true,
154
+ 'clean_user_cache' => true,
155
+ 'process_text_diff_html' => true,
156
+ ) as $hook => $tmp) {
157
+ add_filter($hook, array(__CLASS__, 'free_memory'), 1);
158
+ }
159
+
160
+ /**
161
+ * Flush the cache when something major is changed (files or db values)
162
+ */
163
+ foreach (array(
164
+ 'switch_blog' => true,
165
+ 'upgrader_post_install' => true,
166
+ 'upgrader_process_complete' => true,
167
+ 'switch_theme' => true,
168
+ ) as $hook => $tmp) {
169
+ add_filter($hook, array(__CLASS__, 'clear'), 1);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * This method does nothing @since 2.4.17
175
+ * but we can't delete it because it's public and maybe somebody is calling it
176
+ * @return bool
177
+ */
178
+ public static function is_enabled()
179
+ {
180
+ return true;
181
+ }
182
+
183
+ /**
184
+ * @param mixed $dummy
185
+ * @return mixed
186
+ */
187
+ public static function free_memory($dummy = null)
188
+ {
189
+ while (self::memory_exceeded() && !empty(self::$cache)) {
190
+ reset(self::$cache);
191
+
192
+ $key = key(self::$cache);
193
+
194
+ unset(self::$cache[$key]);
195
+ }
196
+
197
+ ++self::$freed;
198
+
199
+ /**
200
+ * This method is used in add_filter() so to not break anything return filter value
201
+ */
202
+ return $dummy;
203
+ }
204
+
205
+ /**
206
+ * @param $keys
207
+ * @param $value
208
+ * @param $keys_delimiter
209
+ */
210
+ public static function set($keys, $value, $keys_delimiter = '/')
211
+ {
212
+ if (!self::is_enabled()) {
213
+ return;
214
+ }
215
+
216
+ self::free_memory();
217
+
218
+ fw_aks($keys, $value, self::$cache, $keys_delimiter);
219
+
220
+ self::free_memory();
221
+ }
222
+
223
+ /**
224
+ * Unset key from cache
225
+ * @param $keys
226
+ * @param $keys_delimiter
227
+ */
228
+ public static function del($keys, $keys_delimiter = '/')
229
+ {
230
+ fw_aku($keys, self::$cache, $keys_delimiter);
231
+
232
+ self::free_memory();
233
+ }
234
+
235
+ /**
236
+ * @param $keys
237
+ * @param $keys_delimiter
238
+ * @return mixed
239
+ * @throws FW_Cache_Not_Found_Exception
240
+ */
241
+ public static function get($keys, $keys_delimiter = '/')
242
+ {
243
+ $keys = (string)$keys;
244
+ $keys_arr = explode($keys_delimiter, $keys);
245
+
246
+ $key = $keys_arr;
247
+ $key = array_shift($key);
248
+
249
+ if ($key === '' || $key === null) {
250
+ trigger_error('First key must not be empty', E_USER_ERROR);
251
+ }
252
+
253
+ self::free_memory();
254
+
255
+ $value = fw_akg($keys, self::$cache, self::$not_found_value, $keys_delimiter);
256
+
257
+ self::free_memory();
258
+
259
+ if ($value === self::$not_found_value) {
260
+ ++self::$misses;
261
+
262
+ throw new FW_Cache_Not_Found_Exception();
263
+ } else {
264
+ ++self::$hits;
265
+
266
+ return $value;
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Empty the cache
272
+ * @param mixed $dummy When method is used in add_filter()
273
+ * @return mixed
274
+ */
275
+ public static function clear($dummy = null)
276
+ {
277
+ self::$cache = array();
278
+
279
+ /**
280
+ * This method is used in add_filter() so to not break anything return filter value
281
+ */
282
+ return $dummy;
283
+ }
284
+
285
+ /**
286
+ * Debug information
287
+ * <?php add_action('admin_footer', function(){ FW_Cache::stats(); });
288
+ * @since 2.4.17
289
+ */
290
+ public static function stats() {
291
+ echo '<div style="z-index: 10000; position: relative; background: #fff; padding: 15px;">';
292
+ echo '<p>';
293
+ echo '<strong>Cache Hits:</strong> '. self::$hits .'<br />';
294
+ echo '<strong>Cache Misses:</strong> '. self::$misses .'<br />';
295
+ echo '<strong>Cache Freed:</strong> '. self::$freed .'<br />';
296
+ echo '<strong>PHP Memory Peak Usage:</strong> '. fw_human_bytes(memory_get_peak_usage(false)) .'<br />';
297
+ echo '</p>';
298
+ echo '<ul>';
299
+ foreach (self::$cache as $group => $cache) {
300
+ echo "<li><strong>Group:</strong> $group - ( " . number_format( strlen( serialize( $cache ) ) / KB_IN_BYTES, 2 ) . 'k )</li>';
301
+ }
302
+ echo '</ul>';
303
+ echo '</div>';
304
+ }
305
+ }
306
+
307
+ class FW_Cache_Not_Found_Exception extends Exception {}
308
+
309
  FW_Cache::_init();
framework/helpers/class-fw-callback.php CHANGED
@@ -1,132 +1,132 @@
1
- <?php if ( ! defined( 'FW' ) ) {
2
- die( 'Forbidden' );
3
- }
4
-
5
- /**
6
- * Class FW_Callback
7
- *
8
- * @since 2.6.14
9
- */
10
- class FW_Callback {
11
- /**
12
- * @var $callback string|array
13
- */
14
- private $callback;
15
-
16
- /**
17
- * @var array $args
18
- */
19
- private $args;
20
-
21
- /**
22
- * @var bool
23
- */
24
- private $cache;
25
-
26
- /**
27
- * @var string
28
- */
29
- private $id;
30
-
31
- /**
32
- * FW_Callback constructor.
33
- *
34
- * @param string|array|Closure $callback Callback function
35
- * @param array $args Callback arguments
36
- * @param bool $cache Whenever you want to cache the function value after it's first call or not
37
- * Recommend when the function call may require many resources or time (database requests) , or the value is small
38
- * Not recommended using on very large values
39
- *
40
- */
41
- public function __construct( $callback, array $args = array(), $cache = true ) {
42
- $this->callback = $callback;
43
- $this->args = $args;
44
- $this->cache = (bool) $cache;
45
- }
46
-
47
- /**
48
- * Return callback function
49
- * @return array|string
50
- */
51
- public function get_callback() {
52
- return $this->callback;
53
- }
54
-
55
- /**
56
- * Return callback function arguments
57
- * @return array
58
- */
59
- public function get_args() {
60
- return $this->args;
61
- }
62
-
63
- /**
64
- * Execute callback function
65
- * @return mixed
66
- */
67
- public function execute() {
68
- if ( $this->cache ) {
69
- try {
70
- return FW_Cache::get( $this->get_id() );
71
- } catch ( FW_Cache_Not_Found_Exception $e ) {
72
- FW_Cache::set(
73
- $this->get_id(),
74
- $value = $this->get_value()
75
- );
76
-
77
- return $value;
78
- }
79
- } else {
80
- return $this->get_value();
81
- }
82
- }
83
-
84
- /**
85
- * Whenever you want to clear the cached value or the function
86
- */
87
- public function clear_cache() {
88
- FW_Cache::del( $this->get_id() );
89
- }
90
-
91
- /**
92
- * Get raw callback value, ignoring the cache
93
- * @return mixed
94
- */
95
- protected function get_value() {
96
- return call_user_func_array( $this->callback, $this->args );
97
- }
98
-
99
- protected function get_id() {
100
- if ( ! is_string( $this->id ) ) {
101
- //$this->id = 'fw-callback-' . md5( $this->serialize_callback() . serialize( $this->args ) );
102
- //Disabled temporary for optimization reasons
103
- //Maybe later will come with a better idea.
104
- $this->id = uniqid( 'fw-callback-' );
105
- }
106
-
107
- return $this->id;
108
- }
109
-
110
- protected function serialize_callback() {
111
-
112
- if ( is_string( $this->callback ) ) {
113
- return $this->callback;
114
- }
115
-
116
- if ( is_object( $this->callback ) ) {
117
- return spl_object_hash( $this->callback );
118
- }
119
-
120
- if ( is_array( $this->callback ) ) {
121
- $callback = $this->callback;
122
-
123
- if ( is_object( ( $first = array_shift( $callback ) ) ) ) {
124
- return spl_object_hash( $first ) . serialize( $callback );
125
- }
126
-
127
- return serialize( $this->callback );
128
- }
129
-
130
- return uniqid();
131
- }
132
  }
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ /**
6
+ * Class FW_Callback
7
+ *
8
+ * @since 2.6.14
9
+ */
10
+ class FW_Callback {
11
+ /**
12
+ * @var $callback string|array
13
+ */
14
+ private $callback;
15
+
16
+ /**
17
+ * @var array $args
18
+ */
19
+ private $args;
20
+
21
+ /**
22
+ * @var bool
23
+ */
24
+ private $cache;
25
+
26
+ /**
27
+ * @var string
28
+ */
29
+ private $id;
30
+
31
+ /**
32
+ * FW_Callback constructor.
33
+ *
34
+ * @param string|array|Closure $callback Callback function
35
+ * @param array $args Callback arguments
36
+ * @param bool $cache Whenever you want to cache the function value after it's first call or not
37
+ * Recommend when the function call may require many resources or time (database requests) , or the value is small
38
+ * Not recommended using on very large values
39
+ *
40
+ */
41
+ public function __construct( $callback, array $args = array(), $cache = true ) {
42
+ $this->callback = $callback;
43
+ $this->args = $args;
44
+ $this->cache = (bool) $cache;
45
+ }
46
+
47
+ /**
48
+ * Return callback function
49
+ * @return array|string
50
+ */
51
+ public function get_callback() {
52
+ return $this->callback;
53
+ }
54
+
55
+ /**
56
+ * Return callback function arguments
57
+ * @return array
58
+ */
59
+ public function get_args() {
60
+ return $this->args;
61
+ }
62
+
63
+ /**
64
+ * Execute callback function
65
+ * @return mixed
66
+ */
67
+ public function execute() {
68
+ if ( $this->cache ) {
69
+ try {
70
+ return FW_Cache::get( $this->get_id() );
71
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
72
+ FW_Cache::set(
73
+ $this->get_id(),
74
+ $value = $this->get_value()
75
+ );
76
+
77
+ return $value;
78
+ }
79
+ } else {
80
+ return $this->get_value();
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Whenever you want to clear the cached value or the function
86
+ */
87
+ public function clear_cache() {
88
+ FW_Cache::del( $this->get_id() );
89
+ }
90
+
91
+ /**
92
+ * Get raw callback value, ignoring the cache
93
+ * @return mixed
94
+ */
95
+ protected function get_value() {
96
+ return call_user_func_array( $this->callback, $this->args );
97
+ }
98
+
99
+ protected function get_id() {
100
+ if ( ! is_string( $this->id ) ) {
101
+ //$this->id = 'fw-callback-' . md5( $this->serialize_callback() . serialize( $this->args ) );
102
+ //Disabled temporary for optimization reasons
103
+ //Maybe later will come with a better idea.
104
+ $this->id = uniqid( 'fw-callback-' );
105
+ }
106
+
107
+ return $this->id;
108
+ }
109
+
110
+ protected function serialize_callback() {
111
+
112
+ if ( is_string( $this->callback ) ) {
113
+ return $this->callback;
114
+ }
115
+
116
+ if ( is_object( $this->callback ) ) {
117
+ return spl_object_hash( $this->callback );
118
+ }
119
+
120
+ if ( is_array( $this->callback ) ) {
121
+ $callback = $this->callback;
122
+
123
+ if ( is_object( ( $first = array_shift( $callback ) ) ) ) {
124
+ return spl_object_hash( $first ) . serialize( $callback );
125
+ }
126
+
127
+ return serialize( $this->callback );
128
+ }
129
+
130
+ return uniqid();
131
+ }
132
  }
framework/helpers/class-fw-db-options-model.php CHANGED
@@ -1,319 +1,330 @@
1
- <?php if (!defined('FW')) die('Forbidden');
2
-
3
- /**
4
- * Lets you create easy functions for get/set database option values
5
- * it will handle all clever logic with default values, multikeys and processing options fw-storage parameter
6
- * @since 2.5.9
7
- */
8
- abstract class FW_Db_Options_Model {
9
- /**
10
- * @return string Must not contain '/'
11
- */
12
- abstract protected function get_id();
13
-
14
- /**
15
- * @param null|int|string $item_id
16
- * @param array $extra_data
17
- * @return mixed
18
- */
19
- abstract protected function get_values($item_id, array $extra_data = array());
20
-
21
- /**
22
- * @param null|int|string $item_id
23
- * @param mixed $values
24
- * @param array $extra_data
25
- * @return void
26
- */
27
- abstract protected function set_values($item_id, $values, array $extra_data = array());
28
-
29
- /**
30
- * @param null|int|string $item_id
31
- * @param array $extra_data
32
- * @return array
33
- */
34
- abstract protected function get_options($item_id, array $extra_data = array());
35
-
36
- /**
37
- * @param null|int|string $item_id
38
- * @param array $extra_data
39
- * @return array E.g. for post options {'post-id': $item_id}
40
- * @see fw_db_option_storage_type()
41
- */
42
- abstract protected function get_fw_storage_params($item_id, array $extra_data = array());
43
-
44
- abstract protected function _init();
45
-
46
- /**
47
- * @param null|int|string $item_id
48
- * @param null|string $option_id
49
- * @param null|string $sub_keys
50
- * @param mixed $old_value
51
- * @param array $extra_data
52
- */
53
- protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {}
54
-
55
- /**
56
- * Get sub-key. For e.g. if each item must have a separate key or not.
57
- * @param string $key
58
- * @param null|int|string $item_id
59
- * @param array $extra_data
60
- * @return null|string
61
- */
62
- protected function _get_cache_key($key, $item_id, array $extra_data = array()) {
63
- return empty($item_id) ? null : $item_id;
64
- }
65
-
66
- /**
67
- * @var array {'id': mixed}
68
- */
69
- private static $instances = array();
70
-
71
- /**
72
- * @param string $id
73
- * @return FW_Db_Options_Model
74
- * @internal
75
- */
76
- final public static function _get_instance($id) {
77
- return self::$instances[$id];
78
- }
79
-
80
- /**
81
- * @return string
82
- * @since 2.6.7
83
- */
84
- final public function get_main_cache_key() {
85
- return 'fw-options-model:'. $this->get_id();
86
- }
87
-
88
- final public function __construct() {
89
- if (isset(self::$instances[ $this->get_id() ])) {
90
- trigger_error(__CLASS__ .' with id "'. $this->get_id() .'" was already defined', E_USER_ERROR);
91
- } else {
92
- self::$instances[ $this->get_id() ] = $this;
93
- }
94
-
95
- $this->_init();
96
- }
97
-
98
- private function get_cache_key($key, $item_id, array $extra_data = array()) {
99
- $item_key = $this->_get_cache_key($key, $item_id, $extra_data);
100
-
101
- return $this->get_main_cache_key() .'/'. $key . (empty($item_key) ? '' : '/'. $item_key);
102
- }
103
-
104
- /**
105
- * @param null|int|string $item_id
106
- * @param null|string $option_id
107
- * @param mixed $default_value
108
- * @param array $extra_data
109
- * @return mixed
110
- */
111
- final public function get( $item_id = null, $option_id = null, $default_value = null, array $extra_data = array() ) {
112
- if (empty($option_id)) {
113
- $sub_keys = null;
114
- } else {
115
- $option_id = explode('/', $option_id); // 'option_id/sub/keys'
116
- $_option_id = array_shift($option_id); // 'option_id'
117
- $sub_keys = empty($option_id) ? null : implode('/', $option_id); // 'sub/keys'
118
- $option_id = $_option_id;
119
- unset($_option_id);
120
- }
121
-
122
- try {
123
- // Cached because values are merged with extracted default values
124
- $values = FW_Cache::get($cache_key_values = $this->get_cache_key('values', $item_id, $extra_data));
125
- } catch (FW_Cache_Not_Found_Exception $e) {
126
- FW_Cache::set(
127
- $cache_key_values,
128
- $values = (is_array($values = $this->get_values($item_id, $extra_data)) ? $values : array())
129
- );
130
- }
131
-
132
- /**
133
- * If db value is not found and default value is provided
134
- * return default value before the options file is loaded
135
- */
136
- if ( ! is_null($default_value) ) {
137
- if ( empty( $option_id ) ) {
138
- if ( empty( $values )
139
- && (
140
- is_array( $default_value )
141
- ||
142
- fw_is_callback( is_array( $default_value ) )
143
- )
144
- ) {
145
- return fw_call( $default_value );
146
- }
147
- } else {
148
- if ( is_null( $sub_keys ) ) {
149
- if ( ! isset( $values[ $option_id ] ) ) {
150
- return fw_call( $default_value );
151
- }
152
- } else {
153
- if ( ! isset($values[ $option_id ]) || is_null( fw_akg( $sub_keys, $values[ $option_id ] ) ) ) {
154
- return fw_call( $default_value );
155
- }
156
- }
157
- }
158
- }
159
-
160
- try {
161
- $options = FW_Cache::get( $cache_key = $this->get_cache_key('options', $item_id, $extra_data));
162
- } catch (FW_Cache_Not_Found_Exception $e) {
163
- FW_Cache::set($cache_key, array()); // prevent recursion
164
- FW_Cache::set($cache_key, $options = fw_extract_only_options($this->get_options($item_id, $extra_data)));
165
- }
166
-
167
- if ($options) {
168
- try {
169
- FW_Cache::get(
170
- // fixes https://github.com/ThemeFuse/Unyson/issues/2034
171
- $cache_key_values_processed = $this->get_cache_key('values:processed', $item_id, $extra_data)
172
- );
173
- } catch (FW_Cache_Not_Found_Exception $e) {
174
- /**
175
- * Set cache value before processing options
176
- * Fixes https://github.com/ThemeFuse/Unyson/issues/2034#issuecomment-248571149
177
- */
178
- FW_Cache::set($cache_key_values_processed, true);
179
-
180
- // Complete missing db values with default values from options array
181
- {
182
- try {
183
- $skip_types_process = FW_Cache::get($cache_key = 'fw:options-default-values:skip-types');
184
- } catch (FW_Cache_Not_Found_Exception $e) {
185
- FW_Cache::set(
186
- $cache_key,
187
- $skip_types_process = apply_filters('fw:options-default-values:skip-types', array(
188
- // 'type' => true
189
- ))
190
- );
191
- }
192
-
193
- foreach (array_diff_key(fw_extract_only_options( $options ), $values) as $id => $option) {
194
- $values[ $id ] = isset($skip_types_process[ $option['type'] ])
195
- ? (
196
- isset($option['value'])
197
- ? $option['value']
198
- : fw()->backend->option_type( $option['type'] )->get_defaults( 'value' )
199
- )
200
- : fw()->backend->option_type($option['type'])->get_value_from_input($option, null);
201
- }
202
- }
203
-
204
- foreach ($options as $id => $option) {
205
- $values[$id] = fw()->backend->option_type($option['type'])->storage_load(
206
- $id,
207
- $option,
208
- isset($values[$id]) ? $values[$id] : null,
209
- $this->get_fw_storage_params($item_id, $extra_data)
210
- );
211
- }
212
-
213
- FW_Cache::set($cache_key_values, $values);
214
- }
215
- }
216
-
217
- if (empty($option_id)) {
218
- return ( empty( $values ) && ( is_array( $default_value ) || fw_is_callback( $default_value ) ) )
219
- ? fw_call( $default_value )
220
- : $values;
221
- } else {
222
- if (is_null($sub_keys)) {
223
- return isset($values[$option_id])
224
- ? $values[$option_id]
225
- : fw_call( $default_value );
226
- } else {
227
- return isset($values[$option_id])
228
- ? fw_akg($sub_keys, $values[$option_id], $default_value)
229
- : fw_call( $default_value );
230
- }
231
- }
232
- }
233
-
234
- final public function set( $item_id = null, $option_id = null, $value, array $extra_data = array() ) {
235
- FW_Cache::del($cache_key_values = $this->get_cache_key('values', $item_id, $extra_data));
236
- FW_Cache::del($cache_key_values_processed = $this->get_cache_key('values:processed', $item_id, $extra_data));
237
-
238
- try {
239
- $options = FW_Cache::get($cache_key = $this->get_cache_key('options', $item_id, $extra_data));
240
- } catch (FW_Cache_Not_Found_Exception $e) {
241
- FW_Cache::set($cache_key, array()); // prevent recursion
242
- FW_Cache::set($cache_key, $options = fw_extract_only_options($this->get_options($item_id, $extra_data)));
243
- }
244
-
245
- $sub_keys = null;
246
-
247
- if ($option_id) {
248
- $option_id = explode('/', $option_id); // 'option_id/sub/keys'
249
- $_option_id = array_shift($option_id); // 'option_id'
250
- $sub_keys = empty($option_id) ? null : implode('/', $option_id); // 'sub/keys'
251
- $option_id = $_option_id;
252
- unset($_option_id);
253
-
254
- $old_values = is_array($old_values = $this->get_values($item_id, $extra_data)) ? $old_values : array();
255
- $old_value = isset($old_values[$option_id]) ? $old_values[$option_id] : null;
256
-
257
- if ($sub_keys) { // update sub_key in old_value and use the entire value
258
- $new_value = $old_value;
259
- fw_aks($sub_keys, $value, $new_value);
260
- $value = $new_value;
261
- unset($new_value);
262
-
263
- $old_value = fw_akg($sub_keys, $old_value);
264
- }
265
-
266
- if (isset($options[$option_id])) {
267
- $value = fw()->backend->option_type($options[$option_id]['type'])->storage_save(
268
- $option_id,
269
- $options[$option_id],
270
- $value,
271
- $this->get_fw_storage_params($item_id, $extra_data)
272
- );
273
- }
274
-
275
- $old_values[$option_id] = $value;
276
-
277
- $this->set_values($item_id, $old_values, $extra_data);
278
-
279
- unset($old_values);
280
- } else {
281
- $old_value = is_array($old_values = $this->get_values($item_id, $extra_data)) ? $old_values : array();
282
-
283
- if ( ! is_array($value) ) {
284
- $value = array();
285
- }
286
-
287
- if (empty($value)) {
288
- // All options reset. Reset all fw-storage values too
289
- // Fixes https://github.com/ThemeFuse/Unyson/issues/2179
290
- foreach ($options as $_option_id => $_option) {
291
- fw()->backend->option_type($options[$_option_id]['type'])->storage_save(
292
- $_option_id,
293
- $_option,
294
- fw()->backend->option_type($options[$_option_id]['type'])->get_defaults('value'),
295
- $this->get_fw_storage_params($item_id, $extra_data)
296
- );
297
- }
298
- } else {
299
- foreach ($value as $_option_id => $_option_value) {
300
- if (isset($options[$_option_id])) {
301
- $value[$_option_id] = fw()->backend->option_type($options[$_option_id]['type'])->storage_save(
302
- $_option_id,
303
- $options[$_option_id],
304
- $_option_value,
305
- $this->get_fw_storage_params($item_id, $extra_data)
306
- );
307
- }
308
- }
309
- }
310
-
311
- $this->set_values($item_id, $value, $extra_data);
312
- }
313
-
314
- FW_Cache::del($cache_key_values); // fixes https://github.com/ThemeFuse/Unyson/issues/1538
315
- FW_Cache::del($cache_key_values_processed);
316
-
317
- $this->_after_set($item_id, $option_id, $sub_keys, $old_value, $extra_data);
318
- }
319
- }
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (!defined('FW')) die('Forbidden');
2
+
3
+ /**
4
+ * Lets you create easy functions for get/set database option values
5
+ * it will handle all clever logic with default values, multikeys and processing options fw-storage parameter
6
+ * @since 2.5.9
7
+ */
8
+ abstract class FW_Db_Options_Model {
9
+ /**
10
+ * @return string Must not contain '/'
11
+ */
12
+ abstract protected function get_id();
13
+
14
+ /**
15
+ * @param null|int|string $item_id
16
+ * @param array $extra_data
17
+ * @return mixed
18
+ */
19
+ abstract protected function get_values($item_id, array $extra_data = array());
20
+
21
+ /**
22
+ * @param null|int|string $item_id
23
+ * @param mixed $values
24
+ * @param array $extra_data
25
+ * @return void
26
+ */
27
+ abstract protected function set_values($item_id, $values, array $extra_data = array());
28
+
29
+ /**
30
+ * @param null|int|string $item_id
31
+ * @param array $extra_data
32
+ * @return array
33
+ */
34
+ abstract protected function get_options($item_id, array $extra_data = array());
35
+
36
+ /**
37
+ * @param null|int|string $item_id
38
+ * @param array $extra_data
39
+ * @return array E.g. for post options {'post-id': $item_id}
40
+ * @see fw_db_option_storage_type()
41
+ */
42
+ abstract protected function get_fw_storage_params($item_id, array $extra_data = array());
43
+
44
+ abstract protected function _init();
45
+
46
+ /**
47
+ * @param null|int|string $item_id
48
+ * @param null|string $option_id
49
+ * @param null|string $sub_keys
50
+ * @param mixed $old_value
51
+ * @param array $extra_data
52
+ */
53
+ protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {}
54
+
55
+ /**
56
+ * Get sub-key. For e.g. if each item must have a separate key or not.
57
+ * @param string $key
58
+ * @param null|int|string $item_id
59
+ * @param array $extra_data
60
+ * @return null|string
61
+ */
62
+ protected function _get_cache_key($key, $item_id, array $extra_data = array()) {
63
+ return empty($item_id) ? null : $item_id;
64
+ }
65
+
66
+ /**
67
+ * @var array {'id': mixed}
68
+ */
69
+ private static $instances = array();
70
+
71
+ /**
72
+ * @param string $id
73
+ * @return FW_Db_Options_Model
74
+ * @internal
75
+ */
76
+ final public static function _get_instance($id) {
77
+ return self::$instances[$id];
78
+ }
79
+
80
+ /**
81
+ * @return string
82
+ * @since 2.6.7
83
+ */
84
+ final public function get_main_cache_key() {
85
+ return 'fw-options-model:'. $this->get_id();
86
+ }
87
+
88
+ final public function __construct() {
89
+ if (isset(self::$instances[ $this->get_id() ])) {
90
+ trigger_error(__CLASS__ .' with id "'. $this->get_id() .'" was already defined', E_USER_ERROR);
91
+ } else {
92
+ self::$instances[ $this->get_id() ] = $this;
93
+ }
94
+
95
+ $this->_init();
96
+ }
97
+
98
+ private function get_cache_key($key, $item_id, array $extra_data = array()) {
99
+ $item_key = $this->_get_cache_key($key, $item_id, $extra_data);
100
+
101
+ return $this->get_main_cache_key() .'/'. $key . (empty($item_key) ? '' : '/'. $item_key);
102
+ }
103
+
104
+ /**
105
+ * @param null|int|string $item_id
106
+ * @param null|string $option_id
107
+ * @param mixed $default_value
108
+ * @param array $extra_data
109
+ * @return mixed
110
+ */
111
+ final public function get( $item_id = null, $option_id = null, $default_value = null, array $extra_data = array() ) {
112
+
113
+ if ( is_preview() ) {
114
+ global $wp_query;
115
+
116
+ if ( $wp_query->queried_object && ( is_single( $item_id ) || is_page( $item_id ) ) ) {
117
+ $reset_get_rev = wp_get_post_revisions( $item_id );
118
+ $item_id = ( $rewisions = reset( $reset_get_rev ) ) && isset( $rewisions->ID ) ? $rewisions->ID : $item_id;
119
+ }
120
+ }
121
+
122
+ if ( empty( $option_id ) ) {
123
+ $sub_keys = null;
124
+ } else {
125
+ $option_id = explode( '/', $option_id ); // 'option_id/sub/keys'
126
+ $_option_id = array_shift( $option_id ); // 'option_id'
127
+ $sub_keys = empty( $option_id ) ? null : implode( '/', $option_id ); // 'sub/keys'
128
+ $option_id = $_option_id;
129
+ unset( $_option_id );
130
+ }
131
+
132
+ try {
133
+ // Cached because values are merged with extracted default values
134
+ $values = FW_Cache::get( $cache_key_values = $this->get_cache_key( 'values', $item_id, $extra_data ) );
135
+
136
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
137
+
138
+ FW_Cache::set(
139
+ $cache_key_values,
140
+ $values = ( is_array( $values = $this->get_values( $item_id, $extra_data ) ) ? $values : array() )
141
+ );
142
+ }
143
+
144
+ /**
145
+ * If db value is not found and default value is provided
146
+ * return default value before the options file is loaded
147
+ */
148
+ if ( ! is_null( $default_value ) ) {
149
+ if ( empty( $option_id ) ) {
150
+ if ( empty( $values )
151
+ && (
152
+ is_array( $default_value )
153
+ ||
154
+ fw_is_callback( is_array( $default_value ) )
155
+ )
156
+ ) {
157
+ return fw_call( $default_value );
158
+ }
159
+ } else {
160
+ if ( is_null( $sub_keys ) ) {
161
+ if ( ! isset( $values[ $option_id ] ) ) {
162
+ return fw_call( $default_value );
163
+ }
164
+ } else {
165
+ if ( ! isset( $values[ $option_id ] ) || is_null( fw_akg( $sub_keys, $values[ $option_id ] ) ) ) {
166
+ return fw_call( $default_value );
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ try {
173
+ $options = FW_Cache::get( $cache_key = $this->get_cache_key( 'options', $item_id, $extra_data ) );
174
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
175
+ FW_Cache::set( $cache_key, array() ); // prevent recursion
176
+ FW_Cache::set( $cache_key, $options = fw_extract_only_options( $this->get_options( $item_id, $extra_data ) ) );
177
+ }
178
+
179
+ if ( $options ) {
180
+ try {
181
+ FW_Cache::get(
182
+ // fixes https://github.com/ThemeFuse/Unyson/issues/2034
183
+ $cache_key_values_processed = $this->get_cache_key( 'values:processed', $item_id, $extra_data )
184
+ );
185
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
186
+ /**
187
+ * Set cache value before processing options
188
+ * Fixes https://github.com/ThemeFuse/Unyson/issues/2034#issuecomment-248571149
189
+ */
190
+ FW_Cache::set( $cache_key_values_processed, true );
191
+
192
+ // Complete missing db values with default values from options array
193
+ {
194
+ try {
195
+ $skip_types_process = FW_Cache::get( $cache_key = 'fw:options-default-values:skip-types' );
196
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
197
+ FW_Cache::set(
198
+ $cache_key,
199
+ $skip_types_process = apply_filters( 'fw:options-default-values:skip-types', array(// 'type' => true
200
+ ) )
201
+ );
202
+ }
203
+
204
+ foreach ( array_diff_key( fw_extract_only_options( $options ), $values ) as $id => $option ) {
205
+ $values[ $id ] = isset( $skip_types_process[ $option['type'] ] )
206
+ ? (
207
+ isset( $option['value'] )
208
+ ? $option['value']
209
+ : fw()->backend->option_type( $option['type'] )->get_defaults( 'value' )
210
+ )
211
+ : fw()->backend->option_type( $option['type'] )->get_value_from_input( $option, null );
212
+ }
213
+ }
214
+
215
+ foreach ( $options as $id => $option ) {
216
+ $values[ $id ] = fw()->backend->option_type( $option['type'] )->storage_load(
217
+ $id,
218
+ $option,
219
+ isset( $values[ $id ] ) ? $values[ $id ] : null,
220
+ $this->get_fw_storage_params( $item_id, $extra_data )
221
+ );
222
+ }
223
+
224
+ FW_Cache::set( $cache_key_values, $values );
225
+ }
226
+ }
227
+
228
+ if ( empty( $option_id ) ) {
229
+ return ( empty( $values ) && ( is_array( $default_value ) || fw_is_callback( $default_value ) ) )
230
+ ? fw_call( $default_value )
231
+ : $values;
232
+ } else {
233
+ if ( is_null( $sub_keys ) ) {
234
+ return isset( $values[ $option_id ] )
235
+ ? $values[ $option_id ]
236
+ : fw_call( $default_value );
237
+ } else {
238
+ return isset( $values[ $option_id ] )
239
+ ? fw_akg( $sub_keys, $values[ $option_id ], $default_value )
240
+ : fw_call( $default_value );
241
+ }
242
+ }
243
+ }
244
+
245
+ final public function set( $item_id = null, $option_id = null, $value, array $extra_data = array() ) {
246
+ FW_Cache::del($cache_key_values = $this->get_cache_key('values', $item_id, $extra_data));
247
+ FW_Cache::del($cache_key_values_processed = $this->get_cache_key('values:processed', $item_id, $extra_data));
248
+
249
+ try {
250
+ $options = FW_Cache::get($cache_key = $this->get_cache_key('options', $item_id, $extra_data));
251
+ } catch (FW_Cache_Not_Found_Exception $e) {
252
+ FW_Cache::set($cache_key, array()); // prevent recursion
253
+ FW_Cache::set($cache_key, $options = fw_extract_only_options($this->get_options($item_id, $extra_data)));
254
+ }
255
+
256
+ $sub_keys = null;
257
+
258
+ if ($option_id) {
259
+ $option_id = explode('/', $option_id); // 'option_id/sub/keys'
260
+ $_option_id = array_shift($option_id); // 'option_id'
261
+ $sub_keys = empty($option_id) ? null : implode('/', $option_id); // 'sub/keys'
262
+ $option_id = $_option_id;
263
+ unset($_option_id);
264
+
265
+ $old_values = is_array($old_values = $this->get_values($item_id, $extra_data)) ? $old_values : array();
266
+ $old_value = isset($old_values[$option_id]) ? $old_values[$option_id] : null;
267
+
268
+ if ($sub_keys) { // update sub_key in old_value and use the entire value
269
+ $new_value = $old_value;
270
+ fw_aks($sub_keys, $value, $new_value);
271
+ $value = $new_value;
272
+ unset($new_value);
273
+
274
+ $old_value = fw_akg($sub_keys, $old_value);
275
+ }
276
+
277
+ if (isset($options[$option_id])) {
278
+ $value = fw()->backend->option_type($options[$option_id]['type'])->storage_save(
279
+ $option_id,
280
+ $options[$option_id],
281
+ $value,
282
+ $this->get_fw_storage_params($item_id, $extra_data)
283
+ );
284
+ }
285
+
286
+ $old_values[$option_id] = $value;
287
+
288
+ $this->set_values($item_id, $old_values, $extra_data);
289
+
290
+ unset($old_values);
291
+ } else {
292
+ $old_value = is_array($old_values = $this->get_values($item_id, $extra_data)) ? $old_values : array();
293
+
294
+ if ( ! is_array($value) ) {
295
+ $value = array();
296
+ }
297
+
298
+ if (empty($value)) {
299
+ // All options reset. Reset all fw-storage values too
300
+ // Fixes https://github.com/ThemeFuse/Unyson/issues/2179
301
+ foreach ($options as $_option_id => $_option) {
302
+ fw()->backend->option_type($options[$_option_id]['type'])->storage_save(
303
+ $_option_id,
304
+ $_option,
305
+ fw()->backend->option_type($options[$_option_id]['type'])->get_defaults('value'),
306
+ $this->get_fw_storage_params($item_id, $extra_data)
307
+ );
308
+ }
309
+ } else {
310
+ foreach ($value as $_option_id => $_option_value) {
311
+ if (isset($options[$_option_id])) {
312
+ $value[$_option_id] = fw()->backend->option_type($options[$_option_id]['type'])->storage_save(
313
+ $_option_id,
314
+ $options[$_option_id],
315
+ $_option_value,
316
+ $this->get_fw_storage_params($item_id, $extra_data)
317
+ );
318
+ }
319
+ }
320
+ }
321
+
322
+ $this->set_values($item_id, $value, $extra_data);
323
+ }
324
+
325
+ FW_Cache::del($cache_key_values); // fixes https://github.com/ThemeFuse/Unyson/issues/1538
326
+ FW_Cache::del($cache_key_values_processed);
327
+
328
+ $this->_after_set($item_id, $option_id, $sub_keys, $old_value, $extra_data);
329
+ }
330
+ }
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,218 +1,218 @@
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
- if (!session_id()) {
117
- return; // fixes https://github.com/ThemeFuse/Unyson/issues/2219
118
- }
119
-
120
- self::process_pending_remove_ids();
121
-
122
- $html = array_fill_keys(array_keys(self::$available_types), '');
123
-
124
- $all_messages = self::get_messages();
125
-
126
- foreach ($all_messages as $type => $messages) {
127
- if (!empty($messages)) {
128
- foreach ($messages as $id => $data) {
129
- $html[$type] .=
130
- '<div class="'. self::$available_types[$type] .' fw-flash-message">'.
131
- '<p data-id="'. esc_attr($id) .'">'. $data['message'] .'</p>'.
132
- '</div>';
133
-
134
- unset($all_messages[$type][$id]);
135
- }
136
-
137
- $html[$type] = '<div class="fw-flash-type-'. $type .'">'. $html[$type] .'</div>';
138
- }
139
- }
140
-
141
- unset($success, $error, $info);
142
-
143
- self::set_messages($all_messages);
144
-
145
- echo '<div class="fw-flash-messages">'. implode("\n\n", $html) .'</div>';
146
- }
147
-
148
- /**
149
- * Use this method to print messages html in frontend
150
- * @return bool If some html was printed or not
151
- */
152
- public static function _print_frontend()
153
- {
154
- self::process_pending_remove_ids();
155
-
156
- $html = array_fill_keys(array_keys(self::$available_types), '');
157
- $all_messages = self::get_messages();
158
-
159
- $messages_exists = false;
160
-
161
- foreach ($all_messages as $type => $messages) {
162
- if (empty($messages)) {
163
- continue;
164
- }
165
-
166
- foreach ($messages as $id => $data) {
167
- $html[$type] .= '<li class="fw-flash-message">'. nl2br($data['message']) .'</li>';
168
-
169
- unset($all_messages[$type][$id]);
170
- }
171
-
172
- $html[$type] = '<ul class="fw-flash-type-'. $type .'">'. $html[$type] .'</ul>';
173
-
174
- $messages_exists = true;
175
- }
176
-
177
- self::set_messages($all_messages);
178
-
179
- self::$frontend_printed = true;
180
-
181
- if ($messages_exists) {
182
- echo '<div class="fw-flash-messages">';
183
- echo implode("\n\n", $html);
184
- echo '</div>';
185
-
186
- return true;
187
- } else {
188
- return false;
189
- }
190
- }
191
-
192
- public static function _frontend_printed()
193
- {
194
- return self::$frontend_printed;
195
- }
196
-
197
- public static function _get_messages($clear = false)
198
- {
199
- self::process_pending_remove_ids();
200
-
201
- $messages = self::get_messages();
202
-
203
- if ($clear) {
204
- self::_clear();
205
- }
206
-
207
- return $messages;
208
- }
209
-
210
- /**
211
- * Clear the FW_Flash_Messages messages
212
- *
213
- * @since 2.6.15
214
- */
215
- public static function _clear() {
216
- self::set_messages(array());
217
- }
218
- }
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
+ if (!session_id()) {
117
+ return; // fixes https://github.com/ThemeFuse/Unyson/issues/2219
118
+ }
119
+
120
+ self::process_pending_remove_ids();
121
+
122
+ $html = array_fill_keys(array_keys(self::$available_types), '');
123
+
124
+ $all_messages = self::get_messages();
125
+
126
+ foreach ($all_messages as $type => $messages) {
127
+ if (!empty($messages)) {
128
+ foreach ($messages as $id => $data) {
129
+ $html[$type] .=
130
+ '<div class="'. self::$available_types[$type] .' fw-flash-message">'.
131
+ '<p data-id="'. esc_attr($id) .'">'. $data['message'] .'</p>'.
132
+ '</div>';
133
+
134
+ unset($all_messages[$type][$id]);
135
+ }
136
+
137
+ $html[$type] = '<div class="fw-flash-type-'. $type .'">'. $html[$type] .'</div>';
138
+ }
139
+ }
140
+
141
+ unset($success, $error, $info);
142
+
143
+ self::set_messages($all_messages);
144
+
145
+ echo '<div class="fw-flash-messages">'. implode("\n\n", $html) .'</div>';
146
+ }
147
+
148
+ /**
149
+ * Use this method to print messages html in frontend
150
+ * @return bool If some html was printed or not
151
+ */
152
+ public static function _print_frontend()
153
+ {
154
+ self::process_pending_remove_ids();
155
+
156
+ $html = array_fill_keys(array_keys(self::$available_types), '');
157
+ $all_messages = self::get_messages();
158
+
159
+ $messages_exists = false;
160
+
161
+ foreach ($all_messages as $type => $messages) {
162
+ if (empty($messages)) {
163
+ continue;
164
+ }
165
+
166
+ foreach ($messages as $id => $data) {
167
+ $html[$type] .= '<li class="fw-flash-message">'. nl2br($data['message']) .'</li>';
168
+
169
+ unset($all_messages[$type][$id]);
170
+ }
171
+
172
+ $html[$type] = '<ul class="fw-flash-type-'. $type .'">'. $html[$type] .'</ul>';
173
+
174
+ $messages_exists = true;
175
+ }
176
+
177
+ self::set_messages($all_messages);
178
+
179
+ self::$frontend_printed = true;
180
+
181
+ if ($messages_exists) {
182
+ echo '<div class="fw-flash-messages">';
183
+ echo implode("\n\n", $html);
184
+ echo '</div>';
185
+
186
+ return true;
187
+ } else {
188
+ return false;
189
+ }
190
+ }
191
+
192
+ public static function _frontend_printed()
193
+ {
194
+ return self::$frontend_printed;
195
+ }
196
+
197
+ public static function _get_messages($clear = false)
198
+ {
199
+ self::process_pending_remove_ids();
200
+
201
+ $messages = self::get_messages();
202
+
203
+ if ($clear) {
204
+ self::_clear();
205
+ }
206
+
207
+ return $messages;
208
+ }
209
+
210
+ /**
211
+ * Clear the FW_Flash_Messages messages
212
+ *
213
+ * @since 2.6.15
214
+ */
215
+ public static function _clear() {
216
+ self::set_messages(array());
217
+ }
218
+ }
framework/helpers/class-fw-form.php CHANGED
@@ -1,651 +1,651 @@
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
- * @deprecated 2.6.15 Use FW_Form::get_forms()
14
- */
15
- protected static $forms = array();
16
-
17
- /**
18
- * The id of the submitted form id
19
- * @var string
20
- *
21
- * @deprecated 2.6.15
22
- */
23
- protected static $submitted_id;
24
-
25
- /**
26
- * Hidden input name that stores the form id
27
- * @var string
28
- *
29
- * @deprecated 2.6.15 Use self::get_form_id_name()
30
- */
31
- protected static $id_input_name = 'fwf';
32
-
33
- /**
34
- * Form id
35
- * @var string
36
- *
37
- * @deprecated 2.6.15 Use $this->get_id()
38
- */
39
- protected $id;
40
-
41
- /**
42
- * Html attributes for <form> tag
43
- * @var array
44
- *
45
- * @deprecated 2.6.15 Use $this->attr()
46
- */
47
- protected $attr = array();
48
-
49
- /**
50
- * Found validation errors
51
- * @var array
52
- */
53
- protected $errors;
54
-
55
- /**
56
- * If the get_errors() method was called at leas once
57
- * @var bool
58
- */
59
- protected $errors_accessed = false;
60
-
61
- /**
62
- * If current request is the submit of this form
63
- * @var bool
64
- *
65
- * @deprecated 2.6.15 Use $this->is_submitted()
66
- */
67
- protected $is_submitted;
68
-
69
- /**
70
- * @var bool
71
- *
72
- * @deprecated 2.6.15
73
- */
74
- protected $validate_and_save_called = false;
75
-
76
- /**
77
- * @var array
78
- *
79
- * @deprecated 2.6.15 Use $this->get_callbacks()
80
- */
81
- protected $callbacks = array(
82
- 'render' => false,
83
- 'validate' => false,
84
- 'save' => false
85
- );
86
-
87
- private $request;
88
-
89
- /**
90
- * @param string $id Unique
91
- * @param array $data (optional)
92
- * array(
93
- * 'render' => callback // The callback that will render the form's html
94
- * 'validate' => callback // The callback that will validate user input
95
- * 'save' => callback // The callback that will save successfully validated user input
96
- * 'attr' => array() // Custom <form ...> attributes
97
- * )
98
- */
99
- public function __construct( $id, $data = array() ) {
100
- try {
101
- self::get_form( $id );
102
- trigger_error( sprintf( __( 'Form with id "%s" was already defined', 'fw' ), $id ), E_USER_ERROR );
103
-
104
- return;
105
- } catch ( FW_Form_Not_Found_Exception $e ) {
106
- }
107
-
108
- $this
109
- // set id
110
- ->set_id( $id )
111
- // prepare callbacks
112
- ->set_callbacks( array(
113
- 'render' => fw_akg( 'render', $data, false ),
114
- 'validate' => fw_akg( 'validate', $data, false ),
115
- 'save' => fw_akg( 'save', $data, false ),
116
- ) )
117
- // prepare attributes
118
- ->set_attr( (array) fw_akg( 'attr', $data, array() ) );
119
-
120
- self::$forms[ $this->get_id() ] =& $this;
121
-
122
- if ( did_action( 'wp_loaded' ) ) {
123
- // in case if form instance was created after action
124
- $this->_validate_and_save();
125
- } else {
126
- // attach to an action before 'send_headers' action, to be able to do redirects
127
- add_action( 'wp_loaded', array( $this, '_validate_and_save' ), 101 );
128
- }
129
-
130
- add_action( 'fw_form_display:before_form', array( $this, '_action_fw_form_show_errors' ) );
131
- }
132
-
133
- /**
134
- * @return string
135
- */
136
- public function get_id() {
137
- return $this->id;
138
- }
139
-
140
- /**
141
- * Get validation errors
142
- * @return array
143
- */
144
- public function get_errors() {
145
- if ( ! $this->validate_and_save_called ) {
146
- fw_print( debug_backtrace() );
147
- trigger_error( __METHOD__ . ' called before validation', E_USER_WARNING );
148
-
149
- return array( '~' => true );
150
- }
151
-
152
- $this->errors_accessed = true;
153
-
154
- return $this->_get_errors();
155
- }
156
-
157
- public function get_callbacks() {
158
- return $this->callbacks;
159
- }
160
-
161
- public function errors_accessed() {
162
- return $this->errors_accessed;
163
- }
164
-
165
- /**
166
- * If current form was submitted, validate and save it
167
- *
168
- * Note: This callback can abort script execution if save does redirect
169
- *
170
- * @internal
171
- */
172
- public function _validate_and_save() {
173
-
174
- if ( ! self::is_form_submitted( $this->get_id() ) || $this->validate_and_save_called ) {
175
- return;
176
- }
177
-
178
- $this->validate_and_save_called = true;
179
-
180
- try {
181
- $data = $this->submit( self::get_form_request( $this->get_id() ) );
182
-
183
- if ( $this->_is_ajax() ) {
184
- wp_send_json_success( array(
185
- 'save_data' => $data,
186
- 'flash_messages' => self::collect_flash_messages(),
187
- ) );
188
- }
189
-
190
- if ( ( $redirect = fw_akg( 'redirect', $data ) ) ) {
191
- wp_redirect( $redirect );
192
- exit;
193
- }
194
- } catch ( FW_Form_Invalid_Submission_Exception $e ) {
195
- if ( $this->_is_ajax() ) {
196
- wp_send_json_error( array(
197
- 'errors' => $this->get_errors(),
198
- 'flash_messages' => self::collect_flash_messages()
199
- ) );
200
- }
201
- }
202
- }
203
-
204
- /**
205
- * @param FW_Form $form
206
- *
207
- * @internal
208
- *
209
- * You can overwrite it in case you do not need the errors to be shown for your form
210
- */
211
- public function _action_fw_form_show_errors( $form ) {
212
- if (
213
- $form->get_id() != $this->get_id()
214
- // errors in admin side are displayed by a script at the end of this file
215
- || is_admin()
216
- || ! $form->is_submitted()
217
- || $form->is_valid()
218
- || $form->errors_accessed()
219
- ) {
220
-
221
- return;
222
- }
223
-
224
- /**
225
- * Use this action to customize errors display in your theme
226
- */
227
- do_action( 'fw_form_display_errors_frontend', $form );
228
-
229
- $errors = $form->get_errors();
230
-
231
- if ( empty( $errors ) ) {
232
- return;
233
- }
234
-
235
- echo '<ul class="fw-form-errors">';
236
-
237
- foreach ( $errors as $input_name => $error_message ) {
238
- echo fw_html_tag(
239
- 'li',
240
- array(
241
- 'data-input-name' => $input_name,
242
- ),
243
- $error_message
244
- );
245
- }
246
-
247
- echo '</ul>';
248
- }
249
-
250
- /**
251
- * Get html attribute(s)
252
- *
253
- * @param null|string $name
254
- *
255
- * @return array|string
256
- */
257
- public function attr( $name = null ) {
258
- return $name !== null
259
- ? fw_akg( $name, $this->attr )
260
- : $this->attr;
261
- }
262
-
263
- /**
264
- * Render form's html
265
- *
266
- * @param array $data
267
- */
268
- public function render( $data = array() ) {
269
- $render_data = array(
270
- 'submit' => array(
271
- 'value' => __( 'Submit', 'fw' ),
272
- /**
273
- * you can set here custom submit button html
274
- * and the 'value' parameter will not be used
275
- */
276
- 'html' => null,
277
- ),
278
- 'data' => $data,
279
- 'attr' => $this->attr(),
280
- );
281
-
282
- $html = '';
283
-
284
- if ( $render_callback = fw_akg( 'render', $this->get_callbacks() ) ) {
285
- ob_start();
286
-
287
- $data = call_user_func_array( $render_callback, array( $render_data, $this ) );
288
-
289
- $html = ob_get_clean();
290
-
291
- if ( empty( $data ) ) {
292
- // fix if returned wrong data from callback
293
- $data = $render_data;
294
- }
295
-
296
- $render_data = $data;
297
- }
298
-
299
- do_action( 'fw_form_display:before_form', $this );
300
-
301
- // display form errors in frontend
302
- echo '<form ' . fw_attr_to_html( $render_data['attr'] ) . ' >';
303
-
304
- do_action( 'fw_form_display:before', $this );
305
-
306
- echo fw_html_tag( 'input',
307
- array(
308
- 'type' => 'hidden',
309
- 'name' => self::get_form_id_name(),
310
- 'value' => $this->get_id(),
311
- ) );
312
-
313
- wp_nonce_field( $this->get_nonce_action(), $this->get_nonce_name( $render_data ) );
314
-
315
- if ( ! empty( $render_data['attr']['action'] ) && $render_data['attr']['method'] == 'get' ) {
316
- /**
317
- * Add query vars from the action attribute url to hidden inputs to not loose them
318
- */
319
-
320
- parse_str( parse_url( $render_data['attr']['action'], PHP_URL_QUERY ), $query_vars );
321
-
322
- if ( ! empty( $query_vars ) ) {
323
- foreach ( $query_vars as $var_name => $var_value ) {
324
- echo fw_html_tag( 'input',
325
- array(
326
- 'type' => 'hidden',
327
- 'name' => $var_name,
328
- 'value' => $var_value,
329
- ) );
330
- }
331
- }
332
- }
333
-
334
- echo $html;
335
-
336
- // In filter can be defined custom html for submit button
337
- if ( isset( $render_data['submit']['html'] ) ) {
338
- echo $render_data['submit']['html'];
339
- } else {
340
- echo fw_html_tag( 'input',
341
- array(
342
- 'type' => 'submit',
343
- 'value' => $render_data['submit']['value']
344
- ) );
345
- }
346
-
347
- do_action( 'fw_form_display:after', $this );
348
-
349
- echo '</form>';
350
-
351
- do_action( 'fw_form_display:after_form', $this );
352
- }
353
-
354
- /**
355
- * If now is a submit of this form
356
- * @return bool
357
- */
358
- public function is_submitted() {
359
- return $this->request !== null;
360
- }
361
-
362
- /**
363
- * @return bool
364
- */
365
- public function is_valid() {
366
- if ( ! $this->is_submitted() ) {
367
- return null;
368
- }
369
-
370
- return count( $this->_get_errors() ) == 0;
371
- }
372
-
373
- /**
374
- * @param array $request
375
- *
376
- * @throws FW_Form_Invalid_Submission_Exception
377
- *
378
- * @return mixed
379
- */
380
- public function submit( array $request = array() ) {
381
- $this->request = $request;
382
- //Updated the deprecated member for those that extended the class and use it in code
383
- $this->is_submitted = true;
384
-
385
- $errors = $this->validate();
386
-
387
- if ( ! empty( $errors ) ) {
388
- throw new FW_Form_Invalid_Submission_Exception( $errors );
389
- }
390
-
391
- return $this->save();
392
- }
393
-
394
- protected function get_default_attr() {
395
- return array(
396
- 'data-fw-form-id' => $this->get_id(),
397
- 'method' => 'post',
398
- 'action' => fw_current_url(),
399
- 'class' => 'fw_form_' . $this->get_id()
400
- );
401
- }
402
-
403
- /**
404
- * @param array $attr
405
- *
406
- * @return $this
407
- */
408
- protected function set_attr( array $attr ) {
409
- $this->attr = array_merge( $this->get_default_attr(), $attr );
410
-
411
- return $this;
412
- }
413
-
414
- /**
415
- * @param null $key
416
- *
417
- * @return array|mixed|null
418
- *
419
- * @since 2.6.15
420
- */
421
- protected function get_request( $key = null ) {
422
- return $key === null ? (array) $this->request : fw_akg( $key, $this->request );
423
- }
424
-
425
- /**
426
- * @return string|null
427
- *
428
- * @since 2.6.15
429
- */
430
- protected function get_nonce() {
431
- return $this->get_request( $this->get_nonce_name() );
432
- }
433
-
434
- /**
435
- * Returns forms errors without counting them as accessed
436
- * @return array
437
- */
438
- protected function _get_errors() {
439
- return $this->errors;
440
- }
441
-
442
- /**
443
- * @return string
444
- *
445
- * @since 2.6.15
446
- */
447
- protected function get_nonce_action() {
448
- return 'submit_fwf';
449
- }
450
-
451
- protected function check_nonce( $nonce ) {
452
- return wp_verify_nonce( $nonce, $this->get_nonce_action() );
453
- }
454
-
455
- /**
456
- * @return array
457
- */
458
- protected function validate() {
459
- /**
460
- * Errors array {'input[name]' => 'Error message'}
461
- */
462
- $errors = array();
463
-
464
- if ( ! $this->check_nonce( $this->get_nonce() ) ) {
465
- $errors[ $this->get_nonce_name() ] = esc_html__( 'Your session expired. Please refresh page and try again.', 'fw' );
466
- }
467
-
468
- /**
469
- * Call validate callback
470
- *
471
- * Callback must 'manually' extract input values from $_POST (or $_GET)
472
- */
473
- if ( ( $validate = fw_akg( 'validate', $this->get_callbacks() ) ) ) {
474
- $errors = (array) call_user_func( $validate, $errors );
475
- }
476
-
477
- return $this->set_errors( $errors )->_get_errors();
478
- }
479
-
480
- /**
481
- * @return array|mixed
482
- */
483
- protected function save() {
484
- $save_data = array(
485
- // you can set here a url for redirect after save
486
- 'redirect' => null
487
- );
488
-
489
- /**
490
- * Call save callback
491
- *
492
- * Callback must 'manually' extract input values from $_POST (or $_GET)
493
- */
494
- if ( ( $save_callback = fw_akg( 'save', $this->get_callbacks() ) ) ) {
495
- $data = call_user_func_array( $save_callback, array( $save_data ) );
496
-
497
- if ( ! is_array( $data ) ) {
498
- // fix if returned wrong data from callback
499
- $data = $save_data;
500
- }
501
-
502
- $save_data = $data;
503
-
504
- unset( $data );
505
- }
506
-
507
- return $save_data;
508
- }
509
-
510
- /**
511
- * @return bool
512
- *
513
- * @deprecated 2.6.15
514
- */
515
- protected function is_ajax() {
516
- return self::_is_ajax();
517
- }
518
-
519
- protected function set_id( $id ) {
520
- $this->id = $id;
521
-
522
- return $this;
523
- }
524
-
525
- /**
526
- * @param array $callbacks
527
- *
528
- * @return $this
529
- */
530
- protected function set_callbacks( array $callbacks ) {
531
- $this->callbacks = $callbacks;
532
-
533
- return $this;
534
- }
535
-
536
- protected function set_errors( array $errors ) {
537
- $this->errors = $errors;
538
-
539
- return $this;
540
- }
541
-
542
- /**
543
- * Some forms (like Forms extension frontend form) uses the same FW_Form instance for all sub-forms
544
- * and they must be differentiated somehow.
545
- * Fixes https://github.com/ThemeFuse/Unyson/issues/2033
546
- *
547
- * @param array $render_data
548
- *
549
- * @return string
550
- * @since 2.6.6
551
- */
552
- private function get_nonce_name( $render_data = array() ) {
553
- return '_nonce_' . md5( $this->id . apply_filters( 'fw:form:nonce-name-data', '', $this, $render_data ) );
554
- }
555
-
556
- /**
557
- * @return FW_Form[]
558
- *
559
- * @since 2.6.15
560
- */
561
- public static function get_forms() {
562
- return self::$forms;
563
- }
564
-
565
- /**
566
- * @param $id
567
- *
568
- * @return FW_Form
569
- * @throws FW_Form_Not_Found_Exception
570
- *
571
- * @since 2.6.15
572
- */
573
- public static function get_form( $id ) {
574
- if ( ! isset( self::$forms[ $id ] ) ) {
575
- throw new FW_Form_Not_Found_Exception( "FW_Form $id was not defined" );
576
- }
577
-
578
- return self::$forms[ $id ];
579
- }
580
-
581
- /**
582
- * Get submitted form instance (or false if no form is currently submitted)
583
- * @return FW_Form|false
584
- */
585
- public static function get_submitted() {
586
- foreach ( self::get_forms() as $id => $form ) {
587
- if ( self::is_form_submitted( $id ) ) {
588
- return $form;
589
- }
590
- }
591
-
592
- return false;
593
- }
594
-
595
- /**
596
- * @return bool
597
- *
598
- * @since 2.6.15
599
- */
600
- public static function _is_ajax() {
601
- return ( defined( 'DOING_AJAX' ) && DOING_AJAX )
602
- ||
603
- strtolower( fw_akg( 'HTTP_X_REQUESTED_WITH', $_SERVER ) ) == 'xmlhttprequest';
604
- }
605
-
606
- public static function get_form_request( $id ) {
607
- if ( FW_Request::POST( self::get_form_id_name() ) == $id ) {
608
- return FW_Request::POST();
609
- }
610
-
611
- if ( FW_Request::GET( self::get_form_id_name() ) == $id ) {
612
- return FW_Request::GET();
613
- }
614
-
615
- return null;
616
- }
617
-
618
- /**
619
- * @return string
620
- *
621
- * @since 2.6.15
622
- */
623
- protected static function get_form_id_name() {
624
- return 'fwf';
625
- }
626
-
627
- /**
628
- * @param $id
629
- *
630
- * @return bool
631
- *
632
- * @since 2.6.15
633
- */
634
- protected static function is_form_submitted( $id ) {
635
- return self::get_form_request( $id ) !== null;
636
- }
637
-
638
- private static function collect_flash_messages() {
639
- $flash_messages = array();
640
-
641
- foreach ( FW_Flash_Messages::_get_messages( true ) as $type => $messages ) {
642
- $flash_messages[ $type ] = array();
643
-
644
- foreach ( $messages as $id => $message_data ) {
645
- $flash_messages[ $type ][ $id ] = $message_data['message'];
646
- }
647
- }
648
-
649
- return $flash_messages;
650
- }
651
  }
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
+ * @deprecated 2.6.15 Use FW_Form::get_forms()
14
+ */
15
+ protected static $forms = array();
16
+
17
+ /**
18
+ * The id of the submitted form id
19
+ * @var string
20
+ *
21
+ * @deprecated 2.6.15
22
+ */
23
+ protected static $submitted_id;
24
+
25
+ /**
26
+ * Hidden input name that stores the form id
27
+ * @var string
28
+ *
29
+ * @deprecated 2.6.15 Use self::get_form_id_name()
30
+ */
31
+ protected static $id_input_name = 'fwf';
32
+
33
+ /**
34
+ * Form id
35
+ * @var string
36
+ *
37
+ * @deprecated 2.6.15 Use $this->get_id()
38
+ */
39
+ protected $id;
40
+
41
+ /**
42
+ * Html attributes for <form> tag
43
+ * @var array
44
+ *
45
+ * @deprecated 2.6.15 Use $this->attr()
46
+ */
47
+ protected $attr = array();
48
+
49
+ /**
50
+ * Found validation errors
51
+ * @var array
52
+ */
53
+ protected $errors;
54
+
55
+ /**
56
+ * If the get_errors() method was called at leas once
57
+ * @var bool
58
+ */
59
+ protected $errors_accessed = false;
60
+
61
+ /**
62
+ * If current request is the submit of this form
63
+ * @var bool
64
+ *
65
+ * @deprecated 2.6.15 Use $this->is_submitted()
66
+ */
67
+ protected $is_submitted;
68
+
69
+ /**
70
+ * @var bool
71
+ *
72
+ * @deprecated 2.6.15
73
+ */
74
+ protected $validate_and_save_called = false;
75
+
76
+ /**
77
+ * @var array
78
+ *
79
+ * @deprecated 2.6.15 Use $this->get_callbacks()
80
+ */
81
+ protected $callbacks = array(
82
+ 'render' => false,
83
+ 'validate' => false,
84
+ 'save' => false
85
+ );
86
+
87
+ private $request;
88
+
89
+ /**
90
+ * @param string $id Unique
91
+ * @param array $data (optional)
92
+ * array(
93
+ * 'render' => callback // The callback that will render the form's html
94
+ * 'validate' => callback // The callback that will validate user input
95
+ * 'save' => callback // The callback that will save successfully validated user input
96
+ * 'attr' => array() // Custom <form ...> attributes
97
+ * )
98
+ */
99
+ public function __construct( $id, $data = array() ) {
100
+ try {
101
+ self::get_form( $id );
102
+ trigger_error( sprintf( __( 'Form with id "%s" was already defined', 'fw' ), $id ), E_USER_ERROR );
103
+
104
+ return;
105
+ } catch ( FW_Form_Not_Found_Exception $e ) {
106
+ }
107
+
108
+ $this
109
+ // set id
110
+ ->set_id( $id )
111
+ // prepare callbacks
112
+ ->set_callbacks( array(
113
+ 'render' => fw_akg( 'render', $data, false ),
114
+ 'validate' => fw_akg( 'validate', $data, false ),
115
+ 'save' => fw_akg( 'save', $data, false ),
116
+ ) )
117
+ // prepare attributes
118
+ ->set_attr( (array) fw_akg( 'attr', $data, array() ) );
119
+
120
+ self::$forms[ $this->get_id() ] =& $this;
121
+
122
+ if ( did_action( 'wp_loaded' ) ) {
123
+ // in case if form instance was created after action
124
+ $this->_validate_and_save();
125
+ } else {
126
+ // attach to an action before 'send_headers' action, to be able to do redirects
127
+ add_action( 'wp_loaded', array( $this, '_validate_and_save' ), 101 );
128
+ }
129
+
130
+ add_action( 'fw_form_display:before_form', array( $this, '_action_fw_form_show_errors' ) );
131
+ }
132
+
133
+ /**
134
+ * @return string
135
+ */
136
+ public function get_id() {
137
+ return $this->id;
138
+ }
139
+
140
+ /**
141
+ * Get validation errors
142
+ * @return array
143
+ */
144
+ public function get_errors() {
145
+ if ( ! $this->validate_and_save_called ) {
146
+ fw_print( debug_backtrace() );
147
+ trigger_error( __METHOD__ . ' called before validation', E_USER_WARNING );
148
+
149
+ return array( '~' => true );
150
+ }
151
+
152
+ $this->errors_accessed = true;
153
+
154
+ return $this->_get_errors();
155
+ }
156
+
157
+ public function get_callbacks() {
158
+ return $this->callbacks;
159
+ }
160
+
161
+ public function errors_accessed() {
162
+ return $this->errors_accessed;
163
+ }
164
+
165
+ /**
166
+ * If current form was submitted, validate and save it
167
+ *
168
+ * Note: This callback can abort script execution if save does redirect
169
+ *
170
+ * @internal
171
+ */
172
+ public function _validate_and_save() {
173
+
174
+ if ( ! self::is_form_submitted( $this->get_id() ) || $this->validate_and_save_called ) {
175
+ return;
176
+ }
177
+
178
+ $this->validate_and_save_called = true;
179
+
180
+ try {
181
+ $data = $this->submit( self::get_form_request( $this->get_id() ) );
182
+
183
+ if ( $this->_is_ajax() ) {
184
+ wp_send_json_success( array(
185
+ 'save_data' => $data,
186
+ 'flash_messages' => self::collect_flash_messages(),
187
+ ) );
188
+ }
189
+
190
+ if ( ( $redirect = fw_akg( 'redirect', $data ) ) ) {
191
+ wp_redirect( $redirect );
192
+ exit;
193
+ }
194
+ } catch ( FW_Form_Invalid_Submission_Exception $e ) {
195
+ if ( $this->_is_ajax() ) {
196
+ wp_send_json_error( array(
197
+ 'errors' => $this->get_errors(),
198
+ 'flash_messages' => self::collect_flash_messages()
199
+ ) );
200
+ }
201
+ }
202
+ }
203
+
204
+ /**
205
+ * @param FW_Form $form
206
+ *
207
+ * @internal
208
+ *
209
+ * You can overwrite it in case you do not need the errors to be shown for your form
210
+ */
211
+ public function _action_fw_form_show_errors( $form ) {
212
+ if (
213
+ $form->get_id() != $this->get_id()
214
+ // errors in admin side are displayed by a script at the end of this file
215
+ || is_admin()
216
+ || ! $form->is_submitted()
217
+ || $form->is_valid()
218
+ || $form->errors_accessed()
219
+ ) {
220
+
221
+ return;
222
+ }
223
+
224
+ /**
225
+ * Use this action to customize errors display in your theme
226
+ */
227
+ do_action( 'fw_form_display_errors_frontend', $form );
228
+
229
+ $errors = $form->get_errors();
230
+
231
+ if ( empty( $errors ) ) {
232
+ return;
233
+ }
234
+
235
+ echo '<ul class="fw-form-errors">';
236
+
237
+ foreach ( $errors as $input_name => $error_message ) {
238
+ echo fw_html_tag(
239
+ 'li',
240
+ array(
241
+ 'data-input-name' => $input_name,
242
+ ),
243
+ $error_message
244
+ );
245
+ }
246
+
247
+ echo '</ul>';
248
+ }
249
+
250
+ /**
251
+ * Get html attribute(s)
252
+ *
253
+ * @param null|string $name
254
+ *
255
+ * @return array|string
256
+ */
257
+ public function attr( $name = null ) {
258
+ return $name !== null
259
+ ? fw_akg( $name, $this->attr )
260
+ : $this->attr;
261
+ }
262
+
263
+ /**
264
+ * Render form's html
265
+ *
266
+ * @param array $data
267
+ */
268
+ public function render( $data = array() ) {
269
+ $render_data = array(
270
+ 'submit' => array(
271
+ 'value' => __( 'Submit', 'fw' ),
272
+ /**
273
+ * you can set here custom submit button html
274
+ * and the 'value' parameter will not be used
275
+ */
276
+ 'html' => null,
277
+ ),
278
+ 'data' => $data,
279
+ 'attr' => $this->attr(),
280
+ );
281
+
282
+ $html = '';
283
+
284
+ if ( $render_callback = fw_akg( 'render', $this->get_callbacks() ) ) {
285
+ ob_start();
286
+
287
+ $data = call_user_func_array( $render_callback, array( $render_data, $this ) );
288
+
289
+ $html = ob_get_clean();
290
+
291
+ if ( empty( $data ) ) {
292
+ // fix if returned wrong data from callback
293
+ $data = $render_data;
294
+ }
295
+
296
+ $render_data = $data;
297
+ }
298
+
299
+ do_action( 'fw_form_display:before_form', $this );
300
+
301
+ // display form errors in frontend
302
+ echo '<form ' . fw_attr_to_html( $render_data['attr'] ) . ' >';
303
+
304
+ do_action( 'fw_form_display:before', $this );
305
+
306
+ echo fw_html_tag( 'input',
307
+ array(
308
+ 'type' => 'hidden',
309
+ 'name' => self::get_form_id_name(),
310
+ 'value' => $this->get_id(),
311
+ ) );
312
+
313
+ wp_nonce_field( $this->get_nonce_action(), $this->get_nonce_name( $render_data ) );
314
+
315
+ if ( ! empty( $render_data['attr']['action'] ) && $render_data['attr']['method'] == 'get' ) {
316
+ /**
317
+ * Add query vars from the action attribute url to hidden inputs to not loose them
318
+ */
319
+
320
+ parse_str( parse_url( $render_data['attr']['action'], PHP_URL_QUERY ), $query_vars );
321
+
322
+ if ( ! empty( $query_vars ) ) {
323
+ foreach ( $query_vars as $var_name => $var_value ) {
324
+ echo fw_html_tag( 'input',
325
+ array(
326
+ 'type' => 'hidden',
327
+ 'name' => $var_name,
328
+ 'value' => $var_value,
329
+ ) );
330
+ }
331
+ }
332
+ }
333
+
334
+ echo $html;
335
+
336
+ // In filter can be defined custom html for submit button
337
+ if ( isset( $render_data['submit']['html'] ) ) {
338
+ echo $render_data['submit']['html'];
339
+ } else {
340
+ echo fw_html_tag( 'input',
341
+ array(
342
+ 'type' => 'submit',
343
+ 'value' => $render_data['submit']['value']
344
+ ) );
345
+ }
346
+
347
+ do_action( 'fw_form_display:after', $this );
348
+
349
+ echo '</form>';
350
+
351
+ do_action( 'fw_form_display:after_form', $this );
352
+ }
353
+
354
+ /**
355
+ * If now is a submit of this form
356
+ * @return bool
357
+ */
358
+ public function is_submitted() {
359
+ return $this->request !== null;
360
+ }
361
+
362
+ /**
363
+ * @return bool
364
+ */
365
+ public function is_valid() {
366
+ if ( ! $this->is_submitted() ) {
367
+ return null;
368
+ }
369
+
370
+ return count( $this->_get_errors() ) == 0;
371
+ }
372
+
373
+ /**
374
+ * @param array $request
375
+ *
376
+ * @throws FW_Form_Invalid_Submission_Exception
377
+ *
378
+ * @return mixed
379
+ */
380
+ public function submit( array $request = array() ) {
381
+ $this->request = $request;
382
+ //Updated the deprecated member for those that extended the class and use it in code
383
+ $this->is_submitted = true;
384
+
385
+ $errors = $this->validate();
386
+
387
+ if ( ! empty( $errors ) ) {
388
+ throw new FW_Form_Invalid_Submission_Exception( $errors );
389
+ }
390
+
391
+ return $this->save();
392
+ }
393
+
394
+ protected function get_default_attr() {
395
+ return array(
396
+ 'data-fw-form-id' => $this->get_id(),
397
+ 'method' => 'post',
398
+ 'action' => fw_current_url(),
399
+ 'class' => 'fw_form_' . $this->get_id()
400
+ );
401
+ }
402
+
403
+ /**
404
+ * @param array $attr
405
+ *
406
+ * @return $this
407
+ */
408
+ protected function set_attr( array $attr ) {
409
+ $this->attr = array_merge( $this->get_default_attr(), $attr );
410
+
411
+ return $this;
412
+ }
413
+
414
+ /**
415
+ * @param null $key
416
+ *
417
+ * @return array|mixed|null
418
+ *
419
+ * @since 2.6.15
420
+ */
421
+ protected function get_request( $key = null ) {
422
+ return $key === null ? (array) $this->request : fw_akg( $key, $this->request );
423
+ }
424
+
425
+ /**
426
+ * @return string|null
427
+ *
428
+ * @since 2.6.15
429
+ */
430
+ protected function get_nonce() {
431
+ return $this->get_request( $this->get_nonce_name() );
432
+ }
433
+
434
+ /**
435
+ * Returns forms errors without counting them as accessed
436
+ * @return array
437
+ */
438
+ protected function _get_errors() {
439
+ return $this->errors;
440
+ }
441
+
442
+ /**
443
+ * @return string
444
+ *
445
+ * @since 2.6.15
446
+ */
447
+ protected function get_nonce_action() {
448
+ return 'submit_fwf';
449
+ }
450
+
451
+ protected function check_nonce( $nonce ) {
452
+ return wp_verify_nonce( $nonce, $this->get_nonce_action() );
453
+ }
454
+
455
+ /**
456
+ * @return array
457
+ */
458
+ protected function validate() {
459
+ /**
460
+ * Errors array {'input[name]' => 'Error message'}
461
+ */
462
+ $errors = array();
463
+
464
+ if ( ! $this->check_nonce( $this->get_nonce() ) ) {
465
+ $errors[ $this->get_nonce_name() ] = esc_html__( 'Your session expired. Please refresh page and try again.', 'fw' );
466
+ }
467
+
468
+ /**
469
+ * Call validate callback
470
+ *
471
+ * Callback must 'manually' extract input values from $_POST (or $_GET)
472
+ */
473
+ if ( ( $validate = fw_akg( 'validate', $this->get_callbacks() ) ) ) {
474
+ $errors = (array) call_user_func( $validate, $errors );
475
+ }
476
+
477
+ return $this->set_errors( $errors )->_get_errors();
478
+ }
479
+
480
+ /**
481
+ * @return array|mixed
482
+ */
483
+ protected function save() {
484
+ $save_data = array(
485
+ // you can set here a url for redirect after save
486
+ 'redirect' => null
487
+ );
488
+
489
+ /**
490
+ * Call save callback
491
+ *
492
+ * Callback must 'manually' extract input values from $_POST (or $_GET)
493
+ */
494
+ if ( ( $save_callback = fw_akg( 'save', $this->get_callbacks() ) ) ) {
495
+ $data = call_user_func_array( $save_callback, array( $save_data ) );
496
+
497
+ if ( ! is_array( $data ) ) {
498
+ // fix if returned wrong data from callback
499
+ $data = $save_data;
500
+ }
501
+
502
+ $save_data = $data;
503
+
504
+ unset( $data );
505
+ }
506
+
507
+ return $save_data;
508
+ }
509
+
510
+ /**
511
+ * @return bool
512
+ *
513
+ * @deprecated 2.6.15
514
+ */
515
+ protected function is_ajax() {
516
+ return self::_is_ajax();
517
+ }
518
+
519
+ protected function set_id( $id ) {
520
+ $this->id = $id;
521
+
522
+ return $this;
523
+ }
524
+
525
+ /**
526
+ * @param array $callbacks
527
+ *
528
+ * @return $this
529
+ */
530
+ protected function set_callbacks( array $callbacks ) {
531
+ $this->callbacks = $callbacks;
532
+
533
+ return $this;
534
+ }
535
+
536
+ protected function set_errors( array $errors ) {
537
+ $this->errors = $errors;
538
+
539
+ return $this;
540
+ }
541
+
542
+ /**
543
+ * Some forms (like Forms extension frontend form) uses the same FW_Form instance for all sub-forms
544
+ * and they must be differentiated somehow.
545
+ * Fixes https://github.com/ThemeFuse/Unyson/issues/2033
546
+ *
547
+ * @param array $render_data
548
+ *
549
+ * @return string
550
+ * @since 2.6.6
551
+ */
552
+ private function get_nonce_name( $render_data = array() ) {
553
+ return '_nonce_' . md5( $this->id . apply_filters( 'fw:form:nonce-name-data', '', $this, $render_data ) );
554
+ }
555
+
556
+ /**
557
+ * @return FW_Form[]
558
+ *
559
+ * @since 2.6.15
560
+ */
561
+ public static function get_forms() {
562
+ return self::$forms;
563
+ }
564
+
565
+ /**
566
+ * @param $id
567
+ *
568
+ * @return FW_Form
569
+ * @throws FW_Form_Not_Found_Exception
570
+ *
571
+ * @since 2.6.15
572
+ */
573
+ public static function get_form( $id ) {
574
+ if ( ! isset( self::$forms[ $id ] ) ) {
575
+ throw new FW_Form_Not_Found_Exception( "FW_Form $id was not defined" );
576
+ }
577
+
578
+ return self::$forms[ $id ];
579
+ }
580
+
581
+ /**
582
+ * Get submitted form instance (or false if no form is currently submitted)
583
+ * @return FW_Form|false
584
+ */
585
+ public static function get_submitted() {
586
+ foreach ( self::get_forms() as $id => $form ) {
587
+ if ( self::is_form_submitted( $id ) ) {
588
+ return $form;
589
+ }
590
+ }
591
+
592
+ return false;
593
+ }
594
+
595
+ /**
596
+ * @return bool
597
+ *
598
+ * @since 2.6.15
599
+ */
600
+ public static function _is_ajax() {
601
+ return ( defined( 'DOING_AJAX' ) && DOING_AJAX )
602
+ ||
603
+ strtolower( fw_akg( 'HTTP_X_REQUESTED_WITH', $_SERVER ) ) == 'xmlhttprequest';
604
+ }
605
+
606
+ public static function get_form_request( $id ) {
607
+ if ( FW_Request::POST( self::get_form_id_name() ) == $id ) {
608
+ return FW_Request::POST();
609
+ }
610
+
611
+ if ( FW_Request::GET( self::get_form_id_name() ) == $id ) {
612
+ return FW_Request::GET();
613
+ }
614
+
615
+ return null;
616
+ }
617
+
618
+ /**
619
+ * @return string
620
+ *
621
+ * @since 2.6.15
622
+ */
623
+ protected static function get_form_id_name() {
624
+ return 'fwf';
625
+ }
626
+
627
+ /**
628
+ * @param $id
629
+ *
630
+ * @return bool
631
+ *
632
+ * @since 2.6.15
633
+ */
634
+ protected static function is_form_submitted( $id ) {
635
+ return self::get_form_request( $id ) !== null;
636
+ }
637
+
638
+ private static function collect_flash_messages() {
639
+ $flash_messages = array();
640
+
641
+ foreach ( FW_Flash_Messages::_get_messages( true ) as $type => $messages ) {
642
+ $flash_messages[ $type ] = array();
643
+
644
+ foreach ( $messages as $id => $message_data ) {
645
+ $flash_messages[ $type ][ $id ] = $message_data['message'];
646
+ }
647
+ }
648
+
649
+ return $flash_messages;
650
+ }
651
  }
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 fr