Unyson - Version 2.7.13

Version Description

  • Fixed #3344,#3309,#3300,#3302,#2898,#3182,#3194
Download this release

Release Info

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

Code changes from version 2.7.12 to 2.7.13

Files changed (66) hide show
  1. framework/LICENSE +674 -674
  2. framework/autoload.php +309 -306
  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 -571
  7. framework/core/components/backend.php +2054 -2062
  8. framework/core/components/backend/class-fw-settings-form-theme.php +217 -217
  9. framework/core/components/extensions.php +688 -688
  10. framework/core/components/extensions/class-fw-extension-default.php +11 -11
  11. framework/core/components/extensions/manager/available-extensions.php +336 -336
  12. framework/core/components/extensions/manager/class--fw-extensions-manager.php +3688 -3688
  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 -21
  19. framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-bitbucket.php +117 -0
  20. framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-custom.php +214 -214
  21. framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-github.php +188 -188
  22. framework/core/components/extensions/manager/includes/download-source/types/init.php +11 -10
  23. framework/core/components/extensions/manager/includes/parsedown/LICENSE.txt +19 -19
  24. framework/core/components/extensions/manager/includes/parsedown/Parsedown.php +1527 -1527
  25. framework/core/components/extensions/manager/static/extension-page.css +22 -22
  26. framework/core/components/extensions/manager/static/extension-page.js +2 -2
  27. framework/core/components/extensions/manager/static/extensions-page.css +260 -260
  28. framework/core/components/extensions/manager/static/extensions-page.js +107 -107
  29. framework/core/components/extensions/manager/static/unyson-font-icon/fonts/icomoon.svg +10 -10
  30. framework/core/components/extensions/manager/static/unyson-font-icon/style.css +28 -28
  31. framework/core/components/extensions/manager/views/delete-form.php +54 -54
  32. framework/core/components/extensions/manager/views/extension-page-header.php +50 -50
  33. framework/core/components/extensions/manager/views/extension.php +246 -246
  34. framework/core/components/extensions/manager/views/extensions-page.php +267 -267
  35. framework/core/components/extensions/manager/views/install-form.php +51 -51
  36. framework/core/components/theme.php +210 -210
  37. framework/core/exceptions/class-fw-option-type-exception.php +39 -39
  38. framework/core/extends/class-fw-container-type.php +233 -233
  39. framework/core/extends/class-fw-extension.php +515 -515
  40. framework/core/extends/class-fw-option-type.php +465 -465
  41. framework/core/extends/interface-fw-option-handler.php +13 -13
  42. framework/extensions/blog/class-fw-extension-blog.php +89 -89
  43. framework/extensions/blog/manifest.php +13 -13
  44. framework/extensions/update/class-fw-extension-update.php +1013 -1013
  45. framework/extensions/update/config.php +9 -9
  46. framework/extensions/update/extensions/bitbucket-update/class-fw-extension-bitbucket-update.php +231 -0
  47. framework/extensions/update/extensions/bitbucket-update/manifest.php +5 -0
  48. framework/extensions/update/extensions/custom-update/class-fw-extension-custom-update.php +259 -259
  49. framework/extensions/update/extensions/custom-update/manifest.php +5 -5
  50. framework/extensions/update/extensions/github-update/class-fw-extension-github-update.php +433 -433
  51. framework/extensions/update/extensions/github-update/manifest.php +5 -5
  52. framework/extensions/update/includes/classes/class--fw-ext-update-extensions-list-table.php +128 -128
  53. framework/extensions/update/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php +36 -36
  54. framework/extensions/update/includes/classes/class--fw-ext-update-framework-upgrader-skin.php +33 -33
  55. framework/extensions/update/includes/classes/class--fw-ext-update-theme-upgrader-skin.php +33 -33
  56. framework/extensions/update/includes/extends/class-fw-ext-update-service.php +105 -105
  57. framework/extensions/update/manifest.php +10 -10
  58. framework/extensions/update/static.php +14 -14
  59. framework/extensions/update/static/css/admin-update-page.css +2 -2
  60. framework/extensions/update/views/updates-list.php +118 -118
  61. framework/helpers/class-fw-access-key.php +43 -43
  62. framework/helpers/class-fw-cache.php +308 -308
  63. framework/helpers/class-fw-callback.php +131 -131
  64. framework/helpers/class-fw-db-options-model.php +330 -330
  65. framework/helpers/class-fw-dumper.php +123 -123
  66. framework/helpers/class-fw-flash-messages.php +116 -218
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,307 +1,310 @@
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
  }
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_Bitbucket' :
63
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-bitbucket.php';
64
+ break;
65
+ case 'FW_Ext_Download_Source_Custom' :
66
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-custom.php';
67
+ break;
68
+ case '_FW_Available_Extensions_Register' :
69
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/available-ext/class--fw-available-extensions-register.php';
70
+ break;
71
+ case 'FW_Available_Extension' :
72
+ require_once dirname( __FILE__ ) . '/core/components/extensions/manager/includes/available-ext/class-fw-available-extension.php';
73
+ break;
74
+ }
75
+ }
76
+
77
+ spl_autoload_register( '_fw_core_extends_autoload' );
78
+ function _fw_core_extends_autoload( $class ) {
79
+ switch ( $class ) {
80
+ case 'FW_Container_Type' :
81
+ require_once dirname( __FILE__ ) . '/core/extends/class-fw-container-type.php';
82
+ break;
83
+ case 'FW_Option_Type' :
84
+ require_once dirname( __FILE__ ) . '/core/extends/class-fw-option-type.php';
85
+ break;
86
+ case 'FW_Extension' :
87
+ require_once dirname( __FILE__ ) . '/core/extends/class-fw-extension.php';
88
+ break;
89
+ case 'FW_Option_Handler' :
90
+ require_once dirname( __FILE__ ) . '/core/extends/interface-fw-option-handler.php';
91
+ break;
92
+ }
93
+ }
94
+
95
+ spl_autoload_register( '_fw_code_exceptions_autoload' );
96
+ function _fw_code_exceptions_autoload( $class ) {
97
+ switch ( $class ) {
98
+ case 'FW_Option_Type_Exception' :
99
+ case 'FW_Option_Type_Exception_Not_Found' :
100
+ case 'FW_Option_Type_Exception_Invalid_Class' :
101
+ case 'FW_Option_Type_Exception_Already_Registered' :
102
+ require_once dirname( __FILE__ ) . '/core/exceptions/class-fw-option-type-exception.php';
103
+ break;
104
+ }
105
+ }
106
+
107
+ // Autoload helper classes
108
+ function _fw_autoload_helper_classes($class) {
109
+ static $class_to_file = array(
110
+ 'FW_Dumper' => 'class-fw-dumper',
111
+ 'FW_Cache' => 'class-fw-cache',
112
+ 'FW_Callback' => 'class-fw-callback',
113
+ 'FW_Access_Key' => 'class-fw-access-key',
114
+ 'FW_WP_Filesystem' => 'class-fw-wp-filesystem',
115
+ 'FW_Form' => 'class-fw-form',
116
+ 'FW_Form_Not_Found_Exception' => 'exceptions/class-fw-form-not-found-exception',
117
+ 'FW_Form_Invalid_Submission_Exception' => 'exceptions/class-fw-form-invalid-submission-exception',
118
+ 'FW_Settings_Form' => 'class-fw-settings-form',
119
+ 'FW_Request' => 'class-fw-request',
120
+ 'FW_Session' => 'class-fw-session',
121
+ 'FW_WP_Option' => 'class-fw-wp-option',
122
+ 'FW_WP_Meta' => 'class-fw-wp-meta',
123
+ 'FW_Db_Options_Model' => 'class-fw-db-options-model',
124
+ 'FW_Flash_Messages' => 'class-fw-flash-messages',
125
+ 'FW_Resize' => 'class-fw-resize',
126
+ 'FW_WP_List_Table' => 'class-fw-wp-list-table',
127
+ 'FW_Type' => 'type/class-fw-type',
128
+ 'FW_Type_Register' => 'type/class-fw-type-register',
129
+ );
130
+
131
+ if (isset($class_to_file[$class])) {
132
+ require dirname(__FILE__) .'/helpers/'. $class_to_file[$class] .'.php';
133
+ }
134
+ }
135
+ spl_autoload_register('_fw_autoload_helper_classes');
136
+
137
+ spl_autoload_register( '_fw_includes_container_types_autoload' );
138
+ function _fw_includes_container_types_autoload( $class ) {
139
+ switch ( $class ) {
140
+ case 'FW_Container_Type_Undefined' :
141
+ require_once dirname( __FILE__ ) . '/includes/container-types/class-fw-container-type-undefined.php';
142
+ break;
143
+ case 'FW_Container_Type_Group' :
144
+ require_once dirname( __FILE__ ) . '/includes/container-types/simple.php';
145
+ break;
146
+ case 'FW_Container_Type_Box' :
147
+ require_once dirname( __FILE__ ) . '/includes/container-types/box/class-fw-container-type-box.php';
148
+ break;
149
+ case 'FW_Container_Type_Popup' :
150
+ require_once dirname( __FILE__ ) . '/includes/container-types/popup/class-fw-container-type-popup.php';
151
+ break;
152
+ case 'FW_Container_Type_Tab' :
153
+ require_once dirname( __FILE__ ) . '/includes/container-types/tab/class-fw-container-type-tab.php';
154
+ break;
155
+ }
156
+ }
157
+
158
+ spl_autoload_register( '_fw_includes_customizer_autoload' );
159
+ function _fw_includes_customizer_autoload( $class ) {
160
+ switch ( $class ) {
161
+ case '_FW_Customizer_Control_Option_Wrapper' :
162
+ require_once dirname( __FILE__ ) . '/includes/customizer/class--fw-customizer-control-option-wrapper.php';
163
+ break;
164
+ case '_FW_Customizer_Setting_Option' :
165
+ require_once dirname( __FILE__ ) . '/includes/customizer/class--fw-customizer-setting-option.php';
166
+ break;
167
+ }
168
+ }
169
+
170
+ spl_autoload_register( '_fw_includes_option_storage_autoload' );
171
+ function _fw_includes_option_storage_autoload( $class ) {
172
+ switch ( $class ) {
173
+ case '_FW_Option_Storage_Type_Register' :
174
+ require_once dirname( __FILE__ ) . '/includes/option-storage/class--fw-option-storage-type-register.php';
175
+ break;
176
+ case 'FW_Option_Storage_Type' :
177
+ require_once dirname( __FILE__ ) . '/includes/option-storage/class-fw-option-storage-type.php';
178
+ break;
179
+ case 'FW_Option_Storage_Type_Post_Meta' :
180
+ require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-post-meta.php';
181
+ break;
182
+ case 'FW_Option_Storage_Type_Term_Meta' :
183
+ require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-term-meta.php';
184
+ break;
185
+ case 'FW_Option_Storage_Type_WP_Option' :
186
+ require_once dirname( __FILE__ ) . '/includes/option-storage/type/class-fw-option-storage-type-wp-option.php';
187
+ break;
188
+ }
189
+ }
190
+
191
+ spl_autoload_register( '_fw_includes_option_types_autoload' );
192
+ function _fw_includes_option_types_autoload( $class ) {
193
+ switch ( $class ) {
194
+ case 'FW_Option_Type_Undefined' :
195
+ require_once dirname( __FILE__ ) . '/includes/option-types/class-fw-option-type-undefined.php';
196
+ break;
197
+ case 'FW_Option_Type_Hidden' :
198
+ case 'FW_Option_Type_Text' :
199
+ case 'FW_Option_Type_Short_Text' :
200
+ case 'FW_Option_Type_Password' :
201
+ case 'FW_Option_Type_Textarea' :
202
+ case 'FW_Option_Type_Html' :
203
+ case 'FW_Option_Type_Html_Fixed' :
204
+ case 'FW_Option_Type_Html_Full' :
205
+ case 'FW_Option_Type_Checkbox' :
206
+ case 'FW_Option_Type_Checkboxes' :
207
+ case 'FW_Option_Type_Radio' :
208
+ case 'FW_Option_Type_Select' :
209
+ case 'FW_Option_Type_Short_Select' :
210
+ case 'FW_Option_Type_Select_Multiple' :
211
+ case 'FW_Option_Type_Unique' :
212
+ case 'FW_Option_Type_GMap_Key' :
213
+ require_once dirname( __FILE__ ) . '/includes/option-types/simple.php';
214
+ break;
215
+ case 'FW_Option_Type_Addable_Box' :
216
+ require_once dirname( __FILE__ ) . '/includes/option-types/addable-box/class-fw-option-type-addable-box.php';
217
+ break;
218
+ case 'FW_Option_Type_Addable_Popup' :
219
+ case 'FW_Option_Type_Addable_Popup_Full' :
220
+ require_once dirname( __FILE__ ) . '/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php';
221
+ break;
222
+ case 'FW_Option_Type_Addable_Option' :
223
+ require_once dirname( __FILE__ ) . '/includes/option-types/addable-option/class-fw-option-type-addable-option.php';
224
+ break;
225
+ case 'FW_Option_Type_Background_Image' :
226
+ require_once dirname( __FILE__ ) . '/includes/option-types/background-image/class-fw-option-type-background-image.php';
227
+ break;
228
+ case 'FW_Option_Type_Color_Picker' :
229
+ require_once dirname( __FILE__ ) . '/includes/option-types/color-picker/class-fw-option-type-color-picker.php';
230
+ break;
231
+ case 'FW_Option_Type_Date_Picker' :
232
+ require_once dirname( __FILE__ ) . '/includes/option-types/date-picker/class-fw-option-type-wp-date-picker.php';
233
+ break;
234
+ case 'FW_Option_Type_Datetime_Picker' :
235
+ require_once dirname( __FILE__ ) . '/includes/option-types/datetime-picker/class-fw-option-type-datetime-picker.php';
236
+ break;
237
+ case 'FW_Option_Type_Datetime_Range' :
238
+ require_once dirname( __FILE__ ) . '/includes/option-types/datetime-range/class-fw-option-type-datetime-range.php';
239
+ break;
240
+ case 'FW_Option_Type_Gradient' :
241
+ require_once dirname( __FILE__ ) . '/includes/option-types/gradient/class-fw-option-type-gradient.php';
242
+ break;
243
+ case 'FW_Option_Type_Icon' :
244
+ require_once dirname( __FILE__ ) . '/includes/option-types/icon/class-fw-option-type-icon.php';
245
+ break;
246
+ case 'FW_Option_Type_Icon_v2' :
247
+ require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/class-fw-option-type-icon-v2.php';
248
+ break;
249
+ case 'FW_Option_Type_Image_Picker' :
250
+ require_once dirname( __FILE__ ) . '/includes/option-types/image-picker/class-fw-option-type-image-picker.php';
251
+ break;
252
+ case 'FW_Option_Type_Map' :
253
+ require_once dirname( __FILE__ ) . '/includes/option-types/map/class-fw-option-type-map.php';
254
+ break;
255
+ case 'FW_Option_Type_Multi' :
256
+ require_once dirname( __FILE__ ) . '/includes/option-types/multi/class-fw-option-type-multi.php';
257
+ break;
258
+ case 'FW_Option_Type_Multi_Picker' :
259
+ require_once dirname( __FILE__ ) . '/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php';
260
+ break;
261
+ case 'FW_Option_Type_Multi_Select' :
262
+ require_once dirname( __FILE__ ) . '/includes/option-types/multi-select/class-fw-option-type-multi-select.php';
263
+ break;
264
+ case 'FW_Option_Type_Multi_Upload' :
265
+ require_once dirname( __FILE__ ) . '/includes/option-types/multi-upload/class-fw-option-type-multi-upload.php';
266
+ break;
267
+ case 'FW_Option_Type_Oembed' :
268
+ require_once dirname( __FILE__ ) . '/includes/option-types/oembed/class-fw-option-type-oembed.php';
269
+ break;
270
+ case 'FW_Option_Type_Popup' :
271
+ require_once dirname( __FILE__ ) . '/includes/option-types/popup/class-fw-option-type-popup.php';
272
+ break;
273
+ case 'FW_Option_Type_Radio_Text' :
274
+ require_once dirname( __FILE__ ) . '/includes/option-types/radio-text/class-fw-option-type-radio-text.php';
275
+ break;
276
+ case 'FW_Option_Type_Range_Slider' :
277
+ require_once dirname( __FILE__ ) . '/includes/option-types/range-slider/class-fw-option-type-range-slider.php';
278
+ break;
279
+ case 'FW_Option_Type_Rgba_Color_Picker' :
280
+ require_once dirname( __FILE__ ) . '/includes/option-types/rgba-color-picker/class-fw-option-type-rgba-color-picker.php';
281
+ break;
282
+ case 'FW_Option_Type_Slider' :
283
+ require_once dirname( __FILE__ ) . '/includes/option-types/slider/class-fw-option-type-slider.php';
284
+ break;
285
+ case 'FW_Option_Type_Slider_Short' :
286
+ require_once dirname( __FILE__ ) . '/includes/option-types/slider/class-fw-option-type-short-slider.php';
287
+ break;
288
+ case 'FW_Option_Type_Switch' :
289
+ require_once dirname( __FILE__ ) . '/includes/option-types/switch/class-fw-option-type-switch.php';
290
+ break;
291
+ case 'FW_Option_Type_Typography' :
292
+ require_once dirname( __FILE__ ) . '/includes/option-types/typography/class-fw-option-type-typography.php';
293
+ break;
294
+ case 'FW_Option_Type_Typography_v2' :
295
+ require_once dirname( __FILE__ ) . '/includes/option-types/typography-v2/class-fw-option-type-typography-v2.php';
296
+ break;
297
+ case 'FW_Option_Type_Upload' :
298
+ require_once dirname( __FILE__ ) . '/includes/option-types/upload/class-fw-option-type-upload.php';
299
+ break;
300
+ case 'FW_Option_Type_Wp_Editor' :
301
+ require_once dirname( __FILE__ ) . '/includes/option-types/wp-editor/class-fw-option-type-wp-editor.php';
302
+ break;
303
+ case 'FW_Icon_V2_Favorites_Manager' :
304
+ require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/includes/class-fw-icon-v2-favorites.php';
305
+ break;
306
+ case 'FW_Icon_V2_Packs_Loader' :
307
+ require_once dirname( __FILE__ ) . '/includes/option-types/icon-v2/includes/class-fw-icon-v2-packs-loader.php';
308
+ break;
309
+ }
310
  }
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,572 +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
- 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
  }
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,2062 +1,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
- }
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
+
196
+ add_action('customize_register', array($this, '_action_customize_register'), 7);
197
+ }
198
+
199
+ private function add_filters() {
200
+ if ( is_admin() ) {
201
+ add_filter('admin_footer_text', array($this, '_filter_admin_footer_text'), 11);
202
+ add_filter('update_footer', array($this, '_filter_footer_version'), 11);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * @param string|FW_Option_Type $option_type_class
208
+ * @param string|null $type
209
+ *
210
+ * @internal
211
+ */
212
+ private function register_option_type( $option_type_class, $type = null ) {
213
+ if ( $type == null ) {
214
+ try {
215
+ $type = $this->get_instance( $option_type_class )->get_type();
216
+ } catch ( FW_Option_Type_Exception_Invalid_Class $exception ) {
217
+ if ( ! is_subclass_of( $option_type_class, 'FW_Option_Type' ) ) {
218
+ trigger_error( 'Invalid option type class ' . get_class( $option_type_class ), E_USER_WARNING );
219
+
220
+ return;
221
+ }
222
+ }
223
+ }
224
+
225
+ if ( isset( $this->option_types[ $type ] ) ) {
226
+ trigger_error( 'Option type "' . $type . '" already registered', E_USER_WARNING );
227
+
228
+ return;
229
+ }
230
+
231
+ $this->option_types[$type] = $option_type_class;
232
+ }
233
+
234
+ /**
235
+ * @param string|FW_Container_Type $container_type_class
236
+ * @param string|null $type
237
+ *
238
+ * @internal
239
+ */
240
+ private function register_container_type( $container_type_class, $type = null ) {
241
+ if ( $type == null ) {
242
+ try {
243
+ $type = $this->get_instance( $container_type_class )->get_type();
244
+ } catch ( FW_Option_Type_Exception_Invalid_Class $exception ) {
245
+ if ( ! is_subclass_of( $container_type_class, 'FW_Container_Type' ) ) {
246
+ trigger_error( 'Invalid container type class ' . get_class( $container_type_class ), E_USER_WARNING );
247
+
248
+ return;
249
+ }
250
+ }
251
+ }
252
+
253
+ if ( isset( $this->container_types[ $type ] ) ) {
254
+ trigger_error( 'Container type "' . $type . '" already registered', E_USER_WARNING );
255
+
256
+ return;
257
+ }
258
+
259
+ $this->container_types[$type] = $container_type_class;
260
+ }
261
+
262
+ private function register_static() {
263
+ if (
264
+ !doing_action('admin_enqueue_scripts')
265
+ &&
266
+ !did_action('admin_enqueue_scripts')
267
+ ) {
268
+ /**
269
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
270
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
271
+ * So as a result some handles will not be equeued because of not registered dependecies.
272
+ */
273
+ return;
274
+ }
275
+
276
+ if ( $this->static_registered ) {
277
+ return;
278
+ }
279
+
280
+ /**
281
+ * Register styles/scripts only in admin area, on frontend it's not allowed to use styles/scripts from framework backend core
282
+ * because they are meant to be used only in backend and can be changed in the future.
283
+ * If you want to use a style/script from framework backend core, copy it to your theme and enqueue as a theme style/script.
284
+ */
285
+ if ( ! is_admin() ) {
286
+ $this->static_registered = true;
287
+
288
+ return;
289
+ }
290
+
291
+ wp_register_script(
292
+ 'fw-events',
293
+ fw_get_framework_directory_uri( '/static/js/fw-events.js' ),
294
+ array(),
295
+ fw()->manifest->get_version(),
296
+ true
297
+ );
298
+
299
+ wp_register_script(
300
+ 'fw-ie-fixes',
301
+ fw_get_framework_directory_uri( '/static/js/ie-fixes.js' ),
302
+ array(),
303
+ fw()->manifest->get_version(),
304
+ true
305
+ );
306
+
307
+ {
308
+ wp_register_style(
309
+ 'qtip',
310
+ fw_get_framework_directory_uri( '/static/libs/qtip/css/jquery.qtip.min.css' ),
311
+ array(),
312
+ fw()->manifest->get_version()
313
+ );
314
+ wp_register_script(
315
+ 'qtip',
316
+ fw_get_framework_directory_uri( '/static/libs/qtip/jquery.qtip.min.js' ),
317
+ array( 'jquery' ),
318
+ fw()->manifest->get_version()
319
+ );
320
+ }
321
+
322
+ /**
323
+ * Important!
324
+ * Call wp_enqueue_media() before wp_enqueue_script('fw') (or using 'fw' in your script dependencies)
325
+ * otherwise fw.OptionsModal won't work
326
+ */
327
+ {
328
+ wp_register_style(
329
+ 'fw',
330
+ fw_get_framework_directory_uri( '/static/css/fw.css' ),
331
+ array( 'qtip' ),
332
+ fw()->manifest->get_version()
333
+ );
334
+
335
+ wp_register_script(
336
+ 'fw-reactive-options-registry',
337
+ fw_get_framework_directory_uri(
338
+ '/static/js/fw-reactive-options-registry.js'
339
+ ),
340
+ array('fw', 'fw-events'),
341
+ false
342
+ );
343
+
344
+ wp_register_script(
345
+ 'fw-reactive-options-simple-options',
346
+ fw_get_framework_directory_uri(
347
+ '/static/js/fw-reactive-options-simple-options.js'
348
+ ),
349
+ array('fw', 'fw-events', 'fw-reactive-options-undefined-option'),
350
+ false
351
+ );
352
+
353
+ wp_register_script(
354
+ 'fw-reactive-options-undefined-option',
355
+ fw_get_framework_directory_uri(
356
+ '/static/js/fw-reactive-options-undefined-option.js'
357
+ ),
358
+ array(
359
+ 'fw', 'fw-events', 'fw-reactive-options-registry'
360
+ ),
361
+ false
362
+ );
363
+
364
+ wp_register_script(
365
+ 'fw-reactive-options',
366
+ fw_get_framework_directory_uri('/static/js/fw-reactive-options.js'),
367
+ array(
368
+ 'fw', 'fw-events', 'fw-reactive-options-undefined-option',
369
+ 'fw-reactive-options-simple-options'
370
+ ),
371
+ false
372
+ );
373
+
374
+ wp_register_script(
375
+ 'fw',
376
+ fw_get_framework_directory_uri( '/static/js/fw.js' ),
377
+ array( 'jquery', 'fw-events', 'backbone', 'qtip' ),
378
+ fw()->manifest->get_version(),
379
+ false // false fixes https://github.com/ThemeFuse/Unyson/issues/1625#issuecomment-224219454
380
+ );
381
+
382
+ wp_localize_script( 'fw', '_fw_localized', array(
383
+ 'FW_URI' => fw_get_framework_directory_uri(),
384
+ 'SITE_URI' => site_url(),
385
+ 'LOADER_URI' => apply_filters( 'fw_loader_image', fw_get_framework_directory_uri() . '/static/img/logo.svg' ),
386
+ 'l10n' => array_merge(
387
+ $l10n = array(
388
+ 'modal_save_btn' => __( 'Save', 'fw' ),
389
+ 'done' => __( 'Done', 'fw' ),
390
+ 'ah_sorry' => __( 'Ah, Sorry', 'fw' ),
391
+ 'reset' => __( 'Reset', 'fw' ),
392
+ 'apply' => __( 'Apply', 'fw' ),
393
+ 'cancel' => __( 'Cancel', 'fw' ),
394
+ 'ok' => __( 'Ok', 'fw' )
395
+ ),
396
+ /**
397
+ * fixes https://github.com/ThemeFuse/Unyson/issues/2381
398
+ * @since 2.6.14
399
+ */
400
+ apply_filters('fw_js_l10n', $l10n)
401
+ ),
402
+ 'options_modal' => array(
403
+ /** @since 2.6.13 */
404
+ 'default_reset_bnt_disabled' => apply_filters('fw:option-modal:default:reset-btn-disabled', false)
405
+ ),
406
+ ) );
407
+ }
408
+
409
+ {
410
+ wp_register_style(
411
+ 'fw-backend-options',
412
+ fw_get_framework_directory_uri( '/static/css/backend-options.css' ),
413
+ array( 'fw' ),
414
+ fw()->manifest->get_version()
415
+ );
416
+
417
+ wp_register_script(
418
+ 'fw-backend-options',
419
+ fw_get_framework_directory_uri( '/static/js/backend-options.js' ),
420
+ array( 'fw', 'fw-events', 'fw-reactive-options', 'postbox', 'jquery-ui-tabs' ),
421
+ fw()->manifest->get_version(),
422
+ true
423
+ );
424
+
425
+ wp_localize_script( 'fw', '_fw_backend_options_localized', array(
426
+ 'lazy_tabs' => fw()->theme->get_config('lazy_tabs')
427
+ ) );
428
+ }
429
+
430
+ {
431
+ wp_register_style(
432
+ 'fw-selectize',
433
+ fw_get_framework_directory_uri( '/static/libs/selectize/selectize.css' ),
434
+ array(),
435
+ fw()->manifest->get_version()
436
+ );
437
+ wp_register_script(
438
+ 'fw-selectize',
439
+ fw_get_framework_directory_uri( '/static/libs/selectize/selectize.min.js' ),
440
+ array( 'jquery', 'fw-ie-fixes' ),
441
+ fw()->manifest->get_version(),
442
+ true
443
+ );
444
+ }
445
+
446
+ {
447
+ wp_register_script(
448
+ 'fw-mousewheel',
449
+ fw_get_framework_directory_uri( '/static/libs/mousewheel/jquery.mousewheel.min.js' ),
450
+ array( 'jquery' ),
451
+ fw()->manifest->get_version(),
452
+ true
453
+ );
454
+ }
455
+
456
+ {
457
+ wp_register_style(
458
+ 'fw-jscrollpane',
459
+ fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.css' ),
460
+ array(),
461
+ fw()->manifest->get_version()
462
+ );
463
+ wp_register_script( 'fw-jscrollpane',
464
+ fw_get_framework_directory_uri( '/static/libs/jscrollpane/jquery.jscrollpane.min.js' ),
465
+ array( 'jquery', 'fw-mousewheel' ),
466
+ fw()->manifest->get_version(),
467
+ true
468
+ );
469
+ }
470
+
471
+ wp_register_style(
472
+ 'font-awesome',
473
+ fw_get_framework_directory_uri( '/static/libs/font-awesome/css/font-awesome.min.css' ),
474
+ array(),
475
+ fw()->manifest->get_version()
476
+ );
477
+ /**
478
+ * backwards compatibility, in case extensions are not up-to-date
479
+ * todo: remove in next major version
480
+ * https://github.com/ThemeFuse/Unyson/issues/2198
481
+ * @deprecated
482
+ */
483
+ wp_register_style('fw-font-awesome', fw_get_framework_directory_uri( '/static/libs/font-awesome/css/font-awesome.min.css' ));
484
+
485
+ wp_register_script(
486
+ 'backbone-relational',
487
+ fw_get_framework_directory_uri( '/static/libs/backbone-relational/backbone-relational.js' ),
488
+ array( 'backbone' ),
489
+ fw()->manifest->get_version(),
490
+ true
491
+ );
492
+
493
+ wp_register_script(
494
+ 'fw-uri',
495
+ fw_get_framework_directory_uri( '/static/libs/uri/URI.js' ),
496
+ array(),
497
+ fw()->manifest->get_version(),
498
+ true
499
+ );
500
+
501
+ wp_register_script(
502
+ 'fw-moment',
503
+ /**
504
+ * IMPORTANT: At the end of the script is added this line:
505
+ * moment.locale(document.documentElement.lang.slice(0, 2)); // fixes https://github.com/ThemeFuse/Unyson/issues/1767
506
+ */
507
+ fw_get_framework_directory_uri( '/static/libs/moment/moment-with-locales.min.js' ),
508
+ array(),
509
+ fw()->manifest->get_version(),
510
+ true
511
+ );
512
+
513
+ wp_register_script(
514
+ 'fw-form-helpers',
515
+ fw_get_framework_directory_uri( '/static/js/fw-form-helpers.js' ),
516
+ array( 'jquery' ),
517
+ fw()->manifest->get_version(),
518
+ true
519
+ );
520
+
521
+ wp_register_style(
522
+ 'fw-unycon',
523
+ fw_get_framework_directory_uri( '/static/libs/unycon/unycon.css' ),
524
+ array(),
525
+ fw()->manifest->get_version()
526
+ );
527
+
528
+ $this->static_registered = true;
529
+ }
530
+
531
+ /**
532
+ * @param $class
533
+ *
534
+ * @return FW_Option_Type
535
+ * @throws FW_Option_Type_Exception_Invalid_Class
536
+ */
537
+ protected function get_instance( $class ) {
538
+ if ( ! class_exists( $class )
539
+ || (
540
+ ! is_subclass_of( $class, 'FW_Option_Type' )
541
+ &&
542
+ ! is_subclass_of( $class, 'FW_Container_Type' )
543
+ )
544
+ ) {
545
+ throw new FW_Option_Type_Exception_Invalid_Class( $class );
546
+ }
547
+
548
+ return new $class;
549
+ }
550
+
551
+ public function _filter_admin_footer_text( $html ) {
552
+ if (
553
+ (
554
+ current_user_can( 'update_themes' )
555
+ ||
556
+ current_user_can( 'update_plugins' )
557
+ )
558
+ &&
559
+ fw_current_screen_match(array(
560
+ 'only' => array(
561
+ array('parent_base' => fw()->extensions->manager->get_page_slug()) // Unyson Extensions page
562
+ )
563
+ ))
564
+ ) {
565
+ return ( empty( $html ) ? '' : $html . '<br/>' )
566
+ . '<em>'
567
+ . str_replace(
568
+ array(
569
+ '{wp_review_link}',
570
+ '{facebook_share_link}',
571
+ '{twitter_share_link}',
572
+ ),
573
+ array(
574
+ fw_html_tag('a', array(
575
+ 'target' => '_blank',
576
+ 'href' => 'https://wordpress.org/support/view/plugin-reviews/unyson?filter=5#postform',
577
+ ), __('leave a review', 'fw')),
578
+ fw_html_tag('a', array(
579
+ 'target' => '_blank',
580
+ 'href' => 'https://www.facebook.com/sharer/sharer.php?'. http_build_query(array(
581
+ 'u' => 'http://unyson.io',
582
+ )),
583
+ 'onclick' => 'return !window.open(this.href, \'Facebook\', \'width=640,height=300\')',
584
+ ), __('Facebook', 'fw')),
585
+ fw_html_tag('a', array(
586
+ 'target' => '_blank',
587
+ 'href' => 'https://twitter.com/home?'. http_build_query(array(
588
+ 'status' => __('Unyson WordPress Framework is the fastest and easiest way to develop a premium theme. I highly recommend it', 'fw')
589
+ .' http://unyson.io/ #UnysonWP',
590
+ )),
591
+ 'onclick' => 'return !window.open(this.href, \'Twitter\', \'width=640,height=430\')',
592
+ ), __('Twitter', 'fw')),
593
+ ),
594
+ __('If you like Unyson, {wp_review_link}, share on {facebook_share_link} or {twitter_share_link}.', 'fw')
595
+ )
596
+ . '</em>';
597
+ } else {
598
+ return $html;
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Print framework version in the admin footer
604
+ *
605
+ * @param string $html
606
+ *
607
+ * @return string
608
+ * @internal
609
+ */
610
+ public function _filter_footer_version( $html ) {
611
+ if ( current_user_can( 'update_themes' ) || current_user_can( 'update_plugins' ) ) {
612
+ return ( empty( $html ) ? '' : $html . ' | ' ) . fw()->manifest->get_name() . ' ' . fw()->manifest->get_version();
613
+ } else {
614
+ return $html;
615
+ }
616
+ }
617
+
618
+ /**
619
+ * @param string $post_type
620
+ * @param WP_Post $post
621
+ */
622
+ public function _action_create_post_meta_boxes( $post_type, $post ) {
623
+ if ( 'comment' === $post_type || ( isset( $_GET['vc_action'] ) && $_GET['vc_action'] === 'vc_inline' ) ) {
624
+ /**
625
+ * 1. https://github.com/ThemeFuse/Unyson/issues/3052
626
+ * 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)
627
+ */
628
+ return;
629
+ }
630
+
631
+ $options = fw()->theme->get_post_options( $post_type );
632
+
633
+ if ( empty( $options ) ) {
634
+ return;
635
+ }
636
+
637
+ $collected = array();
638
+
639
+ fw_collect_options( $collected, $options, array(
640
+ 'limit_option_types' => false,
641
+ 'limit_container_types' => false,
642
+ 'limit_level' => 1,
643
+ ) );
644
+
645
+ if ( empty( $collected ) ) {
646
+ return;
647
+ }
648
+
649
+ $values = fw_get_db_post_option( $post->ID );
650
+
651
+ foreach ( $collected as $id => &$option ) {
652
+ if ( isset( $option['options'] ) && ( $option['type'] === 'box' || $option['type'] === 'group' ) ) {
653
+ $context = isset( $option['context'] ) ? $option['context'] : 'normal';
654
+ $priority = isset( $option['priority'] ) ? $option['priority'] : 'default';
655
+
656
+ add_meta_box(
657
+ "fw-options-box-{$id}",
658
+ empty( $option['title'] ) ? ' ' : $option['title'],
659
+ array( $this, 'render_meta_box' ),
660
+ $post_type,
661
+ $context,
662
+ $priority,
663
+ $this->render_options( $option['options'], $values )
664
+ );
665
+ } else { // this is not a box, wrap it in auto-generated box
666
+ add_meta_box(
667
+ 'fw-options-box:auto-generated:' . time() . ':' . fw_unique_increment(),
668
+ ' ',
669
+ array( $this, 'render_meta_box' ),
670
+ $post_type,
671
+ 'normal',
672
+ 'default',
673
+ $this->render_options( array( $id => $option ), $values )
674
+ );
675
+ }
676
+ }
677
+ }
678
+
679
+ public function render_meta_box( $post, $args ) {
680
+ echo $args['args'];
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
+
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
+
836
+ do {
837
+ $parent = get_post($post->post_parent);
838
+
839
+ if ( ! $parent instanceof WP_Post ) {
840
+ break;
841
+ }
842
+
843
+ if ( isset($_POST['post_ID']) && intval($_POST['post_ID']) === intval($parent->ID) ) {
844
+
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
+ * Update all post meta `fw_option:<option-id>` with values from post options that has the 'save-in-separate-meta' parameter
896
+ *
897
+ * @param int $post_id
898
+ *
899
+ * @return bool
900
+ * @deprecated since 2.5.0
901
+ */
902
+ public function _sync_post_separate_meta( $post_id ) {
903
+ if ( ! ( $post_type = get_post_type( $post_id ) ) ) {
904
+ return false;
905
+ }
906
+
907
+ $meta_prefix = 'fw_option:';
908
+ $only_options = fw_extract_only_options( fw()->theme->get_post_options( $post_type ) );
909
+ $separate_meta_options = array();
910
+
911
+ // Collect all options that needs to be saved in separate meta
912
+ {
913
+ $options_values = fw_get_db_post_option( $post_id );
914
+
915
+ foreach ($only_options as $option_id => $option) {
916
+ if (
917
+ isset( $option['save-in-separate-meta'] )
918
+ &&
919
+ $option['save-in-separate-meta']
920
+ &&
921
+ array_key_exists( $option_id, $options_values )
922
+ ) {
923
+ if (defined('WP_DEBUG') && WP_DEBUG) {
924
+ FW_Flash_Messages::add(
925
+ 'save-in-separate-meta:deprecated',
926
+ '<p>The <code>save-in-separate-meta</code> option parameter is <strong>deprecated</strong>.</p>'
927
+ .'<p>Please replace</p>'
928
+ .'<pre>\'save-in-separate-meta\' => true</pre>'
929
+ .'<p>with</p>'
930
+ .'<pre>\'fw-storage\' => array('
931
+ ."\n 'type' => 'post-meta',"
932
+ ."\n 'post-meta' => 'fw_option:{your-option-id}',"
933
+ ."\n)</pre>"
934
+ .'<p>in <code>{theme}'. fw_get_framework_customizations_dir_rel_path('/theme/options/posts/'. $post_type .'.php') .'</code></p>'
935
+ .'<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>',
936
+ 'warning'
937
+ );
938
+ }
939
+
940
+ $separate_meta_options[ $meta_prefix . $option_id ] = $options_values[ $option_id ];
941
+ }
942
+ }
943
+
944
+ unset( $options_values );
945
+ }
946
+
947
+ // Delete meta that starts with $meta_prefix
948
+ {
949
+ /** @var wpdb $wpdb */
950
+ global $wpdb;
951
+
952
+ foreach (
953
+ $wpdb->get_results(
954
+ $wpdb->prepare(
955
+ "SELECT meta_key " .
956
+ "FROM {$wpdb->postmeta} " .
957
+ "WHERE meta_key LIKE %s AND post_id = %d",
958
+ $wpdb->esc_like( $meta_prefix ) . '%',
959
+ $post_id
960
+ )
961
+ ) as $row
962
+ ) {
963
+ if (
964
+ array_key_exists( $row->meta_key, $separate_meta_options )
965
+ ||
966
+ ( // skip options containing 'fw-storage'
967
+ ($option_id = substr($row->meta_key, 10))
968
+ &&
969
+ isset($only_options[$option_id]['fw-storage'])
970
+ )
971
+ ) {
972
+ /**
973
+ * This meta exists and will be updated below.
974
+ * Do not delete for performance reasons, instead of delete->insert will be performed only update
975
+ */
976
+ continue;
977
+ } else {
978
+ // this option does not exist anymore
979
+ delete_post_meta( $post_id, $row->meta_key );
980
+ }
981
+ }
982
+ }
983
+
984
+ foreach ( $separate_meta_options as $meta_key => $option_value ) {
985
+ fw_update_post_meta($post_id, $meta_key, $option_value );
986
+ }
987
+
988
+ return true;
989
+ }
990
+
991
+ /**
992
+ * @param int $term_id
993
+ */
994
+ public function _action_save_taxonomy_fields( $term_id ) {
995
+ if (
996
+ isset( $_POST['action'] )
997
+ &&
998
+ 'add-tag' === $_POST['action']
999
+ &&
1000
+ isset( $_POST['taxonomy'] )
1001
+ &&
1002
+ ($taxonomy = get_taxonomy( $_POST['taxonomy'] ))
1003
+ &&
1004
+ current_user_can($taxonomy->cap->edit_terms)
1005
+ ) { /* ok */ } else { return; }
1006
+
1007
+ $options = fw()->theme->get_taxonomy_options( $taxonomy->name );
1008
+ if ( empty( $options ) ) {
1009
+ return;
1010
+ }
1011
+
1012
+ fw_set_db_term_option(
1013
+ $term_id,
1014
+ $taxonomy->name,
1015
+ null,
1016
+ fw_get_options_values_from_input($options)
1017
+ );
1018
+
1019
+ do_action( 'fw_save_term_options', $term_id, $taxonomy->name, array() );
1020
+ }
1021
+
1022
+ public function _action_term_edit( $term_id, $tt_id, $taxonomy ) {
1023
+ if (
1024
+ isset( $_POST['action'] )
1025
+ &&
1026
+ 'editedtag' === $_POST['action']
1027
+ &&
1028
+ isset( $_POST['taxonomy'] )
1029
+ &&
1030
+ ($taxonomy = get_taxonomy( $_POST['taxonomy'] ))
1031
+ &&
1032
+ current_user_can($taxonomy->cap->edit_terms)
1033
+ ) { /* ok */ } else { return; }
1034
+
1035
+ if (intval(FW_Request::POST('tag_ID')) != $term_id) {
1036
+ // the $_POST values belongs to another term, do not save them into this one
1037
+ return;
1038
+ }
1039
+
1040
+ $options = fw()->theme->get_taxonomy_options( $taxonomy->name );
1041
+ if ( empty( $options ) ) {
1042
+ return;
1043
+ }
1044
+
1045
+ $old_values = (array) fw_get_db_term_option( $term_id, $taxonomy->name );
1046
+
1047
+ fw_set_db_term_option(
1048
+ $term_id,
1049
+ $taxonomy->name,
1050
+ null,
1051
+ fw_get_options_values_from_input($options)
1052
+ );
1053
+
1054
+ do_action( 'fw_save_term_options', $term_id, $taxonomy->name, $old_values );
1055
+ }
1056
+
1057
+ public function _action_admin_register_scripts() {
1058
+ $this->register_static();
1059
+ }
1060
+
1061
+ public function _action_admin_enqueue_scripts() {
1062
+ /**
1063
+ * Enqueue settings options static in <head>
1064
+ * @see FW_Settings_Form_Theme::_action_admin_enqueue_scripts()
1065
+ */
1066
+
1067
+ /**
1068
+ * Enqueue post options static in <head>
1069
+ */
1070
+ {
1071
+ if ( 'post' === get_current_screen()->base && get_the_ID() ) {
1072
+ fw()->backend->enqueue_options_static(
1073
+ fw()->theme->get_post_options( get_post_type() )
1074
+ );
1075
+
1076
+ do_action( 'fw_admin_enqueue_scripts:post', get_post() );
1077
+ }
1078
+ }
1079
+
1080
+ /**
1081
+ * Enqueue term options static in <head>
1082
+ */
1083
+ {
1084
+ if (
1085
+ in_array(get_current_screen()->base, array('edit-tags', 'term'), true)
1086
+ &&
1087
+ get_current_screen()->taxonomy
1088
+ ) {
1089
+ fw()->backend->enqueue_options_static(
1090
+ fw()->theme->get_taxonomy_options( get_current_screen()->taxonomy )
1091
+ );
1092
+
1093
+ do_action( 'fw_admin_enqueue_scripts:term', get_current_screen()->taxonomy );
1094
+ }
1095
+ }
1096
+ }
1097
+
1098
+ /**
1099
+ * Render options html from input json
1100
+ *
1101
+ * POST vars:
1102
+ * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1103
+ * - values: {option_id: value, option_id: {...}, ...} // Optional // Object
1104
+ * - data: {id_prefix: 'fw_options-a-b-', name_prefix: 'fw_options[a][b]'} // Optional // Object
1105
+ */
1106
+ public function _action_ajax_options_render() {
1107
+ // options
1108
+ {
1109
+ if ( ! isset( $_POST['options'] ) ) {
1110
+ wp_send_json_error( array(
1111
+ 'message' => 'No options'
1112
+ ) );
1113
+ }
1114
+
1115
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1116
+
1117
+ if ( ! $options ) {
1118
+ wp_send_json_error( array(
1119
+ 'message' => 'Wrong options'
1120
+ ) );
1121
+ }
1122
+ }
1123
+
1124
+ // values
1125
+ {
1126
+ if ( isset( $_POST['values'] ) ) {
1127
+ $values = FW_Request::POST( 'values' );
1128
+
1129
+ if (is_string($values)) {
1130
+ $values = json_decode($values, true);
1131
+ }
1132
+ } else {
1133
+ $values = array();
1134
+ }
1135
+
1136
+ $values = array_intersect_key( $values, fw_extract_only_options( $options ) );
1137
+ }
1138
+
1139
+ // data
1140
+ {
1141
+ if ( isset( $_POST['data'] ) ) {
1142
+ $data = FW_Request::POST( 'data' );
1143
+ } else {
1144
+ $data = array();
1145
+ }
1146
+ }
1147
+
1148
+ wp_send_json_success( array(
1149
+ 'html' => fw()->backend->render_options( $options, $values, $data ),
1150
+ /** @since 2.6.1 */
1151
+ 'default_values' => fw_get_options_values_from_input($options, array()),
1152
+ ) );
1153
+ }
1154
+
1155
+ /**
1156
+ * Get options values from html generated with 'fw_backend_options_render' ajax action
1157
+ *
1158
+ * POST vars:
1159
+ * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1160
+ * - fw_options... // Use a jQuery "ajax form submit" to emulate real form submit
1161
+ *
1162
+ * Tip: Inside form html, add: <input type="hidden" name="options" value="[...json...]">
1163
+ */
1164
+ public function _action_ajax_options_get_values() {
1165
+ // options
1166
+ {
1167
+ if ( ! isset( $_POST['options'] ) ) {
1168
+ wp_send_json_error( array(
1169
+ 'message' => 'No options'
1170
+ ) );
1171
+ }
1172
+
1173
+ $options = FW_Request::POST( 'options' );
1174
+
1175
+ if (is_string( $options )) {
1176
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1177
+ }
1178
+
1179
+ if ( ! $options ) {
1180
+ wp_send_json_error( array(
1181
+ 'message' => 'Wrong options'
1182
+ ) );
1183
+ }
1184
+ }
1185
+
1186
+ // name_prefix
1187
+ {
1188
+ if ( isset( $_POST['name_prefix'] ) ) {
1189
+ $name_prefix = FW_Request::POST( 'name_prefix' );
1190
+ } else {
1191
+ $name_prefix = $this->get_options_name_attr_prefix();
1192
+ }
1193
+ }
1194
+
1195
+ wp_send_json_success( array(
1196
+ 'values' => fw_get_options_values_from_input(
1197
+ $options,
1198
+ FW_Request::POST( fw_html_attr_name_to_array_multi_key( $name_prefix ), array() )
1199
+ )
1200
+ ) );
1201
+ }
1202
+
1203
+ /**
1204
+ * Get options values from html generated with 'fw_backend_options_render' ajax action
1205
+ *
1206
+ * POST vars:
1207
+ * - options: '[{option_id: {...}}, {option_id: {...}}, ...]' // Required // String JSON
1208
+ * - values: {option_id: {...}}
1209
+ *
1210
+ * Tip: Inside form html, add: <input type="hidden" name="options" value="[...json...]">
1211
+ */
1212
+ public function _action_ajax_options_get_values_json() {
1213
+ // options
1214
+ {
1215
+ if ( ! isset( $_POST['options'] ) ) {
1216
+ wp_send_json_error( array(
1217
+ 'message' => 'No options'
1218
+ ) );
1219
+ }
1220
+
1221
+ $options = FW_Request::POST( 'options' );
1222
+
1223
+ if (is_string( $options )) {
1224
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1225
+ }
1226
+
1227
+ if ( ! $options ) {
1228
+ wp_send_json_error( array(
1229
+ 'message' => 'Wrong options'
1230
+ ) );
1231
+ }
1232
+ }
1233
+
1234
+ // values
1235
+ {
1236
+ if ( ! isset( $_POST['values'] ) ) {
1237
+ wp_send_json_error( array(
1238
+ 'message' => 'No values'
1239
+ ) );
1240
+ }
1241
+
1242
+ $values = FW_Request::POST( 'values' );
1243
+
1244
+ if (is_string( $values )) {
1245
+ $values = json_decode( FW_Request::POST( 'values' ), true );
1246
+ }
1247
+
1248
+ if (! is_array($values)) {
1249
+ if ( ! $values ) {
1250
+ wp_send_json_error(array(
1251
+ 'message' => 'Wrong values'
1252
+ ));
1253
+ }
1254
+ }
1255
+ }
1256
+
1257
+ wp_send_json_success( array(
1258
+ 'values' => fw_get_options_values_from_input(
1259
+ $options,
1260
+ $values
1261
+ )
1262
+ ) );
1263
+ }
1264
+
1265
+ /**
1266
+ * Render options array and return the generated HTML
1267
+ *
1268
+ * @param array $options
1269
+ * @param array $values Correct values returned by fw_get_options_values_from_input()
1270
+ * @param array $options_data {id_prefix => ..., name_prefix => ...}
1271
+ * @param string $design
1272
+ *
1273
+ * @return string HTML
1274
+ */
1275
+ public function render_options( $options, $values = array(), $options_data = array(), $design = null ) {
1276
+ if (empty($design)) {
1277
+ $design = $this->default_render_design;
1278
+ }
1279
+
1280
+ if (
1281
+ !doing_action('admin_enqueue_scripts')
1282
+ &&
1283
+ !did_action('admin_enqueue_scripts')
1284
+ ) {
1285
+ /**
1286
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1287
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
1288
+ * So as a result some handles will not be equeued because of not registered dependecies.
1289
+ */
1290
+ } else {
1291
+ /**
1292
+ * register scripts and styles
1293
+ * in case if this method is called before enqueue_scripts action
1294
+ * and option types has some of these in their dependencies
1295
+ */
1296
+ $this->register_static();
1297
+
1298
+ wp_enqueue_media();
1299
+ wp_enqueue_style( 'fw-backend-options' );
1300
+ wp_enqueue_script( 'fw-backend-options' );
1301
+ }
1302
+
1303
+ $collected = array();
1304
+
1305
+ fw_collect_options( $collected, $options, array(
1306
+ 'limit_option_types' => false,
1307
+ 'limit_container_types' => false,
1308
+ 'limit_level' => 1,
1309
+ 'info_wrapper' => true,
1310
+ ) );
1311
+
1312
+ if ( empty( $collected ) ) {
1313
+ return false;
1314
+ }
1315
+
1316
+ $html = '';
1317
+
1318
+ $option = reset( $collected );
1319
+
1320
+ $collected_type = array(
1321
+ 'group' => $option['group'],
1322
+ 'type' => $option['option']['type'],
1323
+ );
1324
+ $collected_type_options = array(
1325
+ $option['id'] => &$option['option']
1326
+ );
1327
+
1328
+ while ( $collected_type_options ) {
1329
+ $option = next( $collected );
1330
+
1331
+ if ( $option ) {
1332
+ if (
1333
+ $option['group'] === $collected_type['group']
1334
+ &&
1335
+ $option['option']['type'] === $collected_type['type']
1336
+ ) {
1337
+ $collected_type_options[ $option['id'] ] = &$option['option'];
1338
+ continue;
1339
+ }
1340
+ }
1341
+
1342
+ switch ( $collected_type['group'] ) {
1343
+ case 'container':
1344
+ if ($design === 'taxonomy') {
1345
+ $html .= fw_render_view(
1346
+ fw_get_framework_directory('/views/backend-container-design-'. $design .'.php'),
1347
+ array(
1348
+ 'type' => $collected_type['type'],
1349
+ 'html' => $this->container_type($collected_type['type'])->render(
1350
+ $collected_type_options, $values, $options_data
1351
+ ),
1352
+ )
1353
+ );
1354
+ } else {
1355
+ $html .= $this->container_type($collected_type['type'])->render(
1356
+ $collected_type_options, $values, $options_data
1357
+ );
1358
+ }
1359
+ break;
1360
+ case 'option':
1361
+ foreach ( $collected_type_options as $id => &$_option ) {
1362
+ $data = $options_data; // do not change directly to not affect next loops
1363
+
1364
+ $maybe_future_value = apply_filters(
1365
+ 'fw:render_options:option_value',
1366
+ null,
1367
+ $values,
1368
+ $_option,
1369
+ $id
1370
+ );
1371
+
1372
+ if (! $maybe_future_value) {
1373
+ $maybe_future_value = isset( $values[ $id ] ) ? $values[ $id ] : null;
1374
+ }
1375
+
1376
+ $data['value'] = $maybe_future_value;
1377
+
1378
+ $html .= $this->render_option(
1379
+ $id,
1380
+ $_option,
1381
+ $data,
1382
+ $design
1383
+ );
1384
+ }
1385
+ unset($_option);
1386
+ break;
1387
+ default:
1388
+ $html .= '<p><em>' . __( 'Unknown collected group', 'fw' ) . ': ' . $collected_type['group'] . '</em></p>';
1389
+ }
1390
+
1391
+ unset( $collected_type, $collected_type_options );
1392
+
1393
+ if ( $option ) {
1394
+ $collected_type = array(
1395
+ 'group' => $option['group'],
1396
+ 'type' => $option['option']['type'],
1397
+ );
1398
+ $collected_type_options = array(
1399
+ $option['id'] => &$option['option']
1400
+ );
1401
+ } else {
1402
+ $collected_type_options = array();
1403
+ }
1404
+ }
1405
+
1406
+ return $html;
1407
+ }
1408
+
1409
+ /**
1410
+ * Enqueue options static
1411
+ *
1412
+ * Useful when you have dynamic options html on the page (for e.g. options modal)
1413
+ * and in order to initialize that html properly, the option types scripts styles must be enqueued on the page
1414
+ *
1415
+ * @param array $options
1416
+ */
1417
+ public function enqueue_options_static( $options ) {
1418
+ static $static_enqueue = true;
1419
+
1420
+ if (
1421
+ !doing_action('admin_enqueue_scripts')
1422
+ &&
1423
+ !did_action('admin_enqueue_scripts')
1424
+ ) {
1425
+ /**
1426
+ * Do not wp_enqueue/register_...() because at this point not all handles has been registered
1427
+ * and maybe they are used in dependencies in handles that are going to be enqueued.
1428
+ * So as a result some handles will not be equeued because of not registered dependecies.
1429
+ */
1430
+ return;
1431
+ } else {
1432
+ /**
1433
+ * register scripts and styles
1434
+ * in case if this method is called before enqueue_scripts action
1435
+ * and option types has some of these in their dependencies
1436
+ */
1437
+ if ($static_enqueue) {
1438
+ $this->register_static();
1439
+
1440
+ wp_enqueue_media();
1441
+ wp_enqueue_style( 'fw-backend-options' );
1442
+ wp_enqueue_script( 'fw-backend-options' );
1443
+
1444
+ $static_enqueue = false;
1445
+ }
1446
+ }
1447
+
1448
+ $collected = array();
1449
+
1450
+ fw_collect_options( $collected, $options, array(
1451
+ 'limit_option_types' => false,
1452
+ 'limit_container_types' => false,
1453
+ 'limit_level' => 0,
1454
+ 'callback' => array(__CLASS__, '_callback_fw_collect_options_enqueue_static'),
1455
+ ) );
1456
+
1457
+ unset($collected);
1458
+ }
1459
+
1460
+ /**
1461
+ * @internal
1462
+ * @param array $data
1463
+ */
1464
+ public static function _callback_fw_collect_options_enqueue_static($data) {
1465
+ if ($data['group'] === 'option') {
1466
+ fw()->backend->option_type($data['option']['type'])->enqueue_static($data['id'], $data['option']);
1467
+ } elseif ($data['group'] === 'container') {
1468
+ fw()->backend->container_type($data['option']['type'])->enqueue_static($data['id'], $data['option']);
1469
+ }
1470
+ }
1471
+
1472
+ /**
1473
+ * Render option enclosed in backend design
1474
+ *
1475
+ * @param string $id
1476
+ * @param array $option
1477
+ * @param array $data
1478
+ * @param string $design default or taxonomy
1479
+ *
1480
+ * @return string
1481
+ */
1482
+ public function render_option( $id, $option, $data = array(), $design = null ) {
1483
+
1484
+ $maybe_forced_design = fw()->backend->option_type( $option['type'] )->get_forced_render_design();
1485
+
1486
+ if (empty($design)) {
1487
+ $design = $this->default_render_design;
1488
+ }
1489
+
1490
+ if ($maybe_forced_design) {
1491
+ $design = $maybe_forced_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
+ array( $this, 'render_meta_box' ),
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
+ }
 
 
 
 
 
 
 
 
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,688 +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
-
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
- }
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,336 +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
- '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
-
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,3688 +1,3688 @@
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
- if (! apply_filters('fw_backend_enable_custom_extensions_menu', true)) {
750
- return;
751
- }
752
-
753
- /**
754
- * Use this action if you what to add the extensions page in a custom place in menu
755
- * Usage example http://pastebin.com/2iWVRPAU
756
- */
757
- do_action('fw_backend_add_custom_extensions_menu', $data);
758
-
759
- /**
760
- * Check if menu was added in the action above
761
- */
762
- {
763
- $menu_exists = false;
764
-
765
- if (!empty($_registered_pages)) {
766
- foreach ( $_registered_pages as $hookname => $b ) {
767
- if (isset($found_hooknames[$hookname])) {
768
- continue;
769
- }
770
-
771
- if ( strpos( $hookname, $data['slug'] ) !== false ) {
772
- $menu_exists = true;
773
- break;
774
- }
775
- }
776
- }
777
- }
778
-
779
- if ($menu_exists) {
780
- // do nothing
781
- } else {
782
- add_menu_page(
783
- $data['title'],
784
- $data['title'],
785
- $data['capability'],
786
- $data['slug'],
787
- $data['content_callback'],
788
- 'none',
789
- 3
790
- );
791
- }
792
- }
793
-
794
- /**
795
- * If output already started, we cannot set the redirect header, do redirect from js
796
- */
797
- private function js_redirect()
798
- {
799
- echo
800
- '<script type="text/javascript">'.
801
- 'window.location.replace("'. esc_js($this->get_link()) .'");'.
802
- '</script>';
803
- }
804
-
805
- /**
806
- * @internal
807
- */
808
- public function _display_page()
809
- {
810
- $page = FW_Request::GET('sub-page');
811
-
812
- switch ($page) {
813
- case 'install':
814
- $this->display_install_page();
815
- break;
816
- case 'delete':
817
- $this->display_delete_page();
818
- break;
819
- case 'extension':
820
- $this->display_extension_page();
821
- break;
822
- case 'activate':
823
- $this->display_activate_page();
824
- break;
825
- case 'deactivate':
826
- $this->display_deactivate_page();
827
- break;
828
- default:
829
- $this->display_list_page();
830
- }
831
- }
832
-
833
- private function display_list_page()
834
- {
835
- // note: static is enqueued in 'admin_enqueue_scripts' action
836
-
837
- /** Prepare extensions list for view */
838
- {
839
- $lists = array(
840
- 'active' => array(),
841
- 'disabled' => array(),
842
- 'installed' => array(),
843
- 'available' => array(),
844
- 'supported' => array(),
845
- );
846
-
847
- foreach ($this->get_installed_extensions() as $ext_name => $ext_data) {
848
- $lists[ $ext_data['active'] ? 'active' : 'disabled' ][$ext_name] = $ext_data;
849
- }
850
-
851
- $lists['installed'] = $lists['active'] + $lists['disabled'];
852
-
853
- unset($ext_data); // prevent change by reference
854
-
855
- foreach ($this->get_available_extensions() as $ext_name => $ext_data) {
856
- $lists['available'][$ext_name] = array(
857
- 'name' => $ext_data['name'],
858
- 'description' => $ext_data['description'],
859
- 'thumbnail' => isset($ext_data['thumbnail'])
860
- ? $ext_data['thumbnail']
861
- : (isset($lists['installed'][$ext_name])
862
- ? fw_akg('thumbnail', $lists['installed'][$ext_name]['manifest'], $this->default_thumbnail)
863
- : $this->default_thumbnail),
864
- 'display' => isset($ext_data['display'])
865
- ? $ext_data['display']
866
- : $this->manifest_default_values['display'],
867
- 'theme' => isset($ext_data['theme']) && $ext_data['theme'],
868
- 'download' => isset( $ext_data['download'] ) ? $ext_data['download'] : array()
869
- );
870
-
871
- if ($lists['available'][$ext_name]['theme']) {
872
- $lists['supported'][$ext_name] = array(
873
- 'name' => $lists['available'][$ext_name]['name'],
874
- 'description' => $lists['available'][$ext_name]['description'],
875
- );
876
- }
877
- }
878
-
879
- foreach (fw()->theme->manifest->get('supported_extensions', array()) as $required_ext_name => $required_ext_data) {
880
- if (isset($lists['installed'][ $required_ext_name ])) {
881
- $lists['supported'][ $required_ext_name ] = array(
882
- 'name' => fw_akg( 'name', $lists['installed'][ $required_ext_name ]['manifest'], fw_id_to_title( $required_ext_name ) ),
883
- 'description' => fw_akg( 'description', $lists['installed'][ $required_ext_name ]['manifest'], '' ),
884
- );
885
- } elseif (isset($lists['available'][$required_ext_name])) {
886
- $lists['supported'][ $required_ext_name ] = array(
887
- 'name' => $lists['available'][ $required_ext_name ]['name'],
888
- 'description' => $lists['available'][ $required_ext_name ]['description'],
889
- );
890
- } else {
891
- $lists['supported'][ $required_ext_name ] = array(
892
- 'name' => fw_id_to_title( $required_ext_name ),
893
- 'description' => '',
894
- );
895
- }
896
- }
897
- }
898
-
899
- echo '<div class="wrap">';
900
-
901
- echo '<h2>'. sprintf(__('%s Extensions', 'fw'), fw()->manifest->get_name()) .'</h2><br/>';
902
-
903
- echo '<div id="fw-extensions-list-wrapper">';
904
-
905
- fw_render_view(dirname(__FILE__) .'/views/extensions-page.php', array(
906
- 'lists' => &$lists,
907
- 'link' => $this->get_link(),
908
- 'display_default_value' => $this->manifest_default_values['display'],
909
- 'default_thumbnail' => $this->default_thumbnail,
910
- 'nonces' => array(
911
- 'delete' => $this->get_nonce('delete'),
912
- 'install' => $this->get_nonce('install'),
913
- 'activate' => $this->get_nonce('activate'),
914
- 'deactivate' => $this->get_nonce('deactivate'),
915
- ),
916
- 'can_install' => $this->can_install(),
917
- ), false);
918
-
919
- echo '</div>';
920
-
921
- echo '</div>';
922
- }
923
-
924
- private function display_install_page()
925
- {
926
- $flash_id = 'fw_extensions_install';
927
-
928
- if (!$this->can_install()) {
929
- FW_Flash_Messages::add(
930
- $flash_id,
931
- __('You are not allowed to install extensions.', 'fw'),
932
- 'error'
933
- );
934
- $this->js_redirect();
935
- return;
936
- }
937
-
938
- if (array_key_exists('supported', $_GET)) {
939
- $supported = true;
940
- $extensions = array_fill_keys(
941
- array_keys($this->get_supported_extensions_for_install()),
942
- array()
943
- );
944
-
945
- if (empty($extensions)) {
946
- FW_Flash_Messages::add(
947
- $flash_id,
948
- __('All supported extensions are already installed.', 'fw'),
949
- 'info'
950
- );
951
- $this->js_redirect();
952
- return;
953
- }
954
- } else {
955
- $supported = false;
956
-
957
- $extensions = array_fill_keys(
958
- array_map( 'trim', explode( ',', FW_Request::GET( 'extension', '' ) )),
959
- array()
960
- );
961
-
962
- // activate already installed extensions
963
- $this->activate_extensions($extensions);
964
- }
965
-
966
- {
967
- $skin = new _FW_Extensions_Install_Upgrader_Skin(array(
968
- 'title' => $supported
969
- ? _n('Install Compatible Extension', 'Install Compatible Extensions', count($extensions), 'fw')
970
- : _n('Install Extension', 'Install Extensions', count($extensions), 'fw'),
971
- ));
972
- }
973
-
974
- $skin->header();
975
-
976
- do {
977
- $nonce = $this->get_nonce('install');
978
-
979
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
980
- if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
981
- $skin->error(__('Invalid nonce.', 'fw'));
982
- break;
983
- }
984
-
985
- if (!FW_WP_Filesystem::request_access(
986
- fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
987
- )) {
988
- break;
989
- }
990
-
991
- $install_result = $this->install_extensions($extensions, array('verbose' => $skin));
992
-
993
- if (is_wp_error($install_result)) {
994
- $skin->error($install_result);
995
- } elseif (is_array($install_result)) {
996
- $error = array();
997
-
998
- foreach ($install_result as $extension_name => $extension_result) {
999
- if (is_wp_error($extension_result)) {
1000
- $error[] = $extension_result->get_error_message();
1001
- }
1002
- }
1003
-
1004
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1005
-
1006
- $skin->error($error);
1007
- } elseif ($install_result === true) {
1008
- $skin->set_result(true);
1009
- }
1010
-
1011
- /** @var WP_Filesystem_Base $wp_filesystem */
1012
- global $wp_filesystem;
1013
-
1014
- $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
1015
-
1016
- if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
1017
- if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
1018
- $skin->error(
1019
- sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
1020
- );
1021
- }
1022
- }
1023
-
1024
- $skin->after(array(
1025
- 'extensions_page_link' => $this->get_link()
1026
- ));
1027
-
1028
- if ($supported && $install_result === true) {
1029
- /**
1030
- * @since 2.6.14
1031
- * Fixes https://github.com/ThemeFuse/Unyson/issues/2330
1032
- */
1033
- do_action( 'fw_after_supported_extensions_install_success' );
1034
- }
1035
- } else {
1036
- echo '<form method="post">';
1037
-
1038
- wp_nonce_field($nonce['action'], $nonce['name']);
1039
-
1040
- $extension_titles = array();
1041
- foreach ($extensions as $extension_name => $not_used_var) {
1042
- $extension_titles[$extension_name] = $this->get_extension_title($extension_name);
1043
- }
1044
-
1045
- fw_render_view(dirname(__FILE__) .'/views/install-form.php', array(
1046
- 'extension_titles' => $extension_titles,
1047
- 'list_page_link' => $this->get_link(),
1048
- 'supported' => $supported
1049
- ), false);
1050
-
1051
- echo '</form>';
1052
- }
1053
- } while(false);
1054
-
1055
- $skin->footer();
1056
- }
1057
-
1058
- /**
1059
- * Download (and activate) extensions
1060
- * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1061
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1062
- * @param array $opts
1063
- * @return WP_Error|bool|array
1064
- * true: when all extensions succeeded
1065
- * array: when some/all failed
1066
- */
1067
- public function install_extensions( array $extensions, $opts = array() ) {
1068
- {
1069
- $opts = array_merge( array(
1070
- /**
1071
- * @type bool
1072
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1073
- * true: return first WP_Error or true on success
1074
- */
1075
- 'cancel_on_error' => false,
1076
- /**
1077
- * @type bool Activate installed extensions
1078
- */
1079
- 'activate' => true,
1080
- /**
1081
- * @type bool|WP_Upgrader_Skin
1082
- */
1083
- 'verbose' => false,
1084
- ), $opts );
1085
-
1086
- $cancel_on_error = $opts['cancel_on_error']; // fixme: remove successfully installed extensions before error?
1087
- $activate = $opts['activate'];
1088
- $verbose = $opts['verbose'];
1089
-
1090
- unset( $opts );
1091
- }
1092
-
1093
- if ( ! $this->can_install() ) {
1094
- return new WP_Error(
1095
- 'access_denied',
1096
- __( 'You have no permissions to install extensions', 'fw' )
1097
- );
1098
- }
1099
-
1100
- if ( empty( $extensions ) ) {
1101
- return new WP_Error(
1102
- 'no_extensions',
1103
- __( 'No extensions provided', 'fw' )
1104
- );
1105
- }
1106
-
1107
- if ( ! FW_WP_Filesystem::is_ready() ) {
1108
- return new WP_Error( 'fs_not_initialized', esc_html__( 'WP Filesystem is not initialized', 'fw' ) );
1109
- }
1110
-
1111
- $timeout = function_exists( 'ini_get' ) ? intval( ini_get( 'max_execution_time' ) ) : false;
1112
- $available_extensions = $this->get_available_extensions();
1113
- $installed_extensions = $this->get_installed_extensions();
1114
- $result = $downloaded_extensions = array();
1115
- $has_errors = false;
1116
-
1117
- while ( ! empty( $extensions ) ) {
1118
- reset( $extensions );
1119
- $extension_name = key( $extensions );
1120
- unset( $extensions[ $extension_name ] );
1121
-
1122
- $extensions_before_install = array_keys( $installed_extensions );
1123
-
1124
- if ( isset( $installed_extensions[ $extension_name ] ) ) {
1125
- $result[ $extension_name ] = new WP_Error(
1126
- 'extension_installed',
1127
- sprintf( __( 'Extension "%s" is already installed.', 'fw' ), $this->get_extension_title( $extension_name ) )
1128
- );
1129
- $has_errors = true;
1130
-
1131
- if ( $cancel_on_error ) {
1132
- break;
1133
- }
1134
- }
1135
-
1136
- if ( ! isset( $available_extensions[ $extension_name ] ) ) {
1137
- $result[ $extension_name ] = new WP_Error(
1138
- 'extension_not_available',
1139
- sprintf(
1140
- __( 'Extension "%s" is not available for install.', 'fw' ),
1141
- $this->get_extension_title( $extension_name )
1142
- )
1143
- );
1144
- $has_errors = true;
1145
-
1146
- if ( $cancel_on_error ) {
1147
- break;
1148
- }
1149
- }
1150
-
1151
- /**
1152
- * Find parent extensions
1153
- * they will be installed if does not exist
1154
- */
1155
- {
1156
- $parents = array( $extension_name );
1157
-
1158
- $current_parent = $extension_name;
1159
- while ( ! empty( $available_extensions[ $current_parent ]['parent'] ) ) {
1160
- $current_parent = $available_extensions[ $current_parent ]['parent'];
1161
-
1162
- if ( ! isset( $available_extensions[ $current_parent ] ) ) {
1163
- $result[ $extension_name ] = new WP_Error(
1164
- 'parent_extension_not_available',
1165
- sprintf(
1166
- __( 'Parent extension "%s" not available.', 'fw' ),
1167
- $this->get_extension_title( $current_parent )
1168
- )
1169
- );
1170
- $has_errors = true;
1171
-
1172
- if ( $cancel_on_error ) {
1173
- break 2;
1174
- } else {
1175
- continue 2;
1176
- }
1177
- }
1178
-
1179
- $parents[] = $current_parent;
1180
- }
1181
-
1182
- $parents = array_reverse( $parents );
1183
- }
1184
-
1185
- /**
1186
- * Install parent extensions and the extension
1187
- */
1188
- {
1189
- $destination_path = array(
1190
- 'framework' => fw_get_framework_directory(),
1191
- 'theme' => fw_fix_path( get_template_directory() ) . fw_get_framework_customizations_dir_rel_path(),
1192
- );
1193
- $current_extension_path = '';
1194
-
1195
- foreach ( $parents as $parent_extension_name ) {
1196
- $current_extension_path .= '/extensions/' . $parent_extension_name;
1197
- $set = $available_extensions[ $parent_extension_name ];
1198
- $destination = isset( $set['theme'] ) && $set['theme'] ? 'theme' : 'framework';
1199
-
1200
- if ( isset( $installed_extensions[ $parent_extension_name ] ) ) {
1201
- continue; // skip already installed extensions
1202
- }
1203
-
1204
- if ( $verbose ) {
1205
- $verbose_message = sprintf( esc_html__( 'Downloading the "%s" extension...', 'fw' ), $this->get_extension_title( $parent_extension_name ) );
1206
-
1207
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1208
- $verbose->feedback( $verbose_message );
1209
- } else {
1210
- echo fw_html_tag( 'p', array(), $verbose_message );
1211
- }
1212
- }
1213
-
1214
- // increase timeout
1215
- if ( $timeout !== false && function_exists( 'set_time_limit' ) ) {
1216
- $timeout += 30;
1217
- set_time_limit( $timeout );
1218
- }
1219
-
1220
- // If is plugin will returned downloadable link zip.
1221
- $wp_fw_downloaded_dir = $this->download( $parent_extension_name, $set );
1222
-
1223
- if ( is_wp_error( $wp_fw_downloaded_dir ) ) {
1224
- if ( $verbose ) {
1225
- $verbose_message = $wp_fw_downloaded_dir->get_error_message();
1226
-
1227
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1228
- $verbose->error( $verbose_message );
1229
- } else {
1230
- echo fw_html_tag( 'p', array(), $verbose_message );
1231
- }
1232
- }
1233
-
1234
- $result[ $extension_name ] = $wp_fw_downloaded_dir;
1235
- $has_errors = true;
1236
-
1237
- if ( $cancel_on_error ) {
1238
- break 2;
1239
- } else {
1240
- continue 2;
1241
- }
1242
- }
1243
-
1244
- if ( $verbose ) {
1245
- $verbose_message = sprintf( esc_html__( 'Installing the "%s" extension...', 'fw' ), $this->get_extension_title( $parent_extension_name ) );
1246
-
1247
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1248
- $verbose->feedback( $verbose_message );
1249
- } else {
1250
- echo fw_html_tag( 'p', array(), $verbose_message );
1251
- }
1252
- }
1253
-
1254
- // Merge directories only for extensions. If we have plugin it will installed via Plugin_Upgrader.
1255
- if ( empty( $set['download']['opts']['plugin'] ) ) {
1256
- $merge_result = $this->merge_extension(
1257
- $wp_fw_downloaded_dir,
1258
- FW_WP_Filesystem::real_path_to_filesystem_path( $destination_path[ $destination ] . $current_extension_path )
1259
- );
1260
-
1261
- if ( is_wp_error( $merge_result ) ) {
1262
- if ( $verbose ) {
1263
- $verbose_message = $merge_result->get_error_message();
1264
-
1265
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1266
- $verbose->error( $verbose_message );
1267
- } else {
1268
- echo fw_html_tag( 'p', array(), $verbose_message );
1269
- }
1270
- }
1271
-
1272
- $result[ $extension_name ] = $merge_result;
1273
- $has_errors = true;
1274
-
1275
- if ( $cancel_on_error ) {
1276
- break 2;
1277
- } else {
1278
- continue 2;
1279
- }
1280
- }
1281
- }
1282
-
1283
- if ( $verbose ) {
1284
- $verbose_message = sprintf( __( 'The %s extension has been successfully installed.', 'fw' ),
1285
- $this->get_extension_title( $parent_extension_name )
1286
- );
1287
-
1288
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1289
- $verbose->feedback( $verbose_message );
1290
- } else {
1291
- echo fw_html_tag( 'p', array(), $verbose_message );
1292
- }
1293
- }
1294
-
1295
- $downloaded_extensions[ $parent_extension_name ] = array();
1296
-
1297
- /**
1298
- * Read again all extensions
1299
- * The downloaded extension may contain more sub extensions
1300
- */
1301
- {
1302
- unset( $installed_extensions );
1303
- $installed_extensions = $this->get_installed_extensions( true );
1304
- }
1305
- }
1306
- }
1307
-
1308
- $result[ $extension_name ] = true;
1309
-
1310
- /**
1311
- * Collect required extensions of the newly installed extensions
1312
- */
1313
- foreach (
1314
- // new extensions
1315
- array_diff(
1316
- array_keys( $installed_extensions ),
1317
- $extensions_before_install
1318
- )
1319
- as $new_extension_name
1320
- ) {
1321
- foreach (
1322
- array_keys(
1323
- fw_akg(
1324
- 'requirements/extensions',
1325
- $installed_extensions[ $new_extension_name ]['manifest'],
1326
- array()
1327
- )
1328
- )
1329
- as $required_extension_name
1330
- ) {
1331
- if ( isset( $installed_extensions[ $required_extension_name ] ) ) {
1332
- // already installed
1333
- continue;
1334
- }
1335
-
1336
- $extensions[ $required_extension_name ] = array();
1337
- }
1338
- }
1339
- }
1340
-
1341
- if ( $activate ) {
1342
- $activate_extensions = array();
1343
-
1344
- foreach ( $result as $extension_name => $extension_result ) {
1345
- if ( ! is_wp_error( $extension_result ) ) {
1346
- $activate_extensions[ $extension_name ] = array();
1347
- }
1348
- }
1349
-
1350
- if ( ! empty( $activate_extensions ) ) {
1351
- if ( $verbose ) {
1352
- $verbose_message = _n(
1353
- 'Activating extension...',
1354
- 'Activating extensions...',
1355
- count( $activate_extensions ),
1356
- 'fw'
1357
- );
1358
-
1359
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1360
- $verbose->feedback( $verbose_message );
1361
- } else {
1362
- echo fw_html_tag( 'p', array(), $verbose_message );
1363
- }
1364
- }
1365
-
1366
- $activation_result = $this->activate_extensions( $activate_extensions );
1367
-
1368
- if ( $verbose ) {
1369
- if ( is_wp_error( $activation_result ) ) {
1370
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1371
- $verbose->error( $activation_result->get_error_message() );
1372
- } else {
1373
- echo fw_html_tag( 'p', array(), $activation_result->get_error_message() );
1374
- }
1375
- } elseif ( is_array( $activation_result ) ) {
1376
- $verbose_message = array();
1377
-
1378
- foreach ( $activation_result as $extension_name => $extension_result ) {
1379
- if ( is_wp_error( $extension_result ) ) {
1380
- $verbose_message[] = $extension_result->get_error_message();
1381
- }
1382
- }
1383
-
1384
- $verbose_message = '<ul><li>' . implode( '</li><li>', $verbose_message ) . '</li></ul>';
1385
-
1386
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1387
- $verbose->error( $verbose_message );
1388
- } else {
1389
- echo fw_html_tag( 'p', array(), $verbose_message );
1390
- }
1391
- } elseif ( $activation_result === true ) {
1392
- $verbose_message = _n(
1393
- 'Extension has been successfully activated.',
1394
- 'Extensions has been successfully activated.',
1395
- count( $activate_extensions ),
1396
- 'fw'
1397
- );
1398
-
1399
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1400
- $verbose->feedback( $verbose_message );
1401
- } else {
1402
- echo fw_html_tag( 'p', array(), $verbose_message );
1403
- }
1404
- }
1405
- }
1406
- }
1407
- }
1408
-
1409
- do_action( 'fw_extensions_install', $result );
1410
-
1411
- if ( $cancel_on_error && $has_errors ) {
1412
- if ( ( $last_result = end( $result ) ) && is_wp_error( $last_result ) ) {
1413
- return $last_result;
1414
- } else {
1415
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
1416
- return new WP_Error(
1417
- 'installation_failed',
1418
- _n( 'Cannot install extension', 'Cannot install extensions', count( $extensions ), 'fw' )
1419
- );
1420
- }
1421
- }
1422
-
1423
- return $has_errors ? $result : true;
1424
- }
1425
-
1426
- private function display_delete_page()
1427
- {
1428
- $flash_id = 'fw_extensions_delete';
1429
-
1430
- if (!$this->can_install()) {
1431
- FW_Flash_Messages::add(
1432
- $flash_id,
1433
- __('You are not allowed to delete extensions.', 'fw'),
1434
- 'error'
1435
- );
1436
- $this->js_redirect();
1437
- return;
1438
- }
1439
-
1440
- $extensions = array_fill_keys(array_map('trim', explode(',', FW_Request::GET('extension', ''))), array());
1441
-
1442
- {
1443
- $skin = new _FW_Extensions_Delete_Upgrader_Skin(array(
1444
- 'title' => _n('Delete Extension', 'Delete Extensions', count($extensions), 'fw'),
1445
- ));
1446
- }
1447
-
1448
- $skin->header();
1449
-
1450
- do {
1451
- $nonce = $this->get_nonce('delete');
1452
-
1453
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1454
- if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1455
- $skin->error(__('Invalid nonce.', 'fw'));
1456
- break;
1457
- }
1458
-
1459
- if (!FW_WP_Filesystem::request_access(
1460
- fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
1461
- )) {
1462
- break;
1463
- }
1464
-
1465
- $uninstall_result = $this->uninstall_extensions($extensions, array('verbose' => $skin));
1466
-
1467
- if (is_wp_error($uninstall_result)) {
1468
- $skin->error($uninstall_result);
1469
- } elseif (is_array($uninstall_result)) {
1470
- $error = array();
1471
-
1472
- foreach ($uninstall_result as $extension_name => $extension_result) {
1473
- if (is_wp_error($extension_result)) {
1474
- $error[] = $extension_result->get_error_message();
1475
- }
1476
- }
1477
-
1478
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1479
-
1480
- $skin->error($error);
1481
- } elseif ($uninstall_result === true) {
1482
- $skin->set_result(true);
1483
- }
1484
-
1485
- $skin->after(array(
1486
- 'extensions_page_link' => $this->get_link()
1487
- ));
1488
- } else {
1489
- echo '<form method="post">';
1490
-
1491
- wp_nonce_field($nonce['action'], $nonce['name']);
1492
-
1493
- fw_render_view(dirname(__FILE__) .'/views/delete-form.php', array(
1494
- 'extension_names' => array_keys($extensions),
1495
- 'installed_extensions' => $this->get_installed_extensions(),
1496
- 'list_page_link' => $this->get_link(),
1497
- ), false);
1498
-
1499
- echo '</form>';
1500
- }
1501
- } while(false);
1502
-
1503
- $skin->footer();
1504
- }
1505
-
1506
- /**
1507
- * Remove extensions
1508
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1509
- * @param array $opts
1510
- * @return WP_Error|bool|array
1511
- * true: when all extensions succeeded
1512
- * array: when some/all failed
1513
- */
1514
- public function uninstall_extensions(array $extensions, $opts = array())
1515
- {
1516
- {
1517
- $opts = array_merge(array(
1518
- /**
1519
- * @type bool
1520
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1521
- * true: return first WP_Error or true on success
1522
- */
1523
- 'cancel_on_error' => false,
1524
- /**
1525
- * @type bool|WP_Upgrader_Skin
1526
- */
1527
- 'verbose' => false,
1528
- ), $opts);
1529
-
1530
- $cancel_on_error = $opts['cancel_on_error']; // fixme: install back successfully removed extensions before error?
1531
- $verbose = $opts['verbose'];
1532
-
1533
- unset($opts);
1534
- }
1535
-
1536
- if (!$this->can_install()) {
1537
- return new WP_Error(
1538
- 'access_denied',
1539
- __('You have no permissions to uninstall extensions', 'fw')
1540
- );
1541
- }
1542
-
1543
- if (empty($extensions)) {
1544
- return new WP_Error(
1545
- 'no_extensions',
1546
- __('No extensions provided', 'fw')
1547
- );
1548
- }
1549
-
1550
- /** @var WP_Filesystem_Base $wp_filesystem */
1551
- global $wp_filesystem;
1552
-
1553
- if (!FW_WP_Filesystem::is_ready()) {
1554
- return new WP_Error(
1555
- 'fs_not_initialized',
1556
- __('WP Filesystem is not initialized', 'fw')
1557
- );
1558
- }
1559
-
1560
- $installed_extensions = $this->get_installed_extensions();
1561
- $available_extensions = $this->get_available_extensions();
1562
- $extensions_before_uninstall = array_fill_keys( array_keys( $installed_extensions ), array() );
1563
-
1564
- $result = $uninstalled_extensions = array();
1565
- $has_errors = false;
1566
-
1567
- while ( ! empty( $extensions ) ) {
1568
-
1569
- reset( $extensions );
1570
- $extension_name = key( $extensions );
1571
- unset( $extensions[ $extension_name ] );
1572
-
1573
- if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
1574
- $unistall = delete_plugins( (array) $available_extensions[ $extension_name ]['download']['opts']['plugin'] );
1575
- $plugin_title = $available_extensions[ $extension_name ]['name'];
1576
-
1577
- if ( $unistall ) {
1578
- $this->verbose( sprintf( esc_html__( 'Extension "%s" has been deleted.', 'fw' ), $plugin_title ), $verbose );
1579
- $result[ $extension_name ] = true;
1580
- } else {
1581
- if ( is_wp_error( $unistall ) ) {
1582
- $msg_error = $unistall->get_error_message() . ' - ' . $plugin_title;
1583
- } else {
1584
- $msg_error = sprintf( esc_html__( 'Plugin %s is empty.' ), $plugin_title );
1585
- }
1586
-
1587
- $result[ $extension_name ] = new WP_Error( 'fw_delete_plugins', $msg_error, $plugin_title );
1588
- $has_errors = true;
1589
-
1590
- if ( $cancel_on_error ) {
1591
- break;
1592
- } else {
1593
- continue;
1594
- }
1595
- }
1596
-
1597
- continue;
1598
- }
1599
-
1600
- $extension_title = $this->get_extension_title($extension_name);
1601
-
1602
- if (!isset($installed_extensions[ $extension_name ])) {
1603
- // already deleted
1604
- $result[$extension_name] = true;
1605
- continue;
1606
- }
1607
-
1608
- if (
1609
- !isset($installed_extensions[ $extension_name ]['path'])
1610
- ||
1611
- empty($installed_extensions[ $extension_name ]['path'])
1612
- ) {
1613
- /**
1614
- * This happens sometimes, but I don't know why
1615
- * If the script will continue, it will delete the root folder
1616
- */
1617
- fw_print(
1618
- 'Please report this to https://github.com/ThemeFuse/Unyson/issues',
1619
- $extension_name,
1620
- $installed_extensions
1621
- );
1622
- die;
1623
- }
1624
-
1625
- $wp_fs_extension_path = FW_WP_Filesystem::real_path_to_filesystem_path(
1626
- $installed_extensions[ $extension_name ]['path']
1627
- );
1628
-
1629
- if (!$wp_filesystem->exists($wp_fs_extension_path)) {
1630
- // already deleted, maybe because it was a sub-extension of an deleted extension
1631
- $result[$extension_name] = true;
1632
- continue;
1633
- }
1634
-
1635
- $this->verbose( sprintf( esc_html__( 'Deleting the "%s" extension...', 'fw' ), $extension_title ), $verbose );
1636
-
1637
- if (!$wp_filesystem->delete($wp_fs_extension_path, true, 'd')) {
1638
- $result[$extension_name] = new WP_Error(
1639
- 'cannot_delete_directory',
1640
- sprintf(__('Cannot delete the "%s" extension.', 'fw'), $extension_title)
1641
- );
1642
- $has_errors = true;
1643
-
1644
- if ($cancel_on_error) {
1645
- break;
1646
- } else {
1647
- continue;
1648
- }
1649
- } else {
1650
- $this->verbose( sprintf( esc_html__( 'The %s extension has been successfully deleted.', 'fw' ), $extension_title ), $verbose );
1651
- $result[$extension_name] = true;
1652
- }
1653
-
1654
- /**
1655
- * Read again all extensions
1656
- * The delete extension may contain more sub extensions
1657
- */
1658
- {
1659
- unset($installed_extensions);
1660
- $installed_extensions = $this->get_installed_extensions(true);
1661
- }
1662
-
1663
- /**
1664
- * Add for deletion not used extensions
1665
- * For e.g. standalone=false extension that were required by the deleted extension
1666
- * and now are not required by any other extension
1667
- */
1668
- {
1669
- $not_used_extensions = array_fill_keys(
1670
- array_keys(
1671
- array_diff_key(
1672
- $installed_extensions,
1673
- $this->get_used_extensions($extensions, array_keys($installed_extensions))
1674
- )
1675
- ),
1676
- array()
1677
- );
1678
-
1679
- $extensions = array_merge($extensions, $not_used_extensions);
1680
- }
1681
- }
1682
-
1683
- do_action('fw_extensions_uninstall', $result);
1684
-
1685
- if (
1686
- $cancel_on_error
1687
- &&
1688
- $has_errors
1689
- ) {
1690
- if (
1691
- ($last_result = end($result))
1692
- &&
1693
- is_wp_error($last_result)
1694
- ) {
1695
- return $last_result;
1696
- } else {
1697
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
1698
- return new WP_Error(
1699
- 'uninstall_failed',
1700
- _n('Cannot uninstall extension', 'Cannot uninstall extensions', count($extensions), 'fw')
1701
- );
1702
- }
1703
- }
1704
-
1705
- // remove from active list the deleted extensions
1706
- {
1707
- update_option(
1708
- fw()->extensions->_get_active_extensions_db_option_name(),
1709
- array_diff_key(
1710
- fw()->extensions->_get_db_active_extensions(),
1711
- array_diff_key(
1712
- $extensions_before_uninstall,
1713
- $installed_extensions
1714
- )
1715
- )
1716
- );
1717
- }
1718
-
1719
- if ($has_errors) {
1720
- return $result;
1721
- } else {
1722
- return true;
1723
- }
1724
- }
1725
-
1726
- public function verbose( $msg, &$verbose ) {
1727
-
1728
- if ( ! $verbose ) {
1729
- return;
1730
- }
1731
-
1732
- if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1733
- $verbose->feedback( $msg );
1734
- } else {
1735
- echo fw_html_tag( 'p', array(), $msg );
1736
- }
1737
- }
1738
-
1739
- private function display_extension_page()
1740
- {
1741
- // note: static is enqueued in 'admin_enqueue_scripts' action
1742
-
1743
- $extension_name = trim(FW_Request::GET('extension', ''));
1744
-
1745
- $installed_extensions = $this->get_installed_extensions();
1746
-
1747
- $flash_id = 'fw_extension_page';
1748
-
1749
- {
1750
- $error = '';
1751
-
1752
- do {
1753
- if (empty($extension_name)) {
1754
- $error = __('Extension not specified.', 'fw');
1755
- break;
1756
- }
1757
-
1758
- if (!isset($installed_extensions[$extension_name])) {
1759
- $error = sprintf(__('Extension "%s" is not installed.', 'fw'), $this->get_extension_title($extension_name));
1760
- break;
1761
- }
1762
- } while(false);
1763
-
1764
- if ($error) {
1765
- FW_Flash_Messages::add($flash_id, $error, 'error');
1766
- $this->js_redirect();
1767
- return;
1768
- }
1769
- }
1770
-
1771
- {
1772
- $tab = fw_akg('tab', $_GET, 'settings');
1773
-
1774
- if (!in_array($tab, array('settings', 'docs'))) {
1775
- $tab = 'settings';
1776
- }
1777
- }
1778
-
1779
- $extension_title = $this->get_extension_title($extension_name);
1780
- $link = $this->get_link();
1781
-
1782
- echo '<div class="wrap" id="fw-extension-page">';
1783
-
1784
- fw_render_view(dirname(__FILE__) .'/views/extension-page-header.php', array(
1785
- 'extension_name' => $extension_name,
1786
- 'extension_data' => $installed_extensions[$extension_name],
1787
- 'link_delete' => $link .'&sub-page=delete',
1788
- 'link_extension' => $link .'&sub-page=extension',
1789
- 'extension_title' => $extension_title,
1790
- 'tab' => $tab,
1791
- 'is_supported' =>
1792
- fw()->theme->manifest->get('supported_extensions/'. $extension_name, false) !== false
1793
- ||
1794
- $installed_extensions[$extension_name]['is']['theme']
1795
- ), false);
1796
-
1797
- unset($installed_extensions);
1798
-
1799
- echo '<div id="fw-extension-tab-content">';
1800
- {
1801
- $method_data = array();
1802
-
1803
- switch ($tab) {
1804
- case 'settings':
1805
- $error = $this->display_extension_settings_page($extension_name, $method_data);
1806
- break;
1807
- case 'docs':
1808
- $error = $this->display_extension_docs_page($extension_name, $method_data);
1809
- break;
1810
- }
1811
- }
1812
- echo '</div>';
1813
-
1814
- echo '</div>';
1815
-
1816
- if ($error) {
1817
- FW_Flash_Messages::add($flash_id, $error, 'error');
1818
- $this->js_redirect();
1819
- return;
1820
- }
1821
- }
1822
-
1823
- private function display_extension_settings_page($extension_name, $data)
1824
- {
1825
- if (!fw()->extensions->get($extension_name)) {
1826
- return sprintf(
1827
- __('Extension "%s" does not exist or is not active.', 'fw'),
1828
- fw_htmlspecialchars($extension_name)
1829
- );
1830
- }
1831
-
1832
- $extension = fw()->extensions->get($extension_name);
1833
-
1834
- if (!$extension->get_settings_options()) {
1835
- return sprintf(
1836
- __('%s extension does not have settings.', 'fw'),
1837
- $extension->manifest->get_name()
1838
- );
1839
- }
1840
-
1841
- echo '<div id="fw-extension-settings">';
1842
-
1843
- echo $this->extension_settings_form->render(array(
1844
- 'extension' => $extension,
1845
- ));
1846
-
1847
- echo '</div>';
1848
- }
1849
-
1850
- private function display_extension_docs_page($extension_name, $data)
1851
- {
1852
- $ext = fw_ext($extension_name);
1853
- $docs = $ext->get_rendered_docs();
1854
-
1855
- if (! $docs) {
1856
- return __(
1857
- 'Extension has no documentation. Maybe ask its developer to write some?',
1858
- 'fw'
1859
- );
1860
- }
1861
-
1862
- echo fw()->backend->render_box(
1863
- 'fw-extension-docs',
1864
- '',
1865
- fw()->backend->render_options(array(
1866
- 'docs' => array(
1867
- 'label' => false,
1868
- 'type' => 'html-full',
1869
- 'html' => $docs
1870
- ),
1871
- ))
1872
- );
1873
- }
1874
-
1875
- private function display_activate_page()
1876
- {
1877
- $error = '';
1878
-
1879
- do {
1880
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
1881
- $error = __('Invalid request method.', 'fw');
1882
- break;
1883
- }
1884
-
1885
- $nonce = $this->get_nonce('activate');
1886
-
1887
- if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
1888
- $error = __('Invalid nonce.', 'fw');
1889
- break;
1890
- }
1891
-
1892
- if (!isset($_GET['extension'])) {
1893
- $error = __('No extension specified.', 'fw');
1894
- break;
1895
- }
1896
-
1897
- $activation_result = $this->activate_extensions(
1898
- array_fill_keys(explode(',', $_GET['extension']), array())
1899
- );
1900
-
1901
- if (is_wp_error($activation_result)) {
1902
- $error = $activation_result->get_error_message();
1903
- } elseif (is_array($activation_result)) {
1904
- $error = array();
1905
-
1906
- foreach ($activation_result as $extension_name => $extension_result) {
1907
- if (is_wp_error($extension_result)) {
1908
- $error[] = $extension_result->get_error_message();
1909
- }
1910
- }
1911
-
1912
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1913
- }
1914
- } while(false);
1915
-
1916
- if ($error) {
1917
- FW_Flash_Messages::add(
1918
- 'fw_extensions_activate_page',
1919
- $error,
1920
- 'error'
1921
- );
1922
- $this->js_redirect();
1923
- return;
1924
- }
1925
-
1926
- $this->js_redirect();
1927
- }
1928
-
1929
- /**
1930
- * Add extensions to active extensions list in database
1931
- * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1932
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1933
- * @param bool $cancel_on_error
1934
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1935
- * true: return first WP_Error or true on success
1936
- * @return WP_Error|bool|array
1937
- * true: when all extensions succeeded
1938
- * array: when some/all failed
1939
- */
1940
- public function activate_extensions(array $extensions, $cancel_on_error = false)
1941
- {
1942
- if (!$this->can_activate()) {
1943
- return new WP_Error(
1944
- 'access_denied',
1945
- __('You have no permissions to activate extensions', 'fw')
1946
- );
1947
- }
1948
-
1949
- if (empty($extensions)) {
1950
- return new WP_Error(
1951
- 'no_extensions',
1952
- __('No extensions provided', 'fw')
1953
- );
1954
- }
1955
-
1956
- $installed_extensions = $this->get_installed_extensions();
1957
- $available_extensions = $this->get_available_extensions();
1958
-
1959
- $result = $extensions_for_activation = array();
1960
- $has_errors = false;
1961
-
1962
- foreach ($extensions as $extension_name => $not_used_var) {
1963
-
1964
- if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
1965
-
1966
- $plugin_file = $available_extensions[ $extension_name ]['download']['opts']['plugin'];
1967
-
1968
- // A small financial support for maintaining the plugin.
1969
- if ( 'translatepress-multilingual/index.php' === $plugin_file ) {
1970
- update_option( 'translatepress_affiliate_id', 1 );
1971
- }
1972
-
1973
- activate_plugin( $plugin_file );
1974
-
1975
- continue;
1976
- }
1977
-
1978
- if (!isset($installed_extensions[$extension_name])) {
1979
- $result[$extension_name] = new WP_Error(
1980
- 'extension_not_installed',
1981
- sprintf(__('Extension "%s" does not exist.', 'fw'), $this->get_extension_title($extension_name))
1982
- );
1983
- $has_errors = true;
1984
-
1985
- if ($cancel_on_error) {
1986
- break;
1987
- } else {
1988
- continue;
1989
- }
1990
- }
1991
-
1992
- $collected = $this->get_extensions_for_activation($extension_name);
1993
-
1994
- if (is_wp_error($collected)) {
1995
- $result[$extension_name] = $collected;
1996
- $has_errors = true;
1997
-
1998
- if ($cancel_on_error) {
1999
- break;
2000
- } else {
2001
- continue;
2002
- }
2003
- }
2004
-
2005
- $extensions_for_activation = array_merge($extensions_for_activation, $collected);
2006
-
2007
- $result[$extension_name] = true;
2008
- }
2009
-
2010
- if (
2011
- $cancel_on_error
2012
- &&
2013
- $has_errors
2014
- ) {
2015
- if (
2016
- ($last_result = end($result))
2017
- &&
2018
- is_wp_error($last_result)
2019
- ) {
2020
- return $last_result;
2021
- } else {
2022
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
2023
- return new WP_Error(
2024
- 'activation_failed',
2025
- _n('Cannot activate extension', 'Cannot activate extensions', count($extensions), 'fw')
2026
- );
2027
- }
2028
- }
2029
-
2030
- update_option(
2031
- fw()->extensions->_get_active_extensions_db_option_name(),
2032
- array_merge(fw()->extensions->_get_db_active_extensions(), $extensions_for_activation)
2033
- );
2034
-
2035
- // remove already active extensions
2036
- foreach ($extensions_for_activation as $extension_name => $not_used_var) {
2037
- if (fw_ext($extension_name)) {
2038
- unset($extensions_for_activation[$extension_name]);
2039
- }
2040
- }
2041
-
2042
- /**
2043
- * Prepare db wp option used to fire the 'fw_extensions_after_activation' action on next refresh
2044
- */
2045
- {
2046
- $db_wp_option_name = 'fw_extensions_activation';
2047
- $db_wp_option_value = get_option($db_wp_option_name, array(
2048
- 'activated' => array(),
2049
- 'deactivated' => array(),
2050
- ));
2051
-
2052
- /**
2053
- * Keep adding to the existing value instead of resetting it on each method call
2054
- * in case the method will be called multiple times
2055
- */
2056
- $db_wp_option_value['activated'] = array_merge($db_wp_option_value['activated'], $extensions_for_activation);
2057
-
2058
- /**
2059
- * Remove activated extensions from deactivated
2060
- */
2061
- $db_wp_option_value['deactivated'] = array_diff_key($db_wp_option_value['deactivated'], $db_wp_option_value['activated']);
2062
-
2063
- update_option($db_wp_option_name, $db_wp_option_value, false);
2064
- }
2065
-
2066
- do_action('fw_extensions_before_activation', $extensions_for_activation);
2067
-
2068
- if ($has_errors) {
2069
- return $result;
2070
- } else {
2071
- return true;
2072
- }
2073
- }
2074
-
2075
- private function collect_sub_extensions($ext_name, &$installed_extensions)
2076
- {
2077
- $result = array();
2078
-
2079
- foreach ($installed_extensions[$ext_name]['children'] as $child_ext_name => $child_ext_data) {
2080
- $result[$child_ext_name] = array();
2081
-
2082
- $result += $this->collect_sub_extensions($child_ext_name, $installed_extensions);
2083
- }
2084
-
2085
- return $result;
2086
- }
2087
-
2088
- private function collect_required_extensions($ext_name, &$installed_extensions, &$collected)
2089
- {
2090
- if (!isset($installed_extensions[$ext_name])) {
2091
- return;
2092
- }
2093
-
2094
- foreach (fw_akg('requirements/extensions', $installed_extensions[$ext_name]['manifest'], array()) as $req_ext_name => $req_ext_data) {
2095
- if (isset($collected[$req_ext_name])) {
2096
- // prevent requirements recursion
2097
- continue;
2098
- }
2099
-
2100
- $collected[$req_ext_name] = array();
2101
-
2102
- $this->collect_required_extensions($req_ext_name, $installed_extensions, $collected);
2103
- }
2104
- }
2105
-
2106
- private function display_deactivate_page()
2107
- {
2108
- $installed_extensions = $this->get_installed_extensions();
2109
-
2110
- $error = '';
2111
-
2112
- do {
2113
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
2114
- $error = __('Invalid request method.', 'fw');
2115
- break;
2116
- }
2117
-
2118
- $nonce = $this->get_nonce('deactivate');
2119
-
2120
- if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
2121
- $error = __('Invalid nonce.', 'fw');
2122
- break;
2123
- }
2124
-
2125
- if (!isset($_GET['extension'])) {
2126
- $error = __('No extension specified.', 'fw');
2127
- break;
2128
- }
2129
-
2130
- $deactivation_result = $this->deactivate_extensions(
2131
- array_fill_keys(explode(',', $_GET['extension']), array())
2132
- );
2133
-
2134
- if (is_wp_error($deactivation_result)) {
2135
- $error = $deactivation_result->get_error_message();
2136
- } elseif (is_array($deactivation_result)) {
2137
- $error = array();
2138
-
2139
- foreach ($deactivation_result as $extension_name => $extension_result) {
2140
- if (is_wp_error($extension_result)) {
2141
- $error[] = $extension_result->get_error_message();
2142
- }
2143
- }
2144
-
2145
- $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
2146
- }
2147
- } while(false);
2148
-
2149
- if ($error) {
2150
- FW_Flash_Messages::add(
2151
- 'fw_extensions_activate_page',
2152
- $error,
2153
- 'error'
2154
- );
2155
- }
2156
-
2157
- $this->js_redirect();
2158
- }
2159
-
2160
- /**
2161
- * Remove extensions from active extensions list in database
2162
- * After refresh they will be inactive
2163
- * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
2164
- * @param bool $cancel_on_error
2165
- * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
2166
- * true: return first WP_Error or true on success
2167
- * @return WP_Error|bool|array
2168
- * true: when all extensions succeeded
2169
- * array: when some/all failed
2170
- */
2171
- public function deactivate_extensions(array $extensions, $cancel_on_error = false)
2172
- {
2173
- if (!$this->can_activate()) {
2174
- return new WP_Error(
2175
- 'access_denied',
2176
- __('You have no permissions to deactivate extensions', 'fw')
2177
- );
2178
- }
2179
-
2180
- if (empty($extensions)) {
2181
- return new WP_Error(
2182
- 'no_extensions',
2183
- __('No extensions provided', 'fw')
2184
- );
2185
- }
2186
-
2187
- $available_extensions = $this->get_available_extensions();
2188
- $installed_extensions = $this->get_installed_extensions();
2189
-
2190
- $result = $extensions_for_deactivation = array();
2191
- $has_errors = false;
2192
-
2193
- foreach ($extensions as $extension_name => $not_used_var) {
2194
-
2195
- if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
2196
- deactivate_plugins( plugin_basename( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) );
2197
- continue;
2198
- }
2199
-
2200
-
2201
- if (!isset($installed_extensions[$extension_name])) {
2202
- // anyway remove from the active list
2203
- $extensions_for_deactivation[$extension_name] = array();
2204
-
2205
- $result[$extension_name] = new WP_Error(
2206
- 'extension_not_installed',
2207
- sprintf(__( 'Extension "%s" does not exist.' , 'fw' ), $this->get_extension_title($extension_name))
2208
- );
2209
- $has_errors = true;
2210
-
2211
- if ($cancel_on_error) {
2212
- break;
2213
- } else {
2214
- continue;
2215
- }
2216
- }
2217
-
2218
- $current_deactivating_extensions = array(
2219
- $extension_name => array()
2220
- );
2221
-
2222
- // add sub-extensions for deactivation
2223
- foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2224
- $current_deactivating_extensions[ $sub_extension_name ] = array();
2225
- }
2226
-
2227
- // add extensions that requires deactivated extensions
2228
- $this->collect_extensions_that_requires($current_deactivating_extensions, $current_deactivating_extensions);
2229
-
2230
- $extensions_for_deactivation = array_merge(
2231
- $extensions_for_deactivation,
2232
- $current_deactivating_extensions
2233
- );
2234
-
2235
- unset($current_deactivating_extensions);
2236
-
2237
- $result[$extension_name] = true;
2238
- }
2239
-
2240
- if (
2241
- $cancel_on_error
2242
- &&
2243
- $has_errors
2244
- ) {
2245
- if (
2246
- ($last_result = end($result))
2247
- &&
2248
- is_wp_error($last_result)
2249
- ) {
2250
- return $last_result;
2251
- } else {
2252
- // this should not happen, but just to be sure (for the future, if the code above will be changed)
2253
- return new WP_Error(
2254
- 'deactivation_failed',
2255
- _n('Cannot deactivate extension', 'Cannot activate extensions', count($extensions), 'fw')
2256
- );
2257
- }
2258
- }
2259
-
2260
- // add not used extensions for deactivation
2261
- $extensions_for_deactivation = array_merge($extensions_for_deactivation,
2262
- array_fill_keys(
2263
- array_keys(
2264
- array_diff_key(
2265
- $installed_extensions,
2266
- $this->get_used_extensions($extensions_for_deactivation, array_keys(fw()->extensions->get_all()))
2267
- )
2268
- ),
2269
- array()
2270
- )
2271
- );
2272
-
2273
- update_option(
2274
- fw()->extensions->_get_active_extensions_db_option_name(),
2275
- array_diff_key(
2276
- fw()->extensions->_get_db_active_extensions(),
2277
- $extensions_for_deactivation
2278
- )
2279
- );
2280
-
2281
- // remove already inactive extensions
2282
- foreach ($extensions_for_deactivation as $extension_name => $not_used_var) {
2283
- if (!fw_ext($extension_name)) {
2284
- unset($extensions_for_deactivation[$extension_name]);
2285
- }
2286
- }
2287
-
2288
- /**
2289
- * Prepare db wp option used to fire the 'fw_extensions_after_deactivation' action on next refresh
2290
- */
2291
- {
2292
- $db_wp_option_name = 'fw_extensions_activation';
2293
- $db_wp_option_value = get_option($db_wp_option_name, array(
2294
- 'activated' => array(),
2295
- 'deactivated' => array(),
2296
- ));
2297
-
2298
- /**
2299
- * Keep adding to the existing value instead of resetting it on each method call
2300
- * in case the method will be called multiple times
2301
- */
2302
- $db_wp_option_value['deactivated'] = array_merge($db_wp_option_value['deactivated'], $extensions_for_deactivation);
2303
-
2304
- /**
2305
- * Remove deactivated extensions from activated
2306
- */
2307
- $db_wp_option_value['activated'] = array_diff_key($db_wp_option_value['activated'], $db_wp_option_value['deactivated']);
2308
-
2309
- update_option($db_wp_option_name, $db_wp_option_value, false);
2310
- }
2311
-
2312
- do_action('fw_extensions_before_deactivation', $extensions_for_deactivation);
2313
-
2314
- if ($has_errors) {
2315
- return $result;
2316
- } else {
2317
- return true;
2318
- }
2319
- }
2320
-
2321
- /**
2322
- * @param array $data
2323
- * @return array
2324
- * @internal
2325
- */
2326
- public function _extension_settings_form_render($data)
2327
- {
2328
- /**
2329
- * @var FW_Extension $extension
2330
- */
2331
- $extension = $data['data']['extension'];
2332
-
2333
- do_action('fw_extension_settings_form_render:'. $extension->get_name());
2334
-
2335
- echo fw_html_tag('input', array(
2336
- 'type' => 'hidden',
2337
- 'name' => 'fw_extension_name',
2338
- 'value' => $extension->get_name(),
2339
- ), true);
2340
-
2341
- echo fw()->backend->render_options(
2342
- $extension->get_settings_options(),
2343
- fw_get_db_ext_settings_option($extension->get_name())
2344
- );
2345
-
2346
- $data['submit']['html'] = '';
2347
-
2348
- echo '<p>';
2349
- echo fw_html_tag('input', array(
2350
- 'type' => 'submit',
2351
- 'class' => 'button-primary',
2352
- 'value' => __('Save', 'fw'),
2353
- ));
2354
- echo '&nbsp;&nbsp;&nbsp;&nbsp;';
2355
- echo fw_html_tag('a', array(
2356
- 'href' => $this->get_link(),
2357
- ), __('Cancel', 'fw'));
2358
- echo '</p>';
2359
-
2360
- return $data;
2361
- }
2362
-
2363
- /**
2364
- * @param array $errors
2365
- * @return array
2366
- * @internal
2367
- */
2368
- public function _extension_settings_form_validate($errors)
2369
- {
2370
- do {
2371
- if (!current_user_can($this->can_activate())) {
2372
- $errors[] = __('You are not allowed to save extensions settings.', 'fw');
2373
- break;
2374
- }
2375
-
2376
- $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2377
-
2378
- if (!$extension) {
2379
- $errors[] = __('Invalid extension.', 'fw');
2380
- break;
2381
- }
2382
-
2383
- if (!$extension->get_settings_options()) {
2384
- $errors[] = __('Extension does not have settings options.', 'fw');
2385
- break;
2386
- }
2387
- } while(false);
2388
-
2389
- return $errors;
2390
- }
2391
-
2392
- /**
2393
- * @param array $data
2394
- * @return array
2395
- * @internal
2396
- */
2397
- public function _extension_settings_form_save($data)
2398
- {
2399
- $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2400
-
2401
- $options_before_save = (array)fw_get_db_ext_settings_option($extension->get_name());
2402
-
2403
- fw_set_db_ext_settings_option(
2404
- $extension->get_name(),
2405
- null,
2406
- array_merge(
2407
- $options_before_save,
2408
- fw_get_options_values_from_input(
2409
- $extension->get_settings_options()
2410
- )
2411
- )
2412
- );
2413
-
2414
- FW_Flash_Messages::add(
2415
- 'fw_extension_settings_saved',
2416
- __('Extensions settings successfully saved.', 'fw'),
2417
- 'success'
2418
- );
2419
-
2420
- $data['redirect'] = fw_current_url();
2421
-
2422
- do_action('fw_extension_settings_form_saved:'. $extension->get_name(), $options_before_save);
2423
-
2424
- return $data;
2425
- }
2426
-
2427
- /**
2428
- * Download an extension
2429
- *
2430
- * global $wp_filesystem; must be initialized
2431
- *
2432
- * @param string $extension_name
2433
- * @param array $data Extension data from the "available extensions" array
2434
- * @return string|WP_Error WP Filesystem path to the downloaded directory
2435
- */
2436
- private function download( $extension_name, $data ) {
2437
- global $wp_filesystem;
2438
- $wp_error_id = 'fw_extension_download';
2439
-
2440
- if ( empty( $data['download'] ) ) {
2441
- return new WP_Error(
2442
- $wp_error_id,
2443
- sprintf( __( 'Extension "%s" has no download sources.', 'fw' ), $this->get_extension_title( $extension_name ) )
2444
- );
2445
- }
2446
-
2447
- $opts = array_merge( array(
2448
- 'item' => $extension_name,
2449
- 'extension_name' => $extension_name,
2450
- 'extension_title' => $this->get_extension_title( $extension_name )
2451
- ), $data['download']['opts'] );
2452
-
2453
- if ( isset( $opts['plugin'] ) && is_plugin_active( $opts['plugin'] ) ) {
2454
- return '';
2455
- }
2456
-
2457
- if ( ( $download_source = $this->get_download_source( $data ) ) && is_wp_error( $download_source ) ) {
2458
- return $download_source;
2459
- }
2460
-
2461
- if ( isset( $opts['plugin'] ) ) {
2462
- return $download_source->download( $opts, '' );
2463
- }
2464
-
2465
- // create temporary directory
2466
- $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path( $this->get_tmp_dir() );
2467
-
2468
- if ( $wp_filesystem->exists( $wp_fs_tmp_dir ) ) {
2469
- // just in case it already exists, clear everything, it may contain old files
2470
- if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
2471
- return new WP_Error(
2472
- $wp_error_id,
2473
- sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
2474
- );
2475
- }
2476
- }
2477
-
2478
- if ( ! FW_WP_Filesystem::mkdir_recursive( $wp_fs_tmp_dir ) ) {
2479
- return new WP_Error(
2480
- $wp_error_id,
2481
- sprintf( __( 'Cannot create temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
2482
- );
2483
- }
2484
-
2485
- return $this->perform_zip_download( $download_source, $opts, $wp_fs_tmp_dir );
2486
- }
2487
-
2488
- private function perform_zip_download( FW_Ext_Download_Source $download_source, array $opts, $wp_fs_tmp_dir ) {
2489
- $wp_error_id = 'fw_extension_download';
2490
-
2491
- /** @var WP_Filesystem_Base $wp_filesystem */
2492
- global $wp_filesystem;
2493
-
2494
- $zip_path = $wp_fs_tmp_dir . '/temp.zip';
2495
-
2496
- $download_result = $download_source->download( $opts, $zip_path );
2497
-
2498
- /**
2499
- * Pass further the error, if the service returned one.
2500
- */
2501
- if ( is_wp_error( $download_result ) ) {
2502
- return $download_result;
2503
- }
2504
-
2505
- $extension_name = $opts['extension_name'];
2506
-
2507
- $unzip_result = unzip_file(
2508
- FW_WP_Filesystem::filesystem_path_to_real_path( $zip_path ),
2509
- $wp_fs_tmp_dir
2510
- );
2511
-
2512
- if ( is_wp_error( $unzip_result ) ) {
2513
- return $unzip_result;
2514
- }
2515
-
2516
- // remove zip file
2517
- if ( ! $wp_filesystem->delete( $zip_path, false, 'f' ) ) {
2518
- return new WP_Error(
2519
- $wp_error_id,
2520
- sprintf( __( 'Cannot remove the "%s" extension downloaded zip.', 'fw' ), $this->get_extension_title( $extension_name ) )
2521
- );
2522
- }
2523
-
2524
- $unzipped_dir_files = $wp_filesystem->dirlist( $wp_fs_tmp_dir );
2525
-
2526
- if ( ! $unzipped_dir_files ) {
2527
- return new WP_Error(
2528
- $wp_error_id,
2529
- __( 'Cannot access the unzipped directory files.', 'fw' )
2530
- );
2531
- }
2532
-
2533
- /**
2534
- * get first found directory
2535
- * (if everything worked well, there should be only one directory)
2536
- */
2537
- foreach ( $unzipped_dir_files as $file ) {
2538
- if ( $file['type'] == 'd' ) {
2539
- return $wp_fs_tmp_dir . '/' . $file['name'];
2540
- }
2541
- }
2542
-
2543
- return new WP_Error(
2544
- $wp_error_id,
2545
- sprintf( __( 'The unzipped "%s" extension directory not found.', 'fw' ), $this->get_extension_title( $extension_name ) )
2546
- );
2547
- }
2548
-
2549
- /**
2550
- * @param $set
2551
- *
2552
- * @return FW_Ext_Download_Source|WP_Error
2553
- */
2554
- private function get_download_source( $set ) {
2555
- require_once dirname( __FILE__ ) . '/includes/download-source/types/init.php';
2556
-
2557
- $register = new _FW_Ext_Download_Source_Register( self::get_access_key()->get_key() );
2558
-
2559
- /**
2560
- * Register download sources for extensions.
2561
- *
2562
- * Usage:
2563
- * $download_source = new FW_Ext_Download_Source();
2564
- * $register->register($download_source);
2565
- */
2566
- do_action( 'fw_register_ext_download_sources', $register );
2567
-
2568
- $download_source = $register->_get_type( self::get_access_key(), $set['download']['source'] );
2569
-
2570
- if ( ! $download_source ) {
2571
- $download_source = new WP_Error( 'invalid_dl_source', sprintf( esc_html__( 'Invalid download source: %s', 'fw' ), $set['download']['source'] ) );
2572
- }
2573
-
2574
- return $download_source;
2575
- }
2576
-
2577
- /**
2578
- * Merge the downloaded extension directory with the existing directory
2579
- *
2580
- * @param string $source_wp_fs_dir Downloaded extension directory
2581
- * @param string $destination_wp_fs_dir
2582
- *
2583
- * @return null|WP_Error
2584
- */
2585
- private function merge_extension( $source_wp_fs_dir, $destination_wp_fs_dir ) {
2586
- /** @var WP_Filesystem_Base $wp_filesystem */
2587
- global $wp_filesystem;
2588
-
2589
- $wp_error_id = 'fw_extensions_merge';
2590
-
2591
- // check source
2592
- {
2593
- $source_files = $wp_filesystem->dirlist( $source_wp_fs_dir );
2594
-
2595
- if ( $source_files === false ) {
2596
- return new WP_Error(
2597
- $wp_error_id,
2598
- sprintf( __( 'Cannot read directory "%s".', 'fw' ), $source_wp_fs_dir )
2599
- );
2600
- }
2601
-
2602
- if ( empty( $source_files ) ) {
2603
- return; // directory is empty, nothing to move
2604
- }
2605
- }
2606
-
2607
- /**
2608
- * Prepare destination directory
2609
- * Remove everything except the extensions/ directory
2610
- */
2611
- if ( $wp_filesystem->exists( $destination_wp_fs_dir ) ) {
2612
- $destination_files = $wp_filesystem->dirlist( $destination_wp_fs_dir );
2613
-
2614
- if ( $destination_files === false ) {
2615
- return new WP_Error(
2616
- $wp_error_id,
2617
- sprintf( __( 'Cannot read directory "%s".', 'fw' ), $destination_wp_fs_dir )
2618
- );
2619
- }
2620
-
2621
- if ( ! empty( $destination_files ) ) {
2622
- if (
2623
- count( $source_files ) == 1
2624
- &&
2625
- ( $file = reset( $source_files ) )
2626
- &&
2627
- $file['name'] === 'extensions'
2628
- &&
2629
- $file['type'] === 'd'
2630
- ) {
2631
- /**
2632
- * Source extension is empty
2633
- * It happens when you merge a directory which contains child extensions
2634
- * Do not delete current destination files, just go in the next child extensions level
2635
- * Used by https://github.com/ThemeFuse/Unyson/issues/1874
2636
- */
2637
- } else {
2638
- // the directory contains some files, delete everything
2639
- foreach ( $destination_files as $file ) {
2640
- if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
2641
- // do not touch the extensions/ directory
2642
- continue;
2643
- }
2644
-
2645
- if ( ! $wp_filesystem->delete(
2646
- $destination_wp_fs_dir . '/' . $file['name'],
2647
- true,
2648
- $file['type']
2649
- ) ) {
2650
- return new WP_Error(
2651
- $wp_error_id,
2652
- sprintf(
2653
- __( 'Cannot delete "%s".', 'fw' ),
2654
- $destination_wp_fs_dir . '/' . $file['name']
2655
- )
2656
- );
2657
- }
2658
- }
2659
- }
2660
-
2661
- unset( $destination_files );
2662
- }
2663
- } else {
2664
- if ( ! FW_WP_Filesystem::mkdir_recursive( $destination_wp_fs_dir ) ) {
2665
- return new WP_Error(
2666
- $wp_error_id,
2667
- sprintf( __( 'Cannot create the "%s" directory.', 'fw' ), $destination_wp_fs_dir )
2668
- );
2669
- }
2670
- }
2671
-
2672
- // Move files from source to destination
2673
- {
2674
- $has_sub_extensions = false;
2675
-
2676
- foreach ( $source_files as $file ) {
2677
- if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
2678
- $has_sub_extensions = true; // do not touch the extensions/ directory
2679
- continue;
2680
- }
2681
-
2682
- if ( ! $wp_filesystem->move( $source_wp_fs_dir . '/' . $file['name'], $destination_wp_fs_dir . '/' . $file['name'] ) ) {
2683
- return new WP_Error(
2684
- $wp_error_id,
2685
- sprintf(
2686
- __( 'Cannot move "%s" to "%s".', 'fw' ),
2687
- $source_wp_fs_dir . '/' . $file['name'],
2688
- $destination_wp_fs_dir . '/' . $file['name']
2689
- )
2690
- );
2691
- }
2692
- }
2693
-
2694
- unset( $source_files );
2695
- }
2696
-
2697
- if ( ! $has_sub_extensions ) {
2698
- return;
2699
- }
2700
-
2701
- $sub_extensions = $wp_filesystem->dirlist( $source_wp_fs_dir . '/extensions' );
2702
-
2703
- if ( $sub_extensions === false ) {
2704
- return new WP_Error(
2705
- $wp_error_id,
2706
- sprintf( __( 'Cannot read directory "%s".', 'fw' ), $source_wp_fs_dir . '/extensions' )
2707
- );
2708
- }
2709
-
2710
- if ( empty( $sub_extensions ) ) {
2711
- // directory is empty, nothing to remove
2712
- return;
2713
- }
2714
-
2715
- foreach ( $sub_extensions as $file ) {
2716
- if ( $file['type'] !== 'd' ) {
2717
- // wrong, only directories must exist in the extensions/ directory
2718
- continue;
2719
- }
2720
-
2721
- $merge_result = $this->merge_extension(
2722
- $source_wp_fs_dir . '/extensions/' . $file['name'],
2723
- $destination_wp_fs_dir . '/extensions/' . $file['name']
2724
- );
2725
-
2726
- if ( is_wp_error( $merge_result ) ) {
2727
- return $merge_result;
2728
- }
2729
- }
2730
- }
2731
-
2732
- /**
2733
- * @since 2.6.9
2734
- */
2735
- public function get_supported_extensions()
2736
- {
2737
- $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2738
-
2739
- // Add Available Extensions registered by the theme
2740
- foreach ($this->get_available_extensions() as $name => $extension) {
2741
- if (isset($extension['theme']) && $extension['theme']) {
2742
- $supported_extensions[$name] = array();
2743
- }
2744
- }
2745
-
2746
- if (empty($supported_extensions)) {
2747
- return array();
2748
- }
2749
-
2750
- // remove not available extensions
2751
- $supported_extensions = array_intersect_key($supported_extensions, $this->get_available_extensions());
2752
-
2753
- if (empty($supported_extensions)) {
2754
- return array();
2755
- }
2756
-
2757
- if (empty($supported_extensions)) {
2758
- return array();
2759
- }
2760
-
2761
- return $supported_extensions;
2762
- }
2763
-
2764
- /**
2765
- * @since 2.6.9
2766
- */
2767
- public function get_supported_extensions_for_install()
2768
- {
2769
- // remove already installed extensions
2770
- return array_diff_key(
2771
- $this->get_supported_extensions(),
2772
- $this->get_installed_extensions()
2773
- );
2774
- }
2775
-
2776
- /**
2777
- * @param $actions
2778
- * @return array
2779
- * @internal
2780
- */
2781
- public function _filter_plugin_action_list($actions)
2782
- {
2783
- return array_merge(
2784
- array(
2785
- 'fw-extensions' => fw_html_tag('a', array(
2786
- 'href' => $this->get_link(),
2787
- ), fw()->manifest->get_name()),
2788
- ),
2789
- $actions
2790
- );
2791
- }
2792
-
2793
- /**
2794
- * @return string Extensions page link
2795
- */
2796
- private function get_link()
2797
- {
2798
- static $cache_link = null;
2799
-
2800
- if ($cache_link === null) {
2801
- $cache_link = menu_page_url( $this->get_page_slug(), false );
2802
-
2803
- // https://core.trac.wordpress.org/ticket/28226
2804
- if (is_multisite() && is_network_admin()) {
2805
- $cache_link = self_admin_url(
2806
- // extract relative link
2807
- preg_replace('/^'. preg_quote(admin_url(), '/') .'/', '', $cache_link)
2808
- );
2809
- }
2810
- }
2811
-
2812
- return $cache_link;
2813
- }
2814
-
2815
- /**
2816
- * @param array $skip_extensions {'ext' => mixed}
2817
- * @param array $check_for_deps ['ext', 'ext', ...] Extensions to check if has in dependencies the used extensions
2818
- *
2819
- * @return array
2820
- */
2821
- private function get_used_extensions($skip_extensions, $check_for_deps)
2822
- {
2823
- $used_extensions = array();
2824
-
2825
- $installed_extensions = $this->get_installed_extensions();
2826
-
2827
- foreach ($installed_extensions as $inst_ext_name => &$inst_ext_data) {
2828
- if (isset($skip_extensions[ $inst_ext_name ])) {
2829
- continue;
2830
- }
2831
-
2832
- if (isset($used_extensions[$inst_ext_name])) {
2833
- // already marked as used
2834
- continue;
2835
- }
2836
-
2837
- do {
2838
- foreach ($check_for_deps as $deps_ext) {
2839
- if (isset($skip_extensions[$deps_ext])) {
2840
- continue;
2841
- }
2842
-
2843
- if (false !== fw_akg(
2844
- 'requirements/extensions/'. $inst_ext_name,
2845
- $installed_extensions[$deps_ext]['manifest'],
2846
- false
2847
- )) {
2848
- // is required by an active extension
2849
- break 2;
2850
- }
2851
- }
2852
-
2853
- if ( true === fw_akg(
2854
- 'standalone',
2855
- $inst_ext_data['manifest'],
2856
- $this->manifest_default_values['standalone']
2857
- ) ) {
2858
- // can exist alone
2859
- break;
2860
- }
2861
-
2862
- // not used
2863
- continue 2;
2864
- } while(false);
2865
-
2866
- $used_extensions[$inst_ext_name] = array();
2867
-
2868
- // Set all sub-extensions as used
2869
- foreach ($this->collect_sub_extensions($inst_ext_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2870
- if (isset($skip_extensions[$sub_extension_name])) {
2871
- continue;
2872
- }
2873
-
2874
- $used_extensions[ $sub_extension_name ] = array();
2875
- }
2876
-
2877
- // Set all parents as used
2878
- {
2879
- $current_parent = $inst_ext_name;
2880
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
2881
- $used_extensions[$current_parent] = array();
2882
- }
2883
- }
2884
- }
2885
- unset($inst_ext_data);
2886
-
2887
- // remove all skipped extensions and sub-extension from used extensions
2888
- foreach (array_keys($skip_extensions) as $skip_extension_name) {
2889
- unset($used_extensions[$skip_extension_name]);
2890
-
2891
- if (isset($installed_extensions[$skip_extension_name])) {
2892
- foreach ($this->collect_sub_extensions($skip_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2893
- unset($used_extensions[$sub_extension_name]);
2894
- }
2895
- }
2896
- }
2897
-
2898
- return $used_extensions;
2899
- }
2900
-
2901
- /**
2902
- * @internal
2903
- */
2904
- public function _action_admin_footer()
2905
- {
2906
- $this->activate_hidden_standalone_extensions();
2907
- }
2908
-
2909
- public function get_extension_title($extension_name)
2910
- {
2911
- $installed_extensions = $this->get_installed_extensions();
2912
-
2913
- if (isset($installed_extensions[$extension_name])) {
2914
- return fw_akg('name', $installed_extensions[$extension_name]['manifest'], fw_id_to_title($extension_name));
2915
- }
2916
-
2917
- unset($installed_extensions);
2918
-
2919
- $available_extensions = $this->get_available_extensions();
2920
-
2921
- if (isset($available_extensions[$extension_name])) {
2922
- return $available_extensions[$extension_name]['name'];
2923
- }
2924
-
2925
- return fw_id_to_title($extension_name);
2926
- }
2927
-
2928
- public function is_extensions_page()
2929
- {
2930
- $current_screen = get_current_screen();
2931
-
2932
- if (empty($current_screen)) {
2933
- return false;
2934
- }
2935
-
2936
- return (
2937
- property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2938
- &&
2939
- property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2940
- &&
2941
- !isset($_GET['sub-page'])
2942
- );
2943
- }
2944
-
2945
- public function is_extension_page()
2946
- {
2947
- $current_screen = get_current_screen();
2948
-
2949
- if (empty($current_screen)) {
2950
- return false;
2951
- }
2952
-
2953
- return (
2954
- property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2955
- &&
2956
- property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2957
- &&
2958
- isset($_GET['sub-page']) && $_GET['sub-page'] === 'extension'
2959
- );
2960
- }
2961
-
2962
- /**
2963
- * @internal
2964
- */
2965
- public function _action_enqueue_scripts()
2966
- {
2967
- wp_enqueue_style(
2968
- 'fw-extensions-menu-icon',
2969
- $this->get_uri('/static/unyson-font-icon/style.css'),
2970
- array(),
2971
- fw()->manifest->get_version()
2972
- );
2973
-
2974
- /**
2975
- * Enqueue only on Extensions List page
2976
- */
2977
- if ($this->is_extensions_page()) {
2978
- wp_enqueue_style(
2979
- 'fw-extensions-page',
2980
- $this->get_uri('/static/extensions-page.css'),
2981
- array(
2982
- 'fw',
2983
- 'fw-unycon', 'font-awesome', // in case some extension has font-icon thumbnail
2984
- ),
2985
- fw()->manifest->get_version()
2986
- );
2987
- wp_enqueue_script(
2988
- 'fw-extensions-page',
2989
- $this->get_uri('/static/extensions-page.js'),
2990
- array('fw'),
2991
- fw()->manifest->get_version(),
2992
- true
2993
- );
2994
- wp_localize_script('fw-extensions-page', '_fw_extensions_script_data', array(
2995
- 'link' => $this->get_link(),
2996
- ));
2997
-
2998
- /**
2999
- * this is needed for fw.soleModal design
3000
- * it is displayed when extension ajax install returns an error
3001
- */
3002
- wp_enqueue_media();
3003
- }
3004
-
3005
- if ($this->is_extension_page()) {
3006
- wp_enqueue_style(
3007
- 'fw-extension-page',
3008
- $this->get_uri('/static/extension-page.css'),
3009
- array('fw'),
3010
- fw()->manifest->get_version()
3011
- );
3012
- wp_enqueue_script(
3013
- 'fw-extension-page',
3014
- $this->get_uri('/static/extension-page.js'),
3015
- array('fw'),
3016
- fw()->manifest->get_version(),
3017
- true
3018
- );
3019
-
3020
- /**
3021
- * Enqueue extension settings options static
3022
- */
3023
- if (
3024
- isset($_GET['extension'])
3025
- &&
3026
- is_string($extension_name = $_GET['extension'])
3027
- &&
3028
- fw()->extensions->get($extension_name)
3029
- &&
3030
- ($extension_settings_options = fw()->extensions->get($extension_name)->get_settings_options())
3031
- ) {
3032
- fw()->backend->enqueue_options_static($extension_settings_options);
3033
- }
3034
- }
3035
- }
3036
-
3037
- private function activate_theme_extensions()
3038
- {
3039
- $db_active_extensions = fw()->extensions->_get_db_active_extensions();
3040
-
3041
- foreach ($this->get_installed_extensions() as $extension_name => $extension) {
3042
- if ($extension['is']['theme']) {
3043
- $db_active_extensions[ $extension_name ] = array();
3044
- }
3045
- }
3046
-
3047
- update_option(
3048
- fw()->extensions->_get_active_extensions_db_option_name(),
3049
- $db_active_extensions
3050
- );
3051
- }
3052
-
3053
- /**
3054
- * @internal
3055
- */
3056
- public function _action_theme_switch()
3057
- {
3058
- if ( ! apply_filters( 'fw_after_switch_theme_activate_exts', true ) ) {
3059
- return;
3060
- }
3061
-
3062
- $this->activate_theme_extensions();
3063
-
3064
- $this->activate_extensions(
3065
- array_fill_keys(
3066
- array_keys(fw()->theme->manifest->get('supported_extensions', array())),
3067
- array()
3068
- )
3069
- );
3070
- }
3071
-
3072
- /**
3073
- * @param array $collected The found extensions {'extension_name' => array()}
3074
- * @param array $extensions {'extension_name' => array()}
3075
- * @param bool $check_all Check all extensions or only active extensions
3076
- */
3077
- private function collect_extensions_that_requires(&$collected, $extensions, $check_all = false)
3078
- {
3079
- if (empty($extensions)) {
3080
- return;
3081
- }
3082
-
3083
- $found_extensions = array();
3084
-
3085
- foreach ($this->get_installed_extensions() as $extension_name => $extension_data) {
3086
- if (isset($collected[$extension_name])) {
3087
- continue;
3088
- }
3089
-
3090
- if (!$check_all) {
3091
- if (!fw_ext($extension_name)) {
3092
- continue;
3093
- }
3094
- }
3095
-
3096
- if (
3097
- array_intersect_key(
3098
- $extensions,
3099
- fw_akg(
3100
- 'requirements/extensions',
3101
- $extension_data['manifest'],
3102
- array()
3103
- )
3104
- )
3105
- ) {
3106
- $found_extensions[$extension_name] = $collected[$extension_name] = array();
3107
- }
3108
- }
3109
-
3110
- $this->collect_extensions_that_requires($collected, $found_extensions, $check_all);
3111
- }
3112
-
3113
- /**
3114
- * Get extension settings page link
3115
- * @param string $extension_name
3116
- * @return string
3117
- */
3118
- public function get_extension_link($extension_name)
3119
- {
3120
- return $this->get_link() .'&sub-page=extension&extension='. $extension_name;
3121
- }
3122
-
3123
- /**
3124
- * @param string $extension_name
3125
- * @return array|WP_Error Extensions to merge with db active extensions list
3126
- */
3127
- private function get_extensions_for_activation($extension_name)
3128
- {
3129
- $installed_extensions = $this->get_installed_extensions();
3130
-
3131
- $wp_error_id = 'fw_ext_activation';
3132
-
3133
- if (!isset($installed_extensions[$extension_name])) {
3134
- return new WP_Error($wp_error_id,
3135
- sprintf(
3136
- __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3137
- $this->get_extension_title($extension_name),
3138
- fw_html_tag('a', array(
3139
- 'href' => $this->get_link() .'&sub-page=install&extension='. $extension_name
3140
- ), __('Install', 'fw'))
3141
- )
3142
- );
3143
- }
3144
-
3145
- {
3146
- $extension_parents = array($extension_name);
3147
-
3148
- $current_parent = $extension_name;
3149
- while ($current_parent = $installed_extensions[$current_parent]['parent']) {
3150
- $extension_parents[] = $current_parent;
3151
- }
3152
-
3153
- $extension_parents = array_reverse($extension_parents);
3154
- }
3155
-
3156
- $extensions = array();
3157
-
3158
- foreach ($extension_parents as $parent_extension_name) {
3159
- $extensions[ $parent_extension_name ] = array();
3160
- }
3161
-
3162
- // search sub-extensions
3163
- foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3164
- $extensions[ $sub_extension_name ] = array();
3165
- }
3166
-
3167
- // search required extensions
3168
- {
3169
- $pending_required_search = $extensions;
3170
-
3171
- while ($pending_required_search) {
3172
- foreach (array_keys($pending_required_search) as $pend_req_extension_name) {
3173
- unset($pending_required_search[$pend_req_extension_name]);
3174
-
3175
- unset($required_extensions); // reset reference
3176
- $required_extensions = array();
3177
- $this->collect_required_extensions($pend_req_extension_name, $installed_extensions, $required_extensions);
3178
-
3179
- foreach ($required_extensions as $required_extension_name => $required_extension_data) {
3180
- if (!isset($installed_extensions[$required_extension_name])) {
3181
- return new WP_Error($wp_error_id,
3182
- sprintf(
3183
- __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3184
- $this->get_extension_title($required_extension_name),
3185
- fw_html_tag('a', array(
3186
- 'href' => $this->get_link() .'&sub-page=install&extension='. $required_extension_name
3187
- ), __('Install', 'fw'))
3188
- )
3189
- );
3190
- }
3191
-
3192
- $extensions[$required_extension_name] = array();
3193
-
3194
- // search sub-extensions
3195
- foreach ($this->collect_sub_extensions($required_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3196
- if (isset($extensions[$sub_extension_name])) {
3197
- continue;
3198
- }
3199
-
3200
- $extensions[$sub_extension_name] = array();
3201
-
3202
- $pending_required_search[$sub_extension_name] = array();
3203
- }
3204
- }
3205
- }
3206
- }
3207
- }
3208
-
3209
- return $extensions;
3210
- }
3211
-
3212
- /**
3213
- * @internal
3214
- */
3215
- public function _action_admin_notices() {
3216
- $should_notify = apply_filters(
3217
- 'fw_notify_about_missing_extensions',
3218
- true
3219
- );
3220
-
3221
- /**
3222
- * In v2.4.12 was done a terrible mistake https://github.com/ThemeFuse/Unyson-Extensions-Approval/issues/160
3223
- * Show a warning with link to install theme supported extensions
3224
- */
3225
- if (
3226
- $should_notify
3227
- &&
3228
- !isset($_GET['supported']) // already on 'Install Supported Extensions' page
3229
- &&
3230
- $this->can_install()
3231
- &&
3232
- (($installed_extensions = $this->get_installed_extensions()) || true)
3233
- &&
3234
- !isset($installed_extensions['page-builder'])
3235
- &&
3236
- $this->get_supported_extensions_for_install()
3237
- ) {
3238
- echo '<div class="error"> <p>'
3239
- , fw_html_tag('a', array('href' => $this->get_link() .'&sub-page=install&supported'),
3240
- __('Install theme compatible extensions', 'fw'))
3241
- , '</p></div>';
3242
- }
3243
- }
3244
-
3245
- /**
3246
- * Copy Theme Available Extensions to a tmp directory
3247
- * Used before theme update
3248
- * @since 2.6.0
3249
- * @return null|WP_Error
3250
- */
3251
- public function theme_available_extensions_copy() {
3252
- /** @var WP_Filesystem_Base $wp_filesystem */
3253
- global $wp_filesystem;
3254
-
3255
- if (!FW_WP_Filesystem::is_ready()) {
3256
- return new WP_Error(
3257
- 'fs_not_initialized',
3258
- __('WP Filesystem is not initialized', 'fw')
3259
- );
3260
- }
3261
-
3262
- // Prepare temporary directory
3263
- {
3264
- $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3265
- $this->get_tmp_dir('/theme-ext')
3266
- );
3267
-
3268
- if (
3269
- $wp_filesystem->exists( $wpfs_tmp_dir )
3270
- &&
3271
- ! $wp_filesystem->rmdir( $wpfs_tmp_dir, true )
3272
- ) {
3273
- return new WP_Error(
3274
- 'tmp_dir_rm_fail',
3275
- sprintf(__('Temporary directory cannot be removed: %s', 'fw'), $wpfs_tmp_dir)
3276
- );
3277
- }
3278
-
3279
- if ( ! FW_WP_Filesystem::mkdir_recursive( $wpfs_tmp_dir ) ) {
3280
- return new WP_Error(
3281
- 'tmp_dir_rm_fail',
3282
- sprintf(__('Temporary directory cannot be created: %s', 'fw'), $wpfs_tmp_dir)
3283
- );
3284
- }
3285
- }
3286
-
3287
- $available_extensions = $this->get_available_extensions();
3288
- $installed_extensions = $this->get_installed_extensions(true);
3289
- $base_dir = fw_get_template_customizations_directory('/extensions');
3290
-
3291
- foreach ($installed_extensions as $name => $ext) {
3292
- if ( ! (
3293
- isset($available_extensions[$name])
3294
- &&
3295
- isset($available_extensions[$name]['theme'])
3296
- &&
3297
- $available_extensions[$name]['theme']
3298
- ) ) {
3299
- continue;
3300
- }
3301
-
3302
- if ( ($rel_path = preg_replace('/^'. preg_quote($base_dir, '/') .'/', '', $ext['path'])) === $base_dir ) {
3303
- return new WP_Error(
3304
- 'rel_path_failed',
3305
- sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3306
- );
3307
- }
3308
-
3309
- if ( ($wpfs_path = FW_WP_Filesystem::real_path_to_filesystem_path($ext['path'])) === false) {
3310
- return new WP_Error(
3311
- 'real_to_wpfs_filed',
3312
- sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3313
- );
3314
- }
3315
-
3316
- $wpfs_dest_dir = $wpfs_tmp_dir . $rel_path;
3317
-
3318
- if ( ! FW_WP_Filesystem::mkdir_recursive($wpfs_dest_dir) ) {
3319
- return new WP_Error(
3320
- 'dest_dir_mk_fail',
3321
- sprintf(__('Failed to create directory %s', 'fw'), $wpfs_dest_dir)
3322
- );
3323
- }
3324
-
3325
- if ( is_wp_error( $copy_result = copy_dir($wpfs_path, $wpfs_dest_dir) ) ) {
3326
- /** @var WP_Error $copy_result */
3327
- return new WP_Error(
3328
- 'ext_copy_failed',
3329
- sprintf( __('Failed to copy extension to %s', 'fw'), $wpfs_dest_dir )
3330
- );
3331
- }
3332
- }
3333
- }
3334
-
3335
- /**
3336
- * Copy Theme Available Extensions from tmp directory to theme
3337
- * Used after theme update
3338
- * @since 2.6.0
3339
- * @return null|WP_Error
3340
- */
3341
- public function theme_available_extensions_restore() {
3342
- /** @var WP_Filesystem_Base $wp_filesystem */
3343
- global $wp_filesystem;
3344
-
3345
- if (!FW_WP_Filesystem::is_ready()) {
3346
- return new WP_Error(
3347
- 'fs_not_initialized',
3348
- __('WP Filesystem is not initialized', 'fw')
3349
- );
3350
- }
3351
-
3352
- if ( ! $wp_filesystem->exists(
3353
- $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3354
- $this->get_tmp_dir('/theme-ext')
3355
- )
3356
- ) ) {
3357
- return new WP_Error(
3358
- 'no_tmp_dir',
3359
- sprintf(__('Temporary directory does not exist: %s', 'fw'), $wpfs_tmp_dir)
3360
- );
3361
- }
3362
-
3363
- /**
3364
- * Fixes the case when the theme path before update was
3365
- * wp-content/themes/theme-name/theme-name-parent
3366
- * but after update it became
3367
- * wp-content/themes/theme-name-parent
3368
- *
3369
- * and at this point get_template_directory() returns old theme directory
3370
- * so fw_get_template_customizations_directory() also returns old path
3371
- */
3372
- $theme_dir = wp_get_theme()->get_theme_root() .'/'. wp_get_theme()->get_template();
3373
-
3374
- if ( ! ($wpfs_base_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3375
- $base_dir = $theme_dir . fw_get_framework_customizations_dir_rel_path('/extensions')
3376
- ) ) ) {
3377
- return new WP_Error(
3378
- 'base_dir_to_wpfs_fail',
3379
- sprintf( __('Cannot obtain WP Filesystem dir for %s', 'fw'), $base_dir )
3380
- );
3381
- }
3382
-
3383
- if ( ! ( $dirlist = $wp_filesystem->dirlist($wpfs_tmp_dir) ) ) {
3384
- return;
3385
- }
3386
-
3387
- foreach ( $dirlist as $filename => $fileinfo ) {
3388
- if ( 'd' !== $fileinfo['type'] ) {
3389
- continue;
3390
- }
3391
-
3392
- if ( is_wp_error($merge_result = $this->merge_extension(
3393
- $wpfs_tmp_dir .'/'. $filename,
3394
- $wpfs_base_dir .'/'. $filename
3395
- )) ) {
3396
- return $merge_result;
3397
- }
3398
- }
3399
-
3400
- $wp_filesystem->rmdir( $wpfs_tmp_dir, true );
3401
- }
3402
-
3403
- /**
3404
- * Copy Theme Available Extensions to tmp dir
3405
- * @param bool|WP_Error $result
3406
- * @param array $data
3407
- *
3408
- * @return bool|WP_Error
3409
- */
3410
- public function _filter_theme_available_extensions_copy($result, $data) {
3411
- if (
3412
- !is_wp_error($result)
3413
- &&
3414
- is_array($data)
3415
- &&
3416
- isset($data['theme'])
3417
- &&
3418
- $data['theme'] === wp_get_theme()->get_template()
3419
- ) {
3420
- if ( is_wp_error( $copy_result = fw()->extensions->manager->theme_available_extensions_copy() ) ) {
3421
- return $copy_result;
3422
- }
3423
- }
3424
-
3425
- return $result;
3426
- }
3427
-
3428
- /**
3429
- * Restore Theme Available Extensions from tmp dir
3430
- * @param Theme_Upgrader $instance
3431
- * @param array $data
3432
- *
3433
- * @return bool|WP_Error
3434
- */
3435
- public function _action_theme_available_extensions_restore($instance, $data) {
3436
- if (
3437
- !is_wp_error($instance->skin->result)
3438
- &&
3439
- is_array($data)
3440
- &&
3441
- isset($data['action']) && $data['action'] === 'update'
3442
- &&
3443
- isset($data['type']) && $data['type'] === 'theme'
3444
- &&
3445
- isset($data['themes'])
3446
- &&
3447
- ($template = wp_get_theme()->get_template())
3448
- &&
3449
- (
3450
- in_array($template, $data['themes'])
3451
- ||
3452
- /**
3453
- * Fixes the case when the theme path before update was
3454
- * wp-content/themes/theme-name/theme-name-parent
3455
- * but after update it became
3456
- * wp-content/themes/theme-name-parent
3457
- */
3458
- ( preg_match($regex = '/\-parent$/', $template)
3459
- ? in_array( preg_replace($regex, '', $template) .'/'. $template, $data['themes'] )
3460
- : false )
3461
- )
3462
- ) {
3463
- fw()->extensions->manager->theme_available_extensions_restore();
3464
- }
3465
- }
3466
-
3467
- /**
3468
- * Install compatible extensions on plugin install -> activate
3469
- *
3470
- * In order for this to work, int TGM config must be set: 'is_automatic' => true
3471
- * http://tgmpluginactivation.com/configuration/
3472
- *
3473
- * @internal
3474
- */
3475
- public function _action_plugin_activate_install_compatible_extensions() {
3476
- if (!FW_WP_Filesystem::is_ready()) {
3477
- return;
3478
- }
3479
-
3480
- if ($compatible_extensions = $this->get_supported_extensions_for_install()) {
3481
- $this->install_extensions($compatible_extensions);
3482
- // the result is not used because we don't know here if we can print the errors or not
3483
- }
3484
- }
3485
-
3486
- /**
3487
- * @since 2.6.9
3488
- */
3489
- public function collect_extension_requirements($extension_name, $can_install = null) {
3490
- $installed_extensions = $this->get_installed_extensions();
3491
-
3492
- if (is_null($can_install)) {
3493
- $can_install = $this->can_install();
3494
- }
3495
-
3496
- if (! isset($installed_extensions[$extension_name])) {
3497
- return array();
3498
- } else {
3499
- $data = $installed_extensions[$extension_name];
3500
- }
3501
-
3502
- $result = array();
3503
-
3504
- $manifest_requirements = fw_akg('requirements', $data['manifest'], array());
3505
-
3506
- foreach ($manifest_requirements as $req_name => $req_data) {
3507
- switch ($req_name) {
3508
- case 'php':
3509
- if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3510
- break;
3511
- }
3512
-
3513
- if ( ! empty( $req_data['min_version'] ) ) {
3514
- if (!version_compare($req_data['min_version'], phpversion(), '<=')) {
3515
- $result[] = sprintf(
3516
- __( 'PHP needs to be updated to %s', 'fw' ),
3517
- $req_data['min_version']
3518
- );
3519
- }
3520
- }
3521
-
3522
- if ( ! empty( $req_data['max_version'] ) ) {
3523
- if (!version_compare($req_data['max_version'], phpversion(), '>=')) {
3524
- $result[] = sprintf(
3525
- __('Maximum supported PHP version is %s', 'fw'),
3526
- $req_data['max_version']
3527
- );
3528
- }
3529
- }
3530
-
3531
- break;
3532
-
3533
- case 'wordpress':
3534
- if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3535
- break;
3536
- }
3537
-
3538
- global $wp_version;
3539
-
3540
- if ( ! empty( $req_data['min_version'] ) ) {
3541
- if (!version_compare($req_data['min_version'], $wp_version, '<=')) {
3542
- if ($can_install) {
3543
- $result[] = sprintf(
3544
- __( 'You need to update WordPress to %s: %s', 'fw' ),
3545
- $req_data['min_version'],
3546
- fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ), __( 'Update WordPress', 'fw' ) )
3547
- );
3548
- } else {
3549
- $result[] = sprintf(
3550
- __( 'WordPress needs to be updated to %s', 'fw' ),
3551
- $req_data['min_version']
3552
- );
3553
- }
3554
- }
3555
- }
3556
-
3557
- if ( ! empty( $req_data['max_version'] ) ) {
3558
- if (!version_compare($req_data['max_version'], $wp_version, '>=')) {
3559
- $result[] = sprintf(
3560
- __('Maximum supported WordPress version is %s', 'fw'),
3561
- $req_data['max_version']
3562
- );
3563
- }
3564
- }
3565
-
3566
- break;
3567
-
3568
- case 'framework':
3569
- if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3570
- break;
3571
- }
3572
-
3573
- if ( ! empty( $req_data['min_version'] ) ) {
3574
- if (!version_compare($req_data['min_version'], fw()->manifest->get_version(), '<=')) {
3575
- if ($can_install) {
3576
- $result[] = sprintf(
3577
- __( 'You need to update %s to %s: %s', 'fw' ),
3578
- fw()->manifest->get_name(),
3579
- $req_data['min_version'],
3580
- fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ),
3581
- sprintf( __( 'Update %s', 'fw' ), fw()->manifest->get_name() )
3582
- )
3583
- );
3584
- } else {
3585
- $result[] = sprintf(
3586
- __( '%s needs to be updated to %s', 'fw' ),
3587
- fw()->manifest->get_name(),
3588
- $req_data['min_version']
3589
- );
3590
- }
3591
- }
3592
- }
3593
-
3594
- if ( ! empty( $req_data['max_version'] ) ) {
3595
- if (!version_compare($req_data['max_version'], fw()->manifest->get_version(), '>=')) {
3596
- $result[] = sprintf(
3597
- __( 'Maximum supported %s version is %s', 'fw' ),
3598
- fw()->manifest->get_name(),
3599
- $req_data['max_version']
3600
- );
3601
- }
3602
- }
3603
-
3604
- break;
3605
-
3606
- case 'extensions':
3607
- foreach ($req_data as $req_ext => $req_ext_data) {
3608
- if ($ext = fw()->extensions->get($req_ext)) {
3609
- if (empty($req_ext_data['min_version']) && empty($req_ext_data['max_version'])) {
3610
- continue;
3611
- }
3612
-
3613
- if ( ! empty( $req_ext_data['min_version'] ) ) {
3614
- if (!version_compare($req_ext_data['min_version'], $ext->manifest->get_version(), '<=')) {
3615
- if ($can_install) {
3616
- $result[] = sprintf(
3617
- __('You need to update the %s extension to %s: %s', 'fw'),
3618
- $ext->manifest->get_name(),
3619
- $req_ext_data['min_version'],
3620
- fw_html_tag('a', array('href' => self_admin_url('update-core.php')),
3621
- sprintf(__('Update %s', 'fw'), $ext->manifest->get_name())
3622
- )
3623
- );
3624
- } else {
3625
- $result[] = sprintf(
3626
- __('The %s extension needs to be updated to %s', 'fw'),
3627
- $ext->manifest->get_name(),
3628
- $req_ext_data['min_version']
3629
- );
3630
- }
3631
- }
3632
- }
3633
-
3634
- if ( ! empty( $req_ext_data['max_version'] ) ) {
3635
- if (!version_compare($req_ext_data['max_version'], $ext->manifest->get_version(), '>=')) {
3636
- $result[] = sprintf(
3637
- __( 'Maximum supported %s extension version is %s', 'fw' ),
3638
- $ext->manifest->get_name(),
3639
- $req_ext_data['max_version']
3640
- );
3641
- }
3642
- }
3643
- } else {
3644
- $ext_title = fw_id_to_title($req_ext);
3645
-
3646
- if (isset($lists['installed'][$req_ext])) {
3647
- $ext_title = fw_akg('name', $lists['installed'][$req_ext]['manifest'], $ext_title);
3648
-
3649
- ob_start(); ?>
3650
- <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($req_ext) ?>" method="post" style="display: inline;">
3651
- <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
3652
- <?php echo sprintf(__( 'The %s extension is disabled', 'fw' ), $ext_title); ?>:
3653
- <a href="#" onclick="jQuery(this).closest('form').submit(); return false;"><?php echo sprintf(__('Activate %s', 'fw'), $ext_title); ?></a>
3654
- </form>
3655
- <?php
3656
- $result[] = ob_get_clean();
3657
- } else {
3658
- if ($can_install && isset($lists['available'][$req_ext])) {
3659
- $ext_title = $lists['available'][ $req_ext ]['name'];
3660
-
3661
- $result[] = sprintf(
3662
- __( 'The %s extension is not installed: %s', 'fw' ),
3663
- $ext_title,
3664
- fw_html_tag( 'a', array( 'href' => $link . '&sub-page=install&extension=' . $req_ext ),
3665
- sprintf( __( 'Install %s', 'fw' ), $ext_title )
3666
- )
3667
- );
3668
- } else {
3669
- $result[] = sprintf(
3670
- __( 'The %s extension is not installed', 'fw' ),
3671
- $ext_title
3672
- );
3673
- }
3674
- }
3675
- }
3676
- }
3677
-
3678
- break;
3679
-
3680
- default:
3681
- trigger_error('Invalid requirement: '. $req_name, E_USER_WARNING);
3682
- continue;
3683
- }
3684
- }
3685
-
3686
- return $result;
3687
- }
3688
- }
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
+ if (! apply_filters('fw_backend_enable_custom_extensions_menu', true)) {
750
+ return;
751
+ }
752
+
753
+ /**
754
+ * Use this action if you what to add the extensions page in a custom place in menu
755
+ * Usage example http://pastebin.com/2iWVRPAU
756
+ */
757
+ do_action('fw_backend_add_custom_extensions_menu', $data);
758
+
759
+ /**
760
+ * Check if menu was added in the action above
761
+ */
762
+ {
763
+ $menu_exists = false;
764
+
765
+ if (!empty($_registered_pages)) {
766
+ foreach ( $_registered_pages as $hookname => $b ) {
767
+ if (isset($found_hooknames[$hookname])) {
768
+ continue;
769
+ }
770
+
771
+ if ( strpos( $hookname, $data['slug'] ) !== false ) {
772
+ $menu_exists = true;
773
+ break;
774
+ }
775
+ }
776
+ }
777
+ }
778
+
779
+ if ($menu_exists) {
780
+ // do nothing
781
+ } else {
782
+ add_menu_page(
783
+ $data['title'],
784
+ $data['title'],
785
+ $data['capability'],
786
+ $data['slug'],
787
+ $data['content_callback'],
788
+ 'none',
789
+ 3
790
+ );
791
+ }
792
+ }
793
+
794
+ /**
795
+ * If output already started, we cannot set the redirect header, do redirect from js
796
+ */
797
+ private function js_redirect()
798
+ {
799
+ echo
800
+ '<script type="text/javascript">'.
801
+ 'window.location.replace("'. esc_js($this->get_link()) .'");'.
802
+ '</script>';
803
+ }
804
+
805
+ /**
806
+ * @internal
807
+ */
808
+ public function _display_page()
809
+ {
810
+ $page = FW_Request::GET('sub-page');
811
+
812
+ switch ($page) {
813
+ case 'install':
814
+ $this->display_install_page();
815
+ break;
816
+ case 'delete':
817
+ $this->display_delete_page();
818
+ break;
819
+ case 'extension':
820
+ $this->display_extension_page();
821
+ break;
822
+ case 'activate':
823
+ $this->display_activate_page();
824
+ break;
825
+ case 'deactivate':
826
+ $this->display_deactivate_page();
827
+ break;
828
+ default:
829
+ $this->display_list_page();
830
+ }
831
+ }
832
+
833
+ private function display_list_page()
834
+ {
835
+ // note: static is enqueued in 'admin_enqueue_scripts' action
836
+
837
+ /** Prepare extensions list for view */
838
+ {
839
+ $lists = array(
840
+ 'active' => array(),
841
+ 'disabled' => array(),
842
+ 'installed' => array(),
843
+ 'available' => array(),
844
+ 'supported' => array(),
845
+ );
846
+
847
+ foreach ($this->get_installed_extensions() as $ext_name => $ext_data) {
848
+ $lists[ $ext_data['active'] ? 'active' : 'disabled' ][$ext_name] = $ext_data;
849
+ }
850
+
851
+ $lists['installed'] = $lists['active'] + $lists['disabled'];
852
+
853
+ unset($ext_data); // prevent change by reference
854
+
855
+ foreach ($this->get_available_extensions() as $ext_name => $ext_data) {
856
+ $lists['available'][$ext_name] = array(
857
+ 'name' => $ext_data['name'],
858
+ 'description' => $ext_data['description'],
859
+ 'thumbnail' => isset($ext_data['thumbnail'])
860
+ ? $ext_data['thumbnail']
861
+ : (isset($lists['installed'][$ext_name])
862
+ ? fw_akg('thumbnail', $lists['installed'][$ext_name]['manifest'], $this->default_thumbnail)
863
+ : $this->default_thumbnail),
864
+ 'display' => isset($ext_data['display'])
865
+ ? $ext_data['display']
866
+ : $this->manifest_default_values['display'],
867
+ 'theme' => isset($ext_data['theme']) && $ext_data['theme'],
868
+ 'download' => isset( $ext_data['download'] ) ? $ext_data['download'] : array()
869
+ );
870
+
871
+ if ($lists['available'][$ext_name]['theme']) {
872
+ $lists['supported'][$ext_name] = array(
873
+ 'name' => $lists['available'][$ext_name]['name'],
874
+ 'description' => $lists['available'][$ext_name]['description'],
875
+ );
876
+ }
877
+ }
878
+
879
+ foreach (fw()->theme->manifest->get('supported_extensions', array()) as $required_ext_name => $required_ext_data) {
880
+ if (isset($lists['installed'][ $required_ext_name ])) {
881
+ $lists['supported'][ $required_ext_name ] = array(
882
+ 'name' => fw_akg( 'name', $lists['installed'][ $required_ext_name ]['manifest'], fw_id_to_title( $required_ext_name ) ),
883
+ 'description' => fw_akg( 'description', $lists['installed'][ $required_ext_name ]['manifest'], '' ),
884
+ );
885
+ } elseif (isset($lists['available'][$required_ext_name])) {
886
+ $lists['supported'][ $required_ext_name ] = array(
887
+ 'name' => $lists['available'][ $required_ext_name ]['name'],
888
+ 'description' => $lists['available'][ $required_ext_name ]['description'],
889
+ );
890
+ } else {
891
+ $lists['supported'][ $required_ext_name ] = array(
892
+ 'name' => fw_id_to_title( $required_ext_name ),
893
+ 'description' => '',
894
+ );
895
+ }
896
+ }
897
+ }
898
+
899
+ echo '<div class="wrap">';
900
+
901
+ echo '<h2>'. sprintf(__('%s Extensions', 'fw'), fw()->manifest->get_name()) .'</h2><br/>';
902
+
903
+ echo '<div id="fw-extensions-list-wrapper">';
904
+
905
+ fw_render_view(dirname(__FILE__) .'/views/extensions-page.php', array(
906
+ 'lists' => &$lists,
907
+ 'link' => $this->get_link(),
908
+ 'display_default_value' => $this->manifest_default_values['display'],
909
+ 'default_thumbnail' => $this->default_thumbnail,
910
+ 'nonces' => array(
911
+ 'delete' => $this->get_nonce('delete'),
912
+ 'install' => $this->get_nonce('install'),
913
+ 'activate' => $this->get_nonce('activate'),
914
+ 'deactivate' => $this->get_nonce('deactivate'),
915
+ ),
916
+ 'can_install' => $this->can_install(),
917
+ ), false);
918
+
919
+ echo '</div>';
920
+
921
+ echo '</div>';
922
+ }
923
+
924
+ private function display_install_page()
925
+ {
926
+ $flash_id = 'fw_extensions_install';
927
+
928
+ if (!$this->can_install()) {
929
+ FW_Flash_Messages::add(
930
+ $flash_id,
931
+ __('You are not allowed to install extensions.', 'fw'),
932
+ 'error'
933
+ );
934
+ $this->js_redirect();
935
+ return;
936
+ }
937
+
938
+ if (array_key_exists('supported', $_GET)) {
939
+ $supported = true;
940
+ $extensions = array_fill_keys(
941
+ array_keys($this->get_supported_extensions_for_install()),
942
+ array()
943
+ );
944
+
945
+ if (empty($extensions)) {
946
+ FW_Flash_Messages::add(
947
+ $flash_id,
948
+ __('All supported extensions are already installed.', 'fw'),
949
+ 'info'
950
+ );
951
+ $this->js_redirect();
952
+ return;
953
+ }
954
+ } else {
955
+ $supported = false;
956
+
957
+ $extensions = array_fill_keys(
958
+ array_map( 'trim', explode( ',', FW_Request::GET( 'extension', '' ) )),
959
+ array()
960
+ );
961
+
962
+ // activate already installed extensions
963
+ $this->activate_extensions($extensions);
964
+ }
965
+
966
+ {
967
+ $skin = new _FW_Extensions_Install_Upgrader_Skin(array(
968
+ 'title' => $supported
969
+ ? _n('Install Compatible Extension', 'Install Compatible Extensions', count($extensions), 'fw')
970
+ : _n('Install Extension', 'Install Extensions', count($extensions), 'fw'),
971
+ ));
972
+ }
973
+
974
+ $skin->header();
975
+
976
+ do {
977
+ $nonce = $this->get_nonce('install');
978
+
979
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
980
+ if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
981
+ $skin->error(__('Invalid nonce.', 'fw'));
982
+ break;
983
+ }
984
+
985
+ if (!FW_WP_Filesystem::request_access(
986
+ fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
987
+ )) {
988
+ break;
989
+ }
990
+
991
+ $install_result = $this->install_extensions($extensions, array('verbose' => $skin));
992
+
993
+ if (is_wp_error($install_result)) {
994
+ $skin->error($install_result);
995
+ } elseif (is_array($install_result)) {
996
+ $error = array();
997
+
998
+ foreach ($install_result as $extension_name => $extension_result) {
999
+ if (is_wp_error($extension_result)) {
1000
+ $error[] = $extension_result->get_error_message();
1001
+ }
1002
+ }
1003
+
1004
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1005
+
1006
+ $skin->error($error);
1007
+ } elseif ($install_result === true) {
1008
+ $skin->set_result(true);
1009
+ }
1010
+
1011
+ /** @var WP_Filesystem_Base $wp_filesystem */
1012
+ global $wp_filesystem;
1013
+
1014
+ $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
1015
+
1016
+ if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
1017
+ if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
1018
+ $skin->error(
1019
+ sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
1020
+ );
1021
+ }
1022
+ }
1023
+
1024
+ $skin->after(array(
1025
+ 'extensions_page_link' => $this->get_link()
1026
+ ));
1027
+
1028
+ if ($supported && $install_result === true) {
1029
+ /**
1030
+ * @since 2.6.14
1031
+ * Fixes https://github.com/ThemeFuse/Unyson/issues/2330
1032
+ */
1033
+ do_action( 'fw_after_supported_extensions_install_success' );
1034
+ }
1035
+ } else {
1036
+ echo '<form method="post">';
1037
+
1038
+ wp_nonce_field($nonce['action'], $nonce['name']);
1039
+
1040
+ $extension_titles = array();
1041
+ foreach ($extensions as $extension_name => $not_used_var) {
1042
+ $extension_titles[$extension_name] = $this->get_extension_title($extension_name);
1043
+ }
1044
+
1045
+ fw_render_view(dirname(__FILE__) .'/views/install-form.php', array(
1046
+ 'extension_titles' => $extension_titles,
1047
+ 'list_page_link' => $this->get_link(),
1048
+ 'supported' => $supported
1049
+ ), false);
1050
+
1051
+ echo '</form>';
1052
+ }
1053
+ } while(false);
1054
+
1055
+ $skin->footer();
1056
+ }
1057
+
1058
+ /**
1059
+ * Download (and activate) extensions
1060
+ * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1061
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1062
+ * @param array $opts
1063
+ * @return WP_Error|bool|array
1064
+ * true: when all extensions succeeded
1065
+ * array: when some/all failed
1066
+ */
1067
+ public function install_extensions( array $extensions, $opts = array() ) {
1068
+ {
1069
+ $opts = array_merge( array(
1070
+ /**
1071
+ * @type bool
1072
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1073
+ * true: return first WP_Error or true on success
1074
+ */
1075
+ 'cancel_on_error' => false,
1076
+ /**
1077
+ * @type bool Activate installed extensions
1078
+ */
1079
+ 'activate' => true,
1080
+ /**
1081
+ * @type bool|WP_Upgrader_Skin
1082
+ */
1083
+ 'verbose' => false,
1084
+ ), $opts );
1085
+
1086
+ $cancel_on_error = $opts['cancel_on_error']; // fixme: remove successfully installed extensions before error?
1087
+ $activate = $opts['activate'];
1088
+ $verbose = $opts['verbose'];
1089
+
1090
+ unset( $opts );
1091
+ }
1092
+
1093
+ if ( ! $this->can_install() ) {
1094
+ return new WP_Error(
1095
+ 'access_denied',
1096
+ __( 'You have no permissions to install extensions', 'fw' )
1097
+ );
1098
+ }
1099
+
1100
+ if ( empty( $extensions ) ) {
1101
+ return new WP_Error(
1102
+ 'no_extensions',
1103
+ __( 'No extensions provided', 'fw' )
1104
+ );
1105
+ }
1106
+
1107
+ if ( ! FW_WP_Filesystem::is_ready() ) {
1108
+ return new WP_Error( 'fs_not_initialized', esc_html__( 'WP Filesystem is not initialized', 'fw' ) );
1109
+ }
1110
+
1111
+ $timeout = function_exists( 'ini_get' ) ? intval( ini_get( 'max_execution_time' ) ) : false;
1112
+ $available_extensions = $this->get_available_extensions();
1113
+ $installed_extensions = $this->get_installed_extensions();
1114
+ $result = $downloaded_extensions = array();
1115
+ $has_errors = false;
1116
+
1117
+ while ( ! empty( $extensions ) ) {
1118
+ reset( $extensions );
1119
+ $extension_name = key( $extensions );
1120
+ unset( $extensions[ $extension_name ] );
1121
+
1122
+ $extensions_before_install = array_keys( $installed_extensions );
1123
+
1124
+ if ( isset( $installed_extensions[ $extension_name ] ) ) {
1125
+ $result[ $extension_name ] = new WP_Error(
1126
+ 'extension_installed',
1127
+ sprintf( __( 'Extension "%s" is already installed.', 'fw' ), $this->get_extension_title( $extension_name ) )
1128
+ );
1129
+ $has_errors = true;
1130
+
1131
+ if ( $cancel_on_error ) {
1132
+ break;
1133
+ }
1134
+ }
1135
+
1136
+ if ( ! isset( $available_extensions[ $extension_name ] ) ) {
1137
+ $result[ $extension_name ] = new WP_Error(
1138
+ 'extension_not_available',
1139
+ sprintf(
1140
+ __( 'Extension "%s" is not available for install.', 'fw' ),
1141
+ $this->get_extension_title( $extension_name )
1142
+ )
1143
+ );
1144
+ $has_errors = true;
1145
+
1146
+ if ( $cancel_on_error ) {
1147
+ break;
1148
+ }
1149
+ }
1150
+
1151
+ /**
1152
+ * Find parent extensions
1153
+ * they will be installed if does not exist
1154
+ */
1155
+ {
1156
+ $parents = array( $extension_name );
1157
+
1158
+ $current_parent = $extension_name;
1159
+ while ( ! empty( $available_extensions[ $current_parent ]['parent'] ) ) {
1160
+ $current_parent = $available_extensions[ $current_parent ]['parent'];
1161
+
1162
+ if ( ! isset( $available_extensions[ $current_parent ] ) ) {
1163
+ $result[ $extension_name ] = new WP_Error(
1164
+ 'parent_extension_not_available',
1165
+ sprintf(
1166
+ __( 'Parent extension "%s" not available.', 'fw' ),
1167
+ $this->get_extension_title( $current_parent )
1168
+ )
1169
+ );
1170
+ $has_errors = true;
1171
+
1172
+ if ( $cancel_on_error ) {
1173
+ break 2;
1174
+ } else {
1175
+ continue 2;
1176
+ }
1177
+ }
1178
+
1179
+ $parents[] = $current_parent;
1180
+ }
1181
+
1182
+ $parents = array_reverse( $parents );
1183
+ }
1184
+
1185
+ /**
1186
+ * Install parent extensions and the extension
1187
+ */
1188
+ {
1189
+ $destination_path = array(
1190
+ 'framework' => fw_get_framework_directory(),
1191
+ 'theme' => fw_fix_path( get_template_directory() ) . fw_get_framework_customizations_dir_rel_path(),
1192
+ );
1193
+ $current_extension_path = '';
1194
+
1195
+ foreach ( $parents as $parent_extension_name ) {
1196
+ $current_extension_path .= '/extensions/' . $parent_extension_name;
1197
+ $set = $available_extensions[ $parent_extension_name ];
1198
+ $destination = isset( $set['theme'] ) && $set['theme'] ? 'theme' : 'framework';
1199
+
1200
+ if ( isset( $installed_extensions[ $parent_extension_name ] ) ) {
1201
+ continue; // skip already installed extensions
1202
+ }
1203
+
1204
+ if ( $verbose ) {
1205
+ $verbose_message = sprintf( esc_html__( 'Downloading the "%s" extension...', 'fw' ), $this->get_extension_title( $parent_extension_name ) );
1206
+
1207
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1208
+ $verbose->feedback( $verbose_message );
1209
+ } else {
1210
+ echo fw_html_tag( 'p', array(), $verbose_message );
1211
+ }
1212
+ }
1213
+
1214
+ // increase timeout
1215
+ if ( $timeout !== false && function_exists( 'set_time_limit' ) ) {
1216
+ $timeout += 30;
1217
+ set_time_limit( $timeout );
1218
+ }
1219
+
1220
+ // If is plugin will returned downloadable link zip.
1221
+ $wp_fw_downloaded_dir = $this->download( $parent_extension_name, $set );
1222
+
1223
+ if ( is_wp_error( $wp_fw_downloaded_dir ) ) {
1224
+ if ( $verbose ) {
1225
+ $verbose_message = $wp_fw_downloaded_dir->get_error_message();
1226
+
1227
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1228
+ $verbose->error( $verbose_message );
1229
+ } else {
1230
+ echo fw_html_tag( 'p', array(), $verbose_message );
1231
+ }
1232
+ }
1233
+
1234
+ $result[ $extension_name ] = $wp_fw_downloaded_dir;
1235
+ $has_errors = true;
1236
+
1237
+ if ( $cancel_on_error ) {
1238
+ break 2;
1239
+ } else {
1240
+ continue 2;
1241
+ }
1242
+ }
1243
+
1244
+ if ( $verbose ) {
1245
+ $verbose_message = sprintf( esc_html__( 'Installing the "%s" extension...', 'fw' ), $this->get_extension_title( $parent_extension_name ) );
1246
+
1247
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1248
+ $verbose->feedback( $verbose_message );
1249
+ } else {
1250
+ echo fw_html_tag( 'p', array(), $verbose_message );
1251
+ }
1252
+ }
1253
+
1254
+ // Merge directories only for extensions. If we have plugin it will installed via Plugin_Upgrader.
1255
+ if ( empty( $set['download']['opts']['plugin'] ) ) {
1256
+ $merge_result = $this->merge_extension(
1257
+ $wp_fw_downloaded_dir,
1258
+ FW_WP_Filesystem::real_path_to_filesystem_path( $destination_path[ $destination ] . $current_extension_path )
1259
+ );
1260
+
1261
+ if ( is_wp_error( $merge_result ) ) {
1262
+ if ( $verbose ) {
1263
+ $verbose_message = $merge_result->get_error_message();
1264
+
1265
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1266
+ $verbose->error( $verbose_message );
1267
+ } else {
1268
+ echo fw_html_tag( 'p', array(), $verbose_message );
1269
+ }
1270
+ }
1271
+
1272
+ $result[ $extension_name ] = $merge_result;
1273
+ $has_errors = true;
1274
+
1275
+ if ( $cancel_on_error ) {
1276
+ break 2;
1277
+ } else {
1278
+ continue 2;
1279
+ }
1280
+ }
1281
+ }
1282
+
1283
+ if ( $verbose ) {
1284
+ $verbose_message = sprintf( __( 'The %s extension has been successfully installed.', 'fw' ),
1285
+ $this->get_extension_title( $parent_extension_name )
1286
+ );
1287
+
1288
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1289
+ $verbose->feedback( $verbose_message );
1290
+ } else {
1291
+ echo fw_html_tag( 'p', array(), $verbose_message );
1292
+ }
1293
+ }
1294
+
1295
+ $downloaded_extensions[ $parent_extension_name ] = array();
1296
+
1297
+ /**
1298
+ * Read again all extensions
1299
+ * The downloaded extension may contain more sub extensions
1300
+ */
1301
+ {
1302
+ unset( $installed_extensions );
1303
+ $installed_extensions = $this->get_installed_extensions( true );
1304
+ }
1305
+ }
1306
+ }
1307
+
1308
+ $result[ $extension_name ] = true;
1309
+
1310
+ /**
1311
+ * Collect required extensions of the newly installed extensions
1312
+ */
1313
+ foreach (
1314
+ // new extensions
1315
+ array_diff(
1316
+ array_keys( $installed_extensions ),
1317
+ $extensions_before_install
1318
+ )
1319
+ as $new_extension_name
1320
+ ) {
1321
+ foreach (
1322
+ array_keys(
1323
+ fw_akg(
1324
+ 'requirements/extensions',
1325
+ $installed_extensions[ $new_extension_name ]['manifest'],
1326
+ array()
1327
+ )
1328
+ )
1329
+ as $required_extension_name
1330
+ ) {
1331
+ if ( isset( $installed_extensions[ $required_extension_name ] ) ) {
1332
+ // already installed
1333
+ continue;
1334
+ }
1335
+
1336
+ $extensions[ $required_extension_name ] = array();
1337
+ }
1338
+ }
1339
+ }
1340
+
1341
+ if ( $activate ) {
1342
+ $activate_extensions = array();
1343
+
1344
+ foreach ( $result as $extension_name => $extension_result ) {
1345
+ if ( ! is_wp_error( $extension_result ) ) {
1346
+ $activate_extensions[ $extension_name ] = array();
1347
+ }
1348
+ }
1349
+
1350
+ if ( ! empty( $activate_extensions ) ) {
1351
+ if ( $verbose ) {
1352
+ $verbose_message = _n(
1353
+ 'Activating extension...',
1354
+ 'Activating extensions...',
1355
+ count( $activate_extensions ),
1356
+ 'fw'
1357
+ );
1358
+
1359
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1360
+ $verbose->feedback( $verbose_message );
1361
+ } else {
1362
+ echo fw_html_tag( 'p', array(), $verbose_message );
1363
+ }
1364
+ }
1365
+
1366
+ $activation_result = $this->activate_extensions( $activate_extensions );
1367
+
1368
+ if ( $verbose ) {
1369
+ if ( is_wp_error( $activation_result ) ) {
1370
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1371
+ $verbose->error( $activation_result->get_error_message() );
1372
+ } else {
1373
+ echo fw_html_tag( 'p', array(), $activation_result->get_error_message() );
1374
+ }
1375
+ } elseif ( is_array( $activation_result ) ) {
1376
+ $verbose_message = array();
1377
+
1378
+ foreach ( $activation_result as $extension_name => $extension_result ) {
1379
+ if ( is_wp_error( $extension_result ) ) {
1380
+ $verbose_message[] = $extension_result->get_error_message();
1381
+ }
1382
+ }
1383
+
1384
+ $verbose_message = '<ul><li>' . implode( '</li><li>', $verbose_message ) . '</li></ul>';
1385
+
1386
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1387
+ $verbose->error( $verbose_message );
1388
+ } else {
1389
+ echo fw_html_tag( 'p', array(), $verbose_message );
1390
+ }
1391
+ } elseif ( $activation_result === true ) {
1392
+ $verbose_message = _n(
1393
+ 'Extension has been successfully activated.',
1394
+ 'Extensions has been successfully activated.',
1395
+ count( $activate_extensions ),
1396
+ 'fw'
1397
+ );
1398
+
1399
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1400
+ $verbose->feedback( $verbose_message );
1401
+ } else {
1402
+ echo fw_html_tag( 'p', array(), $verbose_message );
1403
+ }
1404
+ }
1405
+ }
1406
+ }
1407
+ }
1408
+
1409
+ do_action( 'fw_extensions_install', $result );
1410
+
1411
+ if ( $cancel_on_error && $has_errors ) {
1412
+ if ( ( $last_result = end( $result ) ) && is_wp_error( $last_result ) ) {
1413
+ return $last_result;
1414
+ } else {
1415
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1416
+ return new WP_Error(
1417
+ 'installation_failed',
1418
+ _n( 'Cannot install extension', 'Cannot install extensions', count( $extensions ), 'fw' )
1419
+ );
1420
+ }
1421
+ }
1422
+
1423
+ return $has_errors ? $result : true;
1424
+ }
1425
+
1426
+ private function display_delete_page()
1427
+ {
1428
+ $flash_id = 'fw_extensions_delete';
1429
+
1430
+ if (!$this->can_install()) {
1431
+ FW_Flash_Messages::add(
1432
+ $flash_id,
1433
+ __('You are not allowed to delete extensions.', 'fw'),
1434
+ 'error'
1435
+ );
1436
+ $this->js_redirect();
1437
+ return;
1438
+ }
1439
+
1440
+ $extensions = array_fill_keys(array_map('trim', explode(',', FW_Request::GET('extension', ''))), array());
1441
+
1442
+ {
1443
+ $skin = new _FW_Extensions_Delete_Upgrader_Skin(array(
1444
+ 'title' => _n('Delete Extension', 'Delete Extensions', count($extensions), 'fw'),
1445
+ ));
1446
+ }
1447
+
1448
+ $skin->header();
1449
+
1450
+ do {
1451
+ $nonce = $this->get_nonce('delete');
1452
+
1453
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1454
+ if (!isset($_POST[$nonce['name']]) || !wp_verify_nonce($_POST[$nonce['name']], $nonce['action'])) {
1455
+ $skin->error(__('Invalid nonce.', 'fw'));
1456
+ break;
1457
+ }
1458
+
1459
+ if (!FW_WP_Filesystem::request_access(
1460
+ fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce['name'])
1461
+ )) {
1462
+ break;
1463
+ }
1464
+
1465
+ $uninstall_result = $this->uninstall_extensions($extensions, array('verbose' => $skin));
1466
+
1467
+ if (is_wp_error($uninstall_result)) {
1468
+ $skin->error($uninstall_result);
1469
+ } elseif (is_array($uninstall_result)) {
1470
+ $error = array();
1471
+
1472
+ foreach ($uninstall_result as $extension_name => $extension_result) {
1473
+ if (is_wp_error($extension_result)) {
1474
+ $error[] = $extension_result->get_error_message();
1475
+ }
1476
+ }
1477
+
1478
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1479
+
1480
+ $skin->error($error);
1481
+ } elseif ($uninstall_result === true) {
1482
+ $skin->set_result(true);
1483
+ }
1484
+
1485
+ $skin->after(array(
1486
+ 'extensions_page_link' => $this->get_link()
1487
+ ));
1488
+ } else {
1489
+ echo '<form method="post">';
1490
+
1491
+ wp_nonce_field($nonce['action'], $nonce['name']);
1492
+
1493
+ fw_render_view(dirname(__FILE__) .'/views/delete-form.php', array(
1494
+ 'extension_names' => array_keys($extensions),
1495
+ 'installed_extensions' => $this->get_installed_extensions(),
1496
+ 'list_page_link' => $this->get_link(),
1497
+ ), false);
1498
+
1499
+ echo '</form>';
1500
+ }
1501
+ } while(false);
1502
+
1503
+ $skin->footer();
1504
+ }
1505
+
1506
+ /**
1507
+ * Remove extensions
1508
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1509
+ * @param array $opts
1510
+ * @return WP_Error|bool|array
1511
+ * true: when all extensions succeeded
1512
+ * array: when some/all failed
1513
+ */
1514
+ public function uninstall_extensions(array $extensions, $opts = array())
1515
+ {
1516
+ {
1517
+ $opts = array_merge(array(
1518
+ /**
1519
+ * @type bool
1520
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1521
+ * true: return first WP_Error or true on success
1522
+ */
1523
+ 'cancel_on_error' => false,
1524
+ /**
1525
+ * @type bool|WP_Upgrader_Skin
1526
+ */
1527
+ 'verbose' => false,
1528
+ ), $opts);
1529
+
1530
+ $cancel_on_error = $opts['cancel_on_error']; // fixme: install back successfully removed extensions before error?
1531
+ $verbose = $opts['verbose'];
1532
+
1533
+ unset($opts);
1534
+ }
1535
+
1536
+ if (!$this->can_install()) {
1537
+ return new WP_Error(
1538
+ 'access_denied',
1539
+ __('You have no permissions to uninstall extensions', 'fw')
1540
+ );
1541
+ }
1542
+
1543
+ if (empty($extensions)) {
1544
+ return new WP_Error(
1545
+ 'no_extensions',
1546
+ __('No extensions provided', 'fw')
1547
+ );
1548
+ }
1549
+
1550
+ /** @var WP_Filesystem_Base $wp_filesystem */
1551
+ global $wp_filesystem;
1552
+
1553
+ if (!FW_WP_Filesystem::is_ready()) {
1554
+ return new WP_Error(
1555
+ 'fs_not_initialized',
1556
+ __('WP Filesystem is not initialized', 'fw')
1557
+ );
1558
+ }
1559
+
1560
+ $installed_extensions = $this->get_installed_extensions();
1561
+ $available_extensions = $this->get_available_extensions();
1562
+ $extensions_before_uninstall = array_fill_keys( array_keys( $installed_extensions ), array() );
1563
+
1564
+ $result = $uninstalled_extensions = array();
1565
+ $has_errors = false;
1566
+
1567
+ while ( ! empty( $extensions ) ) {
1568
+
1569
+ reset( $extensions );
1570
+ $extension_name = key( $extensions );
1571
+ unset( $extensions[ $extension_name ] );
1572
+
1573
+ if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
1574
+ $unistall = delete_plugins( (array) $available_extensions[ $extension_name ]['download']['opts']['plugin'] );
1575
+ $plugin_title = $available_extensions[ $extension_name ]['name'];
1576
+
1577
+ if ( $unistall ) {
1578
+ $this->verbose( sprintf( esc_html__( 'Extension "%s" has been deleted.', 'fw' ), $plugin_title ), $verbose );
1579
+ $result[ $extension_name ] = true;
1580
+ } else {
1581
+ if ( is_wp_error( $unistall ) ) {
1582
+ $msg_error = $unistall->get_error_message() . ' - ' . $plugin_title;
1583
+ } else {
1584
+ $msg_error = sprintf( esc_html__( 'Plugin %s is empty.' ), $plugin_title );
1585
+ }
1586
+
1587
+ $result[ $extension_name ] = new WP_Error( 'fw_delete_plugins', $msg_error, $plugin_title );
1588
+ $has_errors = true;
1589
+
1590
+ if ( $cancel_on_error ) {
1591
+ break;
1592
+ } else {
1593
+ continue;
1594
+ }
1595
+ }
1596
+
1597
+ continue;
1598
+ }
1599
+
1600
+ $extension_title = $this->get_extension_title($extension_name);
1601
+
1602
+ if (!isset($installed_extensions[ $extension_name ])) {
1603
+ // already deleted
1604
+ $result[$extension_name] = true;
1605
+ continue;
1606
+ }
1607
+
1608
+ if (
1609
+ !isset($installed_extensions[ $extension_name ]['path'])
1610
+ ||
1611
+ empty($installed_extensions[ $extension_name ]['path'])
1612
+ ) {
1613
+ /**
1614
+ * This happens sometimes, but I don't know why
1615
+ * If the script will continue, it will delete the root folder
1616
+ */
1617
+ fw_print(
1618
+ 'Please report this to https://github.com/ThemeFuse/Unyson/issues',
1619
+ $extension_name,
1620
+ $installed_extensions
1621
+ );
1622
+ die;
1623
+ }
1624
+
1625
+ $wp_fs_extension_path = FW_WP_Filesystem::real_path_to_filesystem_path(
1626
+ $installed_extensions[ $extension_name ]['path']
1627
+ );
1628
+
1629
+ if (!$wp_filesystem->exists($wp_fs_extension_path)) {
1630
+ // already deleted, maybe because it was a sub-extension of an deleted extension
1631
+ $result[$extension_name] = true;
1632
+ continue;
1633
+ }
1634
+
1635
+ $this->verbose( sprintf( esc_html__( 'Deleting the "%s" extension...', 'fw' ), $extension_title ), $verbose );
1636
+
1637
+ if (!$wp_filesystem->delete($wp_fs_extension_path, true, 'd')) {
1638
+ $result[$extension_name] = new WP_Error(
1639
+ 'cannot_delete_directory',
1640
+ sprintf(__('Cannot delete the "%s" extension.', 'fw'), $extension_title)
1641
+ );
1642
+ $has_errors = true;
1643
+
1644
+ if ($cancel_on_error) {
1645
+ break;
1646
+ } else {
1647
+ continue;
1648
+ }
1649
+ } else {
1650
+ $this->verbose( sprintf( esc_html__( 'The %s extension has been successfully deleted.', 'fw' ), $extension_title ), $verbose );
1651
+ $result[$extension_name] = true;
1652
+ }
1653
+
1654
+ /**
1655
+ * Read again all extensions
1656
+ * The delete extension may contain more sub extensions
1657
+ */
1658
+ {
1659
+ unset($installed_extensions);
1660
+ $installed_extensions = $this->get_installed_extensions(true);
1661
+ }
1662
+
1663
+ /**
1664
+ * Add for deletion not used extensions
1665
+ * For e.g. standalone=false extension that were required by the deleted extension
1666
+ * and now are not required by any other extension
1667
+ */
1668
+ {
1669
+ $not_used_extensions = array_fill_keys(
1670
+ array_keys(
1671
+ array_diff_key(
1672
+ $installed_extensions,
1673
+ $this->get_used_extensions($extensions, array_keys($installed_extensions))
1674
+ )
1675
+ ),
1676
+ array()
1677
+ );
1678
+
1679
+ $extensions = array_merge($extensions, $not_used_extensions);
1680
+ }
1681
+ }
1682
+
1683
+ do_action('fw_extensions_uninstall', $result);
1684
+
1685
+ if (
1686
+ $cancel_on_error
1687
+ &&
1688
+ $has_errors
1689
+ ) {
1690
+ if (
1691
+ ($last_result = end($result))
1692
+ &&
1693
+ is_wp_error($last_result)
1694
+ ) {
1695
+ return $last_result;
1696
+ } else {
1697
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
1698
+ return new WP_Error(
1699
+ 'uninstall_failed',
1700
+ _n('Cannot uninstall extension', 'Cannot uninstall extensions', count($extensions), 'fw')
1701
+ );
1702
+ }
1703
+ }
1704
+
1705
+ // remove from active list the deleted extensions
1706
+ {
1707
+ update_option(
1708
+ fw()->extensions->_get_active_extensions_db_option_name(),
1709
+ array_diff_key(
1710
+ fw()->extensions->_get_db_active_extensions(),
1711
+ array_diff_key(
1712
+ $extensions_before_uninstall,
1713
+ $installed_extensions
1714
+ )
1715
+ )
1716
+ );
1717
+ }
1718
+
1719
+ if ($has_errors) {
1720
+ return $result;
1721
+ } else {
1722
+ return true;
1723
+ }
1724
+ }
1725
+
1726
+ public function verbose( $msg, &$verbose ) {
1727
+
1728
+ if ( ! $verbose ) {
1729
+ return;
1730
+ }
1731
+
1732
+ if ( is_subclass_of( $verbose, 'WP_Upgrader_Skin' ) ) {
1733
+ $verbose->feedback( $msg );
1734
+ } else {
1735
+ echo fw_html_tag( 'p', array(), $msg );
1736
+ }
1737
+ }
1738
+
1739
+ private function display_extension_page()
1740
+ {
1741
+ // note: static is enqueued in 'admin_enqueue_scripts' action
1742
+
1743
+ $extension_name = trim(FW_Request::GET('extension', ''));
1744
+
1745
+ $installed_extensions = $this->get_installed_extensions();
1746
+
1747
+ $flash_id = 'fw_extension_page';
1748
+
1749
+ {
1750
+ $error = '';
1751
+
1752
+ do {
1753
+ if (empty($extension_name)) {
1754
+ $error = __('Extension not specified.', 'fw');
1755
+ break;
1756
+ }
1757
+
1758
+ if (!isset($installed_extensions[$extension_name])) {
1759
+ $error = sprintf(__('Extension "%s" is not installed.', 'fw'), $this->get_extension_title($extension_name));
1760
+ break;
1761
+ }
1762
+ } while(false);
1763
+
1764
+ if ($error) {
1765
+ FW_Flash_Messages::add($flash_id, $error, 'error');
1766
+ $this->js_redirect();
1767
+ return;
1768
+ }
1769
+ }
1770
+
1771
+ {
1772
+ $tab = fw_akg('tab', $_GET, 'settings');
1773
+
1774
+ if (!in_array($tab, array('settings', 'docs'))) {
1775
+ $tab = 'settings';
1776
+ }
1777
+ }
1778
+
1779
+ $extension_title = $this->get_extension_title($extension_name);
1780
+ $link = $this->get_link();
1781
+
1782
+ echo '<div class="wrap" id="fw-extension-page">';
1783
+
1784
+ fw_render_view(dirname(__FILE__) .'/views/extension-page-header.php', array(
1785
+ 'extension_name' => $extension_name,
1786
+ 'extension_data' => $installed_extensions[$extension_name],
1787
+ 'link_delete' => $link .'&sub-page=delete',
1788
+ 'link_extension' => $link .'&sub-page=extension',
1789
+ 'extension_title' => $extension_title,
1790
+ 'tab' => $tab,
1791
+ 'is_supported' =>
1792
+ fw()->theme->manifest->get('supported_extensions/'. $extension_name, false) !== false
1793
+ ||
1794
+ $installed_extensions[$extension_name]['is']['theme']
1795
+ ), false);
1796
+
1797
+ unset($installed_extensions);
1798
+
1799
+ echo '<div id="fw-extension-tab-content">';
1800
+ {
1801
+ $method_data = array();
1802
+
1803
+ switch ($tab) {
1804
+ case 'settings':
1805
+ $error = $this->display_extension_settings_page($extension_name, $method_data);
1806
+ break;
1807
+ case 'docs':
1808
+ $error = $this->display_extension_docs_page($extension_name, $method_data);
1809
+ break;
1810
+ }
1811
+ }
1812
+ echo '</div>';
1813
+
1814
+ echo '</div>';
1815
+
1816
+ if ($error) {
1817
+ FW_Flash_Messages::add($flash_id, $error, 'error');
1818
+ $this->js_redirect();
1819
+ return;
1820
+ }
1821
+ }
1822
+
1823
+ private function display_extension_settings_page($extension_name, $data)
1824
+ {
1825
+ if (!fw()->extensions->get($extension_name)) {
1826
+ return sprintf(
1827
+ __('Extension "%s" does not exist or is not active.', 'fw'),
1828
+ fw_htmlspecialchars($extension_name)
1829
+ );
1830
+ }
1831
+
1832
+ $extension = fw()->extensions->get($extension_name);
1833
+
1834
+ if (!$extension->get_settings_options()) {
1835
+ return sprintf(
1836
+ __('%s extension does not have settings.', 'fw'),
1837
+ $extension->manifest->get_name()
1838
+ );
1839
+ }
1840
+
1841
+ echo '<div id="fw-extension-settings">';
1842
+
1843
+ echo $this->extension_settings_form->render(array(
1844
+ 'extension' => $extension,
1845
+ ));
1846
+
1847
+ echo '</div>';
1848
+ }
1849
+
1850
+ private function display_extension_docs_page($extension_name, $data)
1851
+ {
1852
+ $ext = fw_ext($extension_name);
1853
+ $docs = $ext->get_rendered_docs();
1854
+
1855
+ if (! $docs) {
1856
+ return __(
1857
+ 'Extension has no documentation. Maybe ask its developer to write some?',
1858
+ 'fw'
1859
+ );
1860
+ }
1861
+
1862
+ echo fw()->backend->render_box(
1863
+ 'fw-extension-docs',
1864
+ '',
1865
+ fw()->backend->render_options(array(
1866
+ 'docs' => array(
1867
+ 'label' => false,
1868
+ 'type' => 'html-full',
1869
+ 'html' => $docs
1870
+ ),
1871
+ ))
1872
+ );
1873
+ }
1874
+
1875
+ private function display_activate_page()
1876
+ {
1877
+ $error = '';
1878
+
1879
+ do {
1880
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
1881
+ $error = __('Invalid request method.', 'fw');
1882
+ break;
1883
+ }
1884
+
1885
+ $nonce = $this->get_nonce('activate');
1886
+
1887
+ if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
1888
+ $error = __('Invalid nonce.', 'fw');
1889
+ break;
1890
+ }
1891
+
1892
+ if (!isset($_GET['extension'])) {
1893
+ $error = __('No extension specified.', 'fw');
1894
+ break;
1895
+ }
1896
+
1897
+ $activation_result = $this->activate_extensions(
1898
+ array_fill_keys(explode(',', $_GET['extension']), array())
1899
+ );
1900
+
1901
+ if (is_wp_error($activation_result)) {
1902
+ $error = $activation_result->get_error_message();
1903
+ } elseif (is_array($activation_result)) {
1904
+ $error = array();
1905
+
1906
+ foreach ($activation_result as $extension_name => $extension_result) {
1907
+ if (is_wp_error($extension_result)) {
1908
+ $error[] = $extension_result->get_error_message();
1909
+ }
1910
+ }
1911
+
1912
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
1913
+ }
1914
+ } while(false);
1915
+
1916
+ if ($error) {
1917
+ FW_Flash_Messages::add(
1918
+ 'fw_extensions_activate_page',
1919
+ $error,
1920
+ 'error'
1921
+ );
1922
+ $this->js_redirect();
1923
+ return;
1924
+ }
1925
+
1926
+ $this->js_redirect();
1927
+ }
1928
+
1929
+ /**
1930
+ * Add extensions to active extensions list in database
1931
+ * After refresh they should be active, if all dependencies will be met and if parent-extension::_init() will not return false
1932
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
1933
+ * @param bool $cancel_on_error
1934
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
1935
+ * true: return first WP_Error or true on success
1936
+ * @return WP_Error|bool|array
1937
+ * true: when all extensions succeeded
1938
+ * array: when some/all failed
1939
+ */
1940
+ public function activate_extensions(array $extensions, $cancel_on_error = false)
1941
+ {
1942
+ if (!$this->can_activate()) {
1943
+ return new WP_Error(
1944
+ 'access_denied',
1945
+ __('You have no permissions to activate extensions', 'fw')
1946
+ );
1947
+ }
1948
+
1949
+ if (empty($extensions)) {
1950
+ return new WP_Error(
1951
+ 'no_extensions',
1952
+ __('No extensions provided', 'fw')
1953
+ );
1954
+ }
1955
+
1956
+ $installed_extensions = $this->get_installed_extensions();
1957
+ $available_extensions = $this->get_available_extensions();
1958
+
1959
+ $result = $extensions_for_activation = array();
1960
+ $has_errors = false;
1961
+
1962
+ foreach ($extensions as $extension_name => $not_used_var) {
1963
+
1964
+ if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
1965
+
1966
+ $plugin_file = $available_extensions[ $extension_name ]['download']['opts']['plugin'];
1967
+
1968
+ // A small financial support for maintaining the plugin.
1969
+ if ( 'translatepress-multilingual/index.php' === $plugin_file ) {
1970
+ update_option( 'translatepress_affiliate_id', 1 );
1971
+ }
1972
+
1973
+ activate_plugin( $plugin_file );
1974
+
1975
+ continue;
1976
+ }
1977
+
1978
+ if (!isset($installed_extensions[$extension_name])) {
1979
+ $result[$extension_name] = new WP_Error(
1980
+ 'extension_not_installed',
1981
+ sprintf(__('Extension "%s" does not exist.', 'fw'), $this->get_extension_title($extension_name))
1982
+ );
1983
+ $has_errors = true;
1984
+
1985
+ if ($cancel_on_error) {
1986
+ break;
1987
+ } else {
1988
+ continue;
1989
+ }
1990
+ }
1991
+
1992
+ $collected = $this->get_extensions_for_activation($extension_name);
1993
+
1994
+ if (is_wp_error($collected)) {
1995
+ $result[$extension_name] = $collected;
1996
+ $has_errors = true;
1997
+
1998
+ if ($cancel_on_error) {
1999
+ break;
2000
+ } else {
2001
+ continue;
2002
+ }
2003
+ }
2004
+
2005
+ $extensions_for_activation = array_merge($extensions_for_activation, $collected);
2006
+
2007
+ $result[$extension_name] = true;
2008
+ }
2009
+
2010
+ if (
2011
+ $cancel_on_error
2012
+ &&
2013
+ $has_errors
2014
+ ) {
2015
+ if (
2016
+ ($last_result = end($result))
2017
+ &&
2018
+ is_wp_error($last_result)
2019
+ ) {
2020
+ return $last_result;
2021
+ } else {
2022
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
2023
+ return new WP_Error(
2024
+ 'activation_failed',
2025
+ _n('Cannot activate extension', 'Cannot activate extensions', count($extensions), 'fw')
2026
+ );
2027
+ }
2028
+ }
2029
+
2030
+ update_option(
2031
+ fw()->extensions->_get_active_extensions_db_option_name(),
2032
+ array_merge(fw()->extensions->_get_db_active_extensions(), $extensions_for_activation)
2033
+ );
2034
+
2035
+ // remove already active extensions
2036
+ foreach ($extensions_for_activation as $extension_name => $not_used_var) {
2037
+ if (fw_ext($extension_name)) {
2038
+ unset($extensions_for_activation[$extension_name]);
2039
+ }
2040
+ }
2041
+
2042
+ /**
2043
+ * Prepare db wp option used to fire the 'fw_extensions_after_activation' action on next refresh
2044
+ */
2045
+ {
2046
+ $db_wp_option_name = 'fw_extensions_activation';
2047
+ $db_wp_option_value = get_option($db_wp_option_name, array(
2048
+ 'activated' => array(),
2049
+ 'deactivated' => array(),
2050
+ ));
2051
+
2052
+ /**
2053
+ * Keep adding to the existing value instead of resetting it on each method call
2054
+ * in case the method will be called multiple times
2055
+ */
2056
+ $db_wp_option_value['activated'] = array_merge($db_wp_option_value['activated'], $extensions_for_activation);
2057
+
2058
+ /**
2059
+ * Remove activated extensions from deactivated
2060
+ */
2061
+ $db_wp_option_value['deactivated'] = array_diff_key($db_wp_option_value['deactivated'], $db_wp_option_value['activated']);
2062
+
2063
+ update_option($db_wp_option_name, $db_wp_option_value, false);
2064
+ }
2065
+
2066
+ do_action('fw_extensions_before_activation', $extensions_for_activation);
2067
+
2068
+ if ($has_errors) {
2069
+ return $result;
2070
+ } else {
2071
+ return true;
2072
+ }
2073
+ }
2074
+
2075
+ private function collect_sub_extensions($ext_name, &$installed_extensions)
2076
+ {
2077
+ $result = array();
2078
+
2079
+ foreach ($installed_extensions[$ext_name]['children'] as $child_ext_name => $child_ext_data) {
2080
+ $result[$child_ext_name] = array();
2081
+
2082
+ $result += $this->collect_sub_extensions($child_ext_name, $installed_extensions);
2083
+ }
2084
+
2085
+ return $result;
2086
+ }
2087
+
2088
+ private function collect_required_extensions($ext_name, &$installed_extensions, &$collected)
2089
+ {
2090
+ if (!isset($installed_extensions[$ext_name])) {
2091
+ return;
2092
+ }
2093
+
2094
+ foreach (fw_akg('requirements/extensions', $installed_extensions[$ext_name]['manifest'], array()) as $req_ext_name => $req_ext_data) {
2095
+ if (isset($collected[$req_ext_name])) {
2096
+ // prevent requirements recursion
2097
+ continue;
2098
+ }
2099
+
2100
+ $collected[$req_ext_name] = array();
2101
+
2102
+ $this->collect_required_extensions($req_ext_name, $installed_extensions, $collected);
2103
+ }
2104
+ }
2105
+
2106
+ private function display_deactivate_page()
2107
+ {
2108
+ $installed_extensions = $this->get_installed_extensions();
2109
+
2110
+ $error = '';
2111
+
2112
+ do {
2113
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
2114
+ $error = __('Invalid request method.', 'fw');
2115
+ break;
2116
+ }
2117
+
2118
+ $nonce = $this->get_nonce('deactivate');
2119
+
2120
+ if (!wp_verify_nonce(FW_Request::POST($nonce['name']), $nonce['action'])) {
2121
+ $error = __('Invalid nonce.', 'fw');
2122
+ break;
2123
+ }
2124
+
2125
+ if (!isset($_GET['extension'])) {
2126
+ $error = __('No extension specified.', 'fw');
2127
+ break;
2128
+ }
2129
+
2130
+ $deactivation_result = $this->deactivate_extensions(
2131
+ array_fill_keys(explode(',', $_GET['extension']), array())
2132
+ );
2133
+
2134
+ if (is_wp_error($deactivation_result)) {
2135
+ $error = $deactivation_result->get_error_message();
2136
+ } elseif (is_array($deactivation_result)) {
2137
+ $error = array();
2138
+
2139
+ foreach ($deactivation_result as $extension_name => $extension_result) {
2140
+ if (is_wp_error($extension_result)) {
2141
+ $error[] = $extension_result->get_error_message();
2142
+ }
2143
+ }
2144
+
2145
+ $error = '<ul><li>'. implode('</li><li>', $error) .'</li></ul>';
2146
+ }
2147
+ } while(false);
2148
+
2149
+ if ($error) {
2150
+ FW_Flash_Messages::add(
2151
+ 'fw_extensions_activate_page',
2152
+ $error,
2153
+ 'error'
2154
+ );
2155
+ }
2156
+
2157
+ $this->js_redirect();
2158
+ }
2159
+
2160
+ /**
2161
+ * Remove extensions from active extensions list in database
2162
+ * After refresh they will be inactive
2163
+ * @param array $extensions {'ext_1' => array(), 'ext_2' => array(), ...}
2164
+ * @param bool $cancel_on_error
2165
+ * false: return {'ext_1' => true|WP_Error, 'ext_2' => true|WP_Error, ...}
2166
+ * true: return first WP_Error or true on success
2167
+ * @return WP_Error|bool|array
2168
+ * true: when all extensions succeeded
2169
+ * array: when some/all failed
2170
+ */
2171
+ public function deactivate_extensions(array $extensions, $cancel_on_error = false)
2172
+ {
2173
+ if (!$this->can_activate()) {
2174
+ return new WP_Error(
2175
+ 'access_denied',
2176
+ __('You have no permissions to deactivate extensions', 'fw')
2177
+ );
2178
+ }
2179
+
2180
+ if (empty($extensions)) {
2181
+ return new WP_Error(
2182
+ 'no_extensions',
2183
+ __('No extensions provided', 'fw')
2184
+ );
2185
+ }
2186
+
2187
+ $available_extensions = $this->get_available_extensions();
2188
+ $installed_extensions = $this->get_installed_extensions();
2189
+
2190
+ $result = $extensions_for_deactivation = array();
2191
+ $has_errors = false;
2192
+
2193
+ foreach ($extensions as $extension_name => $not_used_var) {
2194
+
2195
+ if ( ! empty( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) ) {
2196
+ deactivate_plugins( plugin_basename( $available_extensions[ $extension_name ]['download']['opts']['plugin'] ) );
2197
+ continue;
2198
+ }
2199
+
2200
+
2201
+ if (!isset($installed_extensions[$extension_name])) {
2202
+ // anyway remove from the active list
2203
+ $extensions_for_deactivation[$extension_name] = array();
2204
+
2205
+ $result[$extension_name] = new WP_Error(
2206
+ 'extension_not_installed',
2207
+ sprintf(__( 'Extension "%s" does not exist.' , 'fw' ), $this->get_extension_title($extension_name))
2208
+ );
2209
+ $has_errors = true;
2210
+
2211
+ if ($cancel_on_error) {
2212
+ break;
2213
+ } else {
2214
+ continue;
2215
+ }
2216
+ }
2217
+
2218
+ $current_deactivating_extensions = array(
2219
+ $extension_name => array()
2220
+ );
2221
+
2222
+ // add sub-extensions for deactivation
2223
+ foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2224
+ $current_deactivating_extensions[ $sub_extension_name ] = array();
2225
+ }
2226
+
2227
+ // add extensions that requires deactivated extensions
2228
+ $this->collect_extensions_that_requires($current_deactivating_extensions, $current_deactivating_extensions);
2229
+
2230
+ $extensions_for_deactivation = array_merge(
2231
+ $extensions_for_deactivation,
2232
+ $current_deactivating_extensions
2233
+ );
2234
+
2235
+ unset($current_deactivating_extensions);
2236
+
2237
+ $result[$extension_name] = true;
2238
+ }
2239
+
2240
+ if (
2241
+ $cancel_on_error
2242
+ &&
2243
+ $has_errors
2244
+ ) {
2245
+ if (
2246
+ ($last_result = end($result))
2247
+ &&
2248
+ is_wp_error($last_result)
2249
+ ) {
2250
+ return $last_result;
2251
+ } else {
2252
+ // this should not happen, but just to be sure (for the future, if the code above will be changed)
2253
+ return new WP_Error(
2254
+ 'deactivation_failed',
2255
+ _n('Cannot deactivate extension', 'Cannot activate extensions', count($extensions), 'fw')
2256
+ );
2257
+ }
2258
+ }
2259
+
2260
+ // add not used extensions for deactivation
2261
+ $extensions_for_deactivation = array_merge($extensions_for_deactivation,
2262
+ array_fill_keys(
2263
+ array_keys(
2264
+ array_diff_key(
2265
+ $installed_extensions,
2266
+ $this->get_used_extensions($extensions_for_deactivation, array_keys(fw()->extensions->get_all()))
2267
+ )
2268
+ ),
2269
+ array()
2270
+ )
2271
+ );
2272
+
2273
+ update_option(
2274
+ fw()->extensions->_get_active_extensions_db_option_name(),
2275
+ array_diff_key(
2276
+ fw()->extensions->_get_db_active_extensions(),
2277
+ $extensions_for_deactivation
2278
+ )
2279
+ );
2280
+
2281
+ // remove already inactive extensions
2282
+ foreach ($extensions_for_deactivation as $extension_name => $not_used_var) {
2283
+ if (!fw_ext($extension_name)) {
2284
+ unset($extensions_for_deactivation[$extension_name]);
2285
+ }
2286
+ }
2287
+
2288
+ /**
2289
+ * Prepare db wp option used to fire the 'fw_extensions_after_deactivation' action on next refresh
2290
+ */
2291
+ {
2292
+ $db_wp_option_name = 'fw_extensions_activation';
2293
+ $db_wp_option_value = get_option($db_wp_option_name, array(
2294
+ 'activated' => array(),
2295
+ 'deactivated' => array(),
2296
+ ));
2297
+
2298
+ /**
2299
+ * Keep adding to the existing value instead of resetting it on each method call
2300
+ * in case the method will be called multiple times
2301
+ */
2302
+ $db_wp_option_value['deactivated'] = array_merge($db_wp_option_value['deactivated'], $extensions_for_deactivation);
2303
+
2304
+ /**
2305
+ * Remove deactivated extensions from activated
2306
+ */
2307
+ $db_wp_option_value['activated'] = array_diff_key($db_wp_option_value['activated'], $db_wp_option_value['deactivated']);
2308
+
2309
+ update_option($db_wp_option_name, $db_wp_option_value, false);
2310
+ }
2311
+
2312
+ do_action('fw_extensions_before_deactivation', $extensions_for_deactivation);
2313
+
2314
+ if ($has_errors) {
2315
+ return $result;
2316
+ } else {
2317
+ return true;
2318
+ }
2319
+ }
2320
+
2321
+ /**
2322
+ * @param array $data
2323
+ * @return array
2324
+ * @internal
2325
+ */
2326
+ public function _extension_settings_form_render($data)
2327
+ {
2328
+ /**
2329
+ * @var FW_Extension $extension
2330
+ */
2331
+ $extension = $data['data']['extension'];
2332
+
2333
+ do_action('fw_extension_settings_form_render:'. $extension->get_name());
2334
+
2335
+ echo fw_html_tag('input', array(
2336
+ 'type' => 'hidden',
2337
+ 'name' => 'fw_extension_name',
2338
+ 'value' => $extension->get_name(),
2339
+ ), true);
2340
+
2341
+ echo fw()->backend->render_options(
2342
+ $extension->get_settings_options(),
2343
+ fw_get_db_ext_settings_option($extension->get_name())
2344
+ );
2345
+
2346
+ $data['submit']['html'] = '';
2347
+
2348
+ echo '<p>';
2349
+ echo fw_html_tag('input', array(
2350
+ 'type' => 'submit',
2351
+ 'class' => 'button-primary',
2352
+ 'value' => __('Save', 'fw'),
2353
+ ));
2354
+ echo '&nbsp;&nbsp;&nbsp;&nbsp;';
2355
+ echo fw_html_tag('a', array(
2356
+ 'href' => $this->get_link(),
2357
+ ), __('Cancel', 'fw'));
2358
+ echo '</p>';
2359
+
2360
+ return $data;
2361
+ }
2362
+
2363
+ /**
2364
+ * @param array $errors
2365
+ * @return array
2366
+ * @internal
2367
+ */
2368
+ public function _extension_settings_form_validate($errors)
2369
+ {
2370
+ do {
2371
+ if (!current_user_can($this->can_activate())) {
2372
+ $errors[] = __('You are not allowed to save extensions settings.', 'fw');
2373
+ break;
2374
+ }
2375
+
2376
+ $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2377
+
2378
+ if (!$extension) {
2379
+ $errors[] = __('Invalid extension.', 'fw');
2380
+ break;
2381
+ }
2382
+
2383
+ if (!$extension->get_settings_options()) {
2384
+ $errors[] = __('Extension does not have settings options.', 'fw');
2385
+ break;
2386
+ }
2387
+ } while(false);
2388
+
2389
+ return $errors;
2390
+ }
2391
+
2392
+ /**
2393
+ * @param array $data
2394
+ * @return array
2395
+ * @internal
2396
+ */
2397
+ public function _extension_settings_form_save($data)
2398
+ {
2399
+ $extension = fw()->extensions->get(FW_Request::POST('fw_extension_name'));
2400
+
2401
+ $options_before_save = (array)fw_get_db_ext_settings_option($extension->get_name());
2402
+
2403
+ fw_set_db_ext_settings_option(
2404
+ $extension->get_name(),
2405
+ null,
2406
+ array_merge(
2407
+ $options_before_save,
2408
+ fw_get_options_values_from_input(
2409
+ $extension->get_settings_options()
2410
+ )
2411
+ )
2412
+ );
2413
+
2414
+ FW_Flash_Messages::add(
2415
+ 'fw_extension_settings_saved',
2416
+ __('Extensions settings successfully saved.', 'fw'),
2417
+ 'success'
2418
+ );
2419
+
2420
+ $data['redirect'] = fw_current_url();
2421
+
2422
+ do_action('fw_extension_settings_form_saved:'. $extension->get_name(), $options_before_save);
2423
+
2424
+ return $data;
2425
+ }
2426
+
2427
+ /**
2428
+ * Download an extension
2429
+ *
2430
+ * global $wp_filesystem; must be initialized
2431
+ *
2432
+ * @param string $extension_name
2433
+ * @param array $data Extension data from the "available extensions" array
2434
+ * @return string|WP_Error WP Filesystem path to the downloaded directory
2435
+ */
2436
+ private function download( $extension_name, $data ) {
2437
+ global $wp_filesystem;
2438
+ $wp_error_id = 'fw_extension_download';
2439
+
2440
+ if ( empty( $data['download'] ) ) {
2441
+ return new WP_Error(
2442
+ $wp_error_id,
2443
+ sprintf( __( 'Extension "%s" has no download sources.', 'fw' ), $this->get_extension_title( $extension_name ) )
2444
+ );
2445
+ }
2446
+
2447
+ $opts = array_merge( array(
2448
+ 'item' => $extension_name,
2449
+ 'extension_name' => $extension_name,
2450
+ 'extension_title' => $this->get_extension_title( $extension_name )
2451
+ ), $data['download']['opts'] );
2452
+
2453
+ if ( isset( $opts['plugin'] ) && is_plugin_active( $opts['plugin'] ) ) {
2454
+ return '';
2455
+ }
2456
+
2457
+ if ( ( $download_source = $this->get_download_source( $data ) ) && is_wp_error( $download_source ) ) {
2458
+ return $download_source;
2459
+ }
2460
+
2461
+ if ( isset( $opts['plugin'] ) ) {
2462
+ return $download_source->download( $opts, '' );
2463
+ }
2464
+
2465
+ // create temporary directory
2466
+ $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path( $this->get_tmp_dir() );
2467
+
2468
+ if ( $wp_filesystem->exists( $wp_fs_tmp_dir ) ) {
2469
+ // just in case it already exists, clear everything, it may contain old files
2470
+ if ( ! $wp_filesystem->rmdir( $wp_fs_tmp_dir, true ) ) {
2471
+ return new WP_Error(
2472
+ $wp_error_id,
2473
+ sprintf( __( 'Cannot remove temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
2474
+ );
2475
+ }
2476
+ }
2477
+
2478
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $wp_fs_tmp_dir ) ) {
2479
+ return new WP_Error(
2480
+ $wp_error_id,
2481
+ sprintf( __( 'Cannot create temporary directory: %s', 'fw' ), $wp_fs_tmp_dir )
2482
+ );
2483
+ }
2484
+
2485
+ return $this->perform_zip_download( $download_source, $opts, $wp_fs_tmp_dir );
2486
+ }
2487
+
2488
+ private function perform_zip_download( FW_Ext_Download_Source $download_source, array $opts, $wp_fs_tmp_dir ) {
2489
+ $wp_error_id = 'fw_extension_download';
2490
+
2491
+ /** @var WP_Filesystem_Base $wp_filesystem */
2492
+ global $wp_filesystem;
2493
+
2494
+ $zip_path = $wp_fs_tmp_dir . '/temp.zip';
2495
+
2496
+ $download_result = $download_source->download( $opts, $zip_path );
2497
+
2498
+ /**
2499
+ * Pass further the error, if the service returned one.
2500
+ */
2501
+ if ( is_wp_error( $download_result ) ) {
2502
+ return $download_result;
2503
+ }
2504
+
2505
+ $extension_name = $opts['extension_name'];
2506
+
2507
+ $unzip_result = unzip_file(
2508
+ FW_WP_Filesystem::filesystem_path_to_real_path( $zip_path ),
2509
+ $wp_fs_tmp_dir
2510
+ );
2511
+
2512
+ if ( is_wp_error( $unzip_result ) ) {
2513
+ return $unzip_result;
2514
+ }
2515
+
2516
+ // remove zip file
2517
+ if ( ! $wp_filesystem->delete( $zip_path, false, 'f' ) ) {
2518
+ return new WP_Error(
2519
+ $wp_error_id,
2520
+ sprintf( __( 'Cannot remove the "%s" extension downloaded zip.', 'fw' ), $this->get_extension_title( $extension_name ) )
2521
+ );
2522
+ }
2523
+
2524
+ $unzipped_dir_files = $wp_filesystem->dirlist( $wp_fs_tmp_dir );
2525
+
2526
+ if ( ! $unzipped_dir_files ) {
2527
+ return new WP_Error(
2528
+ $wp_error_id,
2529
+ __( 'Cannot access the unzipped directory files.', 'fw' )
2530
+ );
2531
+ }
2532
+
2533
+ /**
2534
+ * get first found directory
2535
+ * (if everything worked well, there should be only one directory)
2536
+ */
2537
+ foreach ( $unzipped_dir_files as $file ) {
2538
+ if ( $file['type'] == 'd' ) {
2539
+ return $wp_fs_tmp_dir . '/' . $file['name'];
2540
+ }
2541
+ }
2542
+
2543
+ return new WP_Error(
2544
+ $wp_error_id,
2545
+ sprintf( __( 'The unzipped "%s" extension directory not found.', 'fw' ), $this->get_extension_title( $extension_name ) )
2546
+ );
2547
+ }
2548
+
2549
+ /**
2550
+ * @param $set
2551
+ *
2552
+ * @return FW_Ext_Download_Source|WP_Error
2553
+ */
2554
+ private function get_download_source( $set ) {
2555
+ require_once dirname( __FILE__ ) . '/includes/download-source/types/init.php';
2556
+
2557
+ $register = new _FW_Ext_Download_Source_Register( self::get_access_key()->get_key() );
2558
+
2559
+ /**
2560
+ * Register download sources for extensions.
2561
+ *
2562
+ * Usage:
2563
+ * $download_source = new FW_Ext_Download_Source();
2564
+ * $register->register($download_source);
2565
+ */
2566
+ do_action( 'fw_register_ext_download_sources', $register );
2567
+
2568
+ $download_source = $register->_get_type( self::get_access_key(), $set['download']['source'] );
2569
+
2570
+ if ( ! $download_source ) {
2571
+ $download_source = new WP_Error( 'invalid_dl_source', sprintf( esc_html__( 'Invalid download source: %s', 'fw' ), $set['download']['source'] ) );
2572
+ }
2573
+
2574
+ return $download_source;
2575
+ }
2576
+
2577
+ /**
2578
+ * Merge the downloaded extension directory with the existing directory
2579
+ *
2580
+ * @param string $source_wp_fs_dir Downloaded extension directory
2581
+ * @param string $destination_wp_fs_dir
2582
+ *
2583
+ * @return null|WP_Error
2584
+ */
2585
+ private function merge_extension( $source_wp_fs_dir, $destination_wp_fs_dir ) {
2586
+ /** @var WP_Filesystem_Base $wp_filesystem */
2587
+ global $wp_filesystem;
2588
+
2589
+ $wp_error_id = 'fw_extensions_merge';
2590
+
2591
+ // check source
2592
+ {
2593
+ $source_files = $wp_filesystem->dirlist( $source_wp_fs_dir );
2594
+
2595
+ if ( $source_files === false ) {
2596
+ return new WP_Error(
2597
+ $wp_error_id,
2598
+ sprintf( __( 'Cannot read directory "%s".', 'fw' ), $source_wp_fs_dir )
2599
+ );
2600
+ }
2601
+
2602
+ if ( empty( $source_files ) ) {
2603
+ return; // directory is empty, nothing to move
2604
+ }
2605
+ }
2606
+
2607
+ /**
2608
+ * Prepare destination directory
2609
+ * Remove everything except the extensions/ directory
2610
+ */
2611
+ if ( $wp_filesystem->exists( $destination_wp_fs_dir ) ) {
2612
+ $destination_files = $wp_filesystem->dirlist( $destination_wp_fs_dir );
2613
+
2614
+ if ( $destination_files === false ) {
2615
+ return new WP_Error(
2616
+ $wp_error_id,
2617
+ sprintf( __( 'Cannot read directory "%s".', 'fw' ), $destination_wp_fs_dir )
2618
+ );
2619
+ }
2620
+
2621
+ if ( ! empty( $destination_files ) ) {
2622
+ if (
2623
+ count( $source_files ) == 1
2624
+ &&
2625
+ ( $file = reset( $source_files ) )
2626
+ &&
2627
+ $file['name'] === 'extensions'
2628
+ &&
2629
+ $file['type'] === 'd'
2630
+ ) {
2631
+ /**
2632
+ * Source extension is empty
2633
+ * It happens when you merge a directory which contains child extensions
2634
+ * Do not delete current destination files, just go in the next child extensions level
2635
+ * Used by https://github.com/ThemeFuse/Unyson/issues/1874
2636
+ */
2637
+ } else {
2638
+ // the directory contains some files, delete everything
2639
+ foreach ( $destination_files as $file ) {
2640
+ if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
2641
+ // do not touch the extensions/ directory
2642
+ continue;
2643
+ }
2644
+
2645
+ if ( ! $wp_filesystem->delete(
2646
+ $destination_wp_fs_dir . '/' . $file['name'],
2647
+ true,
2648
+ $file['type']
2649
+ ) ) {
2650
+ return new WP_Error(
2651
+ $wp_error_id,
2652
+ sprintf(
2653
+ __( 'Cannot delete "%s".', 'fw' ),
2654
+ $destination_wp_fs_dir . '/' . $file['name']
2655
+ )
2656
+ );
2657
+ }
2658
+ }
2659
+ }
2660
+
2661
+ unset( $destination_files );
2662
+ }
2663
+ } else {
2664
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $destination_wp_fs_dir ) ) {
2665
+ return new WP_Error(
2666
+ $wp_error_id,
2667
+ sprintf( __( 'Cannot create the "%s" directory.', 'fw' ), $destination_wp_fs_dir )
2668
+ );
2669
+ }
2670
+ }
2671
+
2672
+ // Move files from source to destination
2673
+ {
2674
+ $has_sub_extensions = false;
2675
+
2676
+ foreach ( $source_files as $file ) {
2677
+ if ( $file['name'] === 'extensions' && $file['type'] === 'd' ) {
2678
+ $has_sub_extensions = true; // do not touch the extensions/ directory
2679
+ continue;
2680
+ }
2681
+
2682
+ if ( ! $wp_filesystem->move( $source_wp_fs_dir . '/' . $file['name'], $destination_wp_fs_dir . '/' . $file['name'] ) ) {
2683
+ return new WP_Error(
2684
+ $wp_error_id,
2685
+ sprintf(
2686
+ __( 'Cannot move "%s" to "%s".', 'fw' ),
2687
+ $source_wp_fs_dir . '/' . $file['name'],
2688
+ $destination_wp_fs_dir . '/' . $file['name']
2689
+ )
2690
+ );
2691
+ }
2692
+ }
2693
+
2694
+ unset( $source_files );
2695
+ }
2696
+
2697
+ if ( ! $has_sub_extensions ) {
2698
+ return;
2699
+ }
2700
+
2701
+ $sub_extensions = $wp_filesystem->dirlist( $source_wp_fs_dir . '/extensions' );
2702
+
2703
+ if ( $sub_extensions === false ) {
2704
+ return new WP_Error(
2705
+ $wp_error_id,
2706
+ sprintf( __( 'Cannot read directory "%s".', 'fw' ), $source_wp_fs_dir . '/extensions' )
2707
+ );
2708
+ }
2709
+
2710
+ if ( empty( $sub_extensions ) ) {
2711
+ // directory is empty, nothing to remove
2712
+ return;
2713
+ }
2714
+
2715
+ foreach ( $sub_extensions as $file ) {
2716
+ if ( $file['type'] !== 'd' ) {
2717
+ // wrong, only directories must exist in the extensions/ directory
2718
+ continue;
2719
+ }
2720
+
2721
+ $merge_result = $this->merge_extension(
2722
+ $source_wp_fs_dir . '/extensions/' . $file['name'],
2723
+ $destination_wp_fs_dir . '/extensions/' . $file['name']
2724
+ );
2725
+
2726
+ if ( is_wp_error( $merge_result ) ) {
2727
+ return $merge_result;
2728
+ }
2729
+ }
2730
+ }
2731
+
2732
+ /**
2733
+ * @since 2.6.9
2734
+ */
2735
+ public function get_supported_extensions()
2736
+ {
2737
+ $supported_extensions = fw()->theme->manifest->get('supported_extensions', array());
2738
+
2739
+ // Add Available Extensions registered by the theme
2740
+ foreach ($this->get_available_extensions() as $name => $extension) {
2741
+ if (isset($extension['theme']) && $extension['theme']) {
2742
+ $supported_extensions[$name] = array();
2743
+ }
2744
+ }
2745
+
2746
+ if (empty($supported_extensions)) {
2747
+ return array();
2748
+ }
2749
+
2750
+ // remove not available extensions
2751
+ $supported_extensions = array_intersect_key($supported_extensions, $this->get_available_extensions());
2752
+
2753
+ if (empty($supported_extensions)) {
2754
+ return array();
2755
+ }
2756
+
2757
+ if (empty($supported_extensions)) {
2758
+ return array();
2759
+ }
2760
+
2761
+ return $supported_extensions;
2762
+ }
2763
+
2764
+ /**
2765
+ * @since 2.6.9
2766
+ */
2767
+ public function get_supported_extensions_for_install()
2768
+ {
2769
+ // remove already installed extensions
2770
+ return array_diff_key(
2771
+ $this->get_supported_extensions(),
2772
+ $this->get_installed_extensions()
2773
+ );
2774
+ }
2775
+
2776
+ /**
2777
+ * @param $actions
2778
+ * @return array
2779
+ * @internal
2780
+ */
2781
+ public function _filter_plugin_action_list($actions)
2782
+ {
2783
+ return array_merge(
2784
+ array(
2785
+ 'fw-extensions' => fw_html_tag('a', array(
2786
+ 'href' => $this->get_link(),
2787
+ ), fw()->manifest->get_name()),
2788
+ ),
2789
+ $actions
2790
+ );
2791
+ }
2792
+
2793
+ /**
2794
+ * @return string Extensions page link
2795
+ */
2796
+ private function get_link()
2797
+ {
2798
+ static $cache_link = null;
2799
+
2800
+ if ($cache_link === null) {
2801
+ $cache_link = menu_page_url( $this->get_page_slug(), false );
2802
+
2803
+ // https://core.trac.wordpress.org/ticket/28226
2804
+ if (is_multisite() && is_network_admin()) {
2805
+ $cache_link = self_admin_url(
2806
+ // extract relative link
2807
+ preg_replace('/^'. preg_quote(admin_url(), '/') .'/', '', $cache_link)
2808
+ );
2809
+ }
2810
+ }
2811
+
2812
+ return $cache_link;
2813
+ }
2814
+
2815
+ /**
2816
+ * @param array $skip_extensions {'ext' => mixed}
2817
+ * @param array $check_for_deps ['ext', 'ext', ...] Extensions to check if has in dependencies the used extensions
2818
+ *
2819
+ * @return array
2820
+ */
2821
+ private function get_used_extensions($skip_extensions, $check_for_deps)
2822
+ {
2823
+ $used_extensions = array();
2824
+
2825
+ $installed_extensions = $this->get_installed_extensions();
2826
+
2827
+ foreach ($installed_extensions as $inst_ext_name => &$inst_ext_data) {
2828
+ if (isset($skip_extensions[ $inst_ext_name ])) {
2829
+ continue;
2830
+ }
2831
+
2832
+ if (isset($used_extensions[$inst_ext_name])) {
2833
+ // already marked as used
2834
+ continue;
2835
+ }
2836
+
2837
+ do {
2838
+ foreach ($check_for_deps as $deps_ext) {
2839
+ if (isset($skip_extensions[$deps_ext])) {
2840
+ continue;
2841
+ }
2842
+
2843
+ if (false !== fw_akg(
2844
+ 'requirements/extensions/'. $inst_ext_name,
2845
+ $installed_extensions[$deps_ext]['manifest'],
2846
+ false
2847
+ )) {
2848
+ // is required by an active extension
2849
+ break 2;
2850
+ }
2851
+ }
2852
+
2853
+ if ( true === fw_akg(
2854
+ 'standalone',
2855
+ $inst_ext_data['manifest'],
2856
+ $this->manifest_default_values['standalone']
2857
+ ) ) {
2858
+ // can exist alone
2859
+ break;
2860
+ }
2861
+
2862
+ // not used
2863
+ continue 2;
2864
+ } while(false);
2865
+
2866
+ $used_extensions[$inst_ext_name] = array();
2867
+
2868
+ // Set all sub-extensions as used
2869
+ foreach ($this->collect_sub_extensions($inst_ext_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2870
+ if (isset($skip_extensions[$sub_extension_name])) {
2871
+ continue;
2872
+ }
2873
+
2874
+ $used_extensions[ $sub_extension_name ] = array();
2875
+ }
2876
+
2877
+ // Set all parents as used
2878
+ {
2879
+ $current_parent = $inst_ext_name;
2880
+ while ($current_parent = $installed_extensions[$current_parent]['parent']) {
2881
+ $used_extensions[$current_parent] = array();
2882
+ }
2883
+ }
2884
+ }
2885
+ unset($inst_ext_data);
2886
+
2887
+ // remove all skipped extensions and sub-extension from used extensions
2888
+ foreach (array_keys($skip_extensions) as $skip_extension_name) {
2889
+ unset($used_extensions[$skip_extension_name]);
2890
+
2891
+ if (isset($installed_extensions[$skip_extension_name])) {
2892
+ foreach ($this->collect_sub_extensions($skip_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
2893
+ unset($used_extensions[$sub_extension_name]);
2894
+ }
2895
+ }
2896
+ }
2897
+
2898
+ return $used_extensions;
2899
+ }
2900
+
2901
+ /**
2902
+ * @internal
2903
+ */
2904
+ public function _action_admin_footer()
2905
+ {
2906
+ $this->activate_hidden_standalone_extensions();
2907
+ }
2908
+
2909
+ public function get_extension_title($extension_name)
2910
+ {
2911
+ $installed_extensions = $this->get_installed_extensions();
2912
+
2913
+ if (isset($installed_extensions[$extension_name])) {
2914
+ return fw_akg('name', $installed_extensions[$extension_name]['manifest'], fw_id_to_title($extension_name));
2915
+ }
2916
+
2917
+ unset($installed_extensions);
2918
+
2919
+ $available_extensions = $this->get_available_extensions();
2920
+
2921
+ if (isset($available_extensions[$extension_name])) {
2922
+ return $available_extensions[$extension_name]['name'];
2923
+ }
2924
+
2925
+ return fw_id_to_title($extension_name);
2926
+ }
2927
+
2928
+ public function is_extensions_page()
2929
+ {
2930
+ $current_screen = get_current_screen();
2931
+
2932
+ if (empty($current_screen)) {
2933
+ return false;
2934
+ }
2935
+
2936
+ return (
2937
+ property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2938
+ &&
2939
+ property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2940
+ &&
2941
+ !isset($_GET['sub-page'])
2942
+ );
2943
+ }
2944
+
2945
+ public function is_extension_page()
2946
+ {
2947
+ $current_screen = get_current_screen();
2948
+
2949
+ if (empty($current_screen)) {
2950
+ return false;
2951
+ }
2952
+
2953
+ return (
2954
+ property_exists($current_screen, 'base') && strpos($current_screen->base, $this->get_page_slug()) !== false
2955
+ &&
2956
+ property_exists($current_screen, 'id') && strpos($current_screen->id, $this->get_page_slug()) !== false
2957
+ &&
2958
+ isset($_GET['sub-page']) && $_GET['sub-page'] === 'extension'
2959
+ );
2960
+ }
2961
+
2962
+ /**
2963
+ * @internal
2964
+ */
2965
+ public function _action_enqueue_scripts()
2966
+ {
2967
+ wp_enqueue_style(
2968
+ 'fw-extensions-menu-icon',
2969
+ $this->get_uri('/static/unyson-font-icon/style.css'),
2970
+ array(),
2971
+ fw()->manifest->get_version()
2972
+ );
2973
+
2974
+ /**
2975
+ * Enqueue only on Extensions List page
2976
+ */
2977
+ if ($this->is_extensions_page()) {
2978
+ wp_enqueue_style(
2979
+ 'fw-extensions-page',
2980
+ $this->get_uri('/static/extensions-page.css'),
2981
+ array(
2982
+ 'fw',
2983
+ 'fw-unycon', 'font-awesome', // in case some extension has font-icon thumbnail
2984
+ ),
2985
+ fw()->manifest->get_version()
2986
+ );
2987
+ wp_enqueue_script(
2988
+ 'fw-extensions-page',
2989
+ $this->get_uri('/static/extensions-page.js'),
2990
+ array('fw'),
2991
+ fw()->manifest->get_version(),
2992
+ true
2993
+ );
2994
+ wp_localize_script('fw-extensions-page', '_fw_extensions_script_data', array(
2995
+ 'link' => $this->get_link(),
2996
+ ));
2997
+
2998
+ /**
2999
+ * this is needed for fw.soleModal design
3000
+ * it is displayed when extension ajax install returns an error
3001
+ */
3002
+ wp_enqueue_media();
3003
+ }
3004
+
3005
+ if ($this->is_extension_page()) {
3006
+ wp_enqueue_style(
3007
+ 'fw-extension-page',
3008
+ $this->get_uri('/static/extension-page.css'),
3009
+ array('fw'),
3010
+ fw()->manifest->get_version()
3011
+ );
3012
+ wp_enqueue_script(
3013
+ 'fw-extension-page',
3014
+ $this->get_uri('/static/extension-page.js'),
3015
+ array('fw'),
3016
+ fw()->manifest->get_version(),
3017
+ true
3018
+ );
3019
+
3020
+ /**
3021
+ * Enqueue extension settings options static
3022
+ */
3023
+ if (
3024
+ isset($_GET['extension'])
3025
+ &&
3026
+ is_string($extension_name = $_GET['extension'])
3027
+ &&
3028
+ fw()->extensions->get($extension_name)
3029
+ &&
3030
+ ($extension_settings_options = fw()->extensions->get($extension_name)->get_settings_options())
3031
+ ) {
3032
+ fw()->backend->enqueue_options_static($extension_settings_options);
3033
+ }
3034
+ }
3035
+ }
3036
+
3037
+ private function activate_theme_extensions()
3038
+ {
3039
+ $db_active_extensions = fw()->extensions->_get_db_active_extensions();
3040
+
3041
+ foreach ($this->get_installed_extensions() as $extension_name => $extension) {
3042
+ if ($extension['is']['theme']) {
3043
+ $db_active_extensions[ $extension_name ] = array();
3044
+ }
3045
+ }
3046
+
3047
+ update_option(
3048
+ fw()->extensions->_get_active_extensions_db_option_name(),
3049
+ $db_active_extensions
3050
+ );
3051
+ }
3052
+
3053
+ /**
3054
+ * @internal
3055
+ */
3056
+ public function _action_theme_switch()
3057
+ {
3058
+ if ( ! apply_filters( 'fw_after_switch_theme_activate_exts', true ) ) {
3059
+ return;
3060
+ }
3061
+
3062
+ $this->activate_theme_extensions();
3063
+
3064
+ $this->activate_extensions(
3065
+ array_fill_keys(
3066
+ array_keys(fw()->theme->manifest->get('supported_extensions', array())),
3067
+ array()
3068
+ )
3069
+ );
3070
+ }
3071
+
3072
+ /**
3073
+ * @param array $collected The found extensions {'extension_name' => array()}
3074
+ * @param array $extensions {'extension_name' => array()}
3075
+ * @param bool $check_all Check all extensions or only active extensions
3076
+ */
3077
+ private function collect_extensions_that_requires(&$collected, $extensions, $check_all = false)
3078
+ {
3079
+ if (empty($extensions)) {
3080
+ return;
3081
+ }
3082
+
3083
+ $found_extensions = array();
3084
+
3085
+ foreach ($this->get_installed_extensions() as $extension_name => $extension_data) {
3086
+ if (isset($collected[$extension_name])) {
3087
+ continue;
3088
+ }
3089
+
3090
+ if (!$check_all) {
3091
+ if (!fw_ext($extension_name)) {
3092
+ continue;
3093
+ }
3094
+ }
3095
+
3096
+ if (
3097
+ array_intersect_key(
3098
+ $extensions,
3099
+ fw_akg(
3100
+ 'requirements/extensions',
3101
+ $extension_data['manifest'],
3102
+ array()
3103
+ )
3104
+ )
3105
+ ) {
3106
+ $found_extensions[$extension_name] = $collected[$extension_name] = array();
3107
+ }
3108
+ }
3109
+
3110
+ $this->collect_extensions_that_requires($collected, $found_extensions, $check_all);
3111
+ }
3112
+
3113
+ /**
3114
+ * Get extension settings page link
3115
+ * @param string $extension_name
3116
+ * @return string
3117
+ */
3118
+ public function get_extension_link($extension_name)
3119
+ {
3120
+ return $this->get_link() .'&sub-page=extension&extension='. $extension_name;
3121
+ }
3122
+
3123
+ /**
3124
+ * @param string $extension_name
3125
+ * @return array|WP_Error Extensions to merge with db active extensions list
3126
+ */
3127
+ private function get_extensions_for_activation($extension_name)
3128
+ {
3129
+ $installed_extensions = $this->get_installed_extensions();
3130
+
3131
+ $wp_error_id = 'fw_ext_activation';
3132
+
3133
+ if (!isset($installed_extensions[$extension_name])) {
3134
+ return new WP_Error($wp_error_id,
3135
+ sprintf(
3136
+ __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3137
+ $this->get_extension_title($extension_name),
3138
+ fw_html_tag('a', array(
3139
+ 'href' => $this->get_link() .'&sub-page=install&extension='. $extension_name
3140
+ ), __('Install', 'fw'))
3141
+ )
3142
+ );
3143
+ }
3144
+
3145
+ {
3146
+ $extension_parents = array($extension_name);
3147
+
3148
+ $current_parent = $extension_name;
3149
+ while ($current_parent = $installed_extensions[$current_parent]['parent']) {
3150
+ $extension_parents[] = $current_parent;
3151
+ }
3152
+
3153
+ $extension_parents = array_reverse($extension_parents);
3154
+ }
3155
+
3156
+ $extensions = array();
3157
+
3158
+ foreach ($extension_parents as $parent_extension_name) {
3159
+ $extensions[ $parent_extension_name ] = array();
3160
+ }
3161
+
3162
+ // search sub-extensions
3163
+ foreach ($this->collect_sub_extensions($extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3164
+ $extensions[ $sub_extension_name ] = array();
3165
+ }
3166
+
3167
+ // search required extensions
3168
+ {
3169
+ $pending_required_search = $extensions;
3170
+
3171
+ while ($pending_required_search) {
3172
+ foreach (array_keys($pending_required_search) as $pend_req_extension_name) {
3173
+ unset($pending_required_search[$pend_req_extension_name]);
3174
+
3175
+ unset($required_extensions); // reset reference
3176
+ $required_extensions = array();
3177
+ $this->collect_required_extensions($pend_req_extension_name, $installed_extensions, $required_extensions);
3178
+
3179
+ foreach ($required_extensions as $required_extension_name => $required_extension_data) {
3180
+ if (!isset($installed_extensions[$required_extension_name])) {
3181
+ return new WP_Error($wp_error_id,
3182
+ sprintf(
3183
+ __('Cannot activate the %s extension because it is not installed. %s', 'fw'),
3184
+ $this->get_extension_title($required_extension_name),
3185
+ fw_html_tag('a', array(
3186
+ 'href' => $this->get_link() .'&sub-page=install&extension='. $required_extension_name
3187
+ ), __('Install', 'fw'))
3188
+ )
3189
+ );
3190
+ }
3191
+
3192
+ $extensions[$required_extension_name] = array();
3193
+
3194
+ // search sub-extensions
3195
+ foreach ($this->collect_sub_extensions($required_extension_name, $installed_extensions) as $sub_extension_name => $sub_extension_data) {
3196
+ if (isset($extensions[$sub_extension_name])) {
3197
+ continue;
3198
+ }
3199
+
3200
+ $extensions[$sub_extension_name] = array();
3201
+
3202
+ $pending_required_search[$sub_extension_name] = array();
3203
+ }
3204
+ }
3205
+ }
3206
+ }
3207
+ }
3208
+
3209
+ return $extensions;
3210
+ }
3211
+
3212
+ /**
3213
+ * @internal
3214
+ */
3215
+ public function _action_admin_notices() {
3216
+ $should_notify = apply_filters(
3217
+ 'fw_notify_about_missing_extensions',
3218
+ true
3219
+ );
3220
+
3221
+ /**
3222
+ * In v2.4.12 was done a terrible mistake https://github.com/ThemeFuse/Unyson-Extensions-Approval/issues/160
3223
+ * Show a warning with link to install theme supported extensions
3224
+ */
3225
+ if (
3226
+ $should_notify
3227
+ &&
3228
+ !isset($_GET['supported']) // already on 'Install Supported Extensions' page
3229
+ &&
3230
+ $this->can_install()
3231
+ &&
3232
+ (($installed_extensions = $this->get_installed_extensions()) || true)
3233
+ &&
3234
+ !isset($installed_extensions['page-builder'])
3235
+ &&
3236
+ $this->get_supported_extensions_for_install()
3237
+ ) {
3238
+ echo '<div class="error"> <p>'
3239
+ , fw_html_tag('a', array('href' => $this->get_link() .'&sub-page=install&supported'),
3240
+ __('Install theme compatible extensions', 'fw'))
3241
+ , '</p></div>';
3242
+ }
3243
+ }
3244
+
3245
+ /**
3246
+ * Copy Theme Available Extensions to a tmp directory
3247
+ * Used before theme update
3248
+ * @since 2.6.0
3249
+ * @return null|WP_Error
3250
+ */
3251
+ public function theme_available_extensions_copy() {
3252
+ /** @var WP_Filesystem_Base $wp_filesystem */
3253
+ global $wp_filesystem;
3254
+
3255
+ if (!FW_WP_Filesystem::is_ready()) {
3256
+ return new WP_Error(
3257
+ 'fs_not_initialized',
3258
+ __('WP Filesystem is not initialized', 'fw')
3259
+ );
3260
+ }
3261
+
3262
+ // Prepare temporary directory
3263
+ {
3264
+ $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3265
+ $this->get_tmp_dir('/theme-ext')
3266
+ );
3267
+
3268
+ if (
3269
+ $wp_filesystem->exists( $wpfs_tmp_dir )
3270
+ &&
3271
+ ! $wp_filesystem->rmdir( $wpfs_tmp_dir, true )
3272
+ ) {
3273
+ return new WP_Error(
3274
+ 'tmp_dir_rm_fail',
3275
+ sprintf(__('Temporary directory cannot be removed: %s', 'fw'), $wpfs_tmp_dir)
3276
+ );
3277
+ }
3278
+
3279
+ if ( ! FW_WP_Filesystem::mkdir_recursive( $wpfs_tmp_dir ) ) {
3280
+ return new WP_Error(
3281
+ 'tmp_dir_rm_fail',
3282
+ sprintf(__('Temporary directory cannot be created: %s', 'fw'), $wpfs_tmp_dir)
3283
+ );
3284
+ }
3285
+ }
3286
+
3287
+ $available_extensions = $this->get_available_extensions();
3288
+ $installed_extensions = $this->get_installed_extensions(true);
3289
+ $base_dir = fw_get_template_customizations_directory('/extensions');
3290
+
3291
+ foreach ($installed_extensions as $name => $ext) {
3292
+ if ( ! (
3293
+ isset($available_extensions[$name])
3294
+ &&
3295
+ isset($available_extensions[$name]['theme'])
3296
+ &&
3297
+ $available_extensions[$name]['theme']
3298
+ ) ) {
3299
+ continue;
3300
+ }
3301
+
3302
+ if ( ($rel_path = preg_replace('/^'. preg_quote($base_dir, '/') .'/', '', $ext['path'])) === $base_dir ) {
3303
+ return new WP_Error(
3304
+ 'rel_path_failed',
3305
+ sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3306
+ );
3307
+ }
3308
+
3309
+ if ( ($wpfs_path = FW_WP_Filesystem::real_path_to_filesystem_path($ext['path'])) === false) {
3310
+ return new WP_Error(
3311
+ 'real_to_wpfs_filed',
3312
+ sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path'])
3313
+ );
3314
+ }
3315
+
3316
+ $wpfs_dest_dir = $wpfs_tmp_dir . $rel_path;
3317
+
3318
+ if ( ! FW_WP_Filesystem::mkdir_recursive($wpfs_dest_dir) ) {
3319
+ return new WP_Error(
3320
+ 'dest_dir_mk_fail',
3321
+ sprintf(__('Failed to create directory %s', 'fw'), $wpfs_dest_dir)
3322
+ );
3323
+ }
3324
+
3325
+ if ( is_wp_error( $copy_result = copy_dir($wpfs_path, $wpfs_dest_dir) ) ) {
3326
+ /** @var WP_Error $copy_result */
3327
+ return new WP_Error(
3328
+ 'ext_copy_failed',
3329
+ sprintf( __('Failed to copy extension to %s', 'fw'), $wpfs_dest_dir )
3330
+ );
3331
+ }
3332
+ }
3333
+ }
3334
+
3335
+ /**
3336
+ * Copy Theme Available Extensions from tmp directory to theme
3337
+ * Used after theme update
3338
+ * @since 2.6.0
3339
+ * @return null|WP_Error
3340
+ */
3341
+ public function theme_available_extensions_restore() {
3342
+ /** @var WP_Filesystem_Base $wp_filesystem */
3343
+ global $wp_filesystem;
3344
+
3345
+ if (!FW_WP_Filesystem::is_ready()) {
3346
+ return new WP_Error(
3347
+ 'fs_not_initialized',
3348
+ __('WP Filesystem is not initialized', 'fw')
3349
+ );
3350
+ }
3351
+
3352
+ if ( ! $wp_filesystem->exists(
3353
+ $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3354
+ $this->get_tmp_dir('/theme-ext')
3355
+ )
3356
+ ) ) {
3357
+ return new WP_Error(
3358
+ 'no_tmp_dir',
3359
+ sprintf(__('Temporary directory does not exist: %s', 'fw'), $wpfs_tmp_dir)
3360
+ );
3361
+ }
3362
+
3363
+ /**
3364
+ * Fixes the case when the theme path before update was
3365
+ * wp-content/themes/theme-name/theme-name-parent
3366
+ * but after update it became
3367
+ * wp-content/themes/theme-name-parent
3368
+ *
3369
+ * and at this point get_template_directory() returns old theme directory
3370
+ * so fw_get_template_customizations_directory() also returns old path
3371
+ */
3372
+ $theme_dir = wp_get_theme()->get_theme_root() .'/'. wp_get_theme()->get_template();
3373
+
3374
+ if ( ! ($wpfs_base_dir = FW_WP_Filesystem::real_path_to_filesystem_path(
3375
+ $base_dir = $theme_dir . fw_get_framework_customizations_dir_rel_path('/extensions')
3376
+ ) ) ) {
3377
+ return new WP_Error(
3378
+ 'base_dir_to_wpfs_fail',
3379
+ sprintf( __('Cannot obtain WP Filesystem dir for %s', 'fw'), $base_dir )
3380
+ );
3381
+ }
3382
+
3383
+ if ( ! ( $dirlist = $wp_filesystem->dirlist($wpfs_tmp_dir) ) ) {
3384
+ return;
3385
+ }
3386
+
3387
+ foreach ( $dirlist as $filename => $fileinfo ) {
3388
+ if ( 'd' !== $fileinfo['type'] ) {
3389
+ continue;
3390
+ }
3391
+
3392
+ if ( is_wp_error($merge_result = $this->merge_extension(
3393
+ $wpfs_tmp_dir .'/'. $filename,
3394
+ $wpfs_base_dir .'/'. $filename
3395
+ )) ) {
3396
+ return $merge_result;
3397
+ }
3398
+ }
3399
+
3400
+ $wp_filesystem->rmdir( $wpfs_tmp_dir, true );
3401
+ }
3402
+
3403
+ /**
3404
+ * Copy Theme Available Extensions to tmp dir
3405
+ * @param bool|WP_Error $result
3406
+ * @param array $data
3407
+ *
3408
+ * @return bool|WP_Error
3409
+ */
3410
+ public function _filter_theme_available_extensions_copy($result, $data) {
3411
+ if (
3412
+ !is_wp_error($result)
3413
+ &&
3414
+ is_array($data)
3415
+ &&
3416
+ isset($data['theme'])
3417
+ &&
3418
+ $data['theme'] === wp_get_theme()->get_template()
3419
+ ) {
3420
+ if ( is_wp_error( $copy_result = fw()->extensions->manager->theme_available_extensions_copy() ) ) {
3421
+ return $copy_result;
3422
+ }
3423
+ }
3424
+
3425
+ return $result;
3426
+ }
3427
+
3428
+ /**
3429
+ * Restore Theme Available Extensions from tmp dir
3430
+ * @param Theme_Upgrader $instance
3431
+ * @param array $data
3432
+ *
3433
+ * @return bool|WP_Error
3434
+ */
3435
+ public function _action_theme_available_extensions_restore($instance, $data) {
3436
+ if (
3437
+ !is_wp_error($instance->skin->result)
3438
+ &&
3439
+ is_array($data)
3440
+ &&
3441
+ isset($data['action']) && $data['action'] === 'update'
3442
+ &&
3443
+ isset($data['type']) && $data['type'] === 'theme'
3444
+ &&
3445
+ isset($data['themes'])
3446
+ &&
3447
+ ($template = wp_get_theme()->get_template())
3448
+ &&
3449
+ (
3450
+ in_array($template, $data['themes'])
3451
+ ||
3452
+ /**
3453
+ * Fixes the case when the theme path before update was
3454
+ * wp-content/themes/theme-name/theme-name-parent
3455
+ * but after update it became
3456
+ * wp-content/themes/theme-name-parent
3457
+ */
3458
+ ( preg_match($regex = '/\-parent$/', $template)
3459
+ ? in_array( preg_replace($regex, '', $template) .'/'. $template, $data['themes'] )
3460
+ : false )
3461
+ )
3462
+ ) {
3463
+ fw()->extensions->manager->theme_available_extensions_restore();
3464
+ }
3465
+ }
3466
+
3467
+ /**
3468
+ * Install compatible extensions on plugin install -> activate
3469
+ *
3470
+ * In order for this to work, int TGM config must be set: 'is_automatic' => true
3471
+ * http://tgmpluginactivation.com/configuration/
3472
+ *
3473
+ * @internal
3474
+ */
3475
+ public function _action_plugin_activate_install_compatible_extensions() {
3476
+ if (!FW_WP_Filesystem::is_ready()) {
3477
+ return;
3478
+ }
3479
+
3480
+ if ($compatible_extensions = $this->get_supported_extensions_for_install()) {
3481
+ $this->install_extensions($compatible_extensions);
3482
+ // the result is not used because we don't know here if we can print the errors or not
3483
+ }
3484
+ }
3485
+
3486
+ /**
3487
+ * @since 2.6.9
3488
+ */
3489
+ public function collect_extension_requirements($extension_name, $can_install = null) {
3490
+ $installed_extensions = $this->get_installed_extensions();
3491
+
3492
+ if (is_null($can_install)) {
3493
+ $can_install = $this->can_install();
3494
+ }
3495
+
3496
+ if (! isset($installed_extensions[$extension_name])) {
3497
+ return array();
3498
+ } else {
3499
+ $data = $installed_extensions[$extension_name];
3500
+ }
3501
+
3502
+ $result = array();
3503
+
3504
+ $manifest_requirements = fw_akg('requirements', $data['manifest'], array());
3505
+
3506
+ foreach ($manifest_requirements as $req_name => $req_data) {
3507
+ switch ($req_name) {
3508
+ case 'php':
3509
+ if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3510
+ break;
3511
+ }
3512
+
3513
+ if ( ! empty( $req_data['min_version'] ) ) {
3514
+ if (!version_compare($req_data['min_version'], phpversion(), '<=')) {
3515
+ $result[] = sprintf(
3516
+ __( 'PHP needs to be updated to %s', 'fw' ),
3517
+ $req_data['min_version']
3518
+ );
3519
+ }
3520
+ }
3521
+
3522
+ if ( ! empty( $req_data['max_version'] ) ) {
3523
+ if (!version_compare($req_data['max_version'], phpversion(), '>=')) {
3524
+ $result[] = sprintf(
3525
+ __('Maximum supported PHP version is %s', 'fw'),
3526
+ $req_data['max_version']
3527
+ );
3528
+ }
3529
+ }
3530
+
3531
+ break;
3532
+
3533
+ case 'wordpress':
3534
+ if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3535
+ break;
3536
+ }
3537
+
3538
+ global $wp_version;
3539
+
3540
+ if ( ! empty( $req_data['min_version'] ) ) {
3541
+ if (!version_compare($req_data['min_version'], $wp_version, '<=')) {
3542
+ if ($can_install) {
3543
+ $result[] = sprintf(
3544
+ __( 'You need to update WordPress to %s: %s', 'fw' ),
3545
+ $req_data['min_version'],
3546
+ fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ), __( 'Update WordPress', 'fw' ) )
3547
+ );
3548
+ } else {
3549
+ $result[] = sprintf(
3550
+ __( 'WordPress needs to be updated to %s', 'fw' ),
3551
+ $req_data['min_version']
3552
+ );
3553
+ }
3554
+ }
3555
+ }
3556
+
3557
+ if ( ! empty( $req_data['max_version'] ) ) {
3558
+ if (!version_compare($req_data['max_version'], $wp_version, '>=')) {
3559
+ $result[] = sprintf(
3560
+ __('Maximum supported WordPress version is %s', 'fw'),
3561
+ $req_data['max_version']
3562
+ );
3563
+ }
3564
+ }
3565
+
3566
+ break;
3567
+
3568
+ case 'framework':
3569
+ if (empty($req_data['min_version']) && empty($req_data['max_version'])) {
3570
+ break;
3571
+ }
3572
+
3573
+ if ( ! empty( $req_data['min_version'] ) ) {
3574
+ if (!version_compare($req_data['min_version'], fw()->manifest->get_version(), '<=')) {
3575
+ if ($can_install) {
3576
+ $result[] = sprintf(
3577
+ __( 'You need to update %s to %s: %s', 'fw' ),
3578
+ fw()->manifest->get_name(),
3579
+ $req_data['min_version'],
3580
+ fw_html_tag( 'a', array( 'href' => self_admin_url( 'update-core.php' ) ),
3581
+ sprintf( __( 'Update %s', 'fw' ), fw()->manifest->get_name() )
3582
+ )
3583
+ );
3584
+ } else {
3585
+ $result[] = sprintf(
3586
+ __( '%s needs to be updated to %s', 'fw' ),
3587
+ fw()->manifest->get_name(),
3588
+ $req_data['min_version']
3589
+ );
3590
+ }
3591
+ }
3592
+ }
3593
+
3594
+ if ( ! empty( $req_data['max_version'] ) ) {
3595
+ if (!version_compare($req_data['max_version'], fw()->manifest->get_version(), '>=')) {
3596
+ $result[] = sprintf(
3597
+ __( 'Maximum supported %s version is %s', 'fw' ),
3598
+ fw()->manifest->get_name(),
3599
+ $req_data['max_version']
3600
+ );
3601
+ }
3602
+ }
3603
+
3604
+ break;
3605
+
3606
+ case 'extensions':
3607
+ foreach ($req_data as $req_ext => $req_ext_data) {
3608
+ if ($ext = fw()->extensions->get($req_ext)) {
3609
+ if (empty($req_ext_data['min_version']) && empty($req_ext_data['max_version'])) {
3610
+ continue;
3611
+ }
3612
+
3613
+ if ( ! empty( $req_ext_data['min_version'] ) ) {
3614
+ if (!version_compare($req_ext_data['min_version'], $ext->manifest->get_version(), '<=')) {
3615
+ if ($can_install) {
3616
+ $result[] = sprintf(
3617
+ __('You need to update the %s extension to %s: %s', 'fw'),
3618
+ $ext->manifest->get_name(),
3619
+ $req_ext_data['min_version'],
3620
+ fw_html_tag('a', array('href' => self_admin_url('update-core.php')),
3621
+ sprintf(__('Update %s', 'fw'), $ext->manifest->get_name())
3622
+ )
3623
+ );
3624
+ } else {
3625
+ $result[] = sprintf(
3626
+ __('The %s extension needs to be updated to %s', 'fw'),
3627
+ $ext->manifest->get_name(),
3628
+ $req_ext_data['min_version']
3629
+ );
3630
+ }
3631
+ }
3632
+ }
3633
+
3634
+ if ( ! empty( $req_ext_data['max_version'] ) ) {
3635
+ if (!version_compare($req_ext_data['max_version'], $ext->manifest->get_version(), '>=')) {
3636
+ $result[] = sprintf(
3637
+ __( 'Maximum supported %s extension version is %s', 'fw' ),
3638
+ $ext->manifest->get_name(),
3639
+ $req_ext_data['max_version']
3640
+ );
3641
+ }
3642
+ }
3643
+ } else {
3644
+ $ext_title = fw_id_to_title($req_ext);
3645
+
3646
+ if (isset($lists['installed'][$req_ext])) {
3647
+ $ext_title = fw_akg('name', $lists['installed'][$req_ext]['manifest'], $ext_title);
3648
+
3649
+ ob_start(); ?>
3650
+ <form action="<?php echo esc_attr($link) ?>&sub-page=activate&extension=<?php echo esc_attr($req_ext) ?>" method="post" style="display: inline;">
3651
+ <?php wp_nonce_field($nonces['activate']['action'], $nonces['activate']['name']); ?>
3652
+ <?php echo sprintf(__( 'The %s extension is disabled', 'fw' ), $ext_title); ?>:
3653
+ <a href="#" onclick="jQuery(this).closest('form').submit(); return false;"><?php echo sprintf(__('Activate %s', 'fw'), $ext_title); ?></a>
3654
+ </form>
3655
+ <?php
3656
+ $result[] = ob_get_clean();
3657
+ } else {
3658
+ if ($can_install && isset($lists['available'][$req_ext])) {
3659
+ $ext_title = $lists['available'][ $req_ext ]['name'];
3660
+
3661
+ $result[] = sprintf(
3662
+ __( 'The %s extension is not installed: %s', 'fw' ),
3663
+ $ext_title,
3664
+ fw_html_tag( 'a', array( 'href' => $link . '&sub-page=install&extension=' . $req_ext ),
3665
+ sprintf( __( 'Install %s', 'fw' ), $ext_title )
3666
+ )
3667
+ );
3668
+ } else {
3669
+ $result[] = sprintf(
3670
+ __( 'The %s extension is not installed', 'fw' ),
3671
+ $ext_title
3672
+ );
3673
+ }
3674
+ }
3675
+ }
3676
+ }
3677
+
3678
+ break;
3679
+
3680
+ default:
3681
+ trigger_error('Invalid requirement: '. $req_name, E_USER_WARNING);
3682
+ continue;
3683
+ }
3684
+ }
3685
+
3686
+ return $result;
3687
+ }
3688
+ }
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,21 +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 $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
-
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-bitbucket.php ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'FW' ) ) {
2
+ die( 'Forbidden' );
3
+ }
4
+
5
+ class FW_Ext_Download_Source_Bitbucket extends FW_Ext_Download_Source {
6
+ private $download_timeout = 300;
7
+
8
+ public function get_type() {
9
+ return 'bitbucket';
10
+ }
11
+
12
+ /**
13
+ * @param array $set {user_repo: 'ThemeFuse/Unyson'}
14
+ * @param string $zip_path
15
+ *
16
+ * @return WP_Error|boolean
17
+ */
18
+ public function download( array $set, $zip_path ) {
19
+ $wp_error_id = 'fw_ext_bitbucket_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 bitbucket source "user_repo" parameter is required', 'fw' ), $extension_title )
32
+ );
33
+ }
34
+
35
+ if ( isset( $theme_ext_requirements[ $extension_name ] ) && isset( $theme_ext_requirements[ $extension_name ]['max_version'] ) ) {
36
+ $tag = $theme_ext_requirements[ $extension_name ]['max_version'];
37
+ } else {
38
+ $tag = $this->get_version( $set['user_repo'] );
39
+ }
40
+
41
+ $response = wp_remote_get( "https://bitbucket.org/{$set['user_repo']}/get/{$tag}.zip", array( 'timeout' => $this->download_timeout ) );
42
+
43
+ if ( ( $response_code = intval( wp_remote_retrieve_response_code( $response ) ) ) !== 200 ) {
44
+ if ( $response_code ) {
45
+ return new WP_Error(
46
+ $wp_error_id,
47
+ sprintf( __( 'Cannot download the "%s" extension zip. (Response code: %d)', 'fw' ),
48
+ $extension_title, $response_code
49
+ )
50
+ );
51
+ } elseif ( is_wp_error( $response ) ) {
52
+ return new WP_Error(
53
+ $wp_error_id,
54
+ sprintf( __( 'Cannot download the "%s" extension zip. %s', 'fw' ),
55
+ $extension_title,
56
+ $response->get_error_message()
57
+ )
58
+ );
59
+ } else {
60
+ return new WP_Error(
61
+ $wp_error_id,
62
+ sprintf( __( 'Cannot download the "%s" extension zip.', 'fw' ),
63
+ $extension_title
64
+ )
65
+ );
66
+ }
67
+ }
68
+
69
+ // save zip to file
70
+ if ( ! $wp_filesystem->put_contents( $zip_path, $response['body'] ) ) {
71
+ return new WP_Error(
72
+ $wp_error_id,
73
+ sprintf( __( 'Cannot save the "%s" extension zip.', 'fw' ), $extension_title )
74
+ );
75
+ }
76
+
77
+ return true;
78
+ }
79
+
80
+ private function get_version( $user_repo, $next_page = '' ) {
81
+ /**
82
+ * If at least one request failed, do not do any other requests, to prevent site being blocked on every refresh.
83
+ * This may happen on localhost when develop your theme and you have no internet connection.
84
+ * Then this method will return a fake '0.0.0' version, it will be cached by the transient
85
+ * and will not bother you until the transient will expire, then a new request will be made.
86
+ * @var bool
87
+ */
88
+ static $no_internet_connection = false;
89
+
90
+ if ( $no_internet_connection ) {
91
+ return '0.0.0';
92
+ }
93
+
94
+ $url = $next_page ? $next_page : "https://api.bitbucket.org/2.0/repositories/{$user_repo}/refs/tags/";
95
+ $request = wp_remote_get( $url, array( 'timeout' => $this->download_timeout ) );
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 ( ! ( $versions = json_decode( wp_remote_retrieve_body( $request ), true ) ) || is_wp_error( $versions ) ) {
106
+ return ! $versions ? new WP_Error( sprintf( __( 'Empty version for item: %s', 'fw' ), $user_repo ) ) : $versions;
107
+ }
108
+
109
+ if ( isset( $versions['next'] ) ) {
110
+ return $this->get_version( $user_repo, $versions['next'] );
111
+ }
112
+
113
+ $data_version = end( $versions['values'] );
114
+
115
+ return ! empty( $data_version['name'] ) ? $data_version['name'] : new WP_Error( sprintf( __( 'Wrong Bibucket version for item: %s', 'fw' ), $user_repo ) );
116
+ }
117
+ }
framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-custom.php CHANGED
@@ -1,214 +1,214 @@
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
- // A small financial support for maintaining the plugin.
178
- if ( 'translatepress-multilingual/index.php' === $set['plugin'] ) {
179
- update_option( 'translatepress_affiliate_id', 1 );
180
- }
181
-
182
- return activate_plugin( $set['plugin'] );
183
- }
184
-
185
- public function http_request_args( $r ) {
186
- $r['body'] = json_encode( array_merge( $this->set, array( 'type' => 'extension' ) ) );
187
- return $r;
188
- }
189
-
190
- public function is_wp_org( $url ) {
191
- return strpos( $url, 'downloads.wordpress.org' ) !== false;
192
- }
193
- }
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
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
+ // A small financial support for maintaining the plugin.
178
+ if ( 'translatepress-multilingual/index.php' === $set['plugin'] ) {
179
+ update_option( 'translatepress_affiliate_id', 1 );
180
+ }
181
+
182
+ return activate_plugin( $set['plugin'] );
183
+ }
184
+
185
+ public function http_request_args( $r ) {
186
+ $r['body'] = json_encode( array_merge( $this->set, array( 'type' => 'extension' ) ) );
187
+ return $r;
188
+ }
189
+
190
+ public function is_wp_org( $url ) {
191
+ return strpos( $url, 'downloads.wordpress.org' ) !== false;
192
+ }
193
+ }
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
framework/core/components/extensions/manager/includes/download-source/types/class-fw-download-source-github.php CHANGED
@@ -1,188 +1,188 @@
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
- }
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,10 +1,11 @@
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' );
 
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_Bitbucket() );
7
+ $download_sources->register( new FW_Ext_Download_Source_Custom() );
8
+ }
9
+ }
10
+
11
+ 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,246 +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
- * @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>
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,267 +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
- $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>
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,210 +1,210 @@
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
- if ( is_child_theme() && ( $manifest_file = fw_get_stylesheet_customizations_directory( '/theme/manifest.php' ) ) && is_file( $manifest_file ) ) {
23
- $extracted = fw_get_variables_from_file( $manifest_file, array( 'manifest' => array() ) );
24
- if ( isset( $extracted['manifest'] ) ) {
25
- $manifest = array_merge( $manifest, $extracted['manifest'] );
26
- }
27
- }
28
-
29
- $this->manifest = new FW_Theme_Manifest( $manifest );
30
- }
31
-
32
- /**
33
- * @internal
34
- */
35
- public function _init() {
36
- add_action( 'admin_notices', array( $this, '_action_admin_notices' ) );
37
- }
38
-
39
- /**
40
- * @internal
41
- */
42
- public function _after_components_init() {
43
- }
44
-
45
- /**
46
- * Search relative path in: child theme -> parent "theme" directory and return full path
47
- *
48
- * @param string $rel_path
49
- *
50
- * @return false|string
51
- */
52
- public function locate_path( $rel_path ) {
53
- if ( is_child_theme() && file_exists( fw_get_stylesheet_customizations_directory( '/theme' . $rel_path ) ) ) {
54
- return fw_get_stylesheet_customizations_directory( '/theme' . $rel_path );
55
- } elseif ( file_exists( fw_get_template_customizations_directory( '/theme' . $rel_path ) ) ) {
56
- return fw_get_template_customizations_directory( '/theme' . $rel_path );
57
- } else {
58
- return false;
59
- }
60
- }
61
-
62
- /**
63
- * Return array with options from specified name/path
64
- *
65
- * @param string $name '{theme}/framework-customizations/theme/options/{$name}.php'
66
- * @param array $variables These will be available in options file (like variables for view)
67
- *
68
- * @return array
69
- */
70
- public function get_options( $name, array $variables = array() ) {
71
- $path = $this->locate_path( '/options/' . $name . '.php' );
72
-
73
- if ( ! $path ) {
74
- return array();
75
- }
76
-
77
- $variables = fw_get_variables_from_file( $path, array( 'options' => array() ), $variables );
78
-
79
- return $variables['options'];
80
- }
81
-
82
- public function get_settings_options() {
83
- $cache_key = self::$cache_key . '/options/settings';
84
-
85
- try {
86
- return FW_Cache::get( $cache_key );
87
- } catch ( FW_Cache_Not_Found_Exception $e ) {
88
- $options = apply_filters( 'fw_settings_options', $this->get_options( 'settings' ) );
89
-
90
- FW_Cache::set( $cache_key, $options );
91
-
92
- return $options;
93
- }
94
- }
95
-
96
- public function get_customizer_options() {
97
- $cache_key = self::$cache_key . '/options/customizer';
98
-
99
- try {
100
- return FW_Cache::get( $cache_key );
101
- } catch ( FW_Cache_Not_Found_Exception $e ) {
102
- $options = apply_filters( 'fw_customizer_options', $this->get_options( 'customizer' ) );
103
-
104
- FW_Cache::set( $cache_key, $options );
105
-
106
- return $options;
107
- }
108
- }
109
-
110
- public function get_post_options( $post_type ) {
111
- $cache_key = self::$cache_key . '/options/posts/' . $post_type;
112
-
113
- try {
114
- return FW_Cache::get( $cache_key );
115
- } catch ( FW_Cache_Not_Found_Exception $e ) {
116
- $options = apply_filters(
117
- 'fw_post_options',
118
- apply_filters( "fw_post_options:$post_type", $this->get_options( 'posts/' . $post_type ) ),
119
- $post_type
120
- );
121
-
122
- FW_Cache::set( $cache_key, $options );
123
-
124
- return $options;
125
- }
126
- }
127
-
128
- public function get_taxonomy_options( $taxonomy ) {
129
- $cache_key = self::$cache_key . '/options/taxonomies/' . $taxonomy;
130
-
131
- try {
132
- return FW_Cache::get( $cache_key );
133
- } catch ( FW_Cache_Not_Found_Exception $e ) {
134
- $options = apply_filters(
135
- 'fw_taxonomy_options',
136
- apply_filters( "fw_taxonomy_options:$taxonomy", $this->get_options( 'taxonomies/' . $taxonomy ) ),
137
- $taxonomy
138
- );
139
-
140
- FW_Cache::set( $cache_key, $options );
141
-
142
- return $options;
143
- }
144
- }
145
-
146
- /**
147
- * Return config key value, or entire config array
148
- * Config array is merged from child configs
149
- *
150
- * @param string|null $key Multi key format accepted: 'a/b/c'
151
- * @param mixed $default_value
152
- *
153
- * @return mixed|null
154
- */
155
- final public function get_config( $key = null, $default_value = null ) {
156
- $cache_key = self::$cache_key . '/config';
157
-
158
- try {
159
- $config = FW_Cache::get( $cache_key );
160
- } catch ( FW_Cache_Not_Found_Exception $e ) {
161
- // default values
162
- $config = array(
163
- /** Toggle Theme Settings form ajax submit */
164
- 'settings_form_ajax_submit' => true,
165
- /** Toggle Theme Settings side tabs */
166
- 'settings_form_side_tabs' => false,
167
- /** Toggle Tabs rendered all at once, or initialized only on open/display */
168
- 'lazy_tabs' => true,
169
- );
170
-
171
- if ( file_exists( fw_get_template_customizations_directory( '/theme/config.php' ) ) ) {
172
- $variables = fw_get_variables_from_file( fw_get_template_customizations_directory( '/theme/config.php' ), array( 'cfg' => null ) );
173
-
174
- if ( ! empty( $variables['cfg'] ) ) {
175
- $config = array_merge( $config, $variables['cfg'] );
176
- unset( $variables );
177
- }
178
- }
179
-
180
- if ( is_child_theme() && file_exists( fw_get_stylesheet_customizations_directory( '/theme/config.php' ) ) ) {
181
- $variables = fw_get_variables_from_file( fw_get_stylesheet_customizations_directory( '/theme/config.php' ), array( 'cfg' => null ) );
182
-
183
- if ( ! empty( $variables['cfg'] ) ) {
184
- $config = array_merge( $config, $variables['cfg'] );
185
- unset( $variables );
186
- }
187
- }
188
-
189
- unset( $path );
190
-
191
- FW_Cache::set( $cache_key, $config );
192
- }
193
-
194
- return $key === null ? $config : fw_akg( $key, $config, $default_value );
195
- }
196
-
197
- /**
198
- * @internal
199
- */
200
- public function _action_admin_notices() {
201
- if ( is_admin() && ! fw()->theme->manifest->check_requirements() && current_user_can( 'manage_options' ) ) {
202
- echo
203
- '<div class="notice notice-warning">
204
- <p>' .
205
- __( 'Theme requirements not met:', 'fw' ) . ' ' . fw()->theme->manifest->get_not_met_requirement_text() .
206
- '</p>
207
- </div>';
208
- }
209
- }
210
- }
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
+ if ( is_child_theme() && ( $manifest_file = fw_get_stylesheet_customizations_directory( '/theme/manifest.php' ) ) && is_file( $manifest_file ) ) {
23
+ $extracted = fw_get_variables_from_file( $manifest_file, array( 'manifest' => array() ) );
24
+ if ( isset( $extracted['manifest'] ) ) {
25
+ $manifest = array_merge( $manifest, $extracted['manifest'] );
26
+ }
27
+ }
28
+
29
+ $this->manifest = new FW_Theme_Manifest( $manifest );
30
+ }
31
+
32
+ /**
33
+ * @internal
34
+ */
35
+ public function _init() {
36
+ add_action( 'admin_notices', array( $this, '_action_admin_notices' ) );
37
+ }
38
+
39
+ /**
40
+ * @internal
41
+ */
42
+ public function _after_components_init() {
43
+ }
44
+
45
+ /**
46
+ * Search relative path in: child theme -> parent "theme" directory and return full path
47
+ *
48
+ * @param string $rel_path
49
+ *
50
+ * @return false|string
51
+ */
52
+ public function locate_path( $rel_path ) {
53
+ if ( is_child_theme() && file_exists( fw_get_stylesheet_customizations_directory( '/theme' . $rel_path ) ) ) {
54
+ return fw_get_stylesheet_customizations_directory( '/theme' . $rel_path );
55
+ } elseif ( file_exists( fw_get_template_customizations_directory( '/theme' . $rel_path ) ) ) {
56
+ return fw_get_template_customizations_directory( '/theme' . $rel_path );
57
+ } else {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Return array with options from specified name/path
64
+ *
65
+ * @param string $name '{theme}/framework-customizations/theme/options/{$name}.php'
66
+ * @param array $variables These will be available in options file (like variables for view)
67
+ *
68
+ * @return array
69
+ */
70
+ public function get_options( $name, array $variables = array() ) {
71
+ $path = $this->locate_path( '/options/' . $name . '.php' );
72
+
73
+ if ( ! $path ) {
74
+ return array();
75
+ }
76
+
77
+ $variables = fw_get_variables_from_file( $path, array( 'options' => array() ), $variables );
78
+
79
+ return $variables['options'];
80
+ }
81
+
82
+ public function get_settings_options() {
83
+ $cache_key = self::$cache_key . '/options/settings';
84
+
85
+ try {
86
+ return FW_Cache::get( $cache_key );
87
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
88
+ $options = apply_filters( 'fw_settings_options', $this->get_options( 'settings' ) );
89
+
90
+ FW_Cache::set( $cache_key, $options );
91
+
92
+ return $options;
93
+ }
94
+ }
95
+
96
+ public function get_customizer_options() {
97
+ $cache_key = self::$cache_key . '/options/customizer';
98
+
99
+ try {
100
+ return FW_Cache::get( $cache_key );
101
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
102
+ $options = apply_filters( 'fw_customizer_options', $this->get_options( 'customizer' ) );
103
+
104
+ FW_Cache::set( $cache_key, $options );
105
+
106
+ return $options;
107
+ }
108
+ }
109
+
110
+ public function get_post_options( $post_type ) {
111
+ $cache_key = self::$cache_key . '/options/posts/' . $post_type;
112
+
113
+ try {
114
+ return FW_Cache::get( $cache_key );
115
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
116
+ $options = apply_filters(
117
+ 'fw_post_options',
118
+ apply_filters( "fw_post_options:$post_type", $this->get_options( 'posts/' . $post_type ) ),
119
+ $post_type
120
+ );
121
+
122
+ FW_Cache::set( $cache_key, $options );
123
+
124
+ return $options;
125
+ }
126
+ }
127
+
128
+ public function get_taxonomy_options( $taxonomy ) {
129
+ $cache_key = self::$cache_key . '/options/taxonomies/' . $taxonomy;
130
+
131
+ try {
132
+ return FW_Cache::get( $cache_key );
133
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
134
+ $options = apply_filters(
135
+ 'fw_taxonomy_options',
136
+ apply_filters( "fw_taxonomy_options:$taxonomy", $this->get_options( 'taxonomies/' . $taxonomy ) ),
137
+ $taxonomy
138
+ );
139
+
140
+ FW_Cache::set( $cache_key, $options );
141
+
142
+ return $options;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Return config key value, or entire config array
148
+ * Config array is merged from child configs
149
+ *
150
+ * @param string|null $key Multi key format accepted: 'a/b/c'
151
+ * @param mixed $default_value
152
+ *
153
+ * @return mixed|null
154
+ */
155
+ final public function get_config( $key = null, $default_value = null ) {
156
+ $cache_key = self::$cache_key . '/config';
157
+
158
+ try {
159
+ $config = FW_Cache::get( $cache_key );
160
+ } catch ( FW_Cache_Not_Found_Exception $e ) {
161
+ // default values
162
+ $config = array(
163
+ /** Toggle Theme Settings form ajax submit */
164
+ 'settings_form_ajax_submit' => true,
165
+ /** Toggle Theme Settings side tabs */
166
+ 'settings_form_side_tabs' => false,
167
+ /** Toggle Tabs rendered all at once, or initialized only on open/display */
168
+ 'lazy_tabs' => true,
169
+ );
170
+
171
+ if ( file_exists( fw_get_template_customizations_directory( '/theme/config.php' ) ) ) {
172
+ $variables = fw_get_variables_from_file( fw_get_template_customizations_directory( '/theme/config.php' ), array( 'cfg' => null ) );
173
+
174
+ if ( ! empty( $variables['cfg'] ) ) {
175
+ $config = array_merge( $config, $variables['cfg'] );
176
+ unset( $variables );
177
+ }
178
+ }
179
+
180
+ if ( is_child_theme() && file_exists( fw_get_stylesheet_customizations_directory( '/theme/config.php' ) ) ) {
181
+ $variables = fw_get_variables_from_file( fw_get_stylesheet_customizations_directory( '/theme/config.php' ), array( 'cfg' => null ) );
182
+
183
+ if ( ! empty( $variables['cfg'] ) ) {
184
+ $config = array_merge( $config, $variables['cfg'] );
185
+ unset( $variables );
186
+ }
187
+ }
188
+
189
+ unset( $path );
190
+
191
+ FW_Cache::set( $cache_key, $config );
192
+ }
193
+
194
+ return $key === null ? $config : fw_akg( $key, $config, $default_value );
195
+ }
196
+
197
+ /**
198
+ * @internal
199
+ */
200
+ public function _action_admin_notices() {
201
+ if ( is_admin() && ! fw()->theme->manifest->check_requirements() && current_user_can( 'manage_options' ) ) {
202
+ echo
203
+ '<div class="notice notice-warning">
204
+ <p>' .
205
+ __( 'Theme requirements not met:', 'fw' ) . ' ' . fw()->theme->manifest->get_not_met_requirement_text() .
206
+ '</p>
207
+ </div>';
208
+ }
209
+ }
210
+ }
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,515 +1,515 @@
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
-
378
- return fw_get_db_ext_settings_option( $this->get_name(), $option_id, $default_value, $get_original_value );
379
- }
380
-
381
- /**
382
- * Set extension's setting option value in database
383
- *
384
- * @param string|null $option_id
385
- * @param mixed $value
386
- */
387
- final public function set_db_settings_option( $option_id = null, $value ) {
388
- fw_set_db_ext_settings_option( $this->get_name(), $option_id, $value );
389
- }
390
-
391
- /**
392
- * Get extension's data from the database
393
- *
394
- * @param string|null $multi_key The key of the data you want to get. null - all data
395
- * @param null|mixed $default_value If no option found in the database, this value will be returned
396
- * @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
397
- *
398
- * @return mixed|null
399
- */
400
- final public function get_db_data( $multi_key = null, $default_value = null, $get_original_value = null ) {
401
- return fw_get_db_extension_data( $this->get_name(), $multi_key, $default_value, $get_original_value );
402
- }
403
-
404
- /**
405
- * Set some extension's data in database
406
- *
407
- * @param string|null $multi_key The key of the data you want to set. null - all data
408
- * @param mixed $value
409
- */
410
- final public function set_db_data( $multi_key = null, $value ) {
411
- fw_set_db_extension_data( $this->get_name(), $multi_key, $value );
412
- }
413
-
414
- /**
415
- * Get extension's data from user meta
416
- *
417
- * @param int $user_id
418
- * @param string|null $keys
419
- *
420
- * @return mixed|null
421
- */
422
- final public function get_user_data( $user_id, $keys = null ) {
423
- return fw_get_db_extension_user_data($user_id, $this->get_name(), $keys);
424
- }
425
-
426
- /**
427
- * et some extension's data in user meta
428
- *
429
- * @param int $user_id
430
- * @param mixed $value
431
- * @param string|null $keys
432
- *
433
- * @return bool|int
434
- */
435
- final public function set_user_data( $user_id, $value, $keys = null ) {
436
- return fw_set_db_extension_user_data($user_id, $this->get_name(), $value, $keys);
437
- }
438
-
439
- final public function get_post_options($post_type)
440
- {
441
- return $this->get_options('posts/'. $post_type);
442
- }
443
-
444
- final public function get_taxonomy_options($taxonomy)
445
- {
446
- return $this->get_options('taxonomies/'. $taxonomy);
447
- }
448
-
449
- /**
450
- * @param string $name File name without extension, located in <extension>/static/js/$name.js
451
- * @return string URI
452
- */
453
- final public function locate_js_URI($name)
454
- {
455
- return $this->locate_URI('/static/js/'. $name .'.js');
456
- }
457
-
458
- /**
459
- * @param string $name File name without extension, located in <extension>/static/js/$name.js
460
- * @return string URI
461
- */
462
- final public function locate_css_URI($name)
463
- {
464
- return $this->locate_URI('/static/css/'. $name .'.css');
465
- }
466
-
467
- /**
468
- * @param string $name File name without extension, located in <extension>/views/$name.php
469
- * @return false|string
470
- */
471
- final public function locate_view_path($name)
472
- {
473
- return $this->locate_path('/views/'. $name .'.php');
474
- }
475
-
476
- final public function get_depth()
477
- {
478
- return $this->depth;
479
- }
480
-
481
- final public function get_customizations_locations()
482
- {
483
- return $this->customizations_locations;
484
- }
485
-
486
- final public function get_rel_path()
487
- {
488
- return $this->rel_path;
489
- }
490
-
491
- /**
492
- * Check if child extension is valid
493
- *
494
- * Used for special cases when an extension requires its child extensions to extend some special class
495
- *
496
- * @param FW_Extension $child_extension_instance
497
- * @return bool
498
- * @internal
499
- */
500
- public function _child_extension_is_valid($child_extension_instance)
501
- {
502
- return is_subclass_of($child_extension_instance, 'FW_Extension');
503
- }
504
-
505
- /**
506
- * Get link to the page created by this extension in dashboard
507
- * (Used on the extensions page)
508
- * @internal
509
- * @return string
510
- */
511
- public function _get_link()
512
- {
513
- return false;
514
- }
515
- }
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
+
378
+ return fw_get_db_ext_settings_option( $this->get_name(), $option_id, $default_value, $get_original_value );
379
+ }
380
+
381
+ /**
382
+ * Set extension's setting option value in database
383
+ *
384
+ * @param string|null $option_id
385
+ * @param mixed $value
386
+ */
387
+ final public function set_db_settings_option( $option_id = null, $value ) {
388
+ fw_set_db_ext_settings_option( $this->get_name(), $option_id, $value );
389
+ }
390
+
391
+ /**
392
+ * Get extension's data from the database
393
+ *
394
+ * @param string|null $multi_key The key of the data you want to get. null - all data
395
+ * @param null|mixed $default_value If no option found in the database, this value will be returned
396
+ * @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
397
+ *
398
+ * @return mixed|null
399
+ */
400
+ final public function get_db_data( $multi_key = null, $default_value = null, $get_original_value = null ) {
401
+ return fw_get_db_extension_data( $this->get_name(), $multi_key, $default_value, $get_original_value );
402
+ }
403
+
404
+ /**
405
+ * Set some extension's data in database
406
+ *
407
+ * @param string|null $multi_key The key of the data you want to set. null - all data
408
+ * @param mixed $value
409
+ */
410
+ final public function set_db_data( $multi_key = null, $value ) {
411
+ fw_set_db_extension_data( $this->get_name(), $multi_key, $value );
412
+ }
413
+
414
+ /**
415
+ * Get extension's data from user meta
416
+ *
417
+ * @param int $user_id
418
+ * @param string|null $keys
419
+ *
420
+ * @return mixed|null
421
+ */
422
+ final public function get_user_data( $user_id, $keys = null ) {
423
+ return fw_get_db_extension_user_data($user_id, $this->get_name(), $keys);
424
+ }
425
+
426
+ /**
427
+ * et some extension's data in user meta
428
+ *
429
+ * @param int $user_id
430
+ * @param mixed $value
431
+ * @param string|null $keys
432
+ *
433
+ * @return bool|int
434
+ */
435
+ final public function set_user_data( $user_id, $value, $keys = null ) {
436
+ return fw_set_db_extension_user_data($user_id, $this->get_name(), $value, $keys);
437
+ }
438
+
439
+ final public function get_post_options($post_type)
440
+ {
441
+ return $this->get_options('posts/'. $post_type);
442
+ }
443
+
444
+ final public function get_taxonomy_options($taxonomy)
445
+ {
446
+ return $this->get_options('taxonomies/'. $taxonomy);
447
+ }
448
+
449
+ /**
450
+ * @param string $name File name without extension, located in <extension>/static/js/$name.js
451
+ * @return string URI
452
+ */
453
+ final public function locate_js_URI($name)
454
+ {
455
+ return $this->locate_URI('/static/js/'. $name .'.js');
456
+ }
457
+
458
+ /**
459
+ * @param string $name File name without extension, located in <extension>/static/js/$name.js
460
+ * @return string URI
461
+ */
462
+ final public function locate_css_URI($name)
463
+ {
464
+ return $this->locate_URI('/static/css/'. $name .'.css');
465
+ }
466
+
467
+ /**
468
+ * @param string $name File name without extension, located in <extension>/views/$name.php
469
+ * @return false|string
470
+ */
471
+ final public function locate_view_path($name)
472
+ {
473
+ return $this->locate_path('/views/'. $name .'.php');
474
+ }
475
+
476
+ final public function get_depth()
477
+ {
478
+ return $this->depth;
479
+ }
480
+
481
+ final public function get_customizations_locations()
482
+ {
483
+ return $this->customizations_locations;
484
+ }
485
+
486
+ final public function get_rel_path()
487
+ {
488
+ return $this->rel_path;
489
+ }
490
+
491
+ /**
492
+ * Check if child extension is valid
493
+ *
494
+ * Used for special cases when an extension requires its child extensions to extend some special class
495
+ *
496
+ * @param FW_Extension $child_extension_instance
497
+ * @return bool
498
+ * @internal
499
+ */
500
+ public function _child_extension_is_valid($child_extension_instance)
501
+ {
502
+ return is_subclass_of($child_extension_instance, 'FW_Extension');
503
+ }
504
+
505
+ /**
506
+ * Get link to the page created by this extension in dashboard
507
+ * (Used on the extensions page)
508
+ * @internal
509
+ * @return string
510
+ */
511
+ public function _get_link()
512
+ {
513
+ return false;
514
+ }
515
+ }
framework/core/extends/class-fw-option-type.php CHANGED
@@ -1,465 +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
- * 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
- }
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,1013 +1,1013 @@
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' ) && ! fw_is_cli() ) {
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
- public 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
- * @param string $skin
829
- */
830
- public function _action_update_extensions( $skin = '' ) {
831
- $nonce_name = '_nonce_fw_ext_update_extensions';
832
- if ( ! isset( $_POST[ $nonce_name ] ) || ! wp_verify_nonce( $_POST[ $nonce_name ] ) ) {
833
- wp_die( __( 'Invalid nonce.', 'fw' ) );
834
- }
835
-
836
- $form_input_name = 'extensions';
837
- $extensions_list = FW_Request::POST( $form_input_name );
838
-
839
- if ( empty( $extensions_list ) ) {
840
- FW_Flash_Messages::add(
841
- 'fw_ext_update',
842
- __( 'Please check the extensions you want to update.', 'fw' ),
843
- 'warning'
844
- );
845
- wp_redirect( self_admin_url( 'update-core.php' ) );
846
- exit;
847
- }
848
-
849
- // handle changes by the hack below
850
- {
851
- if ( is_string( $extensions_list ) ) {
852
- $extensions_list = json_decode( $extensions_list );
853
- } else {
854
- $extensions_list = array_keys( $extensions_list );
855
- }
856
- }
857
-
858
- {
859
- if ( ! class_exists( '_FW_Ext_Update_Extensions_Upgrader_Skin' ) ) {
860
- fw_include_file_isolated(
861
- $this->get_declared_path( '/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php' )
862
- );
863
- }
864
-
865
- if ( ! $skin ) {
866
- $skin = new _FW_Ext_Update_Extensions_Upgrader_Skin( array(
867
- 'title' => __( 'Extensions Update', 'fw' ),
868
- ) );
869
- }
870
- }
871
-
872
- if ( ! fw_is_cli() ) {
873
- require_once( ABSPATH . 'wp-admin/admin-header.php' );
874
- }
875
-
876
- $skin->header();
877
-
878
- do {
879
- /**
880
- * Hack for the ftp credentials template that does not support array post values
881
- * https://github.com/WordPress/WordPress/blob/3949a8b6cc50a021ed93798287b4ef9ea8a560d9/wp-admin/includes/file.php#L1144
882
- */
883
- $original_post_value = $_POST[ $form_input_name ];
884
- $_POST[ $form_input_name ] = wp_slash( json_encode( $extensions_list ) );
885
-
886
- if ( ! FW_WP_Filesystem::request_access( fw_get_framework_directory( '/extensions' ), fw_current_url(), array( $nonce_name, $form_input_name ) ) ) {
887
- // revert hack changes
888
- $_POST[ $form_input_name ] = $original_post_value;
889
- unset( $original_post_value );
890
-
891
- break;
892
- }
893
-
894
- // revert hack changes
895
- $_POST[ $form_input_name ] = $original_post_value;
896
- unset( $original_post_value );
897
-
898
- $updates = $this->get_extensions_with_updates();
899
-
900
- if ( empty( $updates ) ) {
901
- $skin->error( __( 'No extensions updates found.', 'fw' ) );
902
- break;
903
- }
904
-
905
- foreach ( $extensions_list as $extension_name ) {
906
- if ( ! ( $extension = fw()->extensions->get( $extension_name ) ) ) {
907
- $skin->error( sprintf( __( 'Extension "%s" does not exist or is disabled.', 'fw' ), $extension_name ) );
908
- continue;
909
- }
910
-
911
- if ( ! isset( $updates[ $extension_name ] ) ) {
912
- $skin->error( sprintf( __( 'No update found for the "%s" extension.', 'fw' ), $extension->manifest->get_name() ) );
913
- continue;
914
- }
915
-
916
- $update = $updates[ $extension_name ];
917
-
918
- if ( is_wp_error( $update ) ) {
919
- $skin->error( $update );
920
- continue;
921
- }
922
-
923
- /** @var FW_Ext_Update_Service $service */
924
- $service = $this->get_child( $update['service'] );
925
-
926
- $update_result = $this->update( array(
927
- 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
928
- $extension->get_declared_path()
929
- ),
930
- 'download_callback' => array( $service, '_download_extension' ),
931
- 'download_callback_args' => array(
932
- $extension,
933
- $update['latest_version'],
934
- $this->get_wp_fs_tmp_dir()
935
- ),
936
- 'skin' => $skin,
937
- 'title' => sprintf( __( '%s extension', 'fw' ), $extension->manifest->get_name() ),
938
- ), true );
939
-
940
- if ( is_wp_error( $update_result ) ) {
941
- $skin->error( $update_result );
942
- continue;
943
- }
944
-
945
- $skin->set_result( true );
946
-
947
- if ( ! $this->get_config( 'extensions_as_one_update' ) ) {
948
- $skin->decrement_extension_update_count( $extension_name );
949
- }
950
- }
951
-
952
- if ( $this->get_config( 'extensions_as_one_update' ) ) {
953
- $skin->decrement_extension_update_count( $extension_name );
954
- }
955
-
956
- $skin->after();
957
-
958
- } while ( false );
959
-
960
- $skin->footer();
961
-
962
- if ( ! fw_is_cli() ) {
963
- require_once( ABSPATH . 'wp-admin/admin-footer.php' );
964
- }
965
- }
966
-
967
- public function _action_admin_notices() {
968
-
969
- $updates = $this->get_updates();
970
-
971
- if ( ( empty( $updates['extensions'] ) && empty( $updates['theme'] ) )
972
- ||
973
- strpos( get_current_screen()->id, 'update-core' ) !== false
974
- ||
975
- ( isset( $_GET['page'] ) && $_GET['page'] === 'fw-update' ) ) {
976
-
977
- return;
978
- }
979
-
980
- foreach ( $updates['extensions'] as $ext_name => $ext_update ) {
981
- if ( is_wp_error( $ext_update ) ) {
982
- return;
983
- }
984
-
985
- break;
986
- }
987
-
988
- $slug = is_multisite() && ! is_plugin_active_for_network( 'unyson/unyson.php' ) ? '?page=fw-update' : 'update-core.php#fw-ext-update-extensions';
989
-
990
- if ( ! empty( $updates['extensions'] ) && empty( $updates['theme'] ) ) {
991
- $text = 'extensions updates';
992
- } elseif ( empty( $updates['extensions'] ) && ! empty( $updates['theme'] ) ) {
993
- $text = 'theme update';
994
- } else {
995
- $text = 'extensions/theme updates';
996
- }
997
-
998
- echo
999
- '<div class="notice notice-warning">
1000
- <p>' .
1001
- sprintf(
1002
- esc_html__( 'New %s available. %s', 'fw' ),
1003
- $text,
1004
- fw_html_tag(
1005
- 'a',
1006
- array( 'href' => self_admin_url( $slug ) ),
1007
- esc_html__( 'Go to Updates page', 'fw' )
1008
- )
1009
- ) .
1010
- '</p>
1011
- </div>';
1012
- }
1013
- }
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' ) && ! fw_is_cli() ) {
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
+ public 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
+ * @param string $skin
829
+ */
830
+ public function _action_update_extensions( $skin = '' ) {
831
+ $nonce_name = '_nonce_fw_ext_update_extensions';
832
+ if ( ! isset( $_POST[ $nonce_name ] ) || ! wp_verify_nonce( $_POST[ $nonce_name ] ) ) {
833
+ wp_die( __( 'Invalid nonce.', 'fw' ) );
834
+ }
835
+
836
+ $form_input_name = 'extensions';
837
+ $extensions_list = FW_Request::POST( $form_input_name );
838
+
839
+ if ( empty( $extensions_list ) ) {
840
+ FW_Flash_Messages::add(
841
+ 'fw_ext_update',
842
+ __( 'Please check the extensions you want to update.', 'fw' ),
843
+ 'warning'
844
+ );
845
+ wp_redirect( self_admin_url( 'update-core.php' ) );
846
+ exit;
847
+ }
848
+
849
+ // handle changes by the hack below
850
+ {
851
+ if ( is_string( $extensions_list ) ) {
852
+ $extensions_list = json_decode( $extensions_list );
853
+ } else {
854
+ $extensions_list = array_keys( $extensions_list );
855
+ }
856
+ }
857
+
858
+ {
859
+ if ( ! class_exists( '_FW_Ext_Update_Extensions_Upgrader_Skin' ) ) {
860
+ fw_include_file_isolated(
861
+ $this->get_declared_path( '/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php' )
862
+ );
863
+ }
864
+
865
+ if ( ! $skin ) {
866
+ $skin = new _FW_Ext_Update_Extensions_Upgrader_Skin( array(
867
+ 'title' => __( 'Extensions Update', 'fw' ),
868
+ ) );
869
+ }
870
+ }
871
+
872
+ if ( ! fw_is_cli() ) {
873
+ require_once( ABSPATH . 'wp-admin/admin-header.php' );
874
+ }
875
+
876
+ $skin->header();
877
+
878
+ do {
879
+ /**
880
+ * Hack for the ftp credentials template that does not support array post values
881
+ * https://github.com/WordPress/WordPress/blob/3949a8b6cc50a021ed93798287b4ef9ea8a560d9/wp-admin/includes/file.php#L1144
882
+ */
883
+ $original_post_value = $_POST[ $form_input_name ];
884
+ $_POST[ $form_input_name ] = wp_slash( json_encode( $extensions_list ) );
885
+
886
+ if ( ! FW_WP_Filesystem::request_access( fw_get_framework_directory( '/extensions' ), fw_current_url(), array( $nonce_name, $form_input_name ) ) ) {
887
+ // revert hack changes
888
+ $_POST[ $form_input_name ] = $original_post_value;
889
+ unset( $original_post_value );
890
+
891
+ break;
892
+ }
893
+
894
+ // revert hack changes
895
+ $_POST[ $form_input_name ] = $original_post_value;
896
+ unset( $original_post_value );
897
+
898
+ $updates = $this->get_extensions_with_updates();
899
+
900
+ if ( empty( $updates ) ) {
901
+ $skin->error( __( 'No extensions updates found.', 'fw' ) );
902
+ break;
903
+ }
904
+
905
+ foreach ( $extensions_list as $extension_name ) {
906
+ if ( ! ( $extension = fw()->extensions->get( $extension_name ) ) ) {
907
+ $skin->error( sprintf( __( 'Extension "%s" does not exist or is disabled.', 'fw' ), $extension_name ) );
908
+ continue;
909
+ }
910
+
911
+ if ( ! isset( $updates[ $extension_name ] ) ) {
912
+ $skin->error( sprintf( __( 'No update found for the "%s" extension.', 'fw' ), $extension->manifest->get_name() ) );
913
+ continue;
914
+ }
915
+
916
+ $update = $updates[ $extension_name ];
917
+
918
+ if ( is_wp_error( $update ) ) {
919
+ $skin->error( $update );
920
+ continue;
921
+ }
922
+
923
+ /** @var FW_Ext_Update_Service $service */
924
+ $service = $this->get_child( $update['service'] );
925
+
926
+ $update_result = $this->update( array(
927
+ 'wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path(
928
+ $extension->get_declared_path()
929
+ ),
930
+ 'download_callback' => array( $service, '_download_extension' ),
931
+ 'download_callback_args' => array(
932
+ $extension,
933
+ $update['latest_version'],
934
+ $this->get_wp_fs_tmp_dir()
935
+ ),
936
+ 'skin' => $skin,
937
+ 'title' => sprintf( __( '%s extension', 'fw' ), $extension->manifest->get_name() ),
938
+ ), true );
939
+
940
+ if ( is_wp_error( $update_result ) ) {
941
+ $skin->error( $update_result );
942
+ continue;
943
+ }
944
+
945
+ $skin->set_result( true );
946
+
947
+ if ( ! $this->get_config( 'extensions_as_one_update' ) ) {
948
+ $skin->decrement_extension_update_count( $extension_name );
949
+ }
950
+ }
951
+
952
+ if ( $this->get_config( 'extensions_as_one_update' ) ) {
953
+ $skin->decrement_extension_update_count( $extension_name );
954
+ }
955
+
956
+ $skin->after();
957
+
958
+ } while ( false );
959
+
960
+ $skin->footer();
961
+
962
+ if ( ! fw_is_cli() ) {
963
+ require_once( ABSPATH . 'wp-admin/admin-footer.php' );
964
+ }
965
+ }
966
+
967
+ public function _action_admin_notices() {
968
+
969
+ $updates = $this->get_updates();
970
+
971
+ if ( ( empty( $updates['extensions'] ) && empty( $updates['theme'] ) )
972
+ ||
973
+ strpos( get_current_screen()->id, 'update-core' ) !== false
974
+ ||
975
+ ( isset( $_GET['page'] ) && $_GET['page'] === 'fw-update' ) ) {
976
+
977
+ return;
978
+ }
979
+
980
+ foreach ( $updates['extensions'] as $ext_name => $ext_update ) {
981
+ if ( is_wp_error( $ext_update ) ) {
982
+ return;
983
+ }
984
+
985
+ break;
986
+ }
987
+
988
+ $slug = is_multisite() && ! is_plugin_active_for_network( 'unyson/unyson.php' ) ? '?page=fw-update' : 'update-core.php#fw-ext-update-extensions';
989
+
990
+ if ( ! empty( $updates['extensions'] ) && empty( $updates['theme'] ) ) {
991
+ $text = 'extensions updates';
992
+ } elseif ( empty( $updates['extensions'] ) && ! empty( $updates['theme'] ) ) {
993
+ $text = 'theme update';
994
+ } else {
995
+ $text = 'extensions/theme updates';
996
+ }
997
+
998
+ echo
999
+ '<div class="notice notice-warning">
1000
+ <p>' .
1001
+ sprintf(
1002
+ esc_html__( 'New %s available. %s', 'fw' ),
1003
+ $text,
1004
+ fw_html_tag(
1005
+ 'a',
1006
+ array( 'href' => self_admin_url( $slug ) ),
1007
+ esc_html__( 'Go to Updates page', 'fw' )
1008
+ )
1009
+ ) .
1010
+ '</p>
1011
+ </div>';
1012
+ }
1013
+ }
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/bitbucket-update/class-fw-extension-bitbucket-update.php ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'FW' ) or die();
2
+
3
+ /**
4
+ * Bitbucket server Update
5
+ *
6
+ * Add {'remote' => 'your_url'} to your manifest and this extension will handle it
7
+ */
8
+ class FW_Extension_Bitbucket_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 $user_repo
32
+ * @param $force_check
33
+ *
34
+ * @return mixed|string|WP_Error
35
+ */
36
+ private function get_latest_version( $user_repo, $force_check ) {
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[ $user_repo ] ) ) {
48
+ return $cache[ $user_repo ];
49
+ }
50
+ }
51
+
52
+ $version = $this->fetch_latest_version( $user_repo );
53
+
54
+ if ( is_wp_error( $version ) ) {
55
+ // Cache fake version to prevent requests to bitbucket on every refresh.
56
+ $cache[ $user_repo ] = $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_bitbucket_update_error', $version->get_error_message(), 'error' );
60
+
61
+ } else {
62
+ $cache[ $user_repo ] = $version;
63
+ }
64
+
65
+ set_site_transient( $transient_name, $cache, $this->transient_expiration );
66
+
67
+ return $version;
68
+ }
69
+
70
+ /**
71
+ * @param $user_repo
72
+ * @param $next_page
73
+ *
74
+ * @return array|string|WP_Error
75
+ */
76
+ private function fetch_latest_version( $user_repo, $next_page = '' ) {
77
+ /**
78
+ * If at least one request failed, do not do any other requests, to prevent site being blocked on every refresh.
79
+ * This may happen on localhost when develop your theme and you have no internet connection.
80
+ * Then this method will return a fake '0.0.0' version, it will be cached by the transient
81
+ * and will not bother you until the transient will expire, then a new request will be made.
82
+ * @var bool
83
+ */
84
+ static $no_internet_connection = false;
85
+
86
+ if ( $no_internet_connection ) {
87
+ return $this->fake_latest_version;
88
+ }
89
+
90
+ $url = $next_page ? $next_page : "https://api.bitbucket.org/2.0/repositories/{$user_repo}/refs/tags/";
91
+ $request = wp_remote_get( $url, array( 'timeout' => $this->download_timeout ) );
92
+
93
+ if ( is_wp_error( $request ) ) {
94
+ if ( $request->get_error_code() === 'http_request_failed' ) {
95
+ $no_internet_connection = true;
96
+ }
97
+
98
+ return $request;
99
+ }
100
+
101
+ if ( ! ( $versions = json_decode( wp_remote_retrieve_body( $request ), true ) ) || is_wp_error( $versions ) ) {
102
+ return ! $versions ? new WP_Error( sprintf( __( 'Empty version for item: %s', 'fw' ), $user_repo ) ) : $versions;
103
+ }
104
+
105
+ if ( isset( $versions['next'] ) ) {
106
+ return $this->fetch_latest_version( $user_repo, $versions['next'] );
107
+ }
108
+
109
+ $data_version = end( $versions['values'] );
110
+
111
+ return ! empty( $data_version['name'] ) ? $data_version['name'] : new WP_Error( sprintf( __( 'Wrong Bibucket version for item: %s', 'fw' ), $user_repo ) );
112
+ }
113
+
114
+ /**
115
+ * @param array $user_repo - user's repsository formtat username/repositoryName.
116
+ * @param string $version Requested version to download
117
+ * @param string $wp_filesystem_download_directory Allocated temporary empty directory
118
+ * @param string $title Used in messages
119
+ *
120
+ * @return string|WP_Error Path to the downloaded directory
121
+ */
122
+ private function download( $user_repo, $version, $wp_filesystem_download_directory, $title ) {
123
+ /** @var WP_Filesystem_Base $wp_filesystem */
124
+ global $wp_filesystem;
125
+
126
+ $error_id = 'fw_ext_update_bitbucket_download_zip';
127
+ $request = wp_remote_get( "https://bitbucket.org/{$user_repo}/get/{$version}.zip", array( 'timeout' => $this->download_timeout ) );
128
+
129
+ if ( is_wp_error( $request ) ) {
130
+ return $request;
131
+ }
132
+
133
+ if ( ! ( $body = wp_remote_retrieve_body( $request ) ) || is_wp_error( $body ) ) {
134
+ return ! $body ? new WP_Error( $error_id, sprintf( esc_html__( 'Empty zip body for item: %s', 'fw' ), $title ) ) : $body;
135
+ }
136
+
137
+ // Try to extract error if server returned json with key error. If not then is an archive zip.
138
+ if ( ( $error = json_decode( $body, true ) ) && isset( $error['error'] ) ) {
139
+ return new WP_Error( $error_id, $error['error'] );
140
+ }
141
+
142
+ $zip_path = $wp_filesystem_download_directory . '/temp.zip';
143
+
144
+ // save zip to file
145
+ if ( ! $wp_filesystem->put_contents( $zip_path, $body ) ) {
146
+ return new WP_Error( $error_id, sprintf( esc_html__( 'Cannot save %s zip.', 'fw' ), $title ) );
147
+ }
148
+
149
+ $unzip_result = unzip_file( FW_WP_Filesystem::filesystem_path_to_real_path( $zip_path ), $wp_filesystem_download_directory );
150
+
151
+ if ( is_wp_error( $unzip_result ) ) {
152
+ return $unzip_result;
153
+ }
154
+
155
+ // remove zip file
156
+ if ( ! $wp_filesystem->delete( $zip_path, false, 'f' ) ) {
157
+ return new WP_Error( $error_id, sprintf( esc_html__( 'Cannot remove %s zip.', 'fw' ), $title ) );
158
+ }
159
+
160
+ $unzipped_dir_files = $wp_filesystem->dirlist( $wp_filesystem_download_directory );
161
+
162
+ if ( ! $unzipped_dir_files ) {
163
+ return new WP_Error( $error_id, esc_html__( 'Cannot access the unzipped directory files.', 'fw' ) );
164
+ }
165
+
166
+ /**
167
+ * get first found directory
168
+ * (if everything worked well, there should be only one directory)
169
+ */
170
+ foreach ( $unzipped_dir_files as $file ) {
171
+ if ( $file['type'] == 'd' ) {
172
+ return $wp_filesystem_download_directory . '/' . $file['name'];
173
+ }
174
+ }
175
+
176
+ return new WP_Error( $error_id, sprintf( esc_html__( 'The unzipped %s directory not found.', 'fw' ), $title ) );
177
+ }
178
+
179
+ /**
180
+ * {@inheritdoc}
181
+ * @internal
182
+ */
183
+ public function _get_framework_latest_version( $force_check ) {
184
+ return false;
185
+ }
186
+
187
+ /**
188
+ * {@inheritdoc}
189
+ * @internal
190
+ */
191
+ public function _get_theme_latest_version( $force_check ) {
192
+
193
+ $user_repo = fw()->theme->manifest->get( 'bitbucket' );
194
+
195
+ if ( empty( $user_repo ) ) {
196
+ return false;
197
+ }
198
+
199
+ return $this->get_latest_version( $user_repo, $force_check );
200
+ }
201
+
202
+ /**
203
+ * {@inheritdoc}
204
+ * @internal
205
+ */
206
+ public function _download_theme( $version, $wp_filesystem_download_directory ) {
207
+ return $this->download( fw()->theme->manifest->get( 'bitbucket' ), $version, $wp_filesystem_download_directory, esc_html__( 'Theme', 'fw' ) );
208
+ }
209
+
210
+ /**
211
+ * {@inheritdoc}
212
+ * @internal
213
+ */
214
+ public function _get_extension_latest_version( FW_Extension $extension, $force_check ) {
215
+
216
+ if ( ! $extension->manifest->get( 'bitbucket' ) ) {
217
+ return false;
218
+ }
219
+
220
+ return $this->get_latest_version( $extension->manifest->get( 'bitbucket' ), $force_check );
221
+ }
222
+
223
+ /**
224
+ * {@inheritdoc}
225
+ * @internal
226
+ */
227
+ public function _download_extension( FW_Extension $extension, $version, $wp_filesystem_download_directory ) {
228
+ return $this->download( $extension->manifest->get( 'bitbucket' ), $version, $wp_filesystem_download_directory, sprintf( esc_html__( '%s extension', 'fw' ), $extension->manifest->get_name() )
229
+ );
230
+ }
231
+ }
framework/extensions/update/extensions/bitbucket-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/custom-update/class-fw-extension-custom-update.php CHANGED
@@ -1,259 +1,259 @@
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
- if ( isset( $manifest ) ) {
253
- $theme_id = isset( $manifest['id'] ) ? $manifest['id'] : '';
254
- return $this->data_manifest( $manifest, 'theme', $theme_id );
255
- }
256
-
257
- return array();
258
- }
259
- }
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
+ if ( isset( $manifest ) ) {
253
+ $theme_id = isset( $manifest['id'] ) ? $manifest['id'] : '';
254
+ return $this->data_manifest( $manifest, 'theme', $theme_id );
255
+ }
256
+
257
+ return array();
258
+ }
259
+ }
framework/extensions/update/extensions/custom-update/manifest.php CHANGED
@@ -1,5 +1,5 @@
1
- <?php defined( 'FW' ) or die();
2
-
3
- $manifest = array();
4
-
5
- $manifest['standalone'] = true;
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,433 +1,433 @@
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 - 12 hours
25
- * @var int seconds
26
- */
27
- private $transient_expiration = 43200;
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
- }
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 - 12 hours
25
+ * @var int seconds
26
+ */
27
+ private $transient_expiration = 43200;
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,118 +1,118 @@
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>
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,330 +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
-
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
- }
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
+