4c19e6fe by Jeff Balicki

ddd

1 parent b439f946
Showing 52 changed files with 11003 additions and 0 deletions
1 GNU GENERAL PUBLIC LICENSE
2 Version 3, 29 June 2007
3
4 Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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 <program> Copyright (C) <year> <name of author>
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 <https://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 <https://www.gnu.org/licenses/why-not-lgpl.html>.
1 /* FVM 3 */
2 #fastvelocity-min{margin-top:10px}
3 #fastvelocity-min .clear{clear:both}
4 .fvm-settings label{font-size:15px;color:#222;padding-top:1px}
5 .fvm-settings span.note-info{font-size:15px;margin-left:8px;color:#666;font-style:italic}
6 .fvm-settings p.description{font-size:15px;color:#666;font-style:italic}
7 #tab-info h4{font-size:15px;margin-bottom:-10px}
8 .fvm-hide{max-height:1px;overflow:none;position:absolute;top:-9999px;left:-9999px;visibility:hidden}
9 .fvm-warning{font-weight:500;color:#A00}
10 h3.fvm-bold-green{font-weight:500;font-size:15px;color:#1196A3}
11 .fvm-bold-green{font-weight:500;color:#1196A3}
12 .fvm-label-special{line-height:38px}
13 .fvm-label-pad{line-height:21px}
14 .fvm-rowintro{padding-bottom:10px;font-size:15px}
15 table.fvm-settings td,table.fvm-settings td p,table.fvm-settings th{font-size:15px}
16 .fvm-wrapper{font-size:16px;padding:18px}
17 .fvm-wrapper h4{font-size:16px;margin:0;color:#1196A3}
18 .fvm-wrapper .accordion p,.fvm-wrapper .accordion code{font-size:16px;line-height:1.3}
19 .fvm-wrapper .accordion a, .fvm-wrapper .accordion a:hover { color: #0073aa; }
20 .fvm-code-full{width:100%;padding:8px;background:#F3F3F3;box-sizing:border-box;font-family:Consolas,Monaco,monospace;font-size:13px;line-height:1.5;margin-bottom:15px}
21 #status .fvm-cache-stats{margin:10px 0 0}
22 #status textarea.row-log{display:block;margin:0;padding:6px;border:none;background:#23282d;color:#ccc;height:300px;max-width:100%;overflow:auto;line-height:1.5;font-size:.8125rem;white-space:pre;font-family:monospace}
23 #status textarea.row-log::-webkit-scrollbar{width:8px;height:8px;background-color:#222}
24 #status textarea.row-log::-webkit-scrollbar-thumb{background-color:#000}
25 #status textarea.row-log::-webkit-scrollbar-track{background-color:#999}
26
27 /* jQuery UI */
28 .ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}
29 .ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}
30 .ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0;padding:.5em .5em .5em .7em;font-size:100%}
31 .ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}
32 .ui-widget{font-size:1em}
33 .ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}
34 .ui-widget-content a{color:#333}
35 .ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:400;color:#454545}
36 .ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:400;color:#2b2b2b}
37 .ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #bbb;background:#0073aa;font-weight:400;color:#fff}
38 .ui-icon{width:16px;height:16px}
39 .ui-icon,.ui-widget-content .ui-icon{background-image:url(images/ui-icons_444444_256x240.png)}
40 .ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url(images/ui-icons_555555_256x240.png)}
41 .ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url(images/ui-icons_ffffff_256x240.png)}
42 .ui-icon-triangle-1-e{background-position:-32px -16px}
43 .ui-icon-triangle-1-s{background-position:-65px -16px}
44 .ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}
45 .ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}
46 .ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}
47 .ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}
48 .ui-state-focus:focus{outline:none}
49
50 /* Mobile */
51 @media screen and (max-width:520px) {
52 .fvm-label-special { line-height: inherit; }
53 }
1 // get logs via ajax
2 function fvm_get_logs() {
3
4 // ajax request
5 jQuery( document ).ready(function() {
6 var data = { 'action': 'fvm_get_logs' };
7 jQuery.post(ajaxurl, data, function(resp) {
8 if(resp.success == 'OK') {
9
10 // logs
11 jQuery('.log-stats textarea').val(resp.log);
12 jQuery('.log-stats textarea').scrollTop(jQuery('.log-stats textarea')[0].scrollHeight);
13
14 } else {
15 // error log
16 console.error(resp.success);
17 }
18 });
19 });
20 }
21
22
23 jQuery( document ).ready(function() {
24
25 // help section
26 jQuery( ".accordion" ).accordion({ active: false, collapsible: true, heightStyle: "content" });
27
28 });
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2 /*
3 Plugin Name: Fast Velocity Minify
4 Plugin URI: http://fastvelocity.com
5 Description: Improve your speed score on GTmetrix, Pingdom Tools and Google PageSpeed Insights by merging and minifying CSS and JavaScript files into groups, compressing HTML and other speed optimizations.
6 Author: Raul Peixoto
7 Author URI: http://fastvelocity.com
8 Text Domain: fast-velocity-minify
9 Version: 3.2.2
10 License: GPL2
11
12 ------------------------------------------------------------------------
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 */
27
28 # Exit if accessed directly
29 if (!defined('ABSPATH')){ exit(); }
30
31 # Invalidate OPCache for current file on WP 5.5+
32 if(function_exists('wp_opcache_invalidate') && stripos(__FILE__, '/fvm.php') !== false) {
33 wp_opcache_invalidate(__FILE__, true);
34 }
35
36 # info, variables, paths
37 $fvm_var_file = __FILE__; # /home/path/plugins/pluginname/wpr.php
38 $fvm_var_basename = plugin_basename($fvm_var_file); # pluginname/wpr.php
39 $fvm_var_dir_path = plugin_dir_path($fvm_var_file); # /home/path/plugins/pluginname/
40 $fvm_var_url_path = plugins_url(dirname($fvm_var_basename)) . '/'; # https://example.com/wp-content/plugins/pluginname/
41 $fvm_var_plugin_version = get_file_data($fvm_var_file, array('Version' => 'Version'), false)['Version'];
42 $fvm_var_inc_dir = $fvm_var_dir_path . 'inc' . DIRECTORY_SEPARATOR; # /home/path/plugins/pluginname/inc/
43 $fvm_var_inc_lib = $fvm_var_dir_path . 'libs' . DIRECTORY_SEPARATOR; # /home/path/plugins/pluginname/libs/
44
45 # global functions for backend, frontend, ajax, etc
46 require_once($fvm_var_inc_dir . 'common.php');
47 require_once($fvm_var_inc_dir . 'updates.php');
48
49 # wp-cli support
50 if (defined('WP_CLI') && WP_CLI) {
51 require_once($fvm_var_inc_dir . 'wp-cli.php');
52 }
53
54 # get all options from database
55 $fvm_settings = fvm_get_settings();
56
57 # site url, domain name
58 $fvm_urls = array('wp_site_url'=>trailingslashit(site_url()), 'wp_domain'=>fvm_get_domain());
59
60
61 # only on backend
62 if(is_admin()) {
63
64 # admin functionality
65 require_once($fvm_var_inc_dir . 'admin.php');
66 require_once($fvm_var_inc_dir . 'serverinfo.php');
67
68 # both backend and frontend, as long as user can manage options
69 add_action('admin_bar_menu', 'fvm_admintoolbar', 100);
70 add_action('init', 'fvm_process_cache_purge_request');
71
72 # do admin stuff, as long as user can manage options
73 add_action('admin_init', 'fvm_save_settings');
74 add_action('admin_init', 'fvm_check_minimum_requirements');
75 add_action('admin_init', 'fvm_check_misconfiguration');
76 add_action('admin_init', 'fvm_update_changes');
77 add_action('admin_enqueue_scripts', 'fvm_add_admin_jscss');
78 add_action('admin_menu', 'fvm_add_admin_menu');
79 add_action('admin_notices', 'fvm_show_admin_notice_from_transient');
80 add_action('wp_ajax_fvm_get_logs', 'fvm_get_logs_callback');
81
82 # purge everything
83 add_action('switch_theme', 'fvm_purge_all');
84 add_action('customize_save', 'fvm_purge_all');
85 add_action('avada_clear_dynamic_css_cache', 'fvm_purge_all');
86 add_action('upgrader_process_complete', 'fvm_purge_all');
87 add_action('update_option_theme_mods_' . get_option('stylesheet'), 'fvm_purge_all');
88
89 }
90
91
92
93 # frontend only, any user permissions
94 if(!is_admin()) {
95
96 # frontend functionality
97 require_once($fvm_var_inc_dir . 'frontend.php');
98
99 # both back and front, as long as the option is enabled
100 add_action('init', 'fvm_disable_emojis');
101 add_action('wp_loaded', 'fvm_ajax_optimizer');
102
103 # both backend and frontend, as long as user can manage options
104 add_action('admin_bar_menu', 'fvm_admintoolbar', 100);
105 add_action('init', 'fvm_process_cache_purge_request');
106
107 # actions for frontend only
108 add_action('template_redirect', 'fvm_start_buffer', -PHP_INT_MAX);
109
110 }
111
1 <?php
2
3 # Exit if accessed directly
4 if (!defined('ABSPATH')){ exit(); }
5
6 # check for minimum requirements and prevent activation or disable if not fully compatible
7 function fvm_check_minimum_requirements() {
8 if(current_user_can('manage_options')) {
9
10 # defaults
11 $error = '';
12
13 # php version requirements
14 if (version_compare( PHP_VERSION, '5.6', '<' )) {
15 $error = __( 'FVM requires PHP 5.6 or higher. You’re still on', 'fast-velocity-minify' ) .' '. PHP_VERSION;
16 }
17
18 # php extension requirements
19 if (!extension_loaded('mbstring')) {
20 $error = __( 'FVM requires the PHP mbstring module to be installed on the server.', 'fast-velocity-minify' );
21 }
22
23 # wp version requirements
24 if ( version_compare( $GLOBALS['wp_version'], '4.9', '<' ) ) {
25 $error = __( 'FVM requires WP 4.9 or higher. You’re still on', 'fast-velocity-minify' ) .' '. $GLOBALS['wp_version'];
26 }
27
28 # check cache directory
29 $ch_info = fvm_get_cache_location();
30 if(isset($ch_info['ch_url']) && !empty($ch_info['ch_url']) && isset($ch_info['ch_dir']) && !empty($ch_info['ch_dir'])) {
31 if(is_dir($ch_info['ch_dir']) && !is_writable($ch_info['ch_dir'])) {
32 $error = __( 'FVM needs writing permissions on ', 'fast-velocity-minify' ). ' ['.$ch_info['ch_dir'].']';
33 }
34 }
35
36 # deactivate plugin forcefully
37 global $fvm_var_basename;
38 if ((is_plugin_active($fvm_var_basename) && !empty($error)) || !empty($error)) {
39 if (isset($_GET['activate'])) { unset($_GET['activate']); }
40 deactivate_plugins($fvm_var_basename);
41 add_settings_error( 'fvm_admin_notice', 'fvm_admin_notice', $error, 'success' );
42 }
43
44 }
45 }
46
47
48 # check for soft errors and misconfiguration
49 function fvm_check_misconfiguration() {
50 try {
51
52 # plugin version
53 global $fvm_var_plugin_version;
54 if(is_null($fvm_var_plugin_version)) { return false; }
55
56 # if no database version, regenerate
57 $plugin_meta = get_option('fvm_plugin_meta');
58 if($plugin_meta === false) {
59
60 # startup routines
61 fvm_plugin_deactivate();
62 fvm_plugin_activate();
63
64 # save
65 update_option('fvm_plugin_meta', json_encode(array('dbv'=>0)) );
66 }
67
68 # updates
69 if($plugin_meta !== false) {
70
71 # future updates
72 $meta = json_decode($plugin_meta, true);
73 $previous_version = $meta['dbv'];
74 if($fvm_var_plugin_version != $previous_version) {
75
76 # startup routines
77 fvm_plugin_deactivate();
78 fvm_plugin_activate();
79
80 # save
81 update_option('fvm_plugin_meta', json_encode(array('dbv'=>$fvm_var_plugin_version)) );
82
83 }
84
85 }
86
87 } catch (Exception $e) {
88 error_log('Caught exception (fvm_initialize_database): '.$e->getMessage(), 0);
89 }
90 }
91
92
93
94
95 # save plugin settings on wp-admin
96 function fvm_save_settings() {
97
98 # save settings
99 if(isset($_POST['fvm_action']) && isset($_POST['fvm_settings_nonce']) && $_POST['fvm_action'] == 'save_settings') {
100
101 if(!current_user_can('manage_options')) {
102 wp_die( __('You do not have sufficient permissions to access this page.', 'fast-velocity-minify'), __('Error:', 'fast-velocity-minify'), array('response'=>200));
103 }
104
105 if(!wp_verify_nonce($_POST['fvm_settings_nonce'], 'fvm_settings_nonce')) {
106 wp_die( __('Invalid nounce. Please refresh and try again.', 'fast-velocity-minify'), __('Error:', 'fast-velocity-minify'), array('response'=>200));
107 }
108
109 # update fvm_settings in the global scope
110 if(isset($_POST['fvm_settings']) && is_array($_POST['fvm_settings'])) {
111
112 # sanitize recursively
113 if(is_array($_POST['fvm_settings'])) {
114 foreach ($_POST['fvm_settings'] as $group=>$arr) {
115 if(is_array($arr)) {
116 foreach ($arr as $k=>$v) {
117
118 # only numeric, string or arrays allowed at this level
119 if(!is_string($v) && !is_numeric($v) && !is_array($v)) { $_POST['fvm_settings'][$group][$k] = ''; }
120
121 # numeric fields, only positive integers allowed
122 if(is_numeric($v)) { $_POST['fvm_settings'][$group][$k] = abs(intval($v)); }
123
124 # sanitize text area content
125 if(is_string($v)) { $_POST['fvm_settings'][$group][$k] = strip_tags($v); }
126
127 # clean cdn url
128 if($group == 'cdn' && $k == 'url') {
129 $_POST['fvm_settings'][$group][$k] = trim(trim(str_replace(array('http://', 'https://'), '', $v), '/'));
130 }
131
132 }
133 }
134 }
135 }
136
137 # get mandatory default exclusions
138 global $fvm_settings;
139 $fvm_settings = fvm_get_default_settings($_POST['fvm_settings']);
140
141 # purge caches
142 fvm_purge_all();
143
144 # save settings
145 update_option('fvm_settings', json_encode($fvm_settings), false);
146 add_settings_error( 'fvm_admin_notice', 'fvm_admin_notice', 'Settings saved successfully!', 'success' );
147
148 } else {
149 wp_die( __('Invalid data!', 'fast-velocity-minify'), __('Error:', 'fast-velocity-minify'), array('response'=>200));
150 }
151 }
152 }
153
154 # return checked, or empty for checkboxes in admin
155 function fvm_get_settings_checkbox($value) {
156 if($value == 1) { return 'checked'; }
157 return '';
158 }
159
160 # return checked, or empty for checkboxes in admin
161 function fvm_get_settings_radio($key, $value) {
162 if($key == $value) { return 'checked'; }
163 return '';
164 }
165
166
167 # add settings link on plugins listing page
168 add_filter("plugin_action_links_".$fvm_var_basename, 'fvm_min_settings_link' );
169 function fvm_min_settings_link($links) {
170 global $fvm_var_basename;
171 if (is_plugin_active($fvm_var_basename)) {
172 $settings_link = '<a href="'.admin_url('admin.php?page=fvm').'">Settings</a>';
173 array_unshift($links, $settings_link);
174 }
175 return $links;
176 }
177
178
179 # Enqueue plugin UI CSS and JS files
180 function fvm_add_admin_jscss($hook) {
181 if(current_user_can('manage_options')) {
182 if ('settings_page_fvm' != $hook) { return; }
183 global $fvm_var_dir_path, $fvm_var_url_path;
184
185 # ui
186 wp_enqueue_script( 'jquery-ui-core' );
187 wp_enqueue_script( 'jquery-ui-accordion' );
188
189 # js
190 wp_enqueue_script('fvm', $fvm_var_url_path . 'assets/fvm.js', array('jquery'), filemtime($fvm_var_dir_path.'assets'. DIRECTORY_SEPARATOR .'fvm.js'));
191
192 # css
193 wp_enqueue_style('fvm', $fvm_var_url_path . 'assets/fvm.css', array(), filemtime($fvm_var_dir_path.'assets'. DIRECTORY_SEPARATOR .'fvm.css'));
194
195 }
196 }
197
198
199 # create sidebar admin menu and add templates to admin
200 function fvm_add_admin_menu() {
201 if (current_user_can('manage_options')) {
202 add_options_page('FVM Settings', 'Fast Velocity Minify', 'manage_options', 'fvm', 'fvm_add_settings_admin');
203 }
204 }
205
206
207 # print admin notices when needed (json)
208 function fvm_show_admin_notice_from_transient() {
209 if(current_user_can('manage_options')) {
210 $inf = get_transient('fvm_admin_notice');
211 if($inf != false && !empty($inf)) {
212 $jsonarr = json_decode($inf, true);
213 if(!is_null($jsonarr) && is_array($jsonarr)){
214
215 # add all
216 $jsonarr = array_unique($jsonarr);
217 foreach ($jsonarr as $notice) {
218 add_settings_error( 'fvm_admin_notice', 'fvm_admin_notice', 'FVM: '.$notice, 'info' );
219 }
220
221 # output on other pages
222 if(!isset($_GET['page']) || (isset($_GET['page']) && $_GET['page'] != 'fvm')) {
223 settings_errors( 'fvm_admin_notice' );
224 }
225 }
226
227 # remove
228 delete_transient('fvm_admin_notice');
229 }
230 }
231 }
232
233 # manage settings page
234 function fvm_add_settings_admin() {
235
236 # admin only
237 if (!current_user_can('manage_options')) {
238 wp_die( __('You do not have sufficient permissions to access this page.'), __('Error:'), array('response'=>200));
239 }
240
241 # include admin html template
242 global $fvm_settings, $fvm_var_dir_path;
243
244 # admin html templates
245 include($fvm_var_dir_path . 'layout' . DIRECTORY_SEPARATOR . 'admin-layout.php');
246
247 }
248
249
250 # function to list all cache files on the status page (js ajax code)
251 function fvm_get_logs_callback() {
252
253 # must be able to cleanup cache
254 if (!current_user_can('manage_options')) {
255 wp_die( __('You do not have sufficient permissions to access this page.'), __('Error:'), array('response'=>200));
256 }
257
258 # must have
259 if(!defined('WP_CONTENT_DIR')) {
260 wp_die( __('WP_CONTENT_DIR is undefined!'), __('Error:'), array('response'=>200));
261 }
262
263 # defaults
264 global $wpdb;
265 if(is_null($wpdb)) {
266 wp_die( __('Database error!'), __('Error:'), array('response'=>200));
267 }
268
269 # initialize log
270 $log = '';
271
272 # build css logs from database
273 $results = $wpdb->get_results("SELECT date, msg FROM ".$wpdb->prefix."fvm_logs ORDER BY id DESC LIMIT 500", 'ARRAY_A');
274
275 # build log
276 if(is_array($results)) {
277 foreach (array_reverse($results, true) as $r) {
278 $log.= 'PROCESSED ON - ' . date('r', $r['date']) . PHP_EOL;
279 $log.= $r['msg'] . PHP_EOL . PHP_EOL;
280 }
281 }
282
283 # default message
284 if(empty($log)) { $log = 'No logs generated yet.'; }
285
286 # build info
287 $result = array(
288 'log' => $log,
289 'success' => 'OK'
290 );
291
292 # return result
293 header('Content-Type: application/json');
294 echo json_encode($result);
295 exit();
296
297 }
298
299
300 # run during activation
301 register_activation_hook($fvm_var_file, 'fvm_plugin_activate');
302 function fvm_plugin_activate() {
303
304 global $wpdb;
305 if(is_null($wpdb)) { return false; }
306
307 # defauls
308 $sql = array();
309 $wpdb_collate = $wpdb->collate;
310
311 # create cache table
312 $sqla_table_name = $wpdb->prefix . 'fvm_cache';
313 $sqla = "CREATE TABLE IF NOT EXISTS {$sqla_table_name} (
314 `id` bigint(20) unsigned NOT NULL auto_increment ,
315 `uid` varchar(64) NOT NULL,
316 `date` bigint(10) unsigned NOT NULL,
317 `type` varchar(3) NOT NULL,
318 `content` mediumtext NOT NULL,
319 `meta` mediumtext NOT NULL,
320 PRIMARY KEY (id),
321 UNIQUE KEY uid (uid),
322 KEY date (date), KEY type (type)
323 )
324 COLLATE {$wpdb_collate}";
325
326 # create logs table
327 $sqlb_table_name = $wpdb->prefix . 'fvm_logs';
328 $sqlb = "CREATE TABLE IF NOT EXISTS {$sqlb_table_name} (
329 `id` bigint(20) unsigned NOT NULL auto_increment,
330 `uid` varchar(64) NOT NULL,
331 `date` bigint(10) unsigned NOT NULL,
332 `type` varchar(10) NOT NULL,
333 `msg` mediumtext NOT NULL,
334 `meta` mediumtext NOT NULL,
335 PRIMARY KEY (id),
336 UNIQUE KEY uid (uid),
337 KEY date (date),
338 KEY type (type)
339 )
340 COLLATE {$wpdb_collate}";
341
342 # run sql
343 $wpdb->query($sqla);
344 $wpdb->query($sqlb);
345
346 }
347
348
349 # run during deactivation
350 register_deactivation_hook($fvm_var_file, 'fvm_plugin_deactivate');
351 function fvm_plugin_deactivate() {
352
353 global $wpdb;
354 if(is_null($wpdb)) { return false; }
355
356 # remove options and tables
357 $wpdb->query("DELETE FROM {$wpdb->prefix}options WHERE option_name = 'fvm_last_cache_update'");
358 $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}fvm_cache");
359 $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}fvm_logs");
360
361 # process cache settings
362 fvm_purge_static_files();
363
364 }
365
366 # run during uninstall
367 register_uninstall_hook($fvm_var_file, 'fvm_plugin_uninstall');
368 function fvm_plugin_uninstall() {
369 global $wpdb;
370 if(is_null($wpdb)) { return false; }
371
372 # remove options and tables
373 $wpdb->query("DELETE FROM {$wpdb->prefix}options WHERE option_name = 'fvm_settings'");
374 $wpdb->query("DELETE FROM {$wpdb->prefix}options WHERE option_name = 'fvm_last_cache_update'");
375 $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}fvm_cache");
376 $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}fvm_logs");
377
378 # process cache settings
379 fvm_purge_static_files();
380
381 }
382
383
384 # get all known roles
385 function fvm_get_user_roles_checkboxes() {
386
387 global $wp_roles, $fvm_settings;
388 $roles_list = array();
389 if(is_object($wp_roles)) {
390 $roles = (array) $wp_roles->get_names();
391 foreach ($roles as $role=>$rname) {
392
393 $roles_list[] = '<label for="fvm_settings_minify_'.$role.'"><input name="fvm_settings[minify]['.$role.']" type="checkbox" id="fvm_settings_minify_'.$role.'" value="1" '. fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'minify', $role)).'> '.$rname.' </label><br />';
394
395 }
396 }
397
398 # return
399 if(!empty($roles_list)) { return implode(PHP_EOL, $roles_list); } else { return __( 'No roles detected!', 'fast-velocity-minify' ); }
400
401 }
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2
3 # Exit if accessed directly
4 if (!defined('ABSPATH')){ exit(); }
5
6 # functions needed for both frontend or backend
7
8 # top admin toolbar for cache purging
9 function fvm_admintoolbar() {
10 if(current_user_can('manage_options')) {
11 global $wp_admin_bar;
12
13 # Add top menu to admin bar
14 $wp_admin_bar->add_node(array(
15 'id' => 'fvm_menu',
16 'title' => __("FVM", 'fvm') . '</span>',
17 'href' => wp_nonce_url(add_query_arg('fvm_do', 'clear_all'), 'fvm_clear', '_wpnonce')
18 ));
19
20 # Add submenu
21 $wp_admin_bar->add_node(array(
22 'id' => 'fvm_submenu_purge_all',
23 'parent' => 'fvm_menu',
24 'title' => __("Clear Everything", 'fvm'),
25 'href' => wp_nonce_url(add_query_arg('fvm_do', 'clear_all'), 'fvm_clear', '_wpnonce')
26 ));
27
28 # Add submenu
29 $wp_admin_bar->add_node(array(
30 'id' => 'fvm_submenu_settings',
31 'parent' => 'fvm_menu',
32 'title' => __("FVM Settings", 'fvm'),
33 'href' => admin_url('admin.php?page=fvm')
34 ));
35
36 /*
37 # Add submenu
38 $wp_admin_bar->add_node(array(
39 'id' => 'fvm_submenu_upgrade',
40 'parent' => 'fvm_menu',
41 'title' => __("Upgrade", 'fvm'),
42 'href' => admin_url('admin.php?page=fvm&tab=upgrade')
43 ));
44 */
45
46 # Add submenu
47 $wp_admin_bar->add_node(array(
48 'id' => 'fvm_submenu_help',
49 'parent' => 'fvm_menu',
50 'title' => __("Help", 'fvm'),
51 'href' => admin_url('admin.php?page=fvm&tab=help')
52 ));
53
54 }
55 }
56
57
58 # get cache directory
59 function fvm_get_cache_location() {
60
61 # custom path
62 if (defined('FVM_DIR') && defined('FVM_URL')){
63
64 # define paths and url
65 $sep = DIRECTORY_SEPARATOR;
66 $dir = trim(rtrim(FVM_DIR, '/\\')). $sep . 'cache' . $sep . 'fvm'. $sep . 'min';
67 $durl = trim(rtrim(FVM_URL, '/')). '/cache/fvm/min';
68
69 # create and return
70 if(!is_dir($dir) && function_exists('wp_mkdir_p')) { wp_mkdir_p($dir); }
71 return array('ch_dir'=>$dir,'ch_url'=>$durl);
72
73 }
74
75
76 # /wp-content/cache
77 if (defined('WP_CONTENT_DIR') && defined('WP_CONTENT_URL')){
78
79 # define paths and url
80 $sep = DIRECTORY_SEPARATOR;
81 $dir = trim(rtrim(WP_CONTENT_DIR, '/\\')). $sep . 'cache' . $sep . 'fvm'. $sep . 'min';
82 $durl = trim(rtrim(WP_CONTENT_URL, '/')). '/cache/fvm/min';
83
84 # create and return
85 if(!is_dir($dir) && function_exists('wp_mkdir_p')) { wp_mkdir_p($dir); }
86 return array('ch_dir'=>$dir,'ch_url'=>$durl);
87
88 }
89
90 # uploads directory
91 $ch_info = wp_upload_dir();
92 if(isset($ch_info['basedir']) && isset($ch_info['baseurl']) && !empty($ch_info['basedir'])) {
93
94 # define and create directory
95 $sep = DIRECTORY_SEPARATOR;
96 $dir = $ch_info['basedir'] . $sep . 'cache' . $sep . 'fvm'. $sep . 'min';
97 $durl = $ch_info['baseurl'] . '/cache/fvm/min';
98
99 # create and return
100 if(!is_dir($dir) && function_exists('wp_mkdir_p')) { wp_mkdir_p($dir); }
101 return array('ch_dir'=>$dir,'ch_url'=>$durl);
102
103 }
104
105 # error
106 return false;
107
108 }
109
110
111
112 # purge all caches when clicking the button on the admin bar
113 function fvm_process_cache_purge_request(){
114
115 if(isset($_GET['fvm_do']) && isset($_GET['_wpnonce'])) {
116
117 # must be able to cleanup cache
118 if (!current_user_can('manage_options')) {
119 wp_die( __('You do not have sufficient permissions to access this page.', 'fast-velocity-minify'), __('Error:', 'fast-velocity-minify'), array('response'=>200));
120 }
121
122 # validate nonce
123 if(!wp_verify_nonce($_GET['_wpnonce'], 'fvm_clear')) {
124 wp_die( __('Invalid or expired request... please go back and refresh before trying again!', 'fast-velocity-minify'), __('Error:', 'fast-velocity-minify'), array('response'=>200));
125 }
126
127 # Purge All
128 if($_GET['fvm_do'] == 'clear_all') {
129
130 # purge everything
131 $cache = fvm_purge_static_files();
132 $others = fvm_purge_others();
133
134 if(is_admin()) {
135
136 # merge notices
137 $notices = array();
138 if(is_string($cache)) { $notices[] = $cache; }
139 if(is_string($others)) { $notices[] = $others; }
140
141 # save transient for after the redirect
142 if(count($notices) == 0) { $notices[] = __( 'All supported caches have been purged ', 'fast-velocity-minify' ) . ' ('.date("D, d M Y @ H:i:s e").')'; }
143 set_transient( 'fvm_admin_notice', json_encode($notices), 10);
144
145 }
146
147 }
148
149 # https://developer.wordpress.org/reference/functions/wp_safe_redirect/
150 nocache_headers();
151 wp_safe_redirect(remove_query_arg('_wpnonce', remove_query_arg('_fvm', wp_get_referer())));
152 exit();
153 }
154 }
155
156
157 # Purge everything
158 function fvm_purge_all() {
159 fvm_purge_static_files();
160 fvm_purge_others();
161 return true;
162 }
163
164
165 # purge supported hosting and plugins
166 function fvm_purge_others(){
167
168 # third party plugins
169
170 # Purge all W3 Total Cache
171 if (function_exists('w3tc_pgcache_flush')) {
172 w3tc_pgcache_flush();
173 return __( 'All caches on <strong>W3 Total Cache</strong> have been purged.', 'fast-velocity-minify' );
174 }
175
176 # Purge WP Super Cache
177 if (function_exists('wp_cache_clear_cache')) {
178 wp_cache_clear_cache();
179 return __( 'All caches on <strong>WP Super Cache</strong> have been purged.', 'fast-velocity-minify' );
180 }
181
182 # Purge WP Rocket
183 if (function_exists('rocket_clean_domain')) {
184 rocket_clean_domain();
185 return __( 'All caches on <strong>WP Rocket</strong> have been purged.', 'fast-velocity-minify' );
186 }
187
188 # Purge Cachify
189 if (function_exists('cachify_flush_cache')) {
190 cachify_flush_cache();
191 return __( 'All caches on <strong>Cachify</strong> have been purged.', 'fast-velocity-minify' );
192 }
193
194 # Purge Comet Cache
195 if ( class_exists("comet_cache") ) {
196 comet_cache::clear();
197 return __( 'All caches on <strong>Comet Cache</strong> have been purged.', 'fast-velocity-minify' );
198 }
199
200 # Purge Zen Cache
201 if ( class_exists("zencache") ) {
202 zencache::clear();
203 return __( 'All caches on <strong>Comet Cache</strong> have been purged.', 'fast-velocity-minify' );
204 }
205
206 # Purge LiteSpeed Cache
207 if ( has_action('litespeed_purge_all') ) {
208 do_action('litespeed_purge_all');
209 return __( 'All caches on <strong>LiteSpeed Cache</strong> have been purged.', 'fast-velocity-minify' );
210 }
211
212 # Purge WP Cloudflare Super Page Cache
213 if( class_exists('SW_CLOUDFLARE_PAGECACHE') ) {
214 do_action("swcfpc_purge_everything");
215 return __( 'All caches on <strong>WP Cloudflare Super Page Cache</strong> have been purged.', 'fast-velocity-minify' );
216 }
217
218 # Purge Hyper Cache
219 if (class_exists( 'HyperCache' )) {
220 do_action( 'autoptimize_action_cachepurged' );
221 return __( 'All caches on <strong>HyperCache</strong> have been purged.', 'fast-velocity-minify' );
222 }
223
224 # purge cache enabler
225 if ( has_action('ce_clear_cache') ) {
226 do_action('ce_clear_cache');
227 return __( 'All caches on <strong>Cache Enabler</strong> have been purged.', 'fast-velocity-minify' );
228 }
229
230 # purge wpfc
231 if (function_exists('wpfc_clear_all_cache')) {
232 wpfc_clear_all_cache(true);
233 }
234
235 # add breeze cache purge support
236 if (class_exists("Breeze_PurgeCache")) {
237 Breeze_PurgeCache::breeze_cache_flush();
238 return __( 'All caches on <strong>Breeze</strong> have been purged.', 'fast-velocity-minify' );
239 }
240
241 # swift
242 if (class_exists("Swift_Performance_Cache")) {
243 Swift_Performance_Cache::clear_all_cache();
244 return __( 'All caches on <strong>Swift Performance</strong> have been purged.', 'fast-velocity-minify' );
245 }
246
247 # Hummingbird
248 if(has_action('wphb_clear_page_cache')) {
249 do_action('wphb_clear_page_cache');
250 return __( 'All caches on <strong>Hummingbird</strong> have been purged.', 'fast-velocity-minify' );
251 }
252
253 # WP-Optimize
254 if(has_action('wpo_cache_flush')) {
255 do_action('wpo_cache_flush');
256 return __( 'All caches on <strong>WP-Optimize</strong> have been purged.', 'fast-velocity-minify' );
257 }
258
259 # hosting companies
260
261 # Purge SG Optimizer (Siteground)
262 if (function_exists('sg_cachepress_purge_everything')) {
263 sg_cachepress_purge_everything();
264 return __( 'All caches on <strong>SG Optimizer</strong> have been purged.', 'fast-velocity-minify' );
265 }
266
267 # Purge Godaddy Managed WordPress Hosting (Varnish + APC)
268 if (class_exists('WPaaS\Plugin') && method_exists( 'WPass\Plugin', 'vip' )) {
269 fvm_godaddy_request('BAN');
270 return __( 'A cache purge request has been sent to <strong>Go Daddy Varnish</strong>', 'fast-velocity-minify' );
271 }
272
273
274 # Purge WP Engine
275 if (class_exists("WpeCommon")) {
276 if (method_exists('WpeCommon', 'purge_memcached')) { WpeCommon::purge_memcached(); }
277 if (method_exists('WpeCommon', 'purge_varnish_cache')) { WpeCommon::purge_varnish_cache(); }
278 if (method_exists('WpeCommon', 'purge_memcached') || method_exists('WpeCommon', 'purge_varnish_cache')) {
279 return __( 'A cache purge request has been sent to <strong>WP Engine</strong>', 'fast-velocity-minify' );
280 }
281 }
282
283 # Purge Kinsta
284 global $kinsta_cache;
285 if ( isset($kinsta_cache) && class_exists('\\Kinsta\\CDN_Enabler')) {
286 if (!empty( $kinsta_cache->kinsta_cache_purge)){
287 $kinsta_cache->kinsta_cache_purge->purge_complete_caches();
288 return __( 'A cache purge request has been sent to <strong>Kinsta</strong>', 'fast-velocity-minify' );
289 }
290 }
291
292 # Purge Pagely
293 if ( class_exists( 'PagelyCachePurge' ) ) {
294 $purge_pagely = new PagelyCachePurge();
295 $purge_pagely->purgeAll();
296 return __( 'A cache purge request has been sent to <strong>Pagely</strong>', 'fast-velocity-minify' );
297 }
298
299 # Purge Pressidum
300 if (defined('WP_NINUKIS_WP_NAME') && class_exists('Ninukis_Plugin')){
301 $purge_pressidum = Ninukis_Plugin::get_instance();
302 $purge_pressidum->purgeAllCaches();
303 return __( 'A cache purge request has been sent to <strong>Pressidium</strong>', 'fast-velocity-minify' );
304 }
305
306 # Purge Savvii
307 if (defined( '\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW')) {
308 $purge_savvii = new \Savvii\CacheFlusherPlugin();
309 if ( method_exists( $plugin, 'domainflush' ) ) {
310 $purge_savvii->domainflush();
311 return __( 'A cache purge request has been sent to <strong>Savvii</strong>', 'fast-velocity-minify' );
312 }
313 }
314
315 # Purge Pantheon Advanced Page Cache plugin
316 if(function_exists('pantheon_wp_clear_edge_all')) {
317 pantheon_wp_clear_edge_all();
318 }
319
320 # wordpress default cache
321 if (function_exists('wp_cache_flush')) {
322 wp_cache_flush();
323 }
324
325 }
326
327
328 # Purge Godaddy Managed WordPress Hosting (Varnish)
329 function fvm_godaddy_request( $method) {
330 $url = home_url();
331 $host = wpraiser_get_domain();
332 $url = set_url_scheme( str_replace( $host, WPaas\Plugin::vip(), $url ), 'http' );
333 update_option( 'gd_system_last_cache_flush', time(), 'no'); # purge apc
334 wp_remote_request( esc_url_raw( $url ), array('method' => $method, 'blocking' => false, 'headers' => array('Host' => $host)) );
335 }
336
337
338
339 # check if we can minify the page
340 function fvm_can_minify_js() {
341
342 # check if we hit any exclusions from the compatibility page
343 if(!fvm_can_process_common()) { return false; }
344 if(fvm_is_amp_page() === true) { return false; }
345
346 # url exclusions
347 if(!fvm_can_process_query_string('js')) { return false; }
348
349 # check if user role is allowed
350 if(!fvm_user_role_processing_allowed('js')) { return false; }
351
352 # settings
353 global $fvm_settings;
354
355 # disabled?
356 if(!isset($fvm_settings['js']['enable']) || (isset($fvm_settings['js']['enable']) && $fvm_settings['js']['enable'] != true)) {
357 return false;
358 }
359
360 # default
361 return true;
362
363 }
364
365 # check if we can minify the page
366 function fvm_can_process_html() {
367
368 # check if we hit any exclusions from the compatibility page
369 if(!fvm_can_process_common()) { return false; }
370 if(fvm_is_amp_page() === true) { return false; }
371
372 # url exclusions
373 if(!fvm_can_process_query_string('html')) { return false; }
374
375 # settings
376 global $fvm_settings;
377
378 # disabled?
379 if(!isset($fvm_settings['html']['enable']) || (isset($fvm_settings['html']['enable']) && $fvm_settings['html']['enable'] != true)) {
380 return false;
381 }
382
383 # check if user role is allowed
384 if(!fvm_user_role_processing_allowed('html')) { return false; }
385
386 # default
387 return true;
388 }
389
390 # check if we can minify the page
391 function fvm_can_process_cdn() {
392
393 # check if we hit any exclusions from the compatibility page
394 if(!fvm_can_process_common()) { return false; }
395 if(fvm_is_amp_page() === true) { return false; }
396
397 # url exclusions
398 if(!fvm_can_process_query_string('cdn')) { return false; }
399
400 # settings
401 global $fvm_settings;
402
403 # disabled?
404 if(!isset($fvm_settings['cdn']['enable']) || (isset($fvm_settings['cdn']['enable']) && $fvm_settings['cdn']['enable'] != true)) {
405 return false;
406 }
407
408 # no domain
409 if(!isset($fvm_settings['cdn']['domain']) || (isset($fvm_settings['cdn']['domain']) && empty($fvm_settings['cdn']['domain']))) {
410 return false;
411 }
412
413 # check if user role is allowed
414 if(!fvm_user_role_processing_allowed('cdn')) { return false; }
415
416 # default
417 return true;
418 }
419
420
421 # check if we can minify the page
422 function fvm_can_minify_css() {
423
424 # check if we hit any exclusions from the compatibility page
425 if(!fvm_can_process_common()) { return false; }
426 if(fvm_is_amp_page() === true) { return false; }
427
428 # url exclusions
429 if(!fvm_can_process_query_string('css')) { return false; }
430
431 # check if user role is allowed
432 if(!fvm_user_role_processing_allowed('css')) { return false; }
433
434 # settings
435 global $fvm_settings;
436
437 # disabled?
438 if(!isset($fvm_settings['css']['enable']) || (isset($fvm_settings['css']['enable']) && $fvm_settings['css']['enable'] != true)) { return false; }
439
440 # default
441 return true;
442 }
443
444
445 # save minified code, if not yet available
446 function fvm_generate_min_url($url, $tkey, $type, $code) {
447
448 # cache date
449 $tvers = get_option('fvm_last_cache_update', '0');
450
451 # parse uripath and check if it matches against our rewrite format
452 $filename = $tvers.'-'.$tkey .'.'. $type;
453
454 # check cache directory
455 $ch_info = fvm_get_cache_location();
456 if(isset($ch_info['ch_url']) && !empty($ch_info['ch_url']) && isset($ch_info['ch_dir']) && !empty($ch_info['ch_dir'])) {
457 if(is_dir($ch_info['ch_dir']) && is_writable($ch_info['ch_dir'])) {
458
459 # filename
460 $file = $ch_info['ch_dir'] . DIRECTORY_SEPARATOR . $filename;
461 $public = $ch_info['ch_url'] . '/' .$filename;
462
463 # wordpress functions
464 require_once (ABSPATH . DIRECTORY_SEPARATOR . 'wp-admin'. DIRECTORY_SEPARATOR .'includes'. DIRECTORY_SEPARATOR .'class-wp-filesystem-base.php');
465 require_once (ABSPATH . DIRECTORY_SEPARATOR .'wp-admin'. DIRECTORY_SEPARATOR .'includes'. DIRECTORY_SEPARATOR .'class-wp-filesystem-direct.php');
466
467 # initialize
468 $fileSystemDirect = new WP_Filesystem_Direct(false);
469
470 # create if doesn't exist
471 if(!$fileSystemDirect->exists($file) || ($fileSystemDirect->exists($file) && $fileSystemDirect->mtime($file) < $tvers)) {
472 $fileSystemDirect->put_contents($file, $code);
473 }
474
475 # return url
476 return $public;
477
478 }
479 }
480
481 # default
482 return $url;
483 }
484
485
486
487
488
489
490
491 # check if PHP has some functions disabled
492 function fvm_function_available($func) {
493 if (ini_get('safe_mode')) return false;
494 $disabled = ini_get('disable_functions');
495 if ($disabled) {
496 $disabled = explode(',', $disabled);
497 $disabled = array_map('trim', $disabled);
498 return !in_array($func, $disabled);
499 }
500 return true;
501 }
502
503
504 # open a multiline string, order, filter duplicates and return as array
505 function fvm_string_toarray($value){
506 $arr = explode(PHP_EOL, $value);
507 return fvm_array_order($arr);}
508
509 # filter duplicates, order and return array
510 function fvm_array_order($arr){
511 if(!is_array($arr)) { return array(); }
512 $a = array_map('trim', $arr);
513 $b = array_filter($a);
514 $c = array_unique($b);
515 sort($c);
516 return $c;
517 }
518
519
520 # return size in human format
521 function fvm_format_filesize($bytes, $decimals = 2) {
522 $units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' );
523 for ($i = 0; ($bytes / 1024) > 0.9; $i++, $bytes /= 1024) {}
524 if($i == 0) { $i = 1; $bytes = $bytes / 1024; } # KB+ only
525 return sprintf( "%1.{$decimals}f %s", round( $bytes, $decimals ), $units[$i] );
526 }
527
528 # purge static cache files directory
529 function fvm_purge_static_files() {
530
531 # globals
532 global $fvm_settings;
533
534 # truncate cache table
535 global $wpdb;
536 if(is_null($wpdb)) { return false; }
537 try {
538 $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}fvm_cache");
539 $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}fvm_logs");
540 } catch (Exception $e) {
541 error_log('Error: '.$e->getMessage(), 0);
542 }
543
544 # increment
545 update_option('fvm_last_cache_update', time());
546
547 # check cache directory
548 $ch_info = fvm_get_cache_location();
549 if(isset($ch_info['ch_url']) && !empty($ch_info['ch_url']) && isset($ch_info['ch_dir']) && !empty($ch_info['ch_dir'])) {
550 if(is_dir($ch_info['ch_dir']) && is_writable($ch_info['ch_dir'])) {
551
552 # wordpress functions
553 require_once (ABSPATH . DIRECTORY_SEPARATOR . 'wp-admin'. DIRECTORY_SEPARATOR .'includes'. DIRECTORY_SEPARATOR .'class-wp-filesystem-base.php');
554 require_once (ABSPATH . DIRECTORY_SEPARATOR .'wp-admin'. DIRECTORY_SEPARATOR .'includes'. DIRECTORY_SEPARATOR .'class-wp-filesystem-direct.php');
555
556 # start
557 $fileSystemDirect = new WP_Filesystem_Direct(false);
558
559 # instant purge
560 global $fvm_settings;
561 if(isset($fvm_settings['cache']['min_instant_purge']) && $fvm_settings['cache']['min_instant_purge'] == true) {
562 $fileSystemDirect->rmdir($ch_info['ch_dir'], true);
563 return true;
564 } else {
565
566 # older than 24h and not matching current timestamp
567 $list = $fileSystemDirect->dirlist($ch_info['ch_dir'], false, true);
568 if(is_array($list) && count($list) > 0) {
569 foreach($list as $k=>$arr) {
570 if(isset($arr['lastmodunix']) && $arr['type'] == 'f' && intval($arr['lastmodunix']) <= time()-86400) {
571 if(substr($arr['name'], 0, 10) !== time()) {
572 $fileSystemDirect->delete($ch_info['ch_dir'] . DIRECTORY_SEPARATOR . $arr['name'], false, 'f');
573 }
574 }
575 }
576 }
577
578 }
579
580 }
581 }
582
583 }
584
585
586 # Fix the permission bits on generated files
587 function fvm_fix_permission_bits($file){
588
589 # must be on the allowed path
590 if(empty($file) || !defined('WP_CONTENT_DIR') || stripos($file, DIRECTORY_SEPARATOR . 'fvm') === false) {
591 return __( 'Requested path is not allowed!', 'fast-velocity-minify' );
592 }
593
594 if(function_exists('stat') && fvm_function_available('stat')) {
595 if ($stat = @stat(dirname($file))) {
596 $perms = $stat['mode'] & 0007777;
597 @chmod($file, $perms);
598 clearstatcache();
599 return true;
600 }
601 }
602
603 # get permissions from parent directory
604 $perms = 0777;
605 if(function_exists('stat') && fvm_function_available('stat')) {
606 if ($stat = @stat(dirname($file))) { $perms = $stat['mode'] & 0007777; }
607 }
608
609 if (file_exists($file)){
610 if ($perms != ($perms & ~umask())){
611 $folder_parts = explode( DIRECTORY_SEPARATOR, substr( $file, strlen(dirname($file)) + 1 ) );
612 for ( $i = 1, $c = count( $folder_parts ); $i <= $c; $i++ ) {
613 @chmod(dirname($file) . DIRECTORY_SEPARATOR . implode( DIRECTORY_SEPARATOR, array_slice( $folder_parts, 0, $i ) ), $perms );
614 }
615 }
616 return true;
617 }
618
619 return false;
620 }
621
622
623 # get options into an array
624 function fvm_get_settings() {
625
626 $fvm_settings = json_decode(get_option('fvm_settings'), true);
627
628 # mandatory default exclusions
629 $fvm_settings_default = fvm_get_default_settings($fvm_settings);
630
631 # check if there are any pending field update routines
632 $fvm_settings_default = fvm_get_updated_field_routines($fvm_settings_default);
633
634 # update database if needed
635 if($fvm_settings != $fvm_settings_default) {
636 update_option('fvm_settings', json_encode($fvm_settings_default), false);
637 }
638
639 # return
640 return $fvm_settings;
641 }
642
643 # return value from section and key name
644 function fvm_get_settings_value($fvm_settings, $section, $key) {
645 if($fvm_settings != false && is_array($fvm_settings) && count($fvm_settings) > 1) {
646 if(isset($fvm_settings[$section][$key])) {
647 return $fvm_settings[$section][$key];
648 }
649 }
650 return '';
651 }
652
653
654 # default exclusions by seting name
655 function fvm_get_default_settings($fvm_settings) {
656 if(!is_array($fvm_settings) || empty($fvm_settings)){
657
658 # initialize
659 $fvm_settings = array();
660
661 # global
662 $fvm_settings['global']['preserve_settings'] = 1;
663
664 # html
665 $fvm_settings['html']['enable'] = 1;
666 $fvm_settings['html']['nocomments'] = 1;
667 $fvm_settings['html']['cleanup_header'] = 1;
668 $fvm_settings['html']['disable_emojis'] = 1;
669
670 # css
671 $fvm_settings['css']['enable'] = 1;
672 $fvm_settings['css']['noprint'] = 1;
673
674 }
675
676 # return
677 return $fvm_settings;
678 }
679
680
681
682 # update routines for new fields and replacements
683 function fvm_get_updated_field_routines($fvm_settings) {
684
685 # current version
686 global $fvm_var_plugin_version;
687
688 # must have
689 if(!is_array($fvm_settings)) { return $fvm_settings; }
690
691 # Version 3.0 routines start
692
693 # settings migration
694 if (get_option("fastvelocity_upgraded") === false) {
695 if (get_option("fastvelocity_plugin_version") !== false) {
696
697 # cache path
698 if (get_option("fastvelocity_min_change_cache_path") !== false && !isset($fvm_settings['cache']['path'])) {
699 $fvm_settings['cache']['path'] = get_option("fastvelocity_min_change_cache_path");
700 }
701
702 # cache base_url
703 if (get_option("fastvelocity_min_change_cache_base_url") !== false && !isset($fvm_settings['cache']['url'])) {
704 $fvm_settings['cache']['url'] = get_option("fastvelocity_min_change_cache_base_url");
705
706 }
707
708 # disable html minification
709 if (get_option("fastvelocity_min_skip_html_minification") !== false && !isset($fvm_settings['html']['min_disable'])) {
710 $fvm_settings['html']['min_disable'] = 1;
711 }
712
713 # do not remove html comments
714 if (get_option("fastvelocity_min_strip_htmlcomments") !== false && !isset($fvm_settings['html']['nocomments'])) {
715 $fvm_settings['html']['nocomments'] = 1;
716 }
717
718 # cdn url
719 $oldcdn = get_option("fastvelocity_min_fvm_cdn_url");
720 if ($oldcdn !== false && !empty($oldcdn)) {
721 if (!isset($fvm_settings['cdn']['domain']) || (isset($fvm_settings['cdn']['domain']) && empty($fvm_settings['cdn']['domain']))) {
722 $fvm_settings['cdn']['enable'] = 1;
723 $fvm_settings['cdn']['cssok'] = 1;
724 $fvm_settings['cdn']['jsok'] = 1;
725 $fvm_settings['cdn']['domain'] = $oldcdn;
726 }
727 }
728
729 # force https
730 if (get_option("fastvelocity_min_default_protocol") == 'https' && !isset($fvm_settings['global']['force-ssl'])) {
731 $fvm_settings['global']['force-ssl'] = 1;
732 }
733
734 # preserve settings on uninstall
735 if (get_option("fastvelocity_preserve_settings_on_uninstall") !== false && !isset($fvm_settings['global']['preserve_settings'])) {
736 $fvm_settings['global']['preserve_settings'] = 1;
737 }
738
739 # inline all css
740 if (get_option("fastvelocity_min_force_inline_css") !== false && !isset($fvm_settings['css']['inline-all'])) {
741 $fvm_settings['css']['inline-all'] = 1;
742 }
743
744 # remove google fonts
745 if (get_option("fastvelocity_min_remove_googlefonts") !== false && !isset($fvm_settings['css']['remove'])) {
746
747 # add fonts.gstatic.com
748 $arr = array('fonts.gstatic.com');
749 $fvm_settings['css']['remove'] = implode(PHP_EOL, fvm_array_order($arr));
750
751 }
752
753 # Skip deferring the jQuery library, add them to the header render blocking
754 if (get_option("fastvelocity_min_exclude_defer_jquery") !== false && !isset($fvm_settings['js']['merge_header'])) {
755
756 # add jquery + jquery migrate
757 $arr = array('/jquery-migrate-', '/jquery-migrate.js', '/jquery-migrate.min.js', '/jquery.js', '/jquery.min.js');
758 $fvm_settings['js']['merge_header'] = implode(PHP_EOL, fvm_array_order($arr));
759
760 }
761
762 # new users, add recommended default scripts settings
763 if ( (!isset($fvm_settings['js']['merge_header']) || isset($fvm_settings['js']['merge_header']) && empty($fvm_settings['js']['merge_header'])) && (!isset($fvm_settings['js']['merge_defer']) || (isset($fvm_settings['js']['merge_defer']) && empty($fvm_settings['js']['merge_defer']))) ) {
764
765 # header
766 $arr = array('/jquery-migrate-', '/jquery-migrate.js', '/jquery-migrate.min.js', '/jquery.js', '/jquery.min.js');
767 $fvm_settings['js']['merge_header'] = implode(PHP_EOL, fvm_array_order($arr));
768
769 # defer
770 $arr = array('/ajax.aspnetcdn.com/ajax/', '/ajax.googleapis.com/ajax/libs/', '/cdnjs.cloudflare.com/ajax/libs/', '/stackpath.bootstrapcdn.com/bootstrap/', '/wp-admin/', '/wp-content/', '/wp-includes/');
771 $fvm_settings['js']['merge_defer'] = implode(PHP_EOL, fvm_array_order($arr));
772
773 # js footer dependencies
774 $arr = array('wp.i18n');
775 $fvm_settings['js']['defer_dependencies'] = implode(PHP_EOL, fvm_array_order($arr));
776
777 # recommended delayed scripts
778 $arr = array('function(f,b,e,v,n,t,s)', 'function(w,d,s,l,i)', 'function(h,o,t,j,a,r)', 'connect.facebook.net', 'www.googletagmanager.com', 'gtag(', 'fbq(', 'assets.pinterest.com/js/pinit_main.js', 'pintrk(');
779 $fvm_settings['js']['thirdparty'] = implode(PHP_EOL, fvm_array_order($arr));
780
781 }
782
783 # mark as done
784 update_option('fastvelocity_upgraded', true);
785
786 }
787 }
788 # Version 3.0 routines end
789
790 # return settings array
791 return $fvm_settings;
792 }
793
794 # save log to database
795 # usage: $arr = array('type'=>'js', 'msg'=>'', 'meta'=>json_encode(array('loc'=>'function')));
796 function fvm_save_log($arr) {
797
798 # must have
799 if(is_null($arr) || !is_array($arr) || !isset($arr['msg'])) { return false; }
800
801 # uid, prevent duplicate or unique by date
802 if(!isset($arr['date'])) {
803 $arr['date'] = time();
804 $arr['uid'] = fvm_generate_hash_with_prefix($arr['msg'], 'log');
805 } else {
806 $arr['uid'] = fvm_generate_hash_with_prefix($arr['date'] . ' / ' . $arr['msg'], 'log');
807 }
808
809 # initialize arrays (fields, types, values)
810 $fld = array();
811 $tpe = array();
812 $vls = array();
813
814 # define possible fields
815 $all = array('date', 'uid', 'type', 'msg', 'meta');
816
817 # process only recognized columns
818 foreach($arr as $k=>$v) {
819 if(in_array($k, $all)) {
820 $tpe[] = '%s';
821 $fld[] = $k;
822 $vls[] = $v;
823 }
824 }
825
826 try {
827
828 # connect
829 global $wpdb;
830 if(is_null($wpdb)) { return false; }
831
832 # check if exists before inserting
833 $result = $wpdb->get_row($wpdb->prepare("SELECT id FROM ".$wpdb->prefix."fvm_logs WHERE uid = %s LIMIT 1", $arr['uid']));
834 if(!isset($result->id)) {
835
836 # prepare and insert to database
837 $wpdb->query($wpdb->prepare("INSERT IGNORE INTO ".$wpdb->prefix."fvm_logs (".implode(', ', $fld).") VALUES (".implode(', ', $tpe).")", $vls));
838 return true;
839
840 }
841
842 } catch (Exception $e) {
843 error_log('Error: '.$e->getMessage(), 0);
844 }
845
846 # fallback
847 return false;
848
849 }
850
851
852 # generate a 64 char string with prefix
853 function fvm_generate_hash_with_prefix($uid, $prefix) {
854 return substr($prefix .hash('sha256', $uid), 0, 64);
855 }
856
857
858 # replace css imports with origin css code
859 function fvm_replace_css_imports($css, $rq=null) {
860
861 # globals
862 global $fvm_urls, $fvm_settings;
863
864 # reset
865 $cssimports = array();
866 $cssimports_prepend = array();
867 $css = trim($css);
868
869 # handle import url rules
870 preg_match_all ("/@import[ ]*['\"]{0,}(url\()*['\"]*([^\(\{'\"\)]*)['\"\)]*[;]{0,}/ui", $css, $cssimports);
871 if(isset($cssimports[0]) && isset($cssimports[2])) {
872 foreach($cssimports[0] as $k=>$cssimport) {
873
874 # if @import url rule, or guess full url
875 if(stripos($cssimport, 'import url') !== false && isset($cssimports[2][$k])) {
876 $url = trim($cssimports[2][$k]);
877 } else {
878 if(!is_null($rq) && !empty($rq)) {
879 $url = dirname($rq) . '/' . trim($cssimports[2][$k]);
880 }
881 }
882
883 # must have
884 if(!empty($url)) {
885
886 # make sure we have a complete url
887 $href = fvm_normalize_url($url);
888
889 # download, minify, cache (no ver query string)
890 $tkey = fvm_generate_hash_with_prefix($href, 'css');
891 $subcss = fvm_get_transient($tkey);
892 if ($subcss === false) {
893
894 # get minification settings for files
895 if(isset($fvm_settings['css']['css_enable_min_files'])) {
896 $enable_css_minification = $fvm_settings['css']['css_enable_min_files'];
897 }
898
899 # force minification on google fonts
900 if(stripos($href, 'fonts.googleapis.com') !== false) {
901 $enable_css_minification = true;
902 }
903
904 # download file, get contents, merge
905 $ddl = array();
906 $ddl = fvm_maybe_download($href);
907
908 # error
909 if(isset($ddl['error'])) {
910 return trim($css);
911 }
912
913 # if success
914 if(isset($ddl['content'])) {
915
916 # contents
917 $subcss = $ddl['content'];
918
919 # minify
920 $subcss = fvm_maybe_minify_css_file($subcss, $href, $enable_css_minification);
921
922 # trim code
923 $subcss = trim($subcss);
924
925 # save
926 fvm_set_transient(array('uid'=>$tkey, 'date'=>$tvers, 'type'=>'css', 'content'=>$subcss));
927 }
928 }
929
930 # remove import rule and prepend imported code
931 if ($subcss !== false && !empty($subcss)) {
932 $css = str_replace($cssimport, '', $css);
933 $cssimports_prepend[] = '/* Import rule from: '.$href . ' */' . PHP_EOL . $subcss;
934 }
935
936 }
937 }
938 }
939
940 # prepend import rules
941 # https://www.w3.org/TR/CSS2/cascade.html#at-import
942 if(count($cssimports_prepend) > 0) {
943 $css = implode(PHP_EOL, $cssimports_prepend) . $css;
944 }
945
946 # return
947 return trim($css);
948
949 }
950
951
952 # remove fonts and icons from final css
953 function fvm_extract_fonts($css_code) {
954
955 global $fvm_settings, $fvm_urls;
956 $critical_fonts = array();
957 $mff = array();
958 $css_preload = array();
959 $css_code_ff = '';
960
961 # get list of fonts that are on the critical path and are to be left alone
962 if( isset($fvm_settings['css']['css_optimize_critical_fonts']) &&
963 !empty($fvm_settings['css']['css_optimize_critical_fonts'])) {
964 $critical_fonts = fvm_string_toarray($fvm_settings['css']['css_optimize_critical_fonts']);
965 }
966
967 # extract font faces
968 preg_match_all('/\@\s*font-face\s*\{([^}]+)\}/iUu', $css_code, $mff);
969 if(isset($mff[0]) && is_array($mff[0])) {
970 foreach($mff[0] as $ff) {
971
972 # strip and collect
973 $css_code = str_replace($ff, '', $css_code);
974 $css_code_ff.= $ff . PHP_EOL;
975
976 }
977 }
978
979 # relative paths
980 $css_code_ff = str_replace('https://'.$fvm_urls['wp_domain'], '', $css_code_ff);
981 $css_code_ff = str_replace('http://'.$fvm_urls['wp_domain'], '', $css_code_ff);
982 $css_code_ff = str_replace('//'.$fvm_urls['wp_domain'], '', $css_code_ff);
983
984 # fixes
985 $css_code_ff = str_replace('/./', '/', $css_code_ff);
986
987 # return
988 $result = array('code'=>$css_code, 'fonts'=>$css_code_ff);
989 return $result;
990 }
991
992
993 # rewrite assets to cdn
994 function fvm_process_cdn($html) {
995
996 # settings
997 global $fvm_settings, $fvm_urls;
998
999 # html must be an object
1000 if (!is_object($html)) {
1001 $nobj = 1;
1002 $html = str_get_html($html, true, true, 'UTF-8', false, PHP_EOL, ' ');
1003 }
1004
1005 # default integration
1006 $integration_defaults = array('a[data-interchange*=/wp-content/], div[data-background-image], section[data-background-image], form[data-product_variations], image[height], img[src*=/wp-content/], img[data-src*=/wp-content/], img[data-srcset*=/wp-content/], link[rel=icon], link[rel=apple-touch-icon], meta[name=msapplication-TileImage], picture source[srcset*=/wp-content/], rs-slide[data-thumb], video source[type*=video]');
1007
1008 # html integration
1009 if(isset($fvm_urls['wp_domain']) && isset($fvm_settings['cdn']['domain']) && isset($fvm_settings['cdn']['integration'])) {
1010 if(!empty($fvm_settings['cdn']['domain']) && !empty($fvm_urls['wp_domain'])) {
1011 $arr = fvm_string_toarray($fvm_settings['cdn']['integration']);
1012 $arr = array_unique(array_merge($arr, $integration_defaults)); # add defaults
1013 if(is_array($arr) && count($arr) > 0) {
1014 foreach($html->find(implode(', ', $arr) ) as $elem) {
1015
1016 # preserve some attributes but replace others
1017 if (is_object($elem) && isset($elem->attr)) {
1018
1019 # get all attributes
1020 foreach ($elem->attr as $key=>$val) {
1021
1022 # skip href attribute for links
1023 if($key == 'href' && stripos($elem->outertext, '<a ') !== false) { continue; }
1024
1025 # skip certain attributes
1026 if(in_array($key, array('id', 'class', 'action'))) { continue; }
1027
1028 # scheme + site url
1029 $fcdn = str_replace($fvm_urls['wp_domain'], $fvm_settings['cdn']['domain'], $fvm_urls['wp_site_url']);
1030
1031 # known replacements
1032 $elem->{$key} = str_ireplace('url(/wp-content/', 'url('.$fcdn.'/wp-content/', $elem->{$key});
1033 $elem->{$key} = str_ireplace('url("/wp-content/', 'url("'.$fcdn.'/wp-content/', $elem->{$key});
1034 $elem->{$key} = str_ireplace('url(\'/wp-content/', 'url(\''.$fcdn.'/wp-content/', $elem->{$key});
1035
1036 # normalize certain field
1037 if(in_array($key, array('src', 'data-bg', 'data-lazy-src', 'audio', 'poster'))) {
1038 $elem->{$key} = fvm_normalize_url($elem->{$key});
1039 }
1040
1041 # replace other attributes
1042 $elem->{$key} = str_replace('//'.$fvm_urls['wp_domain'], '//'.$fvm_settings['cdn']['domain'], $elem->{$key});
1043 $elem->{$key} = str_replace('\/\/'.$fvm_urls['wp_domain'], '\/\/'.$fvm_settings['cdn']['domain'], $elem->{$key});
1044
1045 }
1046
1047 }
1048
1049 }
1050 }
1051 }
1052 }
1053
1054
1055 # add CDN support to Styles, CSS and JS files
1056
1057 # css
1058 if(isset($fvm_settings['cdn']['cssok']) && $fvm_settings['cdn']['cssok'] == true) {
1059
1060 # scheme + site url
1061 $fcdn = str_replace($fvm_urls['wp_domain'], $fvm_settings['cdn']['domain'], $fvm_urls['wp_site_url']);
1062
1063 # replace inside styles
1064 foreach($html->find('style') as $elem) {
1065
1066 # fetch
1067 $css = $elem->outertext;
1068
1069 # known replacements
1070 $css = str_ireplace('url(/wp-content/', 'url('.$fcdn.'/wp-content/', $css);
1071 $css = str_ireplace('url("/wp-content/', 'url("'.$fcdn.'/wp-content/', $css);
1072 $css = str_ireplace('url(\'/wp-content/', 'url(\''.$fcdn.'/wp-content/', $css);
1073 $css = str_replace('//'.$fvm_urls['wp_domain'], '//'.$fvm_settings['cdn']['domain'], $css);
1074
1075 # save
1076 $elem->outertext = $css;
1077
1078 }
1079
1080 # replace link stylesheets
1081 if(isset($fvm_settings['cdn']['enable_css']) && $fvm_settings['cdn']['enable_css'] == true) {
1082 foreach($html->find('link[rel=stylesheet], link[rel=preload]') as $elem) {
1083 if(isset($elem->href)) {
1084 $elem->href = str_replace($fvm_urls['wp_site_url'], $fcdn, $elem->href);
1085 }
1086 }
1087 }
1088 }
1089
1090 # js
1091 if(isset($fvm_settings['cdn']['jsok']) && $fvm_settings['cdn']['jsok'] == true) {
1092
1093 # replace script files
1094 foreach($html->find('script') as $elem) {
1095
1096 # js files
1097 if(isset($fvm_settings['cdn']['enable_js']) && $fvm_settings['cdn']['enable_js'] == true) {
1098 if(isset($elem->src) && stripos($elem->src, $fvm_urls['wp_domain']) !== false) {
1099 $elem->src = str_replace('//'.$fvm_urls['wp_domain'], '//'.$fvm_settings['cdn']['domain'], $elem->src);
1100 }
1101 }
1102 }
1103
1104 }
1105
1106 # convert html object to string, only when needed
1107 if(isset($nobj) && $nobj == 1) {
1108 $html = trim($html->save());
1109 }
1110
1111 # return
1112 return $html;
1113 }
1114
1115
1116 # rewrite url with cdn
1117 function fvm_rewrite_cdn_url($url) {
1118 global $fvm_settings, $fvm_urls;
1119 if(isset($fvm_settings['cdn']['enable']) && $fvm_settings['cdn']['enable'] == true && isset($fvm_settings['cdn']['domain']) && !empty($fvm_settings['cdn']['domain'])) {
1120 $url = str_replace('//'.$fvm_urls['wp_domain'], '//'.$fvm_settings['cdn']['domain'], $url);
1121 }
1122 return $url;
1123 }
1124
1125 # get css font-face rules, original + simplified
1126 function fvm_simplify_fontface($css_code) {
1127
1128 $mff = array();
1129 $before = array();
1130 $after = array();
1131
1132 # extract font faces
1133 preg_match_all('/\@\s*font-face\s*\{([^}]+)\}/iUu', $css_code, $mff);
1134 if(isset($mff[0]) && isset($mff[1]) && is_array($mff[1])) {
1135 foreach($mff[1] as $kf=>$ff) {
1136
1137 # simplify font urls
1138 $cssrules = preg_split("/;(?![^(]*\))/iu", $ff);
1139 foreach ($cssrules as $k=>$csr) {
1140 if(preg_match('/src\s*\:\s*url/Uui', $csr)) {
1141
1142 # woff
1143 $fonts = array();
1144 preg_match('/url\s*\(\s*[\'\"]*([^\'\"]*)[\'\"]*\)\s*format\s*\([\'\"]*woff[\'\"]*\s*\)/Uui', $csr, $fonts);
1145 if(isset($fonts[0])) { $cssrules[$k] = 'src:'.$fonts[0]; break; }
1146
1147 # woff2
1148 $fonts = array();
1149 preg_match('/url\s*\(\s*[\'\"]*([^\'\"]*)[\'\"]*\)\s*format\s*\([\'\"]*woff2[\'\"]*\s*\)/Uui', $csr, $fonts);
1150 if(isset($fonts[0])) { $cssrules[$k] = 'src:'.$fonts[0]; break; }
1151
1152 # svg
1153 $fonts = array();
1154 preg_match('/url\s*\(\s*[\'\"]*([^\'\"]*)[\'\"]*\)\s*format\s*\([\'\"]*svg[\'\"]*\s*\)/Uui', $csr, $fonts);
1155 if(isset($fonts[0])) { $cssrules[$k] = 'src:'.$fonts[0]; break; }
1156
1157 # truetype
1158 $fonts = array();
1159 preg_match('/url\s*\(\s*[\'\"]*([^\'\"]*)[\'\"]*\)\s*format\s*\([\'\"]*truetype[\'\"]*\s*\)/Uui', $csr, $fonts);
1160 if(isset($fonts[0])) { $cssrules[$k] = 'src:'.$fonts[0]; break; }
1161
1162 # delete other src:url rules
1163 if(stripos($csr, 'format') === false) {
1164 unset($cssrules[$k]);
1165 }
1166
1167 }
1168 }
1169
1170 # merge and create font face rule
1171 $after[] = '@font-face{'.implode(';', $cssrules).'}';
1172
1173 # strip and collect
1174 $before[] = $mff[0][$kf];
1175
1176 }
1177 }
1178
1179 # return
1180 if(count($before) > 0) {
1181 return array('before'=>$before, 'after'=>$after);
1182 } else {
1183 return false;
1184 }
1185 }
1186
1187
1188
1189 # get css code from css file
1190 function fvm_get_css_from_file($tag) {
1191
1192 # globals
1193 global $fvm_settings;
1194
1195 # variables
1196 $tvers = get_option('fvm_last_cache_update', '0');
1197
1198 # make sure we have a complete url
1199 $href = fvm_normalize_url($tag->href);
1200
1201 # download, minify, cache (no ver query string)
1202 $tkey = fvm_generate_hash_with_prefix($href, 'css');
1203 $css = fvm_get_transient($tkey);
1204
1205 # download
1206 if ($css === false) {
1207
1208 $ddl = array();
1209 $ddl = fvm_maybe_download($href);
1210
1211 # error
1212 if(isset($ddl['error'])) {
1213 return array('error'=>$ddl['error'], 'tkey'=>$tkey, 'url'=> $href);
1214 }
1215
1216 # success
1217 if(isset($ddl['content'])) {
1218
1219 # minify flag
1220 $min = true;
1221 if(isset($fvm_settings['css']['min_disable']) && $fvm_settings['css']['min_disable'] == true) {
1222 $min = false;
1223 }
1224
1225 # minify
1226 $css = fvm_maybe_minify_css_file($ddl['content'], $href, $min);
1227
1228 # quick integrity check
1229 if($css !== false) {
1230
1231 # handle import rules
1232 $css = fvm_replace_css_imports($css, $href);
1233 $meta = json_encode(array('href'=>$href));
1234
1235 # save transient
1236 $verify = fvm_set_transient(array('uid'=>$tkey, 'date'=>$tvers, 'type'=>'css', 'content'=>$css, 'meta'=>$meta));
1237
1238 # success, from download
1239 return array('code'=>$css, 'tkey'=>$tkey, 'url'=> $href);
1240 }
1241 }
1242
1243 } else {
1244 # success, from transient
1245 return array('code'=>$css, 'tkey'=>$tkey, 'url'=> $href);
1246 }
1247
1248 }
1249
1250
1251 # get js code from css file
1252 function fvm_get_js_from_file($tag) {
1253
1254 # globals
1255 global $fvm_settings;
1256
1257 # variables
1258 $tvers = get_option('fvm_last_cache_update', '0');
1259
1260 # make sure we have a complete url
1261 $href = fvm_normalize_url($tag->src);
1262
1263 # download, minify, cache (no ver query string)
1264 $tkey = fvm_generate_hash_with_prefix($href, 'js');
1265 $js = fvm_get_transient($tkey);
1266
1267 # download
1268 if ($js === false) {
1269
1270 $ddl = array();
1271 $ddl = fvm_maybe_download($href);
1272
1273 # error
1274 if(isset($ddl['error'])) {
1275 return array('error'=>$ddl['error'], 'tkey'=>$tkey, 'url'=> $href);
1276 }
1277
1278 # success
1279 if(isset($ddl['content'])) {
1280
1281 # minify flag
1282 $min = true;
1283 if(isset($fvm_settings['js']['min_disable']) && $fvm_settings['js']['min_disable'] == true) {
1284 $min = false;
1285 }
1286
1287 # minify
1288 $js = fvm_maybe_minify_js($ddl['content'], $href, $min);
1289
1290 # wrap with try catch
1291 $js = fvm_try_catch_wrap($js, $href);
1292
1293 # quick integrity check
1294 if($js !== false) {
1295
1296 # meta
1297 $meta = json_encode(array('href'=>$href));
1298
1299 # save transient
1300 $verify = fvm_set_transient(array('uid'=>$tkey, 'date'=>$tvers, 'type'=>'js', 'content'=>$js, 'meta'=>$meta));
1301
1302 # success, from download
1303 return array('code'=>$js, 'tkey'=>$tkey, 'url'=> $href);
1304 }
1305 }
1306
1307 } else {
1308 # success, from transient
1309 return array('code'=>$js, 'tkey'=>$tkey, 'url'=> $href);
1310 }
1311
1312 # fallback
1313 return false;
1314
1315 }
1316
1317
1318
1319
1320 # get transients
1321 function fvm_get_transient($key, $check=null, $with_meta=null) {
1322
1323 # must have
1324 global $wpdb;
1325 if(is_null($wpdb)) { return false; }
1326 $db_prefix = $wpdb->prefix;
1327
1328 try {
1329
1330 # check or fetch
1331 if(!is_null($check)) {
1332 $sql = $wpdb->prepare("SELECT id FROM {$db_prefix}fvm_cache WHERE uid = %s LIMIT 1", $key);
1333 } else if (!is_null($with_meta)) {
1334 $sql = $wpdb->prepare("SELECT date, content, meta FROM {$db_prefix}fvm_cache WHERE uid = %s LIMIT 1", $key);
1335 } else {
1336 $sql = $wpdb->prepare("SELECT content FROM {$db_prefix}fvm_cache WHERE uid = %s LIMIT 1", $key);
1337 }
1338
1339 # get result from database
1340 $result = $wpdb->get_row($sql);
1341
1342 # return true if just checking
1343 if(!is_null($check) && isset($result->id)) {
1344 return true;
1345 }
1346
1347 # return content only
1348 if(is_null($check) && is_null($with_meta) && isset($result->content)) {
1349 return $result->content;
1350 }
1351
1352 # return content and meta
1353 if(is_null($check) && !is_null($with_meta) && isset($result->date) && isset($result->content) && isset($result->meta)) {
1354 return array('date'=>$result->date, 'content'=>$result->content, 'meta'=>json_decode($result->meta, true), 'cache-method'=>$cache_method);
1355 }
1356
1357 } catch (Exception $e) {
1358 error_log('Error: '.$e->getMessage(), 0);
1359 return false;
1360 }
1361
1362 # fallback
1363 return false;
1364 }
1365
1366 # set cache
1367 function fvm_set_transient($arr) {
1368
1369 # must have
1370 if(!is_array($arr) || (is_array($arr) && (count($arr) == 0 || empty($arr)))) { return false; }
1371 if(!isset($arr['uid']) || !isset($arr['date']) || !isset($arr['type']) || !isset($arr['content'])) { return false; }
1372
1373 # normalize unknown keys
1374 if(strlen($arr['uid']) != 64) { $arr['uid'] = fvm_generate_hash_with_prefix($arr['uid'], $arr['type']); }
1375
1376 # check if it already exists, return early if it does
1377 $status = fvm_get_transient($arr['uid'], true);
1378 if($status) { return $arr['uid']; }
1379
1380 # must have
1381 global $wpdb;
1382 if(is_null($wpdb)) { return false; }
1383 $db_prefix = $wpdb->prefix;
1384
1385 # initialize arrays (fields, types, values)
1386 $fld = array();
1387 $tpe = array();
1388 $vls = array();
1389
1390 # define possible data types
1391 $str = array('uid', 'type', 'content', 'meta');
1392 $int = array('date');
1393 $all = array_merge($str, $int);
1394
1395 # process only recognized columns
1396 foreach($arr as $k=>$v) {
1397 if(in_array($k, $all)) {
1398 if(in_array($k, $str)) { $tpe[] = '%s'; } else { $tpe[] = '%d'; }
1399 $fld[] = $k;
1400 $vls[] = $v;
1401 }
1402 }
1403
1404 try {
1405 # prepare and insert to database
1406 $result = $wpdb->query($wpdb->prepare("INSERT IGNORE INTO {$db_prefix}fvm_cache (".implode(', ', $fld).") VALUES (".implode(', ', $tpe).")", $vls));
1407
1408 # success
1409 if($result) {
1410 return $arr['uid'];
1411 }
1412 } catch (Exception $e) {
1413 error_log('Error: '.$e->getMessage(), 0);
1414 }
1415
1416 # fallback
1417 return false;
1418
1419 }
1420
1421 # delete transient
1422 function fvm_del_transient($key) {
1423
1424 # normalize unknown keys
1425 if(strlen($key) != 64) { $key = fvm_generate_hash_with_prefix($key, ''); }
1426
1427 # must have
1428 global $wpdb;
1429 if(is_null($wpdb)) { return false; }
1430 $db_prefix = $wpdb->prefix;
1431
1432 try {
1433 # delete
1434 $wpdb->query($wpdb->prepare("DELETE FROM {$db_prefix}fvm_cache WHERE uid = %s", $key));
1435 return true;
1436 } catch (Exception $e) {
1437 error_log('Error: '.$e->getMessage(), 0);
1438 }
1439
1440 # fallback
1441 return false;
1442 }
1443
1444
1445 # functions, get full url
1446 function fvm_normalize_url($href, $purl=null) {
1447
1448 # preserve empty source handles
1449 $href = trim($href);
1450 if(empty($href)) { return false; }
1451
1452 # some fixes
1453 $href = str_replace(array('&#038;', '&amp;'), '&', $href);
1454
1455 # external url
1456 if(!is_null($purl) && !empty($purl)) {
1457 $parse = parse_url($purl);
1458 } else {
1459 # local url
1460 global $fvm_urls;
1461 $parse = parse_url($fvm_urls['wp_site_url']);
1462 }
1463
1464 # domain info
1465 $scheme = $parse['scheme'];
1466 $host = $parse['host'];
1467 $path = $parse['path'];
1468
1469 # relative to full urls
1470 if (substr($href, 0, 2) === "//") {
1471 $href = $scheme.':'.$href; # scheme missing
1472 } else if (substr($href, 0, 1) === "/") {
1473 $href = $scheme.'://'.$host . $href; # scheme and domain missing
1474 } else if (substr($href, 0, 3) === '../' && !is_null($purl) && !empty($purl)) {
1475 $href = $scheme.':'.$host . dirname($path) . '/' . $href;
1476 } else if ($scheme == 'https' && substr($href, 0, 4) == 'http' && substr($href, 0, 5) !== $scheme) {
1477 $href = str_replace('http://', 'https://', $href); # force https
1478 } else {
1479 # url should be fine
1480 }
1481
1482 # prevent double forward slashes in the middle
1483 $href = str_replace('###', '://', str_replace('//', '/', str_replace('://', '###', $href)));
1484
1485 return $href;
1486 }
1487
1488
1489 # minify ld+json scripts
1490 function fvm_minify_microdata($data) {
1491 $data = trim(preg_replace('/(\v)+(\h)+[\/]{2}(.*)+(\v)+/u', '', $data));
1492 $data = trim(preg_replace('/\s+/u', ' ', $data));
1493 $data = str_replace(array('" ', ' "'), '"', $data);
1494 $data = str_replace(array('[ ', ' ['), '[', $data);
1495 $data = str_replace(array('] ', ' ]'), ']', $data);
1496 $data = str_replace(array('} ', ' }'), '}', $data);
1497 $data = str_replace(array('{ ', ' {'), '{', $data);
1498 return $data;
1499 }
1500
1501
1502 # check for php or html, skip if found
1503 function fvm_not_php_html($code) {
1504
1505 # return early if not html
1506 $code = trim($code);
1507 $a = '<!doctype'; # start
1508 $b = '<html'; # start
1509 $c = '<?xml'; # start
1510 $d = '<?php'; # anywhere
1511
1512 if ( strcasecmp(substr($code, 0, strlen($a)), $a) != 0 && strcasecmp(substr($code, 0, strlen($b)), $b) != 0 && strcasecmp(substr($code, 0, strlen($c)), $c) != 0 && stripos($code, $d) === false ) {
1513 return true;
1514 }
1515
1516 return false;
1517 }
1518
1519
1520 # find if a string looks like HTML content
1521 function fvm_is_html($html) {
1522
1523 # return early if it's html
1524 $html = trim($html);
1525 $a = '<!doctype';
1526 $b = '<html';
1527 if ( strcasecmp(substr($html, 0, strlen($a)), $a) == 0 || strcasecmp(substr($html, 0, strlen($b)), $b) == 0 ) {
1528 return true;
1529 }
1530
1531 # must have html
1532 $hfound = array(); preg_match_all('/<\s?(html)+(.*)>(.*)<\s?\/\s?html\s?>/Uuis', $html, $hfound);
1533 if(!isset($hfound[0][0])) { return false; }
1534
1535 # must have head
1536 $hfound = array(); preg_match_all('/<\s?(head)+(.*)>(.*)<\s?\/\s?head\s?>/Uuis', $html, $hfound);
1537 if(!isset($hfound[0][0])) { return false; }
1538
1539 # must have body
1540 $hfound = array(); preg_match_all('/<\s?(body)+(.*)>(.*)<\s?\/\s?body\s?>/Uuis', $html, $hfound);
1541 if(!isset($hfound[0][0])) { return false; }
1542
1543 # must have at least one of these
1544 $count = 0;
1545
1546 # css link
1547 $hfound = array(); preg_match_all('/<\s?(link)+(.*)(rel|href)+(.*)>/Uuis', $html, $hfound);
1548 if(!isset($hfound[0][0])) { $count++; }
1549
1550 # style
1551 $hfound = array(); preg_match_all('/<\s?(style)+(.*)(src)+(.*)>(.*)<\s?\/\s?style\s?>/Uuis', $html, $hfound);
1552 if(!isset($hfound[0][0])) { $count++; }
1553
1554 # script
1555 $hfound = array(); preg_match_all('/<\s?(script)+(.*)(src)+(.*)>(.*)<\s?\/\s?script\s?>/Uuis', $html, $hfound);
1556 if(!isset($hfound[0][0])) { $count++; }
1557
1558 # return if not
1559 if($count == 0) { return false; }
1560
1561 # else, it's likely html
1562 return true;
1563
1564 }
1565
1566 # ensure that string is utf8
1567 function fvm_ensure_utf8($str) {
1568 $enc = mb_detect_encoding($str, mb_list_encodings(), true);
1569 if ($enc === false){
1570 return false; // could not detect encoding
1571 } else if ($enc !== "UTF-8") {
1572 return mb_convert_encoding($str, "UTF-8", $enc); // converted to utf8
1573 } else {
1574 return $str; // already utf8
1575 }
1576
1577 # fail
1578 return false;
1579 }
1580
1581
1582 # check if we can process the page, minimum filters
1583 function fvm_can_process_common() {
1584 global $fvm_settings, $fvm_urls;
1585
1586 # only GET requests allowed
1587 if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
1588 return false;
1589 }
1590
1591 # always skip on these tasks
1592 if( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ){ return false; }
1593 if( defined('WP_INSTALLING') && WP_INSTALLING ){ return false; }
1594 if( defined('WP_REPAIRING') && WP_REPAIRING ){ return false; }
1595 if( defined('WP_IMPORTING') && WP_IMPORTING ){ return false; }
1596 if( defined('DOING_AJAX') && DOING_AJAX ){ return false; }
1597 if( defined('WP_CLI') && WP_CLI ){ return false; }
1598 if( defined('XMLRPC_REQUEST') && XMLRPC_REQUEST ){ return false; }
1599 if( defined('WP_ADMIN') && WP_ADMIN ){ return false; }
1600 if( defined('SHORTINIT') && SHORTINIT ){ return false; }
1601 if( defined('IFRAME_REQUEST') && IFRAME_REQUEST ){ return false; }
1602
1603 # detect api requests (only defined after parse_request hook)
1604 if( defined('REST_REQUEST') && REST_REQUEST ){ return false; }
1605
1606 # don't minify specific WordPress areas
1607 if(function_exists('is_404') && is_404()){ return false; }
1608 if(function_exists('is_feed') && is_feed()){ return false; }
1609 if(function_exists('is_comment_feed') && is_comment_feed()){ return false; }
1610 if(function_exists('is_attachment') && is_attachment()){ return false; }
1611 if(function_exists('is_trackback') && is_trackback()){ return false; }
1612 if(function_exists('is_robots') && is_robots()){ return false; }
1613 if(function_exists('is_preview') && is_preview()){ return false; }
1614 if(function_exists('is_customize_preview') && is_customize_preview()){ return false; }
1615 if(function_exists('is_embed') && is_embed()){ return false; }
1616 if(function_exists('is_admin') && is_admin()){ return false; }
1617 if(function_exists('is_blog_admin') && is_blog_admin()){ return false; }
1618 if(function_exists('is_network_admin') && is_network_admin()){ return false; }
1619
1620 # don't minify specific WooCommerce areas
1621 if(function_exists('is_checkout') && is_checkout()){ return false; }
1622 if(function_exists('is_account_page') && is_account_page()){ return false; }
1623 if(function_exists('is_ajax') && is_ajax()){ return false; }
1624 if(function_exists('is_wc_endpoint_url') && is_wc_endpoint_url()){ return false; }
1625
1626 # get requested hostname
1627 $host = fvm_get_domain();
1628
1629 # only for hosts matching the site_url
1630 if(isset($fvm_urls['wp_domain']) && !empty($fvm_urls['wp_domain'])) {
1631 if($host != $fvm_urls['wp_domain']) {
1632 return false;
1633 }
1634 }
1635
1636 # if there is an url, skip common static files
1637 if(isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI'])) {
1638
1639 # parse url (path, query)
1640 $ruri = fvm_get_uripath();
1641
1642 # no cache by extension as well, such as robots.txt and other situations
1643 $noext = array('.txt', '.xml', '.xsl', '.map', '.css', '.js', '.png', '.jpeg', '.jpg', '.gif', '.webp', '.ico', '.php', '.htaccess', '.json', '.pdf', '.mp4', '.webm');
1644 foreach ($noext as $ext) {
1645 if(substr($ruri, -strlen($ext)) == $ext) {
1646 return false;
1647 }
1648 }
1649
1650 }
1651
1652 # default
1653 return true;
1654 }
1655
1656 # check if the user is logged in, and if the user role allows optimization
1657 function fvm_user_role_processing_allowed($group) {
1658 if(function_exists('is_user_logged_in') && function_exists('wp_get_current_user')) {
1659 if(is_user_logged_in()) {
1660
1661 # get user roles
1662 global $fvm_settings;
1663 $user = wp_get_current_user();
1664 $roles = (array) $user->roles;
1665 foreach($roles as $role) {
1666 if(isset($fvm_settings['minify'][$role]) && $fvm_settings['minify'][$role] == true) {
1667 return true;
1668 }
1669 }
1670
1671 # disable for other logged in users by default
1672 return false;
1673 }
1674 }
1675
1676 # allow by default
1677 return true;
1678 }
1679
1680 # check if we can process the page, minimum filters
1681 function fvm_can_process_query_string($loc) {
1682
1683 # host and uri path
1684 $host = fvm_get_domain();
1685 $request_uri = fvm_get_uripath(true);
1686 $scheme = fvm_get_scheme();
1687 $url = $scheme.'://'.$host.$request_uri;
1688 $parse = parse_url($url);
1689
1690 # parse query string to array, check if should be ignored
1691 if(isset($parse["query"]) && !empty($parse["query"])) {
1692
1693 # check
1694 $qsarr = array(); parse_str($parse["query"], $qsarr);
1695
1696 # allowed queries by default
1697 if(isset($qsarr['s'])) { unset($qsarr['s']); } # search
1698 if(isset($qsarr['lang'])) { unset($qsarr['lang']); } # wpml
1699
1700 # if there are other queries left, bypass cache
1701 if(count($qsarr) > 0) {
1702 return false;
1703 }
1704 }
1705
1706 # allow by default
1707 return true;
1708 }
1709
1710
1711
1712 # check if page is amp
1713 function fvm_is_amp_page() {
1714
1715 # don't minify amp pages by the amp plugin
1716 if(function_exists('is_amp_endpoint') && is_amp_endpoint()){ return true; }
1717 if(function_exists('ampforwp_is_amp_endpoint') && ampforwp_is_amp_endpoint()){ return true; }
1718
1719 # query string or /amp/
1720 if(isset($_GET['amp'])) { return true; }
1721 if(substr(fvm_get_uripath(), -5) == '/amp/') { return true; }
1722
1723 # not amp
1724 return false;
1725 }
1726
1727
1728 # validate and minify css
1729 function fvm_maybe_minify_css_file($css, $url, $min) {
1730
1731 # process css only if it's not php or html
1732 if(fvm_not_php_html($css)) {
1733
1734 global $fvm_settings, $fvm_urls;
1735
1736 # do not minify files in the ignore list
1737 if(isset($fvm_settings['css']['css_ignore_min']) && !empty($fvm_settings['css']['css_ignore_min'])) {
1738 $arr = fvm_string_toarray($fvm_settings['css']['css_ignore_min']);
1739 if(is_array($arr) && count($arr) > 0) {
1740 foreach ($arr as $e) {
1741 if(stripos($url, $e) !== false) {
1742 $min = false;
1743 break;
1744 }
1745 }
1746 }
1747 }
1748
1749 # filtering
1750 $css = fvm_ensure_utf8($css);
1751 $css = str_ireplace('@charset "UTF-8";', '', $css);
1752
1753 # remove query strings from fonts
1754 $css = preg_replace('/(.eot|.woff2|.woff|.ttf)+[?+](.+?)(\)|\'|\")/ui', "$1"."$3", $css);
1755
1756 # remove sourceMappingURL
1757 $css = preg_replace('/(\/\/\s*[#]\s*sourceMappingURL\s*[=]\s*)([a-zA-Z0-9-_\.\/]+)(\.map)/ui', '', $css);
1758 $css = preg_replace('/(\/[*]\s*[#]\s*sourceMappingURL\s*[=]\s*)([a-zA-Z0-9-_\.\/]+)(\.map)\s*[*]\s*[\/]/ui', '', $css);
1759
1760 # fix url paths
1761 if(!empty($url)) {
1762 $matches = array(); preg_match_all("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/ui", $css, $matches);
1763 foreach($matches[1] as $a) { $b = trim($a); if($b != $a) { $css = str_replace($a, $b, $css); } }
1764 $css = preg_replace("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"#])(.+?)['\"]?\s*\)/ui", "url(".dirname($url)."/$1)", $css);
1765 }
1766
1767 # minify string with relative urls
1768 if($min === true) {
1769 $css = fvm_minify_css_string($css, $url);
1770 }
1771
1772 # add font-display block for all font faces
1773 # https://developers.google.com/web/updates/2016/02/font-display
1774 $css = preg_replace_callback('/(?:@font-face)\s*{(?<value>[^}]+)}/i',
1775 function ($matches) {
1776 if ( preg_match('/font-display:\s*(?<swap_value>\w*);?/i', $matches['value'], $attribute)) {
1777 return 'swap' === strtolower($attribute['swap_value']) ? $matches[0] : str_replace($attribute['swap_value'], 'swap', $matches[0]);
1778 } else {
1779 $swap = "font-display:swap;{$matches['value']}";
1780 }
1781 return str_replace( $matches['value'], $swap, $matches[0] );
1782 },
1783 $css
1784 );
1785
1786 # make relative urls when possible
1787
1788 # get root url, preserve subdirectories
1789 if(isset($fvm_urls['wp_site_url']) && !empty($fvm_urls['wp_site_url'])) {
1790
1791 # parse url and extract domain without uri path
1792 $use_url = $fvm_urls['wp_site_url'];
1793 $parse = parse_url($use_url);
1794 if(isset($parse['path']) && !empty($parse['path']) && $parse['path'] != '/') {
1795 $use_url = str_replace(str_replace($use_url, $parse['path'], $use_url), '', $use_url);
1796 }
1797
1798 # adjust paths
1799 $bgimgs = array();
1800 preg_match_all ('/url\s*\(\s*[\'\"]*([^;\'\"]*)[\'\"]*\)/Uui', $css, $bgimgs);
1801 if(isset($bgimgs[1]) && is_array($bgimgs[1])) {
1802 foreach($bgimgs[1] as $img) {
1803 if(stripos($img, 'http') !== false || stripos($img, '//') !== false) {
1804
1805 # normalize
1806 $newimg = fvm_normalize_url($img);
1807 if($newimg != $img) { $css = str_replace($img, $newimg, $css); $img = $newimg; }
1808
1809 # process
1810 if(substr($img, 0, strlen($use_url)) == $use_url) {
1811 $pos = strpos($img, $use_url);
1812 if ($pos !== false) {
1813
1814 # relative path image
1815 $relimg = '/' . ltrim(substr_replace($img, '', $pos, strlen($use_url)), '/');
1816
1817 # replace url
1818 $css = str_replace($img, $relimg, $css);
1819
1820 }
1821 }
1822
1823 }
1824 }
1825 }
1826
1827 # remove empty url()
1828 $css = preg_replace('/url\s*\(\s*[\'"]?\s*[\'"]?\)/Uui', 'none', $css);
1829
1830 # relative paths
1831 $css = str_replace('https://'.$fvm_urls['wp_domain'], '', $css);
1832 $css = str_replace('http://'.$fvm_urls['wp_domain'], '', $css);
1833 $css = str_replace('//'.$fvm_urls['wp_domain'], '', $css);
1834
1835 # fixes
1836 $css = str_replace('/./', '/', $css);
1837
1838 }
1839
1840 # simplify font face
1841 $arr = fvm_simplify_fontface($css);
1842 if($arr !== false && is_array($arr)) {
1843 $css = str_replace($arr['before'], $arr['after'], $css);
1844 }
1845
1846 # return css
1847 return trim($css);
1848
1849 }
1850
1851 return false;
1852 }
1853
1854
1855 # validate and minify js
1856 function fvm_maybe_minify_js($js, $url, $enable_js_minification) {
1857
1858 # ensure it's utf8
1859 $js = fvm_ensure_utf8($js);
1860
1861 # return early if empty
1862 if(empty($js) || $js == false) { return false; }
1863
1864 # process js only if it's not php or html
1865 if(fvm_not_php_html($js)) {
1866
1867 # globals
1868 global $fvm_settings;
1869
1870 # filtering
1871 $js = fvm_ensure_utf8($js);
1872
1873 # remove sourceMappingURL
1874 $js = preg_replace('/(\/\/\s*[#]\s*sourceMappingURL\s*[=]\s*)([a-zA-Z0-9-_\.\/]+)(\.map)/ui', '', $js);
1875 $js = preg_replace('/(\/[*]\s*[#]\s*sourceMappingURL\s*[=]\s*)([a-zA-Z0-9-_\.\/]+)(\.map)\s*[*]\s*[\/]/ui', '', $js);
1876
1877 # minify?
1878 if($enable_js_minification == true) {
1879
1880 # PHP Minify from https://github.com/matthiasmullie/minify
1881 $minifier = new FVM\MatthiasMullie\Minify\JS($js);
1882 $min = $minifier->minify();
1883
1884 # return if not empty
1885 if($min !== false && strlen(trim($min)) > 0) {
1886 return $min;
1887 }
1888 }
1889
1890 # return js
1891 return trim($js);
1892
1893 }
1894
1895 return false;
1896 }
1897
1898
1899 # minify css string with PHP Minify
1900 function fvm_minify_css_string($css) {
1901
1902 # return early if empty
1903 if(empty($css) || $css == false) { return $css; }
1904
1905 # minify
1906 $minifier = new FVM\MatthiasMullie\Minify\CSS($css);
1907 $minifier->setMaxImportSize(10); # embed assets up to 10 Kb (default 5Kb) - processes gif, png, jpg, jpeg, svg & woff
1908 $min = $minifier->minify();
1909
1910 # return
1911 if($min != false) {
1912 return $min;
1913 }
1914
1915 # fallback
1916 return $css;
1917 }
1918
1919
1920 # escape html tags for document.write
1921 function fvm_escape_url_js($str) {
1922 $str = trim(preg_replace('/[\t\n\r\s]+/iu', ' ', $str));
1923 return str_replace(array('\\\\\"', '\\\\"', '\\\"', '\\"'), '\"', json_encode($str));
1924 }
1925
1926
1927 # try catch wrapper for merged javascript
1928 function fvm_try_catch_wrap($js, $href=null) {
1929 $loc = ''; if(isset($href)) { $loc = '[ File: '. $href . ' ] '; }
1930 return 'try{'. PHP_EOL . $js . PHP_EOL . '}catch(e){console.error("An error has occurred. '.$loc.'[ "+e.stack+" ]");}';
1931 }
1932
1933
1934 # Disable the emoji's on the frontend
1935 function fvm_disable_emojis() {
1936 global $fvm_settings;
1937 if(isset($fvm_settings['html']['disable_emojis']) && $fvm_settings['html']['disable_emojis'] == true) {
1938 remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
1939 remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
1940 remove_action( 'wp_print_styles', 'print_emoji_styles' );
1941 remove_action( 'admin_print_styles', 'print_emoji_styles' );
1942 remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
1943 remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
1944 remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
1945 }
1946 }
1947
1948
1949 # stop slow ajax requests for bots
1950 function fvm_ajax_optimizer() {
1951 if(isset($_SERVER['HTTP_USER_AGENT']) && (defined('DOING_AJAX') && DOING_AJAX) || (function_exists('is_ajax') && is_ajax()) || (function_exists('wp_doing_ajax') && wp_doing_ajax())){
1952 if (preg_match('/'.implode('|', array('x11.*ox\/54', 'id\s4.*us.*ome\/62', 'oobo', 'ight', 'tmet', 'eadl', 'ngdo', 'PTST')).'/i', $_SERVER['HTTP_USER_AGENT'])){ echo '0'; exit(); }
1953 }
1954 }
1955
1956 # rewrite assets to cdn
1957 function fvm_rewrite_assets_cdn($html) {
1958
1959 # settings
1960 global $fvm_settings, $fvm_urls;
1961
1962 if(isset($fvm_urls['wp_domain']) && !empty($fvm_urls['wp_domain']) &&
1963 isset($fvm_settings['cdn']['enable']) && $fvm_settings['cdn']['enable'] == true &&
1964 isset($fvm_settings['cdn']['domain']) && !empty($fvm_settings['cdn']['domain']) &&
1965 isset($fvm_settings['cdn']['integration']) && !empty($fvm_settings['cdn']['integration'])) {
1966 $arr = fvm_string_toarray($fvm_settings['cdn']['integration']);
1967 if(is_array($arr) && count($arr) > 0) {
1968 foreach($html->find(implode(', ', $arr) ) as $elem) {
1969
1970 # preserve some attributes but replace others
1971 if (is_object($elem) && isset($elem->attr)) {
1972
1973 # get all attributes
1974 foreach ($elem->attr as $key=>$val) {
1975
1976 # skip href attribute for links
1977 if($key == 'href' && stripos($elem->outertext, '<a ') !== false) { continue; }
1978
1979 # skip certain attributes
1980 if(in_array($key, array('id', 'class', 'action'))) { continue; }
1981
1982 # replace other attributes
1983 $elem->{$key} = str_replace('//'.$fvm_urls['wp_domain'], '//'.$fvm_settings['cdn']['domain'], $elem->{$key});
1984 $elem->{$key} = str_replace('\/\/'.$fvm_urls['wp_domain'], '\/\/'.$fvm_settings['cdn']['domain'], $elem->{$key});
1985
1986 }
1987
1988 }
1989
1990 }
1991 }
1992 }
1993
1994 return $html;
1995 }
1996
1997
1998 # try to open the file from the disk, before downloading
1999 function fvm_maybe_download($url) {
2000
2001 # must have
2002 if(is_null($url) || empty($url)) { return false; }
2003
2004 # get domain
2005 global $fvm_urls;
2006
2007 # check if we can open the file locally first
2008 if (stripos($url, $fvm_urls['wp_domain']) !== false && defined('ABSPATH') && !empty('ABSPATH')) {
2009
2010 # file path + windows compatibility
2011 $f = strtok(str_replace('/', DIRECTORY_SEPARATOR, str_replace(rtrim($fvm_urls['wp_site_url'], '/'), rtrim(ABSPATH, '/'), $url)), '?');
2012
2013 # did it work?
2014 if (file_exists($f) && is_file($f)) {
2015 return array('content'=>file_get_contents($f), 'src'=>'Disk');
2016 }
2017 }
2018
2019 # fallback to downloading
2020
2021 # this useragent is needed for google fonts (woff files only + hinted fonts)
2022 $uagent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';
2023
2024 # fetch via wordpress functions
2025 $response = wp_remote_get($url, array('user-agent'=>$uagent, 'timeout' => 7, 'httpversion' => '1.1', 'sslverify'=>false));
2026 if ( is_wp_error( $response ) ) {
2027 $error_message = $response->get_error_message();
2028 return array('error'=>"Something went wrong: $error_message");
2029 } else {
2030 return array('content'=>wp_remote_retrieve_body($response), 'src'=>'Web');
2031 }
2032 }
2033
2034
2035 # add our function in the header
2036 function fvm_add_header_function($html) {
2037
2038 # create function
2039 $lst = array('x11.*ox\/54', 'id\s4.*us.*ome\/62', 'oobo', 'ight', 'tmet', 'eadl', 'ngdo', 'PTST');
2040 $fvmf = '<script data-cfasync="false">function fvmuag(){var e=navigator.userAgent;if(e.match(/'.implode('|', $lst).'/i))return!1;if(e.match(/x11.*me\/86\.0/i)){var r=screen.width;if("number"==typeof r&&1367==r)return!1}return!0}</script>';
2041
2042 # remove duplicates
2043 if(stripos($html, $fvmf) !== false) {
2044 $html = str_ireplace($fvmf, '', $html);
2045 }
2046
2047 # add function
2048 $html = str_replace('<!-- h_header_function -->', $fvmf, $html);
2049 return $html;
2050 }
2051
2052 # add lazy load library
2053 function fvm_add_delay_scripts_logic($html) {
2054
2055 # based on v2.0.0: https://github.com/shinsenter/defer.js
2056 # delay to interaction
2057 $scripts = <<<'EOF'
2058 <script type='text/javascript' id='fvm-delayjs' data-cfasync='false'>
2059 !function(k,e,x){function r(d,a,g,b){return b=(a?e.getElementById(a):t)||e.createElement(d||"SCRIPT"),a&&(b.id=a),g&&(b.onload=g),b}function u(d){f(function(a){a=[].slice.call(e.querySelectorAll(d));(function v(b,c){if(b=a.shift()){b.parentNode.removeChild(b);var l=b,m,n=void 0;var p=r(l.nodeName);var q=0;for(m=l.attributes;q<m.length;q++)"type"!=(n=m[q]).name&&p.setAttribute(n.name,n.value);(c=(p.text=l.text,p)).src&&!c.hasAttribute("async")?(c.onload=c.onerror=v,e.head.appendChild(c)):(e.head.appendChild(c),
2060 v())}})()})}var f,t,h=[],w=/p/.test(e.readyState);Function();(f=function(d,a){w?x(d,a):h.push(d,a)}).all=u;f.js=function(d,a,g,b){f(function(c){(c=r(t,a,b)).src=d;e.head.appendChild(c)},g)};k.addEventListener("onpageshow"in k?"pageshow":"load",function(){for(w=!u();h[0];)f(h.shift(),h.shift())});k.Defer=f}(this,document,setTimeout);
2061 const userInteractionEvents=["mouseover","keydown","touchstart","touchmove","wheel"];userInteractionEvents.forEach(function(event){window.addEventListener(event,triggerScriptLoader,{passive:!0})});function triggerScriptLoader(){fvmloadscripts();userInteractionEvents.forEach(function(event){window.removeEventListener(event,triggerScriptLoader,{passive:!0})})}function fvmloadscripts(){Defer.all('script[type="fvm-script-delay"]')};
2062 </script>
2063 EOF;
2064
2065 # add code
2066 return str_replace('<!-- h_footer_fvm_scripts -->', '<!-- h_footer_fvm_scripts -->' . $scripts, $html);
2067
2068 }
2069
2070
2071 # get the domain name
2072 function fvm_get_scheme() {
2073 if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') { return 'https'; }
2074 if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) { return 'https'; }
2075 if(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { return 'https'; }
2076 return 'http';
2077 }
2078
2079 # get the domain name
2080 function fvm_get_domain() {
2081 if (function_exists('site_url')) {
2082 $parse = parse_url(site_url());
2083 return $parse['host'];
2084 } elseif(isset($_SERVER['SERVER_NAME']) && !empty($_SERVER['SERVER_NAME'])) {
2085 return $_SERVER['SERVER_NAME'];
2086 } elseif (isset($_SERVER['HTTP_HOST']) && !empty($_SERVER['HTTP_HOST'])) {
2087 return $_SERVER['HTTP_HOST'];
2088 } else {
2089 return false;
2090 }
2091 }
2092
2093 # get the settings file path, current domain name, and uri path without query strings
2094 function fvm_get_uripath($full=null) {
2095 if (isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI'])) {
2096
2097 # full or no query string
2098 if(!is_null($full)) {
2099 $current_uri = trim($_SERVER['REQUEST_URI']);
2100 } else {
2101 $current_uri = strtok($_SERVER['REQUEST_URI'], '?');
2102 }
2103
2104 # filter
2105 $current_uri = str_replace('//', '/', str_replace('..', '', preg_replace( '/[ <>\'\"\r\n\t\(\)]/', '', $current_uri)));
2106 return $current_uri;
2107 } else {
2108 return false;
2109 }
2110 }
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2
3 # Exit if accessed directly
4 if (!defined('ABSPATH')){ exit(); }
5
6 # functions needed only for frontend ###########
7
8 # must have for large strings processing during minification
9 @ini_set('pcre.backtrack_limit', 5000000);
10 @ini_set('pcre.recursion_limit', 5000000);
11
12 # our own minification libraries
13 include_once($fvm_var_inc_lib . DIRECTORY_SEPARATOR . 'raisermin' . DIRECTORY_SEPARATOR . 'minify.php');
14
15 # php simple html
16 # https://sourceforge.net/projects/simplehtmldom/
17 include_once($fvm_var_inc_lib . DIRECTORY_SEPARATOR . 'simplehtmldom' . DIRECTORY_SEPARATOR . 'simple_html_dom.php');
18
19 # PHP Minify [1.3.60] for CSS minification only
20 # https://github.com/matthiasmullie/minify
21 $fvm_var_inc_lib_mm = $fvm_var_inc_lib . DIRECTORY_SEPARATOR . 'matthiasmullie' . DIRECTORY_SEPARATOR;
22 include_once($fvm_var_inc_lib_mm . 'minify' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'Minify.php');
23 include_once($fvm_var_inc_lib_mm . 'minify' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'CSS.php');
24 include_once $fvm_var_inc_lib_mm . 'minify'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'JS.php';
25 include_once $fvm_var_inc_lib_mm . 'minify'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'Exception.php';
26 include_once $fvm_var_inc_lib_mm . 'minify'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'Exceptions'. DIRECTORY_SEPARATOR .'BasicException.php';
27 include_once $fvm_var_inc_lib_mm . 'minify'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'Exceptions'. DIRECTORY_SEPARATOR .'FileImportException.php';
28 include_once $fvm_var_inc_lib_mm . 'minify'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'Exceptions'. DIRECTORY_SEPARATOR .'IOException.php';
29 include_once($fvm_var_inc_lib_mm . 'path-converter' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'ConverterInterface.php');
30 include_once($fvm_var_inc_lib_mm . 'path-converter' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'Converter.php');
31
32 ################################################
33
34
35 # start buffering before template
36 function fvm_start_buffer() {
37 if(fvm_can_minify_css() || fvm_can_minify_js() || fvm_can_process_html()) {
38 ob_start('fvm_process_page', 0, PHP_OUTPUT_HANDLER_REMOVABLE);
39 }
40 }
41
42 # process html from fvm_end_buffer
43 function fvm_process_page($html) {
44
45 # get globals
46 global $fvm_settings, $fvm_cache_paths, $fvm_urls;
47
48 # get html into an object
49 # https://simplehtmldom.sourceforge.io/manual.htm
50 $html_object = str_get_html($html, false, true, 'UTF-8', false, PHP_EOL, ' ');
51
52 # return early if html is not an object, or overwrite html into an object for processing
53 if (!is_object($html_object)) {
54 return $html . '<!-- simplehtmldom failed to process the html -->';
55 } else {
56 $html = $html_object;
57 }
58
59 # get globals
60 global $fvm_settings, $fvm_urls;
61
62 # defaults
63 $tvers = get_option('fvm_last_cache_update', '0');
64 $htmlpreloads = array();
65 $htmlcssheader = array();
66 $htmljsheader = array();
67 $htmljsdefer = array();
68
69
70 # collect all link preload headers, skip amp
71 if(fvm_is_amp_page() !== true) {
72
73 # skip on web stories
74 if(count($html->find('script[src*=cdn.ampproject.org]')) > 0) {
75 return $html . '<!-- FVM does not support AMP -->';
76 }
77
78 # add other preloads
79 foreach($html->find('link[rel=preload]') as $tag) {
80 $htmlpreloads[] = $tag->outertext;
81 $tag->outertext = '';
82 }
83
84 }
85
86
87 # START CSS PROCESSING
88 if(fvm_can_minify_css()) {
89
90 # defaults
91 $fvm_styles = array();
92 $allcss = array();
93 $css_lowpriority_code = '';
94
95 # start log
96 $log = array();
97 $css_total = 0;
98
99 # start log
100 $log[]= 'PAGE - '. fvm_get_uripath(true) . PHP_EOL . '---' . PHP_EOL;
101
102
103 # exclude styles and link tags inside scripts, no scripts or html comments
104 $excl = array();
105 foreach($html->find('script link[rel=stylesheet], script style, noscript style, noscript link[rel=stylesheet], comment') as $element) {
106 $excl[] = $element->outertext;
107 }
108
109 # collect all styles, but filter out if excluded
110 foreach($html->find('link[rel=stylesheet], style') as $element) {
111 if(!in_array($element->outertext, $excl)) {
112 $allcss[] = $element;
113 }
114 }
115
116 # START CSS LOOP
117 foreach($allcss as $k=>$tag) {
118
119 # mediatypes
120 if(isset($tag->media)) {
121
122 # Normalize mediatypes
123 if($tag->media == 'screen' || $tag->media == 'screen, print' || strlen($tag->media) == 0) {
124 $tag->media = 'all';
125 }
126
127 # Remove print styles
128 if(isset($fvm_settings['css']['noprint']) && $fvm_settings['css']['noprint'] == true && $tag->media == 'print'){
129 $tag->outertext = '';
130 unset($allcss[$k]);
131 continue;
132 }
133
134 } else {
135 # must have
136 $tag->media = 'all';
137 }
138
139
140 # START CSS FILES
141 if($tag->tag == 'link' && isset($tag->href)) {
142
143 # filter url
144 $href = fvm_normalize_url($tag->href);
145
146 # Ignore css files
147 $ignore_css_merging = false;
148 if(isset($fvm_settings['css']['ignore']) && !empty($fvm_settings['css']['ignore'])) {
149 $arr = fvm_string_toarray($fvm_settings['css']['ignore']);
150 if(is_array($arr) && count($arr) > 0) {
151 foreach ($arr as $e) {
152 if(stripos($href, $e) !== false) {
153 unset($allcss[$k]);
154 continue 2;
155 }
156 }
157 }
158 }
159
160 # Remove CSS files
161 if(isset($fvm_settings['css']['remove']) && !empty($fvm_settings['css']['remove'])) {
162 $arr = fvm_string_toarray($fvm_settings['css']['remove']);
163 if(is_array($arr) && count($arr) > 0) {
164 foreach ($arr as $e) {
165 if(stripos($href, $e) !== false) {
166 $tag->outertext = '';
167 unset($allcss[$k]);
168 continue 2;
169 }
170 }
171 }
172 }
173
174 # css merging functionality
175 if(isset($fvm_settings['css']['combine']) && $fvm_settings['css']['combine'] == true) {
176
177 # download or fetch from transient, minified
178 $css = fvm_get_css_from_file($tag);
179
180 if($css !== false && is_array($css)) {
181
182 # error
183 if(isset($css['error'])) {
184 $tag->outertext = '/* Error on '.$href.' : '.$css['error'].' */'. PHP_EOL . $tag->outertext;
185 unset($allcss[$k]);
186 continue;
187 }
188
189 # extract fonts and icons
190 if(isset($fvm_settings['css']['fonts']) && $fvm_settings['css']['fonts'] == true) {
191 $extract_fonts_arr = fvm_extract_fonts($css['code']);
192 $css_lowpriority_code.= '/* '.$href.' */'. PHP_EOL . $extract_fonts_arr['fonts'];
193 $css_code = $extract_fonts_arr['code'];
194 } else {
195 $css_code = $css['code'];
196 }
197
198
199 # async from the list only
200 if(isset($fvm_settings['css']['async']) && $fvm_settings['css']['async'] == true) {
201 if(isset($fvm_settings['css']['async']) && !empty($fvm_settings['css']['async'])) {
202 $arr = fvm_string_toarray($fvm_settings['css']['async']);
203 if(is_array($arr) && count($arr) > 0) {
204 foreach ($arr as $aa) {
205 if(stripos($href, $aa) !== false) {
206
207 # save css for merging
208 $fvm_styles[$tag->media]['async'][] = $css_code;
209
210 # log
211 $size = strlen($css['code']);
212 $css_total = $css_total + $size;
213 $log[] = '[CSS Async: '.str_pad(fvm_format_filesize($size), 9,' ',STR_PAD_LEFT).']'."\t".str_replace($fvm_urls['wp_site_url'], '', $css['url']). PHP_EOL;
214
215 # finish early
216 $tag->outertext = '';
217 unset($allcss[$k]);
218 continue 2;
219
220 }
221 }
222 }
223 }
224 }
225
226 # else render block
227
228 # save css for merging as fallback
229 $fvm_styles[$tag->media]['block'][] = $css_code;
230
231 # log
232 $size = strlen($css['code']);
233 $css_total = $css_total + $size;
234 $log[] = '[CSS Block: '.str_pad(fvm_format_filesize($size), 9,' ',STR_PAD_LEFT).']'."\t".str_replace($fvm_urls['wp_site_url'], '', $css['url']). PHP_EOL;
235
236 # finish early
237 $tag->outertext = '';
238 unset($allcss[$k]);
239 continue;
240
241 }
242 }
243
244
245 # minify individually, if enabled
246 if(!isset($fvm_settings['css']['min_disable']) || (isset($fvm_settings['css']['min_disable'])&& $fvm_settings['css']['min_disable'] != true)) {
247
248 # download or fetch from transient, minified
249 $css = fvm_get_css_from_file($tag);
250
251 if($css !== false && is_array($css)) {
252
253 # error
254 if(isset($css['error'])) {
255 $tag->outertext = '/* Error on '.$href.' : '.$css['error'].' */'. PHP_EOL . $tag->outertext;
256 unset($allcss[$k]);
257 continue;
258 }
259
260 # extract fonts and icons
261 if(isset($fvm_settings['css']['fonts']) && $fvm_settings['css']['fonts'] == true) {
262 $extract_fonts_arr = fvm_extract_fonts($css['code']);
263 $css_lowpriority_code.= '/* '.$href.' */'. PHP_EOL . $extract_fonts_arr['fonts'];
264 $css_code = $extract_fonts_arr['code'];
265 } else {
266 $css_code = $css['code'];
267 }
268
269 # empty files
270 if(empty(trim($css_code))) {
271 $tag->outertext = '';
272 unset($allcss[$k]);
273 continue;
274 } else {
275 $css_code = '/* '.$href.' */'. PHP_EOL . $css_code;
276 }
277
278 # generate url
279 $ind_css_url = fvm_generate_min_url($href, $css['tkey'], 'css', $css_code);
280
281 # cdn
282 if(isset($fvm_settings['cdn']['cssok']) && $fvm_settings['cdn']['cssok'] == true) {
283 $ind_css_url = fvm_rewrite_cdn_url($ind_css_url);
284 }
285
286 # async from the list only
287 if(isset($fvm_settings['css']['async']) && !empty($fvm_settings['css']['async'])) {
288 $arr = fvm_string_toarray($fvm_settings['css']['async']);
289 if(is_array($arr) && count($arr) > 0) {
290 foreach ($arr as $aa) {
291 if(stripos($href, $aa) !== false) {
292
293 # async attributes
294 $tag->rel = 'preload';
295 $tag->as = 'style';
296 $tag->onload = "this.rel='stylesheet';this.onload=null";
297
298 # log
299 $size = strlen($css['code']);
300 $css_total = $css_total + $size;
301 $log[] = '[CSS Async: '.str_pad(fvm_format_filesize($size), 9,' ',STR_PAD_LEFT).']'."\t".str_replace($fvm_urls['wp_site_url'], '', $css['url']). PHP_EOL;
302
303 # finish early
304 $tag->href = $ind_css_url;
305 unset($allcss[$k]);
306 continue 2;
307
308 }
309 }
310 }
311 }
312
313 # force render blocking for other files
314
315 # http and html preload for render blocking css
316 if(!isset($fvm_settings['css']['nopreload']) || (isset($fvm_settings['css']['nopreload']) && $fvm_settings['css']['nopreload'] != true)) {
317 $htmlpreloads[] = '<link rel="preload" href="'.$ind_css_url.'" as="style" media="'.$tag->media.'" />';
318 }
319
320 # log
321 $size = strlen($css['code']);
322 $css_total = $css_total + $size;
323 $log[] = '[CSS Block: '.str_pad(fvm_format_filesize($size), 9,' ',STR_PAD_LEFT).']'."\t".str_replace($fvm_urls['wp_site_url'], '', $css['url']). PHP_EOL;
324
325 # finish early
326 $tag->href = $ind_css_url;
327 unset($allcss[$k]);
328
329 }
330 }
331 }
332 # END CSS FILES
333
334
335 # START STYLE TAGS
336 if($tag->tag == 'style' && !isset($tag->href)) {
337
338 # remove if empty
339 if(strlen(trim($tag->innertext)) == 0) {
340 $tag->outertext = '';
341 unset($allcss[$k]);
342 continue;
343 }
344
345 # default
346 $css = $tag->innertext;
347
348 # minify?
349 if(!isset($fvm_settings['css']['min_disable_styles']) || (isset($fvm_settings['css']['min_disable_styles'])&& $fvm_settings['css']['min_disable_styles'] != true)) {
350 $css = fvm_minify_css_string($css);
351 }
352
353 # handle import rules
354 $css = fvm_replace_css_imports($css);
355
356 # simplify font face
357 $arr = fvm_simplify_fontface($css);
358 if($arr !== false && is_array($arr)) {
359 $css = str_replace($arr['before'], $arr['after'], $css);
360 }
361
362 # extract fonts and icons
363 if(isset($fvm_settings['css']['fonts']) && $fvm_settings['css']['fonts'] == true) {
364 $extract_fonts_arr = fvm_extract_fonts($css);
365 $css_lowpriority_code.= $extract_fonts_arr['fonts'];
366 $css = $extract_fonts_arr['code'];
367 }
368
369 # add filtered css code
370 if(!empty($css)) {
371 $tag->innertext = $css;
372 unset($allcss[$k]);
373 continue;
374 }
375
376 }
377 # END STYLE TAGS
378
379 }
380 # END CSS LOOP
381
382 # START CSS LOG
383 if(count($log) > 1) {
384 $log[] = str_pad('-', 22, '-',STR_PAD_LEFT) . PHP_EOL;
385 $log[] = '[CSS Total: '.str_pad(fvm_format_filesize($css_total), 9,' ',STR_PAD_LEFT).']' . PHP_EOL;
386 $log[] = str_pad('-', 22, '-',STR_PAD_LEFT);
387 fvm_save_log(array('type'=>'min','msg'=>implode('', $log)));
388 }
389 # END CSS LOG
390
391
392 # START OPTIMIZED FONT DELIVERY
393 if(!empty($css_lowpriority_code)) {
394
395 # minify?
396 if(!isset($fvm_settings['css']['min_disable']) || (isset($fvm_settings['css']['min_disable'])&& $fvm_settings['css']['min_disable'] != true)) {
397 $css_lowpriority_code = fvm_minify_css_string($css_lowpriority_code);
398 }
399
400 # save transient, if not yet saved
401 $tkey = fvm_generate_hash_with_prefix(hash('sha256', $css_lowpriority_code), 'css');
402
403 # generate url
404 $css_fonts_url = fvm_generate_min_url('fonts', $tkey, 'css', $css_lowpriority_code);
405
406 # cdn
407 if(isset($fvm_settings['cdn']['cssok']) && $fvm_settings['cdn']['cssok'] == true) {
408 $css_fonts_url = fvm_rewrite_cdn_url($css_fonts_url);
409 }
410
411 # preload
412 $htmlcssheader[0] = '<link id="fvm-fonts" rel="stylesheet" href="'.$css_fonts_url.'" media="fonts" onload="if(fvmuag()){this.media=\'all\'}" />';
413
414 }
415 # END OPTIMIZED FONT DELIVERY
416
417 # START COMBINING CSS
418 if(is_array($fvm_styles) && count($fvm_styles) > 0) {
419
420 # process mediatypes
421 foreach ($fvm_styles as $mediatype=>$css_arr) {
422
423 # process types, block or async
424 foreach ($css_arr as $css_method=>$css_arr2) {
425
426 # merge and hash
427 $merged_css = implode(PHP_EOL, $css_arr2);
428 $tkey = fvm_generate_hash_with_prefix(hash('sha256', $merged_css), 'css');
429
430 # inline if small
431 if(strlen($merged_css) < 15000 && $css_method == 'block') {
432 $htmlcssheader[] = '<style type="text/css" media="'.$mediatype.'">'.$merged_css.'</style>';
433 continue;
434 }
435
436 # url, preload, add
437 $merged_css_url = fvm_generate_min_url('combined', $tkey, 'css', $merged_css);
438
439 # cdn
440 if(isset($fvm_settings['cdn']['cssok']) && $fvm_settings['cdn']['cssok'] == true) {
441 $merged_css_url = fvm_rewrite_cdn_url($merged_css_url);
442 }
443
444 # http, html preload + header
445 if($css_method == 'block') {
446
447 # add to header
448 $htmlcssheader[] = '<link rel="stylesheet" href="'.$merged_css_url.'" media="'.$mediatype.'" />';
449
450 # http and html preload for render blocking css
451 if(!isset($fvm_settings['css']['nopreload']) || (isset($fvm_settings['css']['nopreload']) && $fvm_settings['css']['nopreload'] != true)) {
452 $htmlpreloads[] = '<link rel="preload" href="'.$merged_css_url.'" as="style" media="'.$mediatype.'" />';
453 }
454
455 } else {
456
457 # async
458 $htmlcssheader[] = '<link rel="preload" as="style" href="'.$merged_css_url.'" media="'.$mediatype.'" onload="this.rel=\'stylesheet\'" />';
459
460 }
461
462 }
463 }
464 }
465 # END COMBINING CSS
466
467 }
468 # END CSS PROCESSING
469
470
471 # START JS PROCESSING
472 if(fvm_can_minify_js()) {
473
474 # defaults
475 $scripts_duplicate_check = array();
476 $fvm_scripts_header = array();
477 $fvm_scripts_defer = array();
478
479 # start log
480 $log = array();
481 $js_total = 0;
482
483 # start log
484 $log[]= 'PAGE - '. fvm_get_uripath(true) . PHP_EOL . '---' . PHP_EOL;
485
486 # get all scripts
487 $allscripts = array();
488 foreach($html->find('script') as $element) {
489 $allscripts[] = $element;
490 }
491
492 # START JS LOOP
493 if (is_array($allscripts) && count($allscripts) > 0) {
494 foreach($allscripts as $k=>$tag) {
495
496 # handle application/ld+json or application/json before anything else
497 if(isset($tag->type) && ($tag->type == 'application/ld+json' || $tag->type == 'application/json')) {
498
499 # minify
500 if(isset($fvm_settings['js']['js_enable_min_inline']) && $fvm_settings['js']['js_enable_min_inline'] == true) { $tag->innertext = fvm_minify_microdata($tag->innertext); }
501
502 # remove
503 unset($allscripts[$k]);
504 continue;
505 }
506
507 # skip unknown script types
508 if(isset($tag->type) && $tag->type != 'text/javascript') {
509 unset($allscripts[$k]);
510 continue;
511 }
512
513 # remove default script type
514 if(isset($tag->type) && $tag->type == 'text/javascript') { unset($tag->type); }
515
516 # START JS FILES
517 if(isset($tag->src)) {
518
519 # filter url
520 $href = fvm_normalize_url($tag->src);
521
522 # ignore js files
523 if(isset($fvm_settings['js']['ignore']) && !empty($fvm_settings['js']['ignore'])) {
524 $arr = fvm_string_toarray($fvm_settings['js']['ignore']);
525 if(is_array($arr) && count($arr) > 0) {
526 foreach ($arr as $a) {
527 if(stripos($tag->src, $a) !== false) {
528 unset($allscripts[$k]);
529 continue 2;
530 }
531 }
532 }
533 }
534
535 # remove js files
536 if(isset($fvm_settings['js']['remove']) && !empty($fvm_settings['js']['remove'])) {
537 $arr = fvm_string_toarray($fvm_settings['js']['remove']);
538 if(is_array($arr) && count($arr) > 0) {
539 foreach ($arr as $a) {
540 if(stripos($tag->src, $a) !== false) {
541 $tag->outertext = '';
542 unset($allscripts[$k]);
543 continue 2;
544 }
545 }
546 }
547 }
548
549
550 # JS files to delay until user interaction (unmergeable)
551 if(isset($fvm_settings['js']['thirdparty']) && !empty($fvm_settings['js']['thirdparty'])) {
552 $arr = fvm_string_toarray($fvm_settings['js']['thirdparty']);
553 if(is_array($arr) && count($arr) > 0) {
554 foreach ($arr as $ac) {
555 if(stripos($tag->src, $ac) !== false) {
556
557 # unique identifier
558 $uid = fvm_generate_hash_with_prefix(hash('sha256', $tag->outertext), 'js');
559
560 # remove exact duplicates, or replace transformed tag
561 if(isset($scripts_duplicate_check[$uid])) {
562 $tag->outertext = '';
563 } else {
564 $tag->type = 'fvm-script-delay';
565 $scripts_duplicate_check[$uid] = $uid;
566 }
567
568 # mark as processed, unset and break inner loop
569 unset($allscripts[$k]);
570 continue 2;
571
572 }
573 }
574 }
575 }
576
577
578 # START MERGING JS
579 if(isset($fvm_settings['js']['combine']) && $fvm_settings['js']['combine'] == true) {
580
581 # force render blocking
582 if(isset($fvm_settings['js']['merge_header']) && !empty($fvm_settings['js']['merge_header'])) {
583 $arr = fvm_string_toarray($fvm_settings['js']['merge_header']);
584 if(is_array($arr) && count($arr) > 0) {
585 foreach ($arr as $aa) {
586 if(stripos($tag->src, $aa) !== false) {
587
588 # download or fetch from transient, minified
589 $js = fvm_get_js_from_file($tag);
590 if($js !== false && is_array($js)) {
591
592 # error
593 if(isset($js['error'])) {
594 $tag->outertext = '/* Error on '.$href.' : '.$js['error'].' */'. PHP_EOL . $tag->outertext;
595 unset($allscripts[$k]);
596 continue 2;
597 }
598
599 # save js for merging
600 $fvm_scripts_header[] = $js['code'];
601
602 # log
603 $size = strlen($js['code']);
604 $js_total = $js_total + $size;
605 $log[] = '[JS Block: '.str_pad(fvm_format_filesize($size), 9,' ',STR_PAD_LEFT).']'."\t".str_replace($fvm_urls['wp_site_url'], '', $js['url']). PHP_EOL;
606
607 # remove from html
608 $tag->outertext = '';
609 unset($allscripts[$k]);
610 continue 2;
611
612 }
613 }
614 }
615 }
616 }
617
618 # force defer
619 if(isset($fvm_settings['js']['merge_defer']) && !empty($fvm_settings['js']['merge_defer'])) {
620 $arr = fvm_string_toarray($fvm_settings['js']['merge_defer']);
621 if(is_array($arr) && count($arr) > 0) {
622 foreach ($arr as $aa) {
623 if(stripos($tag->src, $aa) !== false) {
624
625 # download or fetch from transient, minified
626 $js = fvm_get_js_from_file($tag);
627 if($js !== false && is_array($js)) {
628
629 # error
630 if(isset($js['error'])) {
631 $tag->outertext = '/* Error on '.$href.' : '.$js['error'].' */'. PHP_EOL . $tag->outertext;
632 unset($allscripts[$k]);
633 continue 2;
634 }
635
636 # save js for merging
637 $fvm_scripts_defer[] = $js['code'];
638
639 # log
640 $size = strlen($js['code']);
641 $js_total = $js_total + $size;
642 $log[] = '[JS Defer: '.str_pad(fvm_format_filesize($size), 9,' ',STR_PAD_LEFT).']'."\t".str_replace($fvm_urls['wp_site_url'], '', $js['url']). PHP_EOL;
643
644 # remove from html
645 $tag->outertext = '';
646 unset($allscripts[$k]);
647 continue 2;
648
649 }
650 }
651 }
652 }
653 }
654
655 # jquery needs to load earlier, if not being merged (while merging is active)
656 if(stripos($tag->src, '/jquery.js') !== false || stripos($tag->src, '/jquery.min.js') !== false || stripos($tag->src, '/jquery-migrate') !== false) {
657
658 # http and html preload for render blocking js
659 if(!isset($fvm_settings['js']['nopreload']) || (isset($fvm_settings['js']['nopreload']) && $fvm_settings['js']['nopreload'] != true)) {
660 $htmlpreloads[] = '<link rel="preload" href="'.$href.'" as="script" />';
661 }
662
663 # header
664 if(stripos($tag->src, '/jquery-migrate') !== false) {
665 $htmljsheader[1] = "<script data-cfasync='false' src='".$href."'></script>"; # jquery migrate
666 } else {
667 $htmljsheader[0] = "<script data-cfasync='false' src='".$href."'></script>"; # jquery
668 }
669
670 # content
671 $tag->outertext = '';
672 unset($allscripts[$k]);
673 continue;
674 }
675
676 }
677 # END MERGING JS
678
679
680 # START INDIVIDUAL JS MINIFICATION
681 # minify individually, if enabled
682 if(!isset($fvm_settings['js']['min_disable']) || (isset($fvm_settings['js']['min_disable'])&& $fvm_settings['js']['min_disable'] != true)) {
683
684 # skip third party scripts, unless allowed
685 $allowed = array($fvm_urls['wp_domain'], '/ajax.aspnetcdn.com/ajax/', '/ajax.googleapis.com/ajax/libs/', '/cdnjs.cloudflare.com/ajax/libs/');
686 if(str_replace($allowed, '', $href) == $href) {
687 unset($allscripts[$k]);
688 continue;
689 }
690
691 # force render blocking
692 if(isset($fvm_settings['js']['merge_header']) && !empty($fvm_settings['js']['merge_header'])) {
693 $arr = fvm_string_toarray($fvm_settings['js']['merge_header']);
694 if(is_array($arr) && count($arr) > 0) {
695 foreach ($arr as $aa) {
696 if(stripos($tag->src, $aa) !== false) {
697
698 # download or fetch from transient, minified
699 $js = fvm_get_js_from_file($tag);
700 if($js !== false && is_array($js)) {
701
702 # error
703 if(isset($js['error'])) {
704 $tag->outertext = '/* Error on '.$href.' : '.$js['error'].' */'. PHP_EOL . $tag->outertext;
705 unset($allscripts[$k]);
706 continue 2;
707 }
708
709 # generate url
710 $ind_js_url = fvm_generate_min_url($tag->src, $js['tkey'], 'js', $js['code']);
711
712 # cdn
713 if(isset($fvm_settings['cdn']['jsok']) && $fvm_settings['cdn']['jsok'] == true) {
714 $ind_js_url = fvm_rewrite_cdn_url($ind_js_url);
715 }
716
717 # render block
718 if(isset($tag->async)) { unset($tag->async); }
719 if(isset($tag->defer)) { unset($tag->defer); }
720
721 # http and html preload for render blocking scripts
722 if(!isset($fvm_settings['js']['nopreload']) || (isset($fvm_settings['js']['nopreload']) && $fvm_settings['js']['nopreload'] != true)) {
723 $htmlpreloads[] = '<link rel="preload" href="'.$ind_js_url.'" as="script" />';
724 }
725
726 # log
727 $size = strlen($js['code']);
728 $js_total = $js_total + $size;
729 $log[] = '[JS Block: '.str_pad(fvm_format_filesize($size), 9,' ',STR_PAD_LEFT).']'."\t".str_replace($fvm_urls['wp_site_url'], '', $js['url']). PHP_EOL;
730
731 # finish early
732 $tag->src = $ind_js_url;
733 unset($allscripts[$k]);
734 continue 2;
735
736 }
737 }
738 }
739 }
740 }
741
742 # force defer
743 if(isset($fvm_settings['js']['merge_defer']) && !empty($fvm_settings['js']['merge_defer'])) {
744 $arr = fvm_string_toarray($fvm_settings['js']['merge_defer']);
745 if(is_array($arr) && count($arr) > 0) {
746 foreach ($arr as $aa) {
747 if(stripos($tag->src, $aa) !== false) {
748
749 # download or fetch from transient, minified
750 $js = fvm_get_js_from_file($tag);
751 if($js !== false && is_array($js)) {
752
753 # error
754 if(isset($js['error'])) {
755 $tag->outertext = '/* Error on '.$href.' : '.$js['error'].' */'. PHP_EOL . $tag->outertext;
756 unset($allscripts[$k]);
757 continue 2;
758 }
759
760 # generate url
761 $ind_js_url = fvm_generate_min_url($tag->src, $js['tkey'], 'js', $js['code']);
762
763 # cdn
764 if(isset($fvm_settings['cdn']['jsok']) && $fvm_settings['cdn']['jsok'] == true) {
765 $ind_js_url = fvm_rewrite_cdn_url($ind_js_url);
766 }
767
768 # add defer
769 if(!isset($tag->defer)) { $tag->defer = 'defer'; }
770
771 # log
772 $size = strlen($js['code']);
773 $js_total = $js_total + $size;
774 $log[] = '[JS Defer: '.str_pad(fvm_format_filesize($size), 9,' ',STR_PAD_LEFT).']'."\t".str_replace($fvm_urls['wp_site_url'], '', $js['url']). PHP_EOL;
775
776 # finish early
777 $tag->src = $ind_js_url;
778 unset($allscripts[$k]);
779 continue 2;
780
781 }
782 }
783 }
784 }
785 }
786
787 # remove unprocessed scripts from loop
788 unset($allscripts[$k]);
789 continue;
790 }
791 # END INDIVIDUAL JS MINIFICATION
792
793 }
794 # END JS FILES
795
796
797 # START INLINED SCRIPTS
798 if(!isset($tag->src)) {
799
800 # remove if empty
801 if(strlen(trim($tag->innertext)) == 0) {
802 $tag->outertext = '';
803 unset($allcss[$k]);
804 continue;
805 }
806
807 # default
808 $js = ''; $js = $tag->innertext;
809
810 # minify?
811 if(!isset($fvm_settings['js']['min_disable_inline']) || (isset($fvm_settings['js']['min_disable_inline'])&& $fvm_settings['js']['min_disable_inline'] != true)) {
812 $js = PHP_EOL . fvm_maybe_minify_js($js, null, true) . PHP_EOL;
813 }
814
815 # delay inline scripts until user interaction (unmergeable)
816 if(isset($fvm_settings['js']['thirdparty']) && !empty($fvm_settings['js']['thirdparty'])) {
817 $arr = fvm_string_toarray($fvm_settings['js']['thirdparty']);
818 if(is_array($arr) && count($arr) > 0) {
819 foreach ($arr as $b) {
820 if(stripos($js, $b) !== false || stripos($js, $b) !== false) {
821
822 # delay
823 $tag->type = 'fvm-script-delay';
824
825 # minified
826 if(!empty($js)) {
827 $tag->innertext = $js;
828 }
829
830 # unset
831 unset($allscripts[$k]);
832 continue 2;
833 }
834 }
835 }
836 }
837
838 # defer inline scripts
839 if(isset($fvm_settings['js']['defer_dependencies']) && !empty($fvm_settings['js']['defer_dependencies'])) {
840 $arr = fvm_string_toarray($fvm_settings['js']['defer_dependencies']);
841 if(is_array($arr) && count($arr) > 0) {
842 foreach ($arr as $b) {
843 if((stripos($js, $b) !== false || stripos($js, $b) !== false) && !isset($tag->src)) {
844
845 # defer
846 $tag->type = 'module';
847
848 # html comments are not supported inside module scripts
849 if(!empty($js)) {
850 $tag->innertext = preg_replace('/<!--(.|\s)*?-->/ui', '', $js);
851 }
852
853 # unset
854 unset($allscripts[$k]);
855 continue 2;
856 }
857 }
858 }
859 }
860 }
861 # END INLINED SCRIPTS
862
863 }
864 }
865 # END JS LOOP
866
867
868 # START JS LOG
869 if(count($log) > 1) {
870 $log[] = str_pad('-', 21, '-',STR_PAD_LEFT) . PHP_EOL;
871 $log[] = '[JS Total: '.str_pad(fvm_format_filesize($js_total), 9,' ',STR_PAD_LEFT).']' . PHP_EOL;
872 $log[] = str_pad('-', 21, '-',STR_PAD_LEFT);
873 fvm_save_log(array('type'=>'min','msg'=>implode('', $log)));
874 }
875 # END JS LOG
876
877 # START COMBINING JS
878
879 # header scripts
880 if(is_array($fvm_scripts_header) && count($fvm_scripts_header) > 0) {
881
882 # merge code, hash
883 $merged_js = implode(PHP_EOL, $fvm_scripts_header);
884 $tkey = fvm_generate_hash_with_prefix(hash('sha256', $merged_js), 'js');
885
886 # generate url
887 $merged_js_url = fvm_generate_min_url('combined', $tkey, 'js', $merged_js);
888
889 # cdn
890 if(isset($fvm_settings['cdn']['jsok']) && $fvm_settings['cdn']['jsok'] == true) {
891 $merged_js_url = fvm_rewrite_cdn_url($merged_js_url);
892 }
893
894 # http and html preload for render blocking scripts
895 if(!isset($fvm_settings['js']['nopreload']) || (isset($fvm_settings['js']['nopreload']) && $fvm_settings['js']['nopreload'] != true)) {
896 $htmlpreloads[] = '<link rel="preload" href="'.$merged_js_url.'" as="script" />';
897 }
898
899 # add to header
900 $htmljsheader[] = "<script data-cfasync='false' src='".$merged_js_url."'></script>";
901
902 }
903
904 # deferred scripts
905 if(is_array($fvm_scripts_defer) && count($fvm_scripts_defer) > 0) {
906
907 # merge code, hash
908 $merged_js = implode(PHP_EOL, $fvm_scripts_defer);
909 $tkey = fvm_generate_hash_with_prefix(hash('sha256', $merged_js), 'js');
910
911 # generate url
912 $merged_js_url = fvm_generate_min_url('combined', $tkey, 'js', $merged_js);
913
914 # cdn
915 if(isset($fvm_settings['cdn']['jsok']) && $fvm_settings['cdn']['jsok'] == true) {
916 $merged_js_url = fvm_rewrite_cdn_url($merged_js_url);
917 }
918
919 # header, no preload for deferred files
920 $htmljsheader[] = "<script defer='defer' src='".$merged_js_url."'></script>";
921
922 }
923
924 }
925 # END JS PROCESSING
926
927
928
929 # process HTML minification, if not disabled ###############################
930 if(fvm_can_process_html()) {
931
932 # Remove HTML comments and IE conditionals
933 if(isset($fvm_settings['html']['nocomments']) && $fvm_settings['html']['nocomments'] == true) {
934 foreach($html->find('comment') as $element) {
935 $element->outertext = '';
936 }
937 }
938
939 # Remove generator tags
940 if(isset($fvm_settings['html']['cleanup_header']) && $fvm_settings['html']['cleanup_header'] == true) {
941
942 # remove
943 foreach($html->find('head meta[name=generator], head link[rel=shortlink], head link[rel=dns-prefetch], head link[rel=preconnect], head link[rel=prefetch], head link[rel=prerender], head meta[name*=msapplication], head link[rel=apple-touch-icon], head link[rel=EditURI], head link[rel=preconnect], head link[rel=wlwmanifest], head link[rel=https://api.w.org/], head link[href*=/wp-json/], head link[rel=pingback], head link[type=application/json+oembed], head link[type=text/xml+oembed]') as $element) {
944 $element->outertext = '';
945 }
946
947 # allow only the last link[rel=icon]
948 $ic = array(); $ic = $html->find('head link[rel=icon]');
949 $i = 1; $len = count($ic);
950 if($len > 1) {
951 foreach($ic as $element) {
952 if ($i != $len) { $element->outertext = ''; } $i++; # delete except if last
953 }
954 }
955 }
956
957 }
958
959 # build extra head and footer ###############################
960
961 # header and footer markers
962 $hm = '<!-- h_preheader --><!-- h_header_function -->';
963 $hm_late = '<!-- h_cssheader --><!-- h_jsheader -->';
964 $fm = '<!-- h_footer_fvm_scripts -->';
965
966 # add our function to head
967 if(fvm_can_minify_css() || fvm_can_minify_js()) {
968 $hm = fvm_add_header_function($hm);
969 }
970
971 # remove charset meta tag and collect it to first position
972 if(!is_null($html->find('meta[charset]', 0))) {
973 $hm = str_replace('<!-- h_preheader -->', $html->find('meta[charset]', 0)->outertext.'<!-- h_preheader -->', $hm);
974 foreach($html->find('meta[charset]') as $element) { $element->outertext = ''; }
975 }
976
977 # remove other meta tag and collect them between preload and css/js files
978 foreach($html->find('head meta, head title, head link[rel=canonical], head link[rel=alternate], head link[rel=pingback], head script[type=application/ld+json]') as $element) {
979 $hm_late = str_replace('<!-- h_cssheader -->', $element->outertext.'<!-- h_cssheader -->', $hm_late);
980 $element->outertext = '';
981 }
982
983 # preload headers, by importance
984 if(is_array($htmlpreloads)) {
985
986 # deduplicate
987 $htmlpreloads = array_unique($htmlpreloads);
988
989 # get values
990 $pre_html = array_values($htmlpreloads);
991
992 # add preload html header
993 if(count($pre_html) > 0) {
994 $hm = str_replace('<!-- h_preheader -->', implode(PHP_EOL, $pre_html).'<!-- h_preheader -->', $hm);
995 }
996
997 }
998
999 # add stylesheets
1000 if(isset($htmlcssheader)) {
1001 if(is_array($htmlcssheader) && count($htmlcssheader) > 0) {
1002 ksort($htmlcssheader); # priority
1003 $hm_late = str_replace('<!-- h_cssheader -->', implode(PHP_EOL, $htmlcssheader).'<!-- h_cssheader -->', $hm_late);
1004 }
1005 }
1006
1007 # add header scripts
1008 if(isset($htmljsheader)) {
1009 if(is_array($htmljsheader) && count($htmljsheader) > 0) {
1010 ksort($htmljsheader); # priority
1011 $hm_late = str_replace('<!-- h_jsheader -->', implode(PHP_EOL, $htmljsheader).'<!-- h_jsheader -->', $hm_late);
1012 }
1013 }
1014
1015 # add defer scripts
1016 if(isset($htmljscodedefer)) {
1017 if(is_array($htmljscodedefer) && count($htmljscodedefer) > 0) {
1018 ksort($htmljscodedefer); # priority
1019 $hm_late = str_replace('<!-- h_jsheader -->', implode(PHP_EOL, $htmljscodedefer), $hm_late);
1020 }
1021 }
1022
1023 # add fvm_footer scripts, if enabled
1024 if(fvm_can_minify_js()) {
1025 $fm = fvm_add_delay_scripts_logic($fm);
1026 }
1027
1028 # cleanup leftover markers
1029 $hm = str_replace(array('<!-- h_preheader -->', '<!-- h_header_function -->'), '', $hm);
1030 $hm_late = str_replace(array('<!-- h_cssheader -->', '<!-- h_jsheader -->'), '', $hm_late);
1031 $fm = str_replace('<!-- h_footer_fvm_scripts -->', '', $fm);
1032
1033 # append header and footer
1034 if(!is_null($html->find('head', 0)) && !is_null($html->find('body', -1))) {
1035 if(!is_null($html->find('head', 0)->first_child()) && !is_null($html->find('body', -1)->last_child())) {
1036 $html->find('head', 0)->first_child()->outertext = $hm . $html->find('head', 0)->first_child()->outertext . $hm_late;
1037 $html->find('body', -1)->last_child()->outertext = $html->find('body', -1)->last_child ()->outertext . $fm;
1038 }
1039 }
1040
1041
1042 # convert html object to string, save all objects to string
1043 $html = trim($html->save());
1044
1045 # process cdn optimization
1046 if(fvm_can_process_cdn()) {
1047 $html = fvm_process_cdn($html);
1048 }
1049
1050 # minify remaining HTML at the end, if enabled
1051 if(fvm_can_process_html()) {
1052 if(!isset($fvm_settings['html']['min_disable']) || (isset($fvm_settings['html']['min_disable']) && $fvm_settings['html']['min_disable'] != true)) {
1053 $html = fvm_raisermin_html($html);
1054 }
1055 }
1056
1057 # filter final html if needed
1058 if(function_exists('fvm_filter_final_html')) {
1059 $html = fvm_filter_final_html($html);
1060 }
1061
1062 # return html
1063 return $html;
1064
1065 }
1066
1 <?php
2
3 # Exit if accessed directly
4 if (!defined('ABSPATH')){ exit(); }
5
6 ### Get General Information
7 function fvm_get_generalinfo() {
8
9 # check if user has admin rights
10 if(!current_user_can('manage_options')) {
11 echo __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
12 }
13
14 echo'+++'. PHP_EOL;
15 echo'SERVER INFO:'. PHP_EOL;
16 echo'OS: '. PHP_OS . PHP_EOL;
17 echo'Server: '. $_SERVER["SERVER_SOFTWARE"] . PHP_EOL;
18 echo'PHP: '. PHP_VERSION . PHP_EOL;
19 echo'MySQL: '. fvm_get_mysql_version() . PHP_EOL;
20 echo'CPU Cores: '. fvm_get_servercpu() . PHP_EOL;
21 echo'Server Load: '. fvm_get_serverload() . PHP_EOL;
22
23 echo'---'. PHP_EOL;
24 echo'SITE INFO:'. PHP_EOL;
25 echo'Site Path: '. ABSPATH . PHP_EOL;
26 echo'Hostname: '. $_SERVER['SERVER_NAME'] . PHP_EOL;
27 echo'DB Data Size: '. fvm_format_php_size(fvm_get_mysql_data_usage()) . PHP_EOL;
28 echo'DB Index Size: '. fvm_format_php_size(fvm_get_mysql_index_usage()) . PHP_EOL;
29
30 echo'---'. PHP_EOL;
31 echo'LIMITS:'. PHP_EOL;
32 echo'PHP Max Exec Time: '. fvm_get_php_max_execution(). PHP_EOL;
33 echo'PHP Memory Limit: '. fvm_format_php_size(fvm_get_php_memory_limit()) . PHP_EOL;
34 echo'PHP Max Upload Size: '. fvm_format_php_size(fvm_get_php_upload_max()) . PHP_EOL;
35 echo'PHP Max Post Size: '. fvm_format_php_size(fvm_get_php_post_max()) . PHP_EOL;
36 echo'MySQL Max Packet Size: '. fvm_format_php_size(fvm_get_mysql_max_allowed_packet()) . PHP_EOL;
37 echo'MySQL Max Connections: '. fvm_get_mysql_max_allowed_connections() . PHP_EOL;
38 echo'+++';
39 }
40
41
42 ### Convert PHP Size Format to an int, then readable format
43 function fvm_format_php_size($size) {
44 if (!is_numeric($size)) {
45 if (strpos($size, 'M') !== false) {
46 $size = intval($size)*1024*1024;
47 } elseif (strpos($size, 'K') !== false) {
48 $size = intval($size)*1024;
49 } elseif (strpos($size, 'G') !== false) {
50 $size = intval($size)*1024*1024*1024;
51 }
52 }
53
54 $size = is_numeric($size) ? fvm_format_filesize($size, 0) : $size;
55 return str_pad($size, 8, " ", STR_PAD_LEFT);
56 }
57
58 ### Function: Get PHP Max Upload Size
59 if(!function_exists('fvm_get_php_upload_max')) {
60 function fvm_get_php_upload_max() {
61
62 # check if user has admin rights
63 if(!current_user_can('manage_options')) {
64 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
65 }
66
67 if(ini_get('upload_max_filesize')) {
68 $upload_max = ini_get('upload_max_filesize');
69 } else {
70 $upload_max = strval('N/A');
71 }
72 return $upload_max;
73 }
74 }
75
76
77 ### Function: Get PHP Max Post Size
78 if(!function_exists('fvm_get_php_post_max')) {
79 function fvm_get_php_post_max() {
80
81 # check if user has admin rights
82 if(!current_user_can('manage_options')) {
83 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
84 }
85
86 if(ini_get('post_max_size')) {
87 $post_max = ini_get('post_max_size');
88 } else {
89 $post_max = strval('N/A');
90 }
91 return $post_max;
92 }
93 }
94
95
96 ### Function: PHP Maximum Execution Time
97 if(!function_exists('fvm_get_php_max_execution')) {
98 function fvm_get_php_max_execution() {
99
100 # check if user has admin rights
101 if(!current_user_can('manage_options')) {
102 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
103 }
104
105 if(ini_get('max_execution_time')) {
106 $max_execute = intval(ini_get('max_execution_time'));
107 } else {
108 $max_execute = strval('N/A');
109 }
110
111 return str_pad($max_execute, 5, " ", STR_PAD_LEFT);
112 }
113 }
114
115
116 ### Function: PHP Memory Limit
117 if(!function_exists('fvm_get_php_memory_limit')) {
118 function fvm_get_php_memory_limit() {
119
120 # check if user has admin rights
121 if(!current_user_can('manage_options')) {
122 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
123 }
124
125 if(ini_get('memory_limit')) {
126 $memory_limit = ini_get('memory_limit');
127 } else {
128 $memory_limit = strval('N/A');
129 }
130 return $memory_limit;
131 }
132 }
133
134
135 ### Function: Get MYSQL Version
136 if(!function_exists('fvm_get_mysql_version')) {
137 function fvm_get_mysql_version() {
138
139 # check if user has admin rights
140 if(!current_user_can('manage_options')) {
141 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
142 }
143
144 global $wpdb;
145 return $wpdb->get_var("SELECT VERSION() AS version");
146 }
147 }
148
149
150 ### Function: Get MYSQL Data Usage
151 if(!function_exists('fvm_get_mysql_data_usage')) {
152 function fvm_get_mysql_data_usage() {
153
154 # check if user has admin rights
155 if(!current_user_can('manage_options')) {
156 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
157 }
158
159 global $wpdb;
160 $data_usage = 0;
161 $tablesstatus = $wpdb->get_results("SHOW TABLE STATUS");
162 foreach($tablesstatus as $tablestatus) {
163 if(is_numeric($tablestatus->Data_length)) { $data_usage += $tablestatus->Data_length; } else { $data_usage += 0; }
164 }
165 if (!$data_usage) {
166 $data_usage = strval('N/A');
167 }
168 return $data_usage;
169 }
170 }
171
172
173 ### Function: Get MYSQL Index Usage
174 if(!function_exists('fvm_get_mysql_index_usage')) {
175 function fvm_get_mysql_index_usage() {
176
177 # check if user has admin rights
178 if(!current_user_can('manage_options')) {
179 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
180 }
181
182 global $wpdb;
183 $index_usage = 0;
184 $tablesstatus = $wpdb->get_results("SHOW TABLE STATUS");
185 foreach($tablesstatus as $tablestatus) {
186 if(is_numeric($tablestatus->Index_length)) { $index_usage += $tablestatus->Index_length; } else { $index_usage += 0; }
187 }
188 if (!$index_usage){
189 $index_usage = strval('N/A');
190 }
191 return $index_usage;
192 }
193 }
194
195
196 ### Function: Get MYSQL Max Allowed Packet
197 if(!function_exists('fvm_get_mysql_max_allowed_packet')) {
198 function fvm_get_mysql_max_allowed_packet() {
199
200 # check if user has admin rights
201 if(!current_user_can('manage_options')) {
202 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
203 }
204
205 global $wpdb;
206 $packet_max_query = $wpdb->get_row("SHOW VARIABLES LIKE 'max_allowed_packet'");
207 $packet_max = $packet_max_query->Value;
208 if(!$packet_max) {
209 $packet_max = strval('N/A');
210 }
211 return $packet_max;
212 }
213 }
214
215
216 ### Function:Get MYSQL Max Allowed Connections
217 if(!function_exists('fvm_get_mysql_max_allowed_connections')) {
218 function fvm_get_mysql_max_allowed_connections() {
219
220 # check if user has admin rights
221 if(!current_user_can('manage_options')) {
222 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
223 }
224
225 global $wpdb;
226 $connection_max_query = $wpdb->get_row("SHOW VARIABLES LIKE 'max_connections'");
227 $connection_max = $connection_max_query->Value;
228 if(!$connection_max) {
229 $connection_max = strval('N/A');
230 }
231
232 return str_pad($connection_max, 5, " ", STR_PAD_LEFT);
233 }
234 }
235
236
237 ### Function: Get The Server Load
238 if(!function_exists('fvm_get_serverload')) {
239 function fvm_get_serverload() {
240
241 # check if user has admin rights
242 if(!current_user_can('manage_options')) {
243 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
244 }
245
246 $server_load = 0;
247 $numCpus = 'N/A';
248 if(PHP_OS != 'WINNT' && PHP_OS != 'WIN32') {
249 clearstatcache();
250 if(@file_exists('/proc/loadavg') ) {
251 if ($fh = @fopen( '/proc/loadavg', 'r' )) {
252 $data = @fread( $fh, 6 );
253 @fclose( $fh );
254 $load_avg = explode( " ", $data );
255 $server_load = trim($load_avg[0]);
256 }
257 } else if ('WIN' == strtoupper(substr(PHP_OS, 0, 3)) && function_exists('popen') && fvm_function_available('popen')) {
258 $process = @popen('wmic cpu get NumberOfCores', 'rb');
259 if (false !== $process && null !== $process) {
260 fgets($process);
261 $numCpus = intval(fgets($process));
262 pclose($process);
263 }
264 } else if (function_exists('system') && fvm_function_available('system')){
265 $data = @system('uptime');
266 preg_match('/(.*):{1}(.*)/', $data, $matches);
267 if(isset($matches[2])) {
268 $load_arr = explode(',', $matches[2]);
269 $server_load = trim($load_arr[0]);
270 } else {
271 $server_load = strval('N/A');
272 }
273 } else {
274 $server_load = strval('N/A');
275 }
276 }
277 if(empty($server_load)) {
278 $server_load = strval('N/A');
279 }
280 return $server_load;
281 }
282 }
283
284
285 ### Function: Get The Server CPU's
286 if(!function_exists('fvm_get_servercpu')) {
287 function fvm_get_servercpu() {
288
289 # check if user has admin rights
290 if(!current_user_can('manage_options')) {
291 return __( 'You are not allowed to execute this function!', 'fast-velocity-minify' );
292 }
293
294 $numCpus = 0;
295 if(PHP_OS != 'WINNT' && PHP_OS != 'WIN32') {
296 clearstatcache();
297 if (@is_file('/proc/cpuinfo')) {
298 $cpuinfo = file_get_contents('/proc/cpuinfo');
299 preg_match_all('/^processor/m', $cpuinfo, $matches);
300 $numCpus = count($matches[0]);
301 } else if (function_exists('popen') && fvm_function_available('popen')) {
302 $process = @popen('sysctl -a', 'rb');
303 if (false !== $process && null !== $process) {
304 $output = stream_get_contents($process);
305 preg_match('/hw.ncpu: (\d+)/', $output, $matches);
306 if ($matches) { $numCpus = intval($matches[1][0]); }
307 pclose($process);
308 }
309 } else {
310 $numCpus = strval('N/A');
311 }
312 }
313 if(empty($numCpus)) {
314 $numCpus = strval('N/A');
315 }
316 return $numCpus;
317 }
318 }
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2
3 # Exit if accessed directly
4 if (!defined('ABSPATH')){ exit(); }
5
6 # update routines for new fields and replacements
7 function fvm_update_changes() {
8
9 # current version
10 global $fvm_var_plugin_version;
11
12 # Version 3.0 routines start
13
14 # delete old FVM files
15 global $fvm_var_dir_path, $fvm_var_inc_lib, $fvm_var_inc_dir;
16
17 # prevent deleting if the paths are empty
18 # can happen when a user runs wp-cli on a child directory
19 if(isset($fvm_var_dir_path) && isset($fvm_var_inc_lib) && isset($fvm_var_inc_dir) && !empty($fvm_var_dir_path) && !empty($fvm_var_inc_lib) && !empty($fvm_var_inc_dir)) {
20
21 # must be inside a fast-velocity-minify directory
22 if (stripos($fvm_var_dir_path, 'fast-velocity-minify') !== false) {
23
24 # delete
25 if(file_exists($fvm_var_inc_dir.'functions-cache.php')) { @unlink($fvm_var_inc_dir.'functions-cache.php'); }
26 if(file_exists($fvm_var_inc_dir.'functions-cli.php')) { @unlink($fvm_var_inc_dir.'functions-cli.php'); }
27 if(file_exists($fvm_var_inc_dir.'functions-serverinfo.php')) { @unlink($fvm_var_inc_dir.'functions-serverinfo.php'); }
28 if(file_exists($fvm_var_inc_dir.'functions-upgrade.php')) { @unlink($fvm_var_inc_dir.'functions-upgrade.php'); }
29 if(file_exists($fvm_var_inc_dir.'functions.php')) { @unlink($fvm_var_inc_dir.'functions.php'); }
30 if(file_exists($fvm_var_dir_path.'fvm.css')) { @unlink($fvm_var_dir_path.'fvm.css'); }
31 if(file_exists($fvm_var_dir_path.'fvm.js')) { @unlink($fvm_var_dir_path.'fvm.js'); }
32 if(file_exists($fvm_var_inc_lib.'mrclay' . DIRECTORY_SEPARATOR . 'HTML.php')) {
33 @unlink($fvm_var_inc_lib.'mrclay' . DIRECTORY_SEPARATOR . 'HTML.php');
34 @unlink($fvm_var_inc_lib.'mrclay' . DIRECTORY_SEPARATOR . 'index.html');
35 @rmdir($fvm_var_inc_lib.'mrclay');
36 }
37
38 }
39 }
40
41 # Version 3.2 routines start
42 if (get_option("fastvelocity_plugin_version") !== false) {
43 if (version_compare($fvm_var_plugin_version, '3.2.0', '>=') && get_option("fastvelocity_min_ignore") !== false) {
44
45 # cleanup
46 delete_option('fastvelocity_min_change_cache_path');
47 delete_option('fastvelocity_min_change_cache_base_url');
48 delete_option('fastvelocity_min_fvm_cdn_url');
49 delete_option('fastvelocity_plugin_version');
50 delete_option('fvm-last-cache-update');
51 delete_option('fastvelocity_min_ignore');
52 delete_option('fastvelocity_min_blacklist');
53 delete_option('fastvelocity_min_ignorelist');
54 delete_option('fastvelocity_min_excludecsslist');
55 delete_option('fastvelocity_min_excludejslist');
56 delete_option('fastvelocity_min_enable_purgemenu');
57 delete_option('fastvelocity_min_default_protocol');
58 delete_option('fastvelocity_min_disable_js_merge');
59 delete_option('fastvelocity_min_disable_css_merge');
60 delete_option('fastvelocity_min_disable_js_minification');
61 delete_option('fastvelocity_min_disable_css_minification');
62 delete_option('fastvelocity_min_remove_print_mediatypes');
63 delete_option('fastvelocity_min_skip_html_minification');
64 delete_option('fastvelocity_min_strip_htmlcomments');
65 delete_option('fastvelocity_min_skip_cssorder');
66 delete_option('fastvelocity_min_skip_google_fonts');
67 delete_option('fastvelocity_min_skip_emoji_removal');
68 delete_option('fastvelocity_fvm_clean_header_one');
69 delete_option('fastvelocity_min_enable_defer_js');
70 delete_option('fastvelocity_min_exclude_defer_jquery');
71 delete_option('fastvelocity_min_force_inline_css');
72 delete_option('fastvelocity_min_force_inline_css_footer');
73 delete_option('fastvelocity_min_remove_googlefonts');
74 delete_option('fastvelocity_min_defer_for_pagespeed');
75 delete_option('fastvelocity_min_defer_for_pagespeed_optimize');
76 delete_option('fastvelocity_min_exclude_defer_login');
77 delete_option('fastvelocity_min_skip_defer_lists');
78 delete_option('fastvelocity_min_fvm_fix_editor');
79 delete_option('fastvelocity_min_loadcss');
80 delete_option('fastvelocity_min_fvm_removecss');
81 delete_option('fastvelocity_enabled_css_preload');
82 delete_option('fastvelocity_enabled_js_preload');
83 delete_option('fastvelocity_fontawesome_method');
84 delete_option('fastvelocity_gfonts_method');
85
86 }
87 }
88 # Version 3.2 routines end
89
90 }
1 <?php
2
3 # Exit if accessed directly
4 if (!defined('ABSPATH')){ exit(); }
5
6 ###################################################
7 # extend wp-cli to purge cache, usage: wp fvm purge
8 ###################################################
9
10 # only for wp-cli
11 if ( defined( 'WP_CLI' ) && WP_CLI ) {
12
13 class fastvelocity_WPCLI {
14
15 # purge files + cache
16 public function purge() {
17 WP_CLI::success( __( 'FVM and other caches were purged.', 'fast-velocity-minify' ) );
18 fvm_purge_static_files();
19 fvm_purge_others();
20
21 # purge everything
22 $cache = fvm_purge_static_files();
23 $others = fvm_purge_others();
24
25 # notices
26 WP_CLI::success( __( 'FVM: All Caches are now cleared.', 'fast-velocity-minify' ) .' ('.date("D, d M Y @ H:i:s e").')');
27 if(is_string($cache)) { WP_CLI::warning($cache); }
28 if(is_string($others)) { WP_CLI::success($others); }
29
30 }
31
32 # get cache size
33 public function stats() {
34 WP_CLI::error( __( 'This feature is currently under development.', 'fast-velocity-minify' ) );
35 }
36
37 }
38
39 # add commands
40 WP_CLI::add_command( 'fvm', 'fastvelocity_WPCLI' );
41
42 }
43
1 <?php if( $active_tab == 'help' ) { ?>
2
3 <div class="fvm-wrapper">
4
5 <h2 class="title">FVM 3 Release Notes</h2>
6
7 <div class="accordion">
8 <h3>Important JS and JavaScript changes</h3>
9 <div>
10 <p><strong>Notes:</strong></p>
11 <p>JavaScript merging functionality went through a significant change on FVM 3 and it now requires manual configuration to work.</p>
12 <p>If you are upgrading from FVM 2 please refer to the help section below for more information on the settings.</p>
13 <p>If you just installed the plugin, please note that JS is not being optimized yet. You have to choose which files to be render blocking and which ones to be deferred, plus it's inline script dependencies (if any). </p>
14 <p>Previously, FVM merged everything and relied on having options to ignore scripts. This option frequently created issues with other plugin updates, when they changed something JavaScript related.</p>
15 <p>Please understand that this plugin is and it has always been aimed at being a tool for advanced users and developers, so it's not meant just be plug and play, without manual settings in place.</p>
16 <p>There is a new method to optimize third party scripts and load them on user interaction or automatically, after 5 seconds. This is a more recommended method to optimize scripts, as compared to FVM 2 which used document.write and other deprecated methods.</p>
17 <p>Please refer to the JavaScript help section further down on this page to understand how you can optimize your scripts.</p>
18 </div>
19 <h3>Relevant CSS and Fonts changes</h3>
20 <div>
21 <p><strong>Notes:</strong></p>
22 <p>You can now ignore or completely remove CSS files by URI Path or domain name (such as google fonts or other unwanted CSS files).</p>
23 <p>Known fonts, icon, animation and some other CSS files now have an option to be merged separately and loaded Async.</p>
24 <p>FVM now preloads the external CSS files on the header, before render blocking them later on the page.</p>
25 </div>
26 <h3>Relevant Cache changes</h3>
27 <div>
28 <p><strong>Notes:</strong></p>
29 <p>Purging cache on FVM, renames the file name in order to bypass CDN and browser cache, however, expired CSS and JS cache files are only deleted 24 hours (by default) after your last cache purge request.</p>
30 <p>This is needed because some hosting services can cache your HTML regardless of your cache purge request. If we were to delete the FVM cached files right away, it would break your layout for anonymous users, as the files would no longer exist but your page would still be referencing them.</p>
31 </div>
32 <h3>Other changes</h3>
33 <div>
34 <p><strong>Notes:</strong></p>
35 <p>Preconnect and Preload Headers have been removed (please use your own PHP code and conditional tags for that).</p>
36 <p>Critical Path CSS option has been removed (add your own code with code and <code>&lt;style id=&quot;critical-path&quot;&gt;</code> your code &lt;/style&gt; ).</p>
37 </div>
38 </div>
39
40
41 <div style="height: 20px;"></div>
42 <h2 class="title">Global Settings</h2>
43
44 <div class="accordion">
45 <h3>Purge Minified CSS/JS files instantly</h3>
46 <div>
47 <p><strong>Notes:</strong></p>
48 <p>When you purge CSS/JS files instantly, all CSS and JS cache files are deleted immediately when you clear everything on FVM. While this is great during development, it may cause issues when your hosting is doing page caching that cannot be purged by FVM. For example, if you were to delete the CSS and JS files instantly without purging the full page cache, that page cache would now be pointing to deleted files.</p>
49 <p>If you deselect this option, whenever you clear everything on FVM, it will only try to delete files that are older than 24h (and hopefully this should be enough for your hosting to expire the page cache automatically). That way, even if your hosting is still showing your page cache to anonymous users, the referred CSS and JS files will still be available.</p>
50 <p>If you are not sure about page caching on your server or hosting provider, you should keep this option disabled.</p>
51 </div>
52 </div>
53
54 <div style="height: 20px;"></div>
55 <h2 class="title">HTML Settings</h2>
56
57 <div class="accordion">
58 <h3>Enable HTML Processing</h3>
59 <div>
60 <p><strong>Notes:</strong></p>
61 <p>You need to enable this option, for any other options in the HTML section to work.</p>
62 </div>
63 <h3>Strip HTML Comments</h3>
64 <div>
65 <p><strong>Notes:</strong></p>
66 <p>Some plugins may need to use comments for certain functionality to work, however this is quite rare. The benefit of removing comments is usually very small, so if needed you can disable this setting.</p>
67 </div>
68 <h3>Cleanup Header</h3>
69 <div>
70 <p><strong>Notes:</strong></p>
71 <p>This options removes resource hints, generator tags, shortlinks, manifest link, etc from the header.</p>
72 <p>The header should be kept as lean as possible for the best TTFB response times and LCP metrics, but if you later find that you need some of the stuff that is removed by this option, you can disable this setting.</p>
73 </div>
74 <h3>Remove Emoji</h3>
75 <div>
76 <p><strong>Notes:</strong></p>
77 <p>This will remove the default emoji scripts from wordpress, thus reducing the amount of code during page loading. If you use WordPress emoji when posting content, you should keep this option disabled.</p>
78 </div>
79 <h3>Disable HTML Minification</h3>
80 <div>
81 <p><strong>Notes:</strong></p>
82 <p>Although rare, it's possible for the HTML minification to strip too much code thus breaking something. You can use this option to test if that is the case and keep this option disabled if that is the case.</p>
83 <p>HTML minification is no longer a recommendation by gtmetrix or pagespeed insights, so you can keep this option disabled if there is any incompatibility issue.</p>
84 </div>
85 </div>
86
87
88 <div style="height: 20px;"></div>
89 <h2 class="title">CSS Settings</h2>
90
91 <div class="accordion">
92 <h3>Enable CSS Processing</h3>
93 <div>
94 <p><strong>Notes:</strong></p>
95 <p>You need to enable this option, for any other options in the CSS section to work.</p>
96 </div>
97 <h3>Merge Fonts and Icons Separately</h3>
98 <div>
99 <p><strong>Notes:</strong></p>
100 <p>This will try to collect and simplify all your CSS font face rules into a separate CSS file and load it async. It may be useful for de-duplication of fonts, or to evaluate how many fonts are in use. </p>
101 </div>
102 <h3>Remove "Print" stylesheets</h3>
103 <div>
104 <p><strong>Notes:</strong></p>
105 <p>As a generic rule this it's safe to remove it for the vast majority of sites, unless your users often need to print pages from your site and you have customized styles for when they do so.</p>
106 </div>
107 <h3>Combine CSS Files</h3>
108 <div>
109 <p><strong>Notes:</strong></p>
110 <p>Merging CSS is no longer recommended if your server supports HTTP/2. The feature also usually causes conflicts when merging multiple CSS files into one, due to the lack of specificity or other errors in the code. It's still available for legacy support on old installations or outdated servers that do not support HTTP/2 delivery.</p>
111 </div>
112 <h3>Disable CSS Files Minification</h3>
113 <div>
114 <p><strong>Notes:</strong></p>
115 <p>Although rare, it's possible that CSS files minification may strip too much code and break some style rules that are not supported by the minification library. You can always try to disable this and check if this fixes anything, then use the ignore list to exclude the file that is causing issues.</p>
116 </div>
117 <h3>Disable CSS Styles Minification</h3>
118 <div>
119 <p><strong>Notes:</strong></p>
120 <p>Although rare, it's possible that CSS Styles minification may strip too much code and break some style rules that are not supported by the minification library. You can always try to disable this and check if this fixes anything.</p>
121 </div>
122 <h3>Disable CSS Link Preload</h3>
123 <div>
124 <p><strong>Notes:</strong></p>
125 <p>CSS Link Preloading is useful when you are merging CSS files or when you defined certain paths as Async CSS. By default, FVM will send an HTTP preload request as well as adding the html preload tag on the header, which will prioritize downloading the critical styles earlier than the other resources.</p>
126 <p>If you select this option, these headers will be removed and your default preload resources will load earlier than the CSS files. You need to test and see what works best for you.</p>
127 </div>
128 <h3>Ignore CSS files</h3>
129 <div>
130 <p><strong>Notes:</strong></p>
131 <p>If there is a conflict when merging CSS files or during individual minification of a specific CSS file, you can add the path on this section and FVM will ignore the file, thus leaving it alone.</p>
132 </div>
133 <h3>Remove CSS Files</h3>
134 <div>
135 <p><strong>Notes:</strong></p>
136 <p>If your plugins enqueue multiple duplicate libraries with different url paths, you can add the path on this section and FVM will remove the file from the frontend, thus reducing your total CSS size.</p>
137 </div>
138 <h3>Async CSS Files</h3>
139 <div>
140 <p><strong>Notes:</strong></p>
141 <p>CSS files from plugins that are not rendered above the fold, should ideally be loaded async. For example, if a plugin has CSS files but you only use the element on the footer, you could add the /plugins/plugin-name/ here to async it. But you should not async CSS files that are needed for content that is visible on the critical path.</p>
142 <p>Also note that by loading certain CSS files async, you are changing the order of styles. This means, some styles may or may not work as intended, because now they load on a different position.</p>
143 </div>
144 </div>
145
146
147 <div style="height: 20px;"></div>
148 <h2 class="title">JS Settings</h2>
149
150 <div class="accordion">
151 <h3>Enable JS Processing</h3>
152 <div>
153 <p><strong>Notes:</strong></p>
154 <p>You need to enable this option, for any other options in the JS section to work.</p>
155 </div>
156 <h3>Combine JS Files</h3>
157 <div>
158 <p><strong>Notes:</strong></p>
159 <p>Merging JS is no longer recommended if your server supports HTTP/2. The feature also usually causes conflicts when merging multiple JS files into one, due to a different order of loading or other errors in the code. It's still available for legacy support on old installations or outdated servers that do not support HTTP/2 delivery.</p>
160 </div>
161 <h3>Disable JS Files Minification</h3>
162 <div>
163 <p><strong>Notes:</strong></p>
164 <p>JS files minification may strip too much code and break some code that is not supported by the minification library. You can always try to disable this and check if this fixes anything, then use the ignore list to exclude the file that is causing issues.</p>
165 </div>
166 <h3>Disable JS Inline Minification</h3>
167 <div>
168 <p><strong>Notes:</strong></p>
169 <p>Inline JS Styles minification may strip too much code and break some code that is not supported by the minification library. You can always try to disable this and check if this fixes anything.</p>
170 </div>
171 <h3>Disable JS Link Preload</h3>
172 <div>
173 <p><strong>Notes:</strong></p>
174 <p>JS Link Preloading is useful when you are merging JS files or when you defined certain paths as Defer JS. By default, FVM will send an HTTP preload request as well as adding the html preload tag on the header, which will prioritize downloading the render blocking scripts earlier than the other resources.</p>
175 <p>If you select this option, these headers will be removed and your default preload resources will load earlier than the JS files. You need to test and see what works best for you.</p>
176 </div>
177
178 <h3>Ignore Script Files</h3>
179 <div>
180 <p><strong>Notes:</strong></p>
181 <p>When you are merging JS files, the order of scripts change positions and dependencies may break. Other times, a specific script is not supported by the minification and breaks as well. If you encounter issues while merging or minifying JS files, you can exclude them here and those files will be left alone.</p>
182 </div>
183 <h3>Render Blocking JS files</h3>
184 <div>
185 <p><strong>Notes:</strong></p>
186 <p>In most WordPress themes and for a significant amount of plugins, you usually need to render block jQuery and jQuery Migrate for compatibility reasons.</p>
187 <p>Some plugins may not work at all if they are not render blocking, so you should look out for browser console log errors in incognito mode.</p>
188 <p><strong>Recommended Default Settings:</strong></p>
189 <p class="fvm-code-full">
190 /jquery-migrate<br>
191 /jquery.js<br>
192 /jquery.min.js<br>
193 </p>
194 </div>
195 <h3>Defer JS Files</h3>
196 <div>
197 <p><strong>Notes:</strong></p>
198 <p>Deferring every single script is not always the best solution, especially if those are needed to generate content above the fold. You must check your browser console log in incognito mode for possible errors after enabling this feature, and either move them to the render blocking section or check if there is any inline script that also needs to be deferred (so the order of loading is preserved).</p>
199 <p>Note that this is an advanced feature, hence it requires manual configuration for it to work.</p>
200 <p><strong>Recommended Default Settings:</strong></p>
201 <p class="fvm-code-full">
202 /ajax.aspnetcdn.com/ajax/<br>
203 /ajax.googleapis.com/ajax/libs/<br>
204 /cdnjs.cloudflare.com/ajax/libs/<br>
205 /stackpath.bootstrapcdn.com/bootstrap/<br>
206 /wp-admin/<br>
207 /wp-content/<br>
208 /wp-includes/
209 </p>
210 </div>
211 <h3>Defer Inline JavaScript</h3>
212 <div>
213 <p><strong>Notes:</strong></p>
214 <p>When you merge JS files and defer them, you are effectively changing the order in which they load, however for certain scripts, the order of loading matters. If you are deferring a certain JS file and you see an "undefined" error triggered on some inline code, you can try this option to also defer the inline code and preserve the order of execution.</p>
215 <p>Note however, not all scripts can work with defer so you need to test if everything is working without errors.</p>
216 <p>This is empty by default, unless you determine that it's needed.</p>
217 <p><strong>Recommended Default Settings:</strong></p>
218 <p class="fvm-code-full">
219 wp.i18n<br>
220 wp.apiFetch.use<br>
221 window.lodash<br>
222 wp.hooks<br>
223 wp.url
224 </p>
225 </div>
226 <h3>Delay third party scripts until user interaction</h3>
227 <div>
228 <p><strong>Notes:</strong></p>
229 <p>Scripts like analytics, ads, tracking codes, etc, consume important CPU and network resources needed for the initial page view. You can force certain plugins to wait for user interaction (mouseover, keydown, touchstart, touchmove and wheel) and the scripts will only run on these events.</p>
230 <p>Delaying these scripts will improve your speed because most third party scripts are not needed for showing any content (if they are, then they are on the critical path and you should not delay them). In addition, note that some codes make use of document.write and other methods, which do not support delaying (delaying will work as usual, but the script will not do anything or stop working).</p>
231 <p>If you have render blocking third party scripts, ask your provider if they can provide you with an async or defer implementation (else remove them, because render blocking scripts are not recommended for speed).</p>
232 <p><strong>Example Settings:</strong></p>
233 <p class="fvm-code-full">
234 function(w,d,s,l,i)<br>
235 function(f,b,e,v,n,t,s)<br>
236 function(h,o,t,j,a,r)<br>
237 www.googletagmanager.com/gtm.js<br>
238 gtag(<br>
239 fbq(<br>
240 </p>
241 </div>
242 <h3>Remove JavaScript Scripts</h3>
243 <div>
244 <p><strong>Notes:</strong></p>
245 <p>This can be used when you want to remove a duplicate JS file from the frontend source. However, if this is for a third party script you added to the header or footer (either code or via some plugin), it's better if you delete it at the source.</p>
246 </div>
247 </div>
248
249
250
251 <div style="height: 20px;"></div>
252 <h2 class="title">CDN Settings</h2>
253
254 <div class="accordion">
255 <h3>Enable CDN Processing</h3>
256 <div>
257 <p><strong>Notes:</strong></p>
258 <p>You need to enable this option, for any other options in the CDN section to work.</p>
259 </div>
260 <h3>Enable CDN for merged CSS files</h3>
261 <div>
262 <p><strong>Notes:</strong></p>
263 <p>When selecting this option, FVM will replace your domain name with the CDN domain, for the generated CSS cache file.</p>
264 <p>Under certain situations, you may not want to serve the CSS file from the CDN, such as when your server compression level is significantly higher than the CDN (smaller file than the one delivered by the CDN).</p>
265 <p>Also bare in mind, that if the CSS file is served from the CDN, any static assets inside the CSS file that make use of relative paths, will also be cached and served from the CDN, which may also be undesirable in certain situations.</p>
266 </div>
267 <h3>Enable CDN for merged JS files</h3>
268 <div>
269 <p><strong>Notes:</strong></p>
270 <p>When selecting this option, FVM will replace your domain name with the CDN domain, for the generated JS cache file.</p>
271 <p>Under certain situations, you may not want to serve the JS file from the CDN, such as when your server compression level is significantly higher than the CDN (smaller file than the one delivered by the CDN).</p>
272 <p>Also bare in mind, that if the JS file is served from the CDN, any static assets inside the JS file that make use of relative paths, will also be cached and served from the CDN, which may also be undesirable in certain situations.</p>
273 </div>
274 <h3>CDN URL</h3>
275 <div>
276 <p><strong>Notes:</strong></p>
277 <p>This is not required for providers such as Cloudflare.com as well as any reverse proxy CDN service that doesn't change your domain name (the whole site goes through their service).</p>
278 <p>For other types of CDN, you are usually provided with an alternative domain name from where your static files can be served and in those cases, you would introduce your new domain name here.</p>
279 </div>
280 <h3>CDN Integration</h3>
281 <div>
282 <p><strong>Notes:</strong></p>
283 <p>Uses syntax from <a target="_blank" href="https://simplehtmldom.sourceforge.io/manual.htm">https://simplehtmldom.sourceforge.io/manual.htm</a> for modifying the HTML.</p>
284 <p>The plugin will replace your site domain url with the CDN domain for the matching HTML tags.</p>
285 </div>
286 </div>
287
288 <div style="height: 20px;"></div>
289 <h2 class="title">User Settings</h2>
290
291 <div class="accordion">
292 <h3>User Options</h3>
293 <div>
294 <p><strong>Notes:</strong></p>
295 <p>This will allow you to force CSS, HTML and JS processing for specific user roles.</p>
296 <p>By default, only anonymous users should be optimized, to ensure that there is nothing broken for logged in users (unless you know what you are doing).</p>
297 </div>
298 </div>
299
300
301
302 <div style="height: 20px;"></div>
303 <h2 class="title">Other FAQ's</h2>
304
305 <div class="accordion">
306 <h3>Is the plugin GDPR compatible?</h3>
307 <div>
308 <p><strong>Notes:</strong></p>
309 <p>FVM does not collect any information from you, your site or your users. It also doesn't require cookies to work, therefore it's fully GDPR compatible.</p>
310 </div>
311 <h3>How do I know if the plugin is working?</h3>
312 <div>
313 <p><strong>Notes:</strong></p>
314 <p>For compatibility reasons, the plugin only optimizes anonymous users by default. That means, you need to open another browser window, or use incognito mode to test and see what it's doing. Logged in users will not see the optimizations, unless you manually enable certain user roles (not recommended for complex websites, unless you know what you are doing).</p>
315 </div>
316 <h3>How do I purge the cache?</h3>
317 <div>
318 <p><strong>Notes:</strong></p>
319 <p>Please note that FVM is not a page cache plugin and it doesn't cache your content. The only time you should need to purge it's cache, is when you edit a css or js file.</p>
320 <p>If your HTML page is being cached somewhere else, you must purge your cache either, unless FVM supports it natively.</p>
321 </div>
322 <h3>Why am I getting 404 error not found for the generated CSS or JS files?</h3>
323 <div>
324 <p><strong>Notes:</strong></p>
325 <p>You deleted the FVM cache but forgot to purge the HTML page cache, or you are lacking writing permissions on the cache directory and files are not being created. </p>
326 <p>You must purge your page cache either on some other cache plugin or at your server/hosting level for your page to update and find the latest merged file paths.</p>
327 <p>Note that some hosts rate limit the amount of times you can purge caches to once every few minutes, so you may be purging and it doesn't work, because you are being rate limited by your hosting cache system.</p>
328 <p>Avoid doing development on live sites and use a staging server without cache for testing. Production servers are for deploying once and leave it until the next deployment cycle.</p>
329 </div>
330 <h3>Why is my site layout broke after an update, a configuration change, or some other change?</h3>
331 <div>
332 <p><strong>Notes:</strong></p>
333 <p>You must check your browser console log in incognito mode, for possible errors after enabling certain features, deferring scripts or using some other optimization plugins, which may be conflicting with FVM optimization.</p>
334 <p>If there are no errors, disable each option one by one on FVM (html processing, css processing, js processing, etc) until you find the feature breaking it. After that, adjust and tweak those settings accordingly, or hire a developer to help you.</p>
335 </div>
336 <h3>How can I download an older version of FVM for testing purposes?</h3>
337 <div>
338 <p><strong>Notes:</strong></p>
339 <p>It's not recommended you do that, but if you want to test something, you can do so from the <a target="_blank" href="https://plugins.svn.wordpress.org/fast-velocity-minify/tags/">SVN repository</a> on WordPress.</p>
340 </div>
341 <h3>How do I undo all optimizations done by FVM?</h3>
342 <div>
343 <p><strong>Notes:</strong></p>
344 <p>Simply disable the plugin, and make sure to purge all page caches (Cache Plugins, Hosting, OPCache, etc).</p>
345 <p>Note that some hosts rate limit the amount of times you can purge caches to once every few minutes, so you may be purging and it doesn't work, because you are being rate limited by your hosting cache system. If that happens, just wait and try again later or ask your hosting to manually purge all caches on the server.</p>
346 <p>You can also delete any database entries on the wp_options table starting with fastvelocity to completely purge it.</p>
347 <p>FVM does not modify your site. It runs after your template loads and filters the final HTML to present it to your users, in a more optimized way.</p>
348 </div>
349 <h3>I have disabled FVM but somehow the cache files are still being generated?</h3>
350 <div>
351 <p><strong>Notes:</strong></p>
352 <p>If you already purged all caches available, please ensure you have deleted the plugin and that it's no longer visible via wp-admin. If you still see references to FVM in incognito mode via google chrome, that means your server still has full page cache in memory that needs to be cleared.</p>
353 <p>A few hosting providers will put your files on a remote storage and cache your disk and files in memory to speed things up, which may cause code to be cached even if you have completely deleted it from the remote storage. This means, it may take a few minutes, or several hours for the actual code to update. In that case, restart the server or ask your hosting to purge all disk and page caching memory.</p>
354 </div>
355 <h3>Where can I get support or ask questions about the plugin?</h3>
356 <div>
357 <p><strong>Notes:</strong></p>
358 <p>You can ask for help on <a href="https://wordpress.org/support/plugin/fast-velocity-minify/">https://wordpress.org/support/plugin/fast-velocity-minify/</a> but please note we cannot guide you on which files to merge or how to solve JavaScript conflicts. You need to try different settings (trial and error) and open a separate window in incognito mode, to look for console log errors on your browser, and adjust settings as needed.</p>
359 </div>
360 <h3>How is it possible that some scan is showing malware on the plugin?</h3>
361 <div>
362 <p><strong>Notes:</strong></p>
363 <p>I guarantee that the plugin is 100% clean of malware, provided you have downloaded the plugin from the official WordPress source AND that your other plugins, theme or core is not compromised.</p>
364 <p>Malware can infect any plugin (even security plugins) regardless of the point of entry. Sometimes it propagates from different areas (including other sites you may have on the same server) or through a vulnerability on another theme or plugin (even disabled plugins or themes sometimes). </p>
365 <p>For that reason, if there is already malware on any JS files or CSS being merged by FVM, they would still be merged as they are, as FVM also reads them as they are. </p>
366 <p>If you are seeing malware on any cache file related to FVM, simply purge all caches and delete the FVM plugin. Then hire someone to manually audit the site (plugins or automated malware checks are not 100% accurate). You can then reinstall the FVM plugin from the official source on wordpress.org or via wp-admin if you like.</p>
367 </div>
368 <h3>How do I report a security issue or file a bug report?</h3>
369 <div>
370 <p><strong>Notes:</strong></p>
371 <p>If you are sure it's a bug and not a misconfiguration specific to your site, thank you for taking the time to report it.</p>
372 <p>You can contact me on <a href="https://fastvelocity.com/">https://fastvelocity.com/</a> using the contact form.</p>
373 <p>You are also welcome to submit patches and fixes via <a href="https://github.com/peixotorms/fast-velocity-minify">https://github.com/peixotorms/fast-velocity-minify</a> if you are a developer.</p>
374
375 </div>
376 <h3>I'm not a developer, can I hire you for a more complete speed optimization?</h3>
377 <div>
378 <p>You can contact me on <a href="https://fastvelocity.com/">https://fastvelocity.com/</a> using the contact form, providing me your site URL and what issues you are trying to fix, for a more exact quote.</p>
379 <p>My speed optimization starts from $500 for small sites and from $850 for woocommerce and membership sites.</p>
380 <p>I do not use the free FVM for my professional work, but I guarantee as best performance as possible for your site content.</p>
381 </div>
382 <h3>How can I donate to the plugin author?</h3>
383 <div>
384 <p><strong>Notes:</strong></p>
385 <p>While not required, if you are happy with my work and would like to buy me a <del>beer</del> green tea, you can do it via PayPal at <a target="_blank" href="https://goo.gl/vpLrSV">https://goo.gl/vpLrSV</a> and thank you in advance :)</p>
386 </div>
387 </div>
388
389
390 </div>
391 <?php
392 }
1 <?php if( $active_tab == 'settings' ) { ?>
2 <div class="fvm-wrapper">
3
4 <form method="post" id="fvm-save-changes">
5
6 <?php
7 # nounce
8 wp_nonce_field('fvm_settings_nonce', 'fvm_settings_nonce');
9 ?>
10
11 <h2 class="title"><?php _e( 'Global Settings', 'fast-velocity-minify' ); ?></h2>
12 <h3 class="fvm-bold-green"><?php _e( 'Settings that affect the plugin and are not specific to speed optimization.', 'fast-velocity-minify' ); ?></h3>
13
14 <table class="form-table fvm-settings">
15 <tbody>
16
17 <tr>
18 <th scope="row"><?php _e( 'Global Options', 'fast-velocity-minify' ); ?></th>
19 <td>
20 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Handle with Care', 'fast-velocity-minify' ); ?></p>
21
22 <fieldset>
23 <label for="fvm_settings_cache_min_instant_purge">
24 <input name="fvm_settings[cache][min_instant_purge]" type="checkbox" id="fvm_settings_cache_min_instant_purge" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'cache', 'min_instant_purge')); ?>>
25 <?php _e( 'Purge Minified CSS/JS files instantly', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Cache files are only deleted if older than 24h by default, for compatibility with certain hosting providers.', 'fast-velocity-minify' ); ?> ]</span></label>
26 <br />
27
28 </fieldset></td>
29 </tr>
30
31 </tbody>
32 </table>
33
34
35 <h2 class="title"><?php _e( 'HTML Settings', 'fast-velocity-minify' ); ?></h2>
36 <h3 class="fvm-bold-green"><?php _e( 'Optimize your HTML and remove some clutter from the HTML page.', 'fast-velocity-minify' ); ?></h3>
37
38 <table class="form-table fvm-settings">
39 <tbody>
40
41 <tr>
42 <th scope="row"><?php _e( 'HTML Options', 'fast-velocity-minify' ); ?></th>
43 <td>
44 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Recommended Settings', 'fast-velocity-minify' ); ?></p>
45
46 <fieldset>
47 <label for="fvm_settings_html_enable">
48 <input name="fvm_settings[html][enable]" type="checkbox" id="fvm_settings_html_enable" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'html', 'enable')); ?>>
49 <?php _e( 'Enable HTML Processing', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will enable processing for the settings below', 'fast-velocity-minify' ); ?> ]</span></label>
50 <br />
51
52 <label for="fvm_settings_html_nocomments">
53 <input name="fvm_settings[html][nocomments]" type="checkbox" id="fvm_settings_html_nocomments" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'html', 'nocomments')); ?>>
54 <?php _e( 'Strip HTML Comments', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will strip HTML comments from your HTML page', 'fast-velocity-minify' ); ?> ]</span></label>
55 <br />
56
57 <label for="fvm_settings_html_cleanup_header">
58 <input name="fvm_settings[html][cleanup_header]" type="checkbox" id="fvm_settings_html_cleanup_header" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'html', 'cleanup_header')); ?>>
59 <?php _e( 'Cleanup Header', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Removes resource hints, generator tag, shortlinks, manifest link, etc', 'fast-velocity-minify' ); ?> ]</span></label>
60 <br />
61
62 <label for="fvm_settings_html_disable_emojis">
63 <input name="fvm_settings[html][disable_emojis]" type="checkbox" id="fvm_settings_html_disable_emojis" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'html', 'disable_emojis')); ?>>
64 <?php _e( 'Remove Emoji', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Removes the default emoji scripts and styles that come with WordPress', 'fast-velocity-minify' ); ?> ]</span></label>
65 <br />
66
67 </fieldset></td>
68 </tr>
69
70 <tr>
71 <th scope="row"><?php _e( 'Advanced HTML Options', 'fast-velocity-minify' ); ?></th>
72 <td>
73 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Handle with Care', 'fast-velocity-minify' ); ?></p>
74
75 <fieldset>
76 <label for="fvm_settings_html_min_disable">
77 <input name="fvm_settings[html][min_disable]" type="checkbox" id="fvm_settings_html_min_disable" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'html', 'min_disable')); ?>>
78 <?php _e( 'Disable HTML Minification', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will disable HTML minification for compatibility purposes', 'fast-velocity-minify' ); ?> ]</span></label>
79 <br />
80
81 </fieldset></td>
82 </tr>
83
84 </tbody>
85 </table>
86
87 <div style="height: 60px;"></div>
88 <h2 class="title"><?php _e( 'CSS Settings', 'fast-velocity-minify' ); ?></h2>
89 <h3 class="fvm-bold-green"><?php _e( 'Optimize your CSS and Styles settings.', 'fast-velocity-minify' ); ?></h3>
90
91 <table class="form-table fvm-settings">
92 <tbody>
93
94 <tr>
95 <th scope="row"><?php _e( 'CSS Options', 'fast-velocity-minify' ); ?></th>
96 <td>
97 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Recommended Settings', 'fast-velocity-minify' ); ?></p>
98
99 <fieldset>
100 <label for="fvm_settings_css_enable">
101 <input name="fvm_settings[css][enable]" type="checkbox" id="fvm_settings_css_enable" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'css', 'enable')); ?>>
102 <?php _e( 'Enable CSS Processing', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will enable processing for the settings below', 'fast-velocity-minify' ); ?> ]</span></label>
103 <br />
104
105 <label for="fvm_settings_css_fonts">
106 <input name="fvm_settings[css][fonts]" type="checkbox" id="fvm_settings_css_fonts" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'css', 'fonts')); ?>>
107 <?php _e( 'Merge Fonts and Icons separately', 'fast-velocity-minify' ); ?><span class="note-info">[ <?php _e( 'Will merge fonts and icons into a separate CSS file', 'fast-velocity-minify' ); ?> ]</span></label>
108 <br />
109
110 <label for="fvm_settings_css_noprint">
111 <input name="fvm_settings[css][noprint]" type="checkbox" id="fvm_settings_css_noprint" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'css', 'noprint')); ?>>
112 <?php _e( 'Remove "Print" CSS files', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will remove CSS files of mediatype "print" from the frontend', 'fast-velocity-minify' ); ?> ]</span></label>
113 <br />
114
115 </fieldset></td>
116 </tr>
117
118 <tr>
119 <th scope="row"><?php _e( 'Advanced CSS Options', 'fast-velocity-minify' ); ?></th>
120 <td>
121 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Handle with Care', 'fast-velocity-minify' ); ?></p>
122
123 <fieldset>
124
125 <label for="fvm_settings_css_combine">
126 <input name="fvm_settings[css][combine]" type="checkbox" id="fvm_settings_css_combine" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'css', 'combine')); ?>>
127 <?php _e( 'Combine CSS Files', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Deprecated: Will combine all CSS files by mediatype groups in the header (no longer recommended for HTTP/2 servers)', 'fast-velocity-minify' ); ?> ]</span></label>
128 <br />
129
130 <label for="fvm_settings_css_min_disable">
131 <input name="fvm_settings[css][min_disable]" type="checkbox" id="fvm_settings_css_min_disable" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'css', 'min_disable')); ?>>
132 <?php _e( 'Disable CSS Files Minification', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will disable CSS Files minification for compatibility purposes', 'fast-velocity-minify' ); ?> ]</span></label>
133 <br />
134
135 <label for="fvm_settings_css_min_disable_styles">
136 <input name="fvm_settings[css][min_disable_styles]" type="checkbox" id="fvm_settings_css_min_disable_styles" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'css', 'min_disable_styles')); ?>>
137 <?php _e( 'Disable CSS Styles Minification', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will disable CSS Styles minification for compatibility purposes', 'fast-velocity-minify' ); ?> ]</span></label>
138 <br />
139
140 <label for="fvm_settings_css_nopreload">
141 <input name="fvm_settings[css][nopreload]" type="checkbox" id="fvm_settings_css_nopreload" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'css', 'nopreload')); ?>>
142 <?php _e( 'Disable CSS Link Preload', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will remove the Render Blocking CSS files Link Preload from the header', 'fast-velocity-minify' ); ?> ]</span></label>
143 <br />
144
145 </fieldset></td>
146 </tr>
147
148 <tr>
149 <th scope="row"><?php _e( 'Ignore CSS files', 'fast-velocity-minify' ); ?></th>
150 <td><fieldset>
151 <label for="fvm_settings_css_ignore"><span class="fvm-bold-green fvm-rowintro"><?php _e( "Ignore the following CSS URL's", 'fast-velocity-minify' ); ?></span></label>
152 <p><textarea name="fvm_settings[css][ignore]" rows="7" cols="50" id="fvm_settings_css_ignore" class="large-text code" placeholder="ex: /plugins/something/assets/problem.css"><?php echo fvm_get_settings_value($fvm_settings, 'css', 'ignore'); ?></textarea></p>
153 <p class="description">[ <?php _e( 'CSS files are merged and grouped automatically by mediatype, hence you have an option to exclude files.', 'fast-velocity-minify' ); ?> ]</p>
154 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the <code>href attribute</code> on the <code>link tag</code>', 'fast-velocity-minify' ); ?> ]</p>
155 </fieldset></td>
156 </tr>
157
158 <tr>
159 <th scope="row"><?php _e( 'Remove CSS Files', 'fast-velocity-minify' ); ?></th>
160 <td><fieldset>
161 <label for="fvm_settings_css_remove"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'Remove the following CSS files', 'fast-velocity-minify' ); ?></span></label>
162 <p><textarea name="fvm_settings[css][remove]" rows="7" cols="50" id="fvm_settings_css_remove" class="large-text code" placeholder="ex: fonts.googleapis.com"><?php echo fvm_get_settings_value($fvm_settings, 'css', 'remove'); ?></textarea></p>
163 <p class="description">[ <?php _e( 'This will allow you to remove unwanted CSS files by URL path from the frontend', 'fast-velocity-minify' ); ?> ]</p>
164 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the <code>href attribute</code> on the <code>link tag</code>', 'fast-velocity-minify' ); ?> ]</p>
165 </fieldset></td>
166 </tr>
167
168 <tr>
169 <th scope="row"><?php _e( 'Async CSS Files', 'fast-velocity-minify' ); ?></th>
170 <td><fieldset>
171 <label for="fvm_settings_css_async"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'Async the following CSS files', 'fast-velocity-minify' ); ?></span></label>
172 <p><textarea name="fvm_settings[css][async]" rows="7" cols="50" id="fvm_settings_css_async" class="large-text code" placeholder="ex: /plugins/something/assets/low-priority.css"><?php echo fvm_get_settings_value($fvm_settings, 'css', 'async'); ?></textarea></p>
173 <p class="description">[ <?php _e( 'This will allow you to remove unwanted CSS files by URL path from the frontend', 'fast-velocity-minify' ); ?> ]</p>
174 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the <code>href attribute</code> on the <code>link tag</code>', 'fast-velocity-minify' ); ?> ]</p>
175 </fieldset></td>
176 </tr>
177
178
179 </tbody>
180 </table>
181
182
183
184 <div style="height: 60px;"></div>
185 <h2 class="title"><?php _e( 'JS Settings', 'fast-velocity-minify' ); ?></h2>
186 <h3 class="fvm-bold-green"><?php _e( 'In this section, you can optimize your JS files and inline scripts manually (by default all scripts are ignored for compatibility reasons).', 'fast-velocity-minify' ); ?></h3>
187
188 <table class="form-table fvm-settings">
189 <tbody>
190
191 <tr>
192 <th scope="row"><?php _e( 'JS Options', 'fast-velocity-minify' ); ?></th>
193 <td>
194 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Recommended Settings', 'fast-velocity-minify' ); ?></p>
195
196 <fieldset>
197 <label for="fvm_settings_js_enable">
198 <input name="fvm_settings[js][enable]" type="checkbox" id="fvm_settings_js_enable" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'js', 'enable')); ?>>
199 <?php _e( 'Enable JS Processing', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will enable processing for the settings below', 'fast-velocity-minify' ); ?> ]</span></label>
200 <br />
201
202 </fieldset></td>
203 </tr>
204
205 <tr>
206 <th scope="row"><?php _e( 'Advanced JS Options', 'fast-velocity-minify' ); ?></th>
207 <td>
208 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Handle with Care', 'fast-velocity-minify' ); ?></p>
209
210 <fieldset>
211
212 <label for="fvm_settings_js_combine">
213 <input name="fvm_settings[js][combine]" type="checkbox" id="fvm_settings_js_combine" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'js', 'combine')); ?>>
214 <?php _e( 'Combine JS Files', 'fast-velocity-minify' ); ?> <span class="note-info">[<?php _e( 'Deprecated: Will combine all JS files by render blocking type (no longer recommended for HTTP/2 servers)', 'fast-velocity-minify' ); ?> ]</span></label>
215 <br />
216
217
218 <label for="fvm_settings_js_min_disable">
219 <input name="fvm_settings[js][min_disable]" type="checkbox" id="fvm_settings_js_min_disable" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'js', 'min_disable')); ?>>
220 <?php _e( 'Disable JS Files Minification', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will disable JS Files minification for testing purposes', 'fast-velocity-minify' ); ?> ]</span></label>
221 <br />
222
223 <label for="fvm_settings_js_min_disable_inline">
224 <input name="fvm_settings[js][min_disable_inline]" type="checkbox" id="fvm_settings_js_min_disable_inline" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'js', 'min_disable_inline')); ?>>
225 <?php _e( 'Disable JS Inline Minification', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will disable JS Inline minification for testing purposes', 'fast-velocity-minify' ); ?> ]</span></label>
226 <br />
227
228 <label for="fvm_settings_js_inline-all">
229 <input name="fvm_settings[js][nopreload]" type="checkbox" id="fvm_settings_js_nopreload" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'js', 'nopreload')); ?>>
230 <?php _e( 'Disable JS link Preload', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will remove the Render Blocking JS files Link Preload from the header', 'fast-velocity-minify' ); ?> ]</span></label>
231 <br />
232
233 </fieldset></td>
234 </tr>
235
236
237 <tr>
238 <th scope="row"><?php _e( 'Ignore Script Files', 'fast-velocity-minify' ); ?></th>
239 <td><fieldset>
240 <label for="fvm_settings_js_ignore"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'Will prevent merging or minification for all JS files matching the paths below', 'fast-velocity-minify' ); ?></span></label>
241 <p><textarea name="fvm_settings[js][ignore]" rows="7" cols="50" id="fvm_settings_js_ignore" class="large-text code" placeholder="<?php _e( '--- ex: /plugins/something/assets/problem.js ---', 'fast-velocity-minify' ); ?>"><?php echo fvm_get_settings_value($fvm_settings, 'js', 'ignore'); ?></textarea></p>
242 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the script <code>src</code> attribute', 'fast-velocity-minify' ); ?> ]</p>
243 <p class="description">[ <?php _e( 'It is highly recommended to try to leave this empty and later be more specific on what to merge', 'fast-velocity-minify' ); ?> ]</p>
244 </fieldset></td>
245 </tr>
246
247 <tr>
248 <th scope="row"><?php _e( 'Render Blocking JS files', 'fast-velocity-minify' ); ?></th>
249 <td><fieldset>
250 <label for="fvm_settings_merge_header"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'This will render block all JS files matching the paths below', 'fast-velocity-minify' ); ?></span></label>
251 <p><textarea name="fvm_settings[js][merge_header]" rows="7" cols="50" id="fvm_settings_js_merge_header" class="large-text code" placeholder="<?php _e( '--- example ---', 'fast-velocity-minify' ); ?>
252 /jquery-migrate.js
253 /jquery.js
254 /jquery.min.js"><?php echo fvm_get_settings_value($fvm_settings, 'js', 'merge_header'); ?></textarea></p>
255 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the script <code>src attribute</code>', 'fast-velocity-minify' ); ?> ]</p>
256 </fieldset></td>
257 </tr>
258
259 <tr>
260 <th scope="row"><?php _e( 'Defer JS Files', 'fast-velocity-minify' ); ?></th>
261 <td><fieldset>
262 <label for="fvm_settings_merge_defer"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'This will defer all JS files matching the paths below', 'fast-velocity-minify' ); ?></span></label>
263 <p><textarea name="fvm_settings[js][merge_defer]" rows="7" cols="50" id="fvm_settings_js_merge_defer" class="large-text code" placeholder="<?php _e( '--- example ---', 'fast-velocity-minify' ); ?>
264 /wp-admin/
265 /wp-includes/
266 /wp-content/"><?php echo fvm_get_settings_value($fvm_settings, 'js', 'merge_defer'); ?></textarea></p>
267 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the script <code>src attribute', 'fast-velocity-minify' ); ?></code> ]</p>
268 </fieldset></td>
269 </tr>
270
271 <tr>
272 <th scope="row"><?php _e( 'Defer Inline JavaScript', 'fast-velocity-minify' ); ?></th>
273 <td><fieldset>
274 <label for="fvm_settings_defer_dependencies"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'Preserve the order of scripts execution when deferring JS files dependencies', 'fast-velocity-minify' ); ?></span></label>
275 <p><textarea name="fvm_settings[js][defer_dependencies]" rows="7" cols="50" id="fvm_settings_js_defer_dependencies" class="large-text code" placeholder="<?php _e( '--- example ---', 'fast-velocity-minify' ); ?>
276 wp.i18n
277 wp.apiFetch.use
278 window.lodash
279 wp.hooks
280 wp.url"><?php echo fvm_get_settings_value($fvm_settings, 'js', 'defer_dependencies'); ?></textarea></p>
281 <p class="description">[ <?php _e( 'Inline JavaScript matching these rules, will be deferred with script type module', 'fast-velocity-minify' ); ?> ]</p>
282 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the script <code>innerHTML</code>', 'fast-velocity-minify' ); ?> ]</p>
283 </fieldset></td>
284 </tr>
285
286 <tr>
287 <th scope="row"><?php _e( 'Delay third party scripts until user interaction', 'fast-velocity-minify' ); ?></th>
288 <td><fieldset>
289 <label for="fvm_settings_js_thirdparty"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'Delay JS files or inline scripts until user interaction', 'fast-velocity-minify' ); ?></span></label>
290 <p><textarea name="fvm_settings[js][thirdparty]" rows="7" cols="50" id="fvm_settings_js_thirdparty" class="large-text code" placeholder="<?php _e( '--- example ---', 'fast-velocity-minify' ); ?>
291 function(w,d,s,l,i)
292 function(f,b,e,v,n,t,s)
293 function(h,o,t,j,a,r)
294 www.googletagmanager.com/gtm.js"><?php echo fvm_get_settings_value($fvm_settings, 'js', 'thirdparty'); ?></textarea></p>
295 <p class="description">[ <?php _e( 'Used interaction events: mouseover, keydown, touchstart, touchmove and wheel', 'fast-velocity-minify' ); ?> ]</p>
296 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the inline script <code>innerHTML</code> or <code>src</code> attribute for JS files', 'fast-velocity-minify' ); ?> ]</p>
297 </fieldset></td>
298 </tr>
299
300 <tr>
301 <th scope="row"><?php _e( 'Remove JavaScript Scripts', 'fast-velocity-minify' ); ?></th>
302 <td><fieldset>
303 <label for="fvm_settings_js_remove"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'Remove the following JS files or Inline Scripts', 'fast-velocity-minify' ); ?></span></label>
304 <p><textarea name="fvm_settings[js][remove]" rows="7" cols="50" id="fvm_settings_js_remove" class="large-text code" placeholder="<?php _e( '--- example ---', 'fast-velocity-minify' ); ?>
305 /some/duplicate/file.js"><?php echo fvm_get_settings_value($fvm_settings, 'js', 'remove'); ?></textarea></p>
306 <p class="description">[ <?php _e( 'This will allow you to remove unwanted script tags from the frontend', 'fast-velocity-minify' ); ?> ]</p>
307 <p class="description">[ <?php _e( 'Will match using <code>PHP stripos</code> against the script <code>outerHTML</code>', 'fast-velocity-minify' ); ?> ]</p>
308 </fieldset></td>
309 </tr>
310
311
312 </tbody>
313 </table>
314
315
316
317 <div style="height: 60px;"></div>
318 <h2 class="title"><?php _e( 'CDN Settings', 'fast-velocity-minify' ); ?></h2>
319 <h3 class="fvm-bold-green"><?php _e( 'If your CDN provider gives you a different URL for your assets, you can use it here', 'fast-velocity-minify' ); ?></h3>
320 <table class="form-table fvm-settings">
321 <tbody>
322 <tr>
323 <th scope="row"><?php _e( 'CDN Options', 'fast-velocity-minify' ); ?></th>
324 <td>
325 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Select your options below', 'fast-velocity-minify' ); ?></p>
326
327 <fieldset>
328 <label for="fvm_settings_cdn_enable">
329 <input name="fvm_settings[cdn][enable]" type="checkbox" id="fvm_settings_cdn_enable" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'cdn', 'enable')); ?>>
330 <?php _e( 'Enable CDN Processing', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will enable processing for the settings below', 'fast-velocity-minify' ); ?> ]</span></label>
331 <br />
332
333 <label for="fvm_settings_cdn_cssok">
334 <input name="fvm_settings[cdn][cssok]" type="checkbox" id="fvm_settings_cdn_cssok" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'cdn', 'cssok')); ?>>
335 <?php _e( 'Enable CDN for merged CSS files', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will serve the FVM generated CSS files from the CDN', 'fast-velocity-minify' ); ?> ]</span></label>
336 <br />
337
338 <label for="fvm_settings_cdn_jsok">
339 <input name="fvm_settings[cdn][jsok]" type="checkbox" id="fvm_settings_cdn_jsok" value="1" <?php echo fvm_get_settings_checkbox(fvm_get_settings_value($fvm_settings, 'cdn', 'jsok')); ?>>
340 <?php _e( 'Enable CDN for merged JS files', 'fast-velocity-minify' ); ?> <span class="note-info">[ <?php _e( 'Will serve the FVM generated JS files from the CDN', 'fast-velocity-minify' ); ?> ]</span></label>
341 <br />
342
343 </fieldset></td>
344 </tr>
345 <tr>
346 <th scope="row"><span class="fvm-label-special"><?php _e( 'CDN URL', 'fast-velocity-minify' ); ?></span></th>
347 <td><fieldset>
348 <label for="fvm_settings_cdn_domain">
349 <p><input type="text" name="fvm_settings[cdn][domain]" id="fvm_settings_cdn_domain" value="<?php echo fvm_get_settings_value($fvm_settings, 'cdn', 'domain'); ?>" size="80" /></p>
350 <p class="description">[ <?php _e( 'You can ignore this if your CDN url matches your domain name (ie: Cloudflare)', 'fast-velocity-minify' ); ?> ]</p>
351 </label>
352 <br />
353 </fieldset></td>
354 </tr>
355 <tr>
356 <th scope="row"><?php _e( 'CDN Integration', 'fast-velocity-minify' ); ?></th>
357 <td><fieldset>
358 <label for="fvm_settings_cdn_integration"><span class="fvm-bold-green fvm-rowintro"><?php _e( 'Missing HTML elements to replace', 'fast-velocity-minify' ); ?></span></label>
359 <p><textarea name="fvm_settings[cdn][integration]" rows="7" cols="50" id="fvm_settings_cdn_integration" class="large-text code" placeholder="--- check the help section for suggestions ---"><?php echo fvm_get_settings_value($fvm_settings, 'cdn', 'integration'); ?></textarea></p>
360 <p class="description">[ <?php _e( 'Additional replacement rules with syntax from <code>https://simplehtmldom.sourceforge.io/manual.htm</code>', 'fast-velocity-minify' ); ?> ]</p>
361 </fieldset></td>
362 </tr>
363 </tbody></table>
364
365
366 <div style="height: 60px;"></div>
367 <h2 class="title"><?php _e( 'User Settings', 'fast-velocity-minify' ); ?></h2>
368 <h3 class="fvm-bold-green"><?php _e( 'For compatibility reasons, only anonymous users should be optimized by default.', 'fast-velocity-minify' ); ?></h3>
369 <table class="form-table fvm-settings">
370 <tbody>
371
372 <tr>
373 <th scope="row"><?php _e( 'User Options', 'fast-velocity-minify' ); ?></th>
374 <td>
375 <p class="fvm-bold-green fvm-rowintro"><?php _e( 'Force optimization for the following user roles', 'fast-velocity-minify' ); ?></p>
376
377 <fieldset>
378 <?php
379 # output user roles checkboxes
380 echo fvm_get_user_roles_checkboxes();
381 ?>
382 </fieldset></td>
383 </tbody></table>
384
385
386 <input type="hidden" name="fvm_action" value="save_settings" />
387 <p class="submit"><input type="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'fast-velocity-minify' ); ?>"></p>
388
389 </form>
390 </div>
391 <?php
392 }
1 <?php
2
3 # server info
4 if( $active_tab == 'status' ) {
5 ?>
6 <div class="fvm-wrapper">
7
8 <div id="status">
9
10 <div style="height: 40px;"></div>
11 <h2 class="title"><?php _e( 'Latest Logs', 'fast-velocity-minify' ); ?></h2>
12 <h3 class="fvm-bold-green"><?php _e( 'In this section, you can check the latest logs for CSS and JS files', 'fast-velocity-minify' ); ?></h3>
13 <div class="row-log log-stats wpraiser-textarea"><textarea rows="10" cols="50" class="large-text code row-log log-css" disabled></textarea></div>
14
15 <div style="height: 40px;"></div>
16 <h2 class="title"><?php _e( 'Server Info', 'fast-velocity-minify' ); ?></h2>
17 <h3 class="fvm-bold-green"><?php _e( 'In this section, you can check some server stats and information', 'fast-velocity-minify' ); ?></h3>
18 <textarea rows="10" cols="50" class="large-text code row-log" disabled><?php fvm_get_generalinfo(); ?></textarea>
19
20
21 <script>fvm_get_logs();</script>
22 </div>
23
24 </div>
25 <?php
26
27 }
...\ No newline at end of file ...\ No newline at end of file
1 <?php if( $active_tab == 'upgrade' ) { ?>
2
3 <div class="fvm-wrapper">
4
5 <h2 class="title">Upgrade Your Speed</h2>
6 <h3 class="fvm-bold-green">Would you like to improve your speed and google scores further?</h3>
7
8
9
10 </div>
11 <?php
12 }
1 <div class="wrap">
2 <h1>Fast Velocity Minify</h1>
3 <div style="height: 20px;"></div>
4
5 <?php
6 # get active tab, set default
7 $active_tab = isset($_GET['tab']) ? $_GET['tab'] : 'settings';
8 ?>
9
10 <h2 class="nav-tab-wrapper wp-clearfix">
11 <a href="?page=fvm" class="nav-tab <?php echo $active_tab == 'settings' ? 'nav-tab-active' : ''; ?>"><?php _e( 'Settings', 'fast-velocity-minify' ); ?></a>
12 <a href="?page=fvm&tab=status" class="nav-tab <?php echo $active_tab == 'status' ? 'nav-tab-active' : ''; ?>"><?php _e( 'Status', 'fast-velocity-minify' ); ?></a>
13 <?php /*<a href="?page=fvm&tab=upgrade" class="nav-tab <?php echo $active_tab == 'upgrade' ? 'nav-tab-active' : ''; ?>"><?php _e( 'Upgrade', 'fast-velocity-minify' ); ?></a>*/ ?>
14 <a href="?page=fvm&tab=help" class="nav-tab <?php echo $active_tab == 'help' ? 'nav-tab-active' : ''; ?>"><?php _e( 'Help', 'fast-velocity-minify' ); ?></a>
15 </h2>
16
17 <div id="fvm">
18
19 <?php
20 # settings
21 include_once($fvm_var_dir_path . 'layout' . DIRECTORY_SEPARATOR . 'admin-layout-settings.php');
22
23 # include other tabs
24 include_once($fvm_var_dir_path . 'layout' . DIRECTORY_SEPARATOR . 'admin-layout-status.php');
25 #include_once($fvm_var_dir_path . 'layout' . DIRECTORY_SEPARATOR . 'admin-layout-upgrade.php');
26 include_once($fvm_var_dir_path . 'layout' . DIRECTORY_SEPARATOR . 'admin-layout-help.php');
27 ?>
28
29 </div>
...\ No newline at end of file ...\ No newline at end of file
1
2 # replace
3 namespace MatthiasMullie > namespace FVM\MatthiasMullie
4 use MatthiasMullie > use FVM\MatthiasMullie
...\ No newline at end of file ...\ No newline at end of file
1 #!/usr/bin/env php
2 <?php
3 use MatthiasMullie\Minify;
4
5 // command line utility to minify CSS
6 if (file_exists(__DIR__ . '/../../../autoload.php')) {
7 // if composer install
8 require_once __DIR__ . '/../../../autoload.php';
9 } else {
10 require_once __DIR__ . '/../src/Minify.php';
11 require_once __DIR__ . '/../src/CSS.php';
12 require_once __DIR__ . '/../src/Exception.php';
13 }
14
15 error_reporting(E_ALL);
16 // check PHP setup for cli arguments
17 if (!isset($_SERVER['argv']) && !isset($argv)) {
18 fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
19 exit(1);
20 } elseif (!isset($argv)) {
21 $argv = $_SERVER['argv'];
22 }
23 // check if path to file given
24 if (!isset($argv[1])) {
25 fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
26 exit(1);
27 }
28 // check if script run in cli environment
29 if ('cli' !== php_sapi_name()) {
30 fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
31 exit(1);
32 }
33 // check if source file exists
34 if (!file_exists($argv[1])) {
35 fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
36 exit(1);
37 }
38
39 try {
40 $minifier = new Minify\CSS($argv[1]);
41 echo $minifier->minify();
42 } catch (Exception $e) {
43 fwrite(STDERR, $e->getMessage(), PHP_EOL);
44 exit(1);
45 }
1 #!/usr/bin/env php
2 <?php
3 use MatthiasMullie\Minify;
4
5 // command line utility to minify JS
6 if (file_exists(__DIR__ . '/../../../autoload.php')) {
7 // if composer install
8 require_once __DIR__ . '/../../../autoload.php';
9 } else {
10 require_once __DIR__ . '/../src/Minify.php';
11 require_once __DIR__ . '/../src/JS.php';
12 require_once __DIR__ . '/../src/Exception.php';
13 }
14
15 error_reporting(E_ALL);
16 // check PHP setup for cli arguments
17 if (!isset($_SERVER['argv']) && !isset($argv)) {
18 fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
19 exit(1);
20 } elseif (!isset($argv)) {
21 $argv = $_SERVER['argv'];
22 }
23 // check if path to file given
24 if (!isset($argv[1])) {
25 fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
26 exit(1);
27 }
28 // check if script run in cli environment
29 if ('cli' !== php_sapi_name()) {
30 fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
31 exit(1);
32 }
33 // check if source file exists
34 if (!file_exists($argv[1])) {
35 fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
36 exit(1);
37 }
38
39 try {
40 $minifier = new Minify\JS($argv[1]);
41 echo $minifier->minify();
42 } catch (Exception $e) {
43 fwrite(STDERR, $e->getMessage(), PHP_EOL);
44 exit(1);
45 }
1 in
2 public
3 extends
4 private
5 protected
6 implements
7 instanceof
...\ No newline at end of file ...\ No newline at end of file
1 do
2 in
3 let
4 new
5 var
6 case
7 else
8 enum
9 void
10 with
11 class
12 const
13 yield
14 delete
15 export
16 import
17 public
18 static
19 typeof
20 extends
21 package
22 private
23 function
24 protected
25 implements
26 instanceof
...\ No newline at end of file ...\ No newline at end of file
1 do
2 if
3 in
4 for
5 let
6 new
7 try
8 var
9 case
10 else
11 enum
12 eval
13 null
14 this
15 true
16 void
17 with
18 break
19 catch
20 class
21 const
22 false
23 super
24 throw
25 while
26 yield
27 delete
28 export
29 import
30 public
31 return
32 static
33 switch
34 typeof
35 default
36 extends
37 finally
38 package
39 private
40 continue
41 debugger
42 function
43 arguments
44 interface
45 protected
46 implements
47 instanceof
48 abstract
49 boolean
50 byte
51 char
52 double
53 final
54 float
55 goto
56 int
57 long
58 native
59 short
60 synchronized
61 throws
62 transient
63 volatile
...\ No newline at end of file ...\ No newline at end of file
1 +
2 -
3 *
4 /
5 %
6 =
7 +=
8 -=
9 *=
10 /=
11 %=
12 <<=
13 >>=
14 >>>=
15 &=
16 ^=
17 |=
18 &
19 |
20 ^
21 ~
22 <<
23 >>
24 >>>
25 ==
26 ===
27 !=
28 !==
29 >
30 <
31 >=
32 <=
33 &&
34 ||
35 !
36 .
37 [
38 ]
39 ?
40 :
41 ,
42 ;
43 (
44 )
45 {
46 }
...\ No newline at end of file ...\ No newline at end of file
1 +
2 -
3 *
4 /
5 %
6 =
7 +=
8 -=
9 *=
10 /=
11 %=
12 <<=
13 >>=
14 >>>=
15 &=
16 ^=
17 |=
18 &
19 |
20 ^
21 <<
22 >>
23 >>>
24 ==
25 ===
26 !=
27 !==
28 >
29 <
30 >=
31 <=
32 &&
33 ||
34 .
35 [
36 ]
37 ?
38 :
39 ,
40 ;
41 (
42 )
43 }
...\ No newline at end of file ...\ No newline at end of file
1 +
2 -
3 *
4 /
5 %
6 =
7 +=
8 -=
9 *=
10 /=
11 %=
12 <<=
13 >>=
14 >>>=
15 &=
16 ^=
17 |=
18 &
19 |
20 ^
21 ~
22 <<
23 >>
24 >>>
25 ==
26 ===
27 !=
28 !==
29 >
30 <
31 >=
32 <=
33 &&
34 ||
35 !
36 .
37 [
38 ?
39 :
40 ,
41 ;
42 (
43 {
1 <?php
2 /**
3 * CSS Minifier
4 *
5 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6 *
7 * @author Matthias Mullie <minify@mullie.eu>
8 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9 * @license MIT License
10 */
11
12 namespace FVM\MatthiasMullie\Minify;
13
14 use FVM\MatthiasMullie\Minify\Exceptions\FileImportException;
15 use FVM\MatthiasMullie\PathConverter\ConverterInterface;
16 use FVM\MatthiasMullie\PathConverter\Converter;
17
18 /**
19 * CSS minifier
20 *
21 * Please report bugs on https://github.com/matthiasmullie/minify/issues
22 *
23 * @package Minify
24 * @author Matthias Mullie <minify@mullie.eu>
25 * @author Tijs Verkoyen <minify@verkoyen.eu>
26 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
27 * @license MIT License
28 */
29 class CSS extends Minify
30 {
31 /**
32 * @var int maximum inport size in kB
33 */
34 protected $maxImportSize = 5;
35
36 /**
37 * @var string[] valid import extensions
38 */
39 protected $importExtensions = array(
40 'gif' => 'data:image/gif',
41 'png' => 'data:image/png',
42 'jpe' => 'data:image/jpeg',
43 'jpg' => 'data:image/jpeg',
44 'jpeg' => 'data:image/jpeg',
45 'svg' => 'data:image/svg+xml',
46 'woff' => 'data:application/x-font-woff',
47 'tif' => 'image/tiff',
48 'tiff' => 'image/tiff',
49 'xbm' => 'image/x-xbitmap',
50 );
51
52 /**
53 * Set the maximum size if files to be imported.
54 *
55 * Files larger than this size (in kB) will not be imported into the CSS.
56 * Importing files into the CSS as data-uri will save you some connections,
57 * but we should only import relatively small decorative images so that our
58 * CSS file doesn't get too bulky.
59 *
60 * @param int $size Size in kB
61 */
62 public function setMaxImportSize($size)
63 {
64 $this->maxImportSize = $size;
65 }
66
67 /**
68 * Set the type of extensions to be imported into the CSS (to save network
69 * connections).
70 * Keys of the array should be the file extensions & respective values
71 * should be the data type.
72 *
73 * @param string[] $extensions Array of file extensions
74 */
75 public function setImportExtensions(array $extensions)
76 {
77 $this->importExtensions = $extensions;
78 }
79
80 /**
81 * Move any import statements to the top.
82 *
83 * @param string $content Nearly finished CSS content
84 *
85 * @return string
86 */
87 protected function moveImportsToTop($content)
88 {
89 if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) {
90 // remove from content
91 foreach ($matches[0] as $import) {
92 $content = str_replace($import, '', $content);
93 }
94
95 // add to top
96 $content = implode(';', $matches[2]).';'.trim($content, ';');
97 }
98
99 return $content;
100 }
101
102 /**
103 * Combine CSS from import statements.
104 *
105 * @import's will be loaded and their content merged into the original file,
106 * to save HTTP requests.
107 *
108 * @param string $source The file to combine imports for
109 * @param string $content The CSS content to combine imports for
110 * @param string[] $parents Parent paths, for circular reference checks
111 *
112 * @return string
113 *
114 * @throws FileImportException
115 */
116 protected function combineImports($source, $content, $parents)
117 {
118 $importRegexes = array(
119 // @import url(xxx)
120 '/
121 # import statement
122 @import
123
124 # whitespace
125 \s+
126
127 # open url()
128 url\(
129
130 # (optional) open path enclosure
131 (?P<quotes>["\']?)
132
133 # fetch path
134 (?P<path>.+?)
135
136 # (optional) close path enclosure
137 (?P=quotes)
138
139 # close url()
140 \)
141
142 # (optional) trailing whitespace
143 \s*
144
145 # (optional) media statement(s)
146 (?P<media>[^;]*)
147
148 # (optional) trailing whitespace
149 \s*
150
151 # (optional) closing semi-colon
152 ;?
153
154 /ix',
155
156 // @import 'xxx'
157 '/
158
159 # import statement
160 @import
161
162 # whitespace
163 \s+
164
165 # open path enclosure
166 (?P<quotes>["\'])
167
168 # fetch path
169 (?P<path>.+?)
170
171 # close path enclosure
172 (?P=quotes)
173
174 # (optional) trailing whitespace
175 \s*
176
177 # (optional) media statement(s)
178 (?P<media>[^;]*)
179
180 # (optional) trailing whitespace
181 \s*
182
183 # (optional) closing semi-colon
184 ;?
185
186 /ix',
187 );
188
189 // find all relative imports in css
190 $matches = array();
191 foreach ($importRegexes as $importRegex) {
192 if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
193 $matches = array_merge($matches, $regexMatches);
194 }
195 }
196
197 $search = array();
198 $replace = array();
199
200 // loop the matches
201 foreach ($matches as $match) {
202 // get the path for the file that will be imported
203 $importPath = dirname($source).'/'.$match['path'];
204
205 // only replace the import with the content if we can grab the
206 // content of the file
207 if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
208 continue;
209 }
210
211 // check if current file was not imported previously in the same
212 // import chain.
213 if (in_array($importPath, $parents)) {
214 throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
215 }
216
217 // grab referenced file & minify it (which may include importing
218 // yet other @import statements recursively)
219 $minifier = new static($importPath);
220 $minifier->setMaxImportSize($this->maxImportSize);
221 $minifier->setImportExtensions($this->importExtensions);
222 $importContent = $minifier->execute($source, $parents);
223
224 // check if this is only valid for certain media
225 if (!empty($match['media'])) {
226 $importContent = '@media '.$match['media'].'{'.$importContent.'}';
227 }
228
229 // add to replacement array
230 $search[] = $match[0];
231 $replace[] = $importContent;
232 }
233
234 // replace the import statements
235 return str_replace($search, $replace, $content);
236 }
237
238 /**
239 * Import files into the CSS, base64-ized.
240 *
241 * @url(image.jpg) images will be loaded and their content merged into the
242 * original file, to save HTTP requests.
243 *
244 * @param string $source The file to import files for
245 * @param string $content The CSS content to import files for
246 *
247 * @return string
248 */
249 protected function importFiles($source, $content)
250 {
251 $regex = '/url\((["\']?)(.+?)\\1\)/i';
252 if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
253 $search = array();
254 $replace = array();
255
256 // loop the matches
257 foreach ($matches as $match) {
258 $extension = substr(strrchr($match[2], '.'), 1);
259 if ($extension && !array_key_exists($extension, $this->importExtensions)) {
260 continue;
261 }
262
263 // get the path for the file that will be imported
264 $path = $match[2];
265 $path = dirname($source).'/'.$path;
266
267 // only replace the import with the content if we're able to get
268 // the content of the file, and it's relatively small
269 if ($this->canImportFile($path) && $this->canImportBySize($path)) {
270 // grab content && base64-ize
271 $importContent = $this->load($path);
272 $importContent = base64_encode($importContent);
273
274 // build replacement
275 $search[] = $match[0];
276 $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
277 }
278 }
279
280 // replace the import statements
281 $content = str_replace($search, $replace, $content);
282 }
283
284 return $content;
285 }
286
287 /**
288 * Minify the data.
289 * Perform CSS optimizations.
290 *
291 * @param string[optional] $path Path to write the data to
292 * @param string[] $parents Parent paths, for circular reference checks
293 *
294 * @return string The minified data
295 */
296 public function execute($path = null, $parents = array())
297 {
298 $content = '';
299
300 // loop CSS data (raw data and files)
301 foreach ($this->data as $source => $css) {
302 /*
303 * Let's first take out strings & comments, since we can't just
304 * remove whitespace anywhere. If whitespace occurs inside a string,
305 * we should leave it alone. E.g.:
306 * p { content: "a test" }
307 */
308 $this->extractStrings();
309 $this->stripComments();
310 $this->extractCalcs();
311 $css = $this->replace($css);
312
313 $css = $this->stripWhitespace($css);
314 $css = $this->shortenColors($css);
315 $css = $this->shortenZeroes($css);
316 $css = $this->shortenFontWeights($css);
317 $css = $this->stripEmptyTags($css);
318
319 // restore the string we've extracted earlier
320 $css = $this->restoreExtractedData($css);
321
322 $source = is_int($source) ? '' : $source;
323 $parents = $source ? array_merge($parents, array($source)) : $parents;
324 $css = $this->combineImports($source, $css, $parents);
325 $css = $this->importFiles($source, $css);
326
327 /*
328 * If we'll save to a new path, we'll have to fix the relative paths
329 * to be relative no longer to the source file, but to the new path.
330 * If we don't write to a file, fall back to same path so no
331 * conversion happens (because we still want it to go through most
332 * of the move code, which also addresses url() & @import syntax...)
333 */
334 $converter = $this->getPathConverter($source, $path ?: $source);
335 $css = $this->move($converter, $css);
336
337 // combine css
338 $content .= $css;
339 }
340
341 $content = $this->moveImportsToTop($content);
342
343 return $content;
344 }
345
346 /**
347 * Moving a css file should update all relative urls.
348 * Relative references (e.g. ../images/image.gif) in a certain css file,
349 * will have to be updated when a file is being saved at another location
350 * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
351 *
352 * @param ConverterInterface $converter Relative path converter
353 * @param string $content The CSS content to update relative urls for
354 *
355 * @return string
356 */
357 protected function move(ConverterInterface $converter, $content)
358 {
359 /*
360 * Relative path references will usually be enclosed by url(). @import
361 * is an exception, where url() is not necessary around the path (but is
362 * allowed).
363 * This *could* be 1 regular expression, where both regular expressions
364 * in this array are on different sides of a |. But we're using named
365 * patterns in both regexes, the same name on both regexes. This is only
366 * possible with a (?J) modifier, but that only works after a fairly
367 * recent PCRE version. That's why I'm doing 2 separate regular
368 * expressions & combining the matches after executing of both.
369 */
370 $relativeRegexes = array(
371 // url(xxx)
372 '/
373 # open url()
374 url\(
375
376 \s*
377
378 # open path enclosure
379 (?P<quotes>["\'])?
380
381 # fetch path
382 (?P<path>.+?)
383
384 # close path enclosure
385 (?(quotes)(?P=quotes))
386
387 \s*
388
389 # close url()
390 \)
391
392 /ix',
393
394 // @import "xxx"
395 '/
396 # import statement
397 @import
398
399 # whitespace
400 \s+
401
402 # we don\'t have to check for @import url(), because the
403 # condition above will already catch these
404
405 # open path enclosure
406 (?P<quotes>["\'])
407
408 # fetch path
409 (?P<path>.+?)
410
411 # close path enclosure
412 (?P=quotes)
413
414 /ix',
415 );
416
417 // find all relative urls in css
418 $matches = array();
419 foreach ($relativeRegexes as $relativeRegex) {
420 if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
421 $matches = array_merge($matches, $regexMatches);
422 }
423 }
424
425 $search = array();
426 $replace = array();
427
428 // loop all urls
429 foreach ($matches as $match) {
430 // determine if it's a url() or an @import match
431 $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
432
433 $url = $match['path'];
434 if ($this->canImportByPath($url)) {
435 // attempting to interpret GET-params makes no sense, so let's discard them for awhile
436 $params = strrchr($url, '?');
437 $url = $params ? substr($url, 0, -strlen($params)) : $url;
438
439 // fix relative url
440 $url = $converter->convert($url);
441
442 // now that the path has been converted, re-apply GET-params
443 $url .= $params;
444 }
445
446 /*
447 * Urls with control characters above 0x7e should be quoted.
448 * According to Mozilla's parser, whitespace is only allowed at the
449 * end of unquoted urls.
450 * Urls with `)` (as could happen with data: uris) should also be
451 * quoted to avoid being confused for the url() closing parentheses.
452 * And urls with a # have also been reported to cause issues.
453 * Urls with quotes inside should also remain escaped.
454 *
455 * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
456 * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
457 * @see https://github.com/matthiasmullie/minify/issues/193
458 */
459 $url = trim($url);
460 if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) {
461 $url = $match['quotes'] . $url . $match['quotes'];
462 }
463
464 // build replacement
465 $search[] = $match[0];
466 if ($type === 'url') {
467 $replace[] = 'url('.$url.')';
468 } elseif ($type === 'import') {
469 $replace[] = '@import "'.$url.'"';
470 }
471 }
472
473 // replace urls
474 return str_replace($search, $replace, $content);
475 }
476
477 /**
478 * Shorthand hex color codes.
479 * #FF0000 -> #F00.
480 *
481 * @param string $content The CSS content to shorten the hex color codes for
482 *
483 * @return string
484 */
485 protected function shortenColors($content)
486 {
487 $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content);
488
489 // remove alpha channel if it's pointless...
490 $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content);
491 $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content);
492
493 $colors = array(
494 // we can shorten some even more by replacing them with their color name
495 '#F0FFFF' => 'azure',
496 '#F5F5DC' => 'beige',
497 '#A52A2A' => 'brown',
498 '#FF7F50' => 'coral',
499 '#FFD700' => 'gold',
500 '#808080' => 'gray',
501 '#008000' => 'green',
502 '#4B0082' => 'indigo',
503 '#FFFFF0' => 'ivory',
504 '#F0E68C' => 'khaki',
505 '#FAF0E6' => 'linen',
506 '#800000' => 'maroon',
507 '#000080' => 'navy',
508 '#808000' => 'olive',
509 '#CD853F' => 'peru',
510 '#FFC0CB' => 'pink',
511 '#DDA0DD' => 'plum',
512 '#800080' => 'purple',
513 '#F00' => 'red',
514 '#FA8072' => 'salmon',
515 '#A0522D' => 'sienna',
516 '#C0C0C0' => 'silver',
517 '#FFFAFA' => 'snow',
518 '#D2B48C' => 'tan',
519 '#FF6347' => 'tomato',
520 '#EE82EE' => 'violet',
521 '#F5DEB3' => 'wheat',
522 // or the other way around
523 'WHITE' => '#fff',
524 'BLACK' => '#000',
525 );
526
527 return preg_replace_callback(
528 '/(?<=[: ])('.implode('|', array_keys($colors)).')(?=[; }])/i',
529 function ($match) use ($colors) {
530 return $colors[strtoupper($match[0])];
531 },
532 $content
533 );
534 }
535
536 /**
537 * Shorten CSS font weights.
538 *
539 * @param string $content The CSS content to shorten the font weights for
540 *
541 * @return string
542 */
543 protected function shortenFontWeights($content)
544 {
545 $weights = array(
546 'normal' => 400,
547 'bold' => 700,
548 );
549
550 $callback = function ($match) use ($weights) {
551 return $match[1].$weights[$match[2]];
552 };
553
554 return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
555 }
556
557 /**
558 * Shorthand 0 values to plain 0, instead of e.g. -0em.
559 *
560 * @param string $content The CSS content to shorten the zero values for
561 *
562 * @return string
563 */
564 protected function shortenZeroes($content)
565 {
566 // we don't want to strip units in `calc()` expressions:
567 // `5px - 0px` is valid, but `5px - 0` is not
568 // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but
569 // `10 * 0` is invalid
570 // we've extracted calcs earlier, so we don't need to worry about this
571
572 // reusable bits of code throughout these regexes:
573 // before & after are used to make sure we don't match lose unintended
574 // 0-like values (e.g. in #000, or in http://url/1.0)
575 // units can be stripped from 0 values, or used to recognize non 0
576 // values (where wa may be able to strip a .0 suffix)
577 $before = '(?<=[:(, ])';
578 $after = '(?=[ ,);}])';
579 $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
580
581 // strip units after zeroes (0px -> 0)
582 // NOTE: it should be safe to remove all units for a 0 value, but in
583 // practice, Webkit (especially Safari) seems to stumble over at least
584 // 0%, potentially other units as well. Only stripping 'px' for now.
585 // @see https://github.com/matthiasmullie/minify/issues/60
586 $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
587
588 // strip 0-digits (.0 -> 0)
589 $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
590 // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
591 $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
592 // strip trailing 0: 50.00 -> 50, 50.00px -> 50px
593 $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
594 // strip leading 0: 0.1 -> .1, 01.1 -> 1.1
595 $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
596
597 // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
598 $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
599
600 // IE doesn't seem to understand a unitless flex-basis value (correct -
601 // it goes against the spec), so let's add it in again (make it `%`,
602 // which is only 1 char: 0%, 0px, 0 anything, it's all just the same)
603 // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex
604 $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content);
605 $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
606
607 return $content;
608 }
609
610 /**
611 * Strip empty tags from source code.
612 *
613 * @param string $content
614 *
615 * @return string
616 */
617 protected function stripEmptyTags($content)
618 {
619 $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content);
620 $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content);
621
622 return $content;
623 }
624
625 /**
626 * Strip comments from source code.
627 */
628 protected function stripComments()
629 {
630 // PHP only supports $this inside anonymous functions since 5.4
631 $minifier = $this;
632 $callback = function ($match) use ($minifier) {
633 $count = count($minifier->extracted);
634 $placeholder = '/*'.$count.'*/';
635 $minifier->extracted[$placeholder] = $match[0];
636
637 return $placeholder;
638 };
639 $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
640
641 $this->registerPattern('/\/\*.*?\*\//s', '');
642 }
643
644 /**
645 * Strip whitespace.
646 *
647 * @param string $content The CSS content to strip the whitespace for
648 *
649 * @return string
650 */
651 protected function stripWhitespace($content)
652 {
653 // remove leading & trailing whitespace
654 $content = preg_replace('/^\s*/m', '', $content);
655 $content = preg_replace('/\s*$/m', '', $content);
656
657 // replace newlines with a single space
658 $content = preg_replace('/\s+/', ' ', $content);
659
660 // remove whitespace around meta characters
661 // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
662 $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
663 $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content);
664 $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content);
665 $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
666
667 // whitespace around + and - can only be stripped inside some pseudo-
668 // classes, like `:nth-child(3+2n)`
669 // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
670 // selectors like `div.weird- p`
671 $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type');
672 $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
673
674 // remove semicolon/whitespace followed by closing bracket
675 $content = str_replace(';}', '}', $content);
676
677 return trim($content);
678 }
679
680 /**
681 * Replace all `calc()` occurrences.
682 */
683 protected function extractCalcs()
684 {
685 // PHP only supports $this inside anonymous functions since 5.4
686 $minifier = $this;
687 $callback = function ($match) use ($minifier) {
688 $length = strlen($match[1]);
689 $expr = '';
690 $opened = 0;
691
692 for ($i = 0; $i < $length; $i++) {
693 $char = $match[1][$i];
694 $expr .= $char;
695 if ($char === '(') {
696 $opened++;
697 } elseif ($char === ')' && --$opened === 0) {
698 break;
699 }
700 }
701 $rest = str_replace($expr, '', $match[1]);
702 $expr = trim(substr($expr, 1, -1));
703
704 $count = count($minifier->extracted);
705 $placeholder = 'calc('.$count.')';
706 $minifier->extracted[$placeholder] = 'calc('.$expr.')';
707
708 return $placeholder.$rest;
709 };
710
711 $this->registerPattern('/calc(\(.+?)(?=$|;|}|calc\()/', $callback);
712 $this->registerPattern('/calc(\(.+?)(?=$|;|}|calc\()/m', $callback);
713 }
714
715 /**
716 * Check if file is small enough to be imported.
717 *
718 * @param string $path The path to the file
719 *
720 * @return bool
721 */
722 protected function canImportBySize($path)
723 {
724 return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
725 }
726
727 /**
728 * Check if file a file can be imported, going by the path.
729 *
730 * @param string $path
731 *
732 * @return bool
733 */
734 protected function canImportByPath($path)
735 {
736 return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
737 }
738
739 /**
740 * Return a converter to update relative paths to be relative to the new
741 * destination.
742 *
743 * @param string $source
744 * @param string $target
745 *
746 * @return ConverterInterface
747 */
748 protected function getPathConverter($source, $target)
749 {
750 return new Converter($source, $target);
751 }
752 }
1 <?php
2 /**
3 * Base Exception
4 *
5 * @deprecated Use Exceptions\BasicException instead
6 *
7 * @author Matthias Mullie <minify@mullie.eu>
8 */
9 namespace FVM\MatthiasMullie\Minify;
10
11 /**
12 * Base Exception Class
13 * @deprecated Use Exceptions\BasicException instead
14 *
15 * @package Minify
16 * @author Matthias Mullie <minify@mullie.eu>
17 */
18 abstract class Exception extends \Exception
19 {
20 }
1 <?php
2 /**
3 * Basic exception
4 *
5 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6 *
7 * @author Matthias Mullie <minify@mullie.eu>
8 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9 * @license MIT License
10 */
11 namespace FVM\MatthiasMullie\Minify\Exceptions;
12
13 use FVM\MatthiasMullie\Minify\Exception;
14
15 /**
16 * Basic Exception Class
17 *
18 * @package Minify\Exception
19 * @author Matthias Mullie <minify@mullie.eu>
20 */
21 abstract class BasicException extends Exception
22 {
23 }
1 <?php
2 /**
3 * File Import Exception
4 *
5 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6 *
7 * @author Matthias Mullie <minify@mullie.eu>
8 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9 * @license MIT License
10 */
11 namespace FVM\MatthiasMullie\Minify\Exceptions;
12
13 /**
14 * File Import Exception Class
15 *
16 * @package Minify\Exception
17 * @author Matthias Mullie <minify@mullie.eu>
18 */
19 class FileImportException extends BasicException
20 {
21 }
1 <?php
2 /**
3 * IO Exception
4 *
5 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6 *
7 * @author Matthias Mullie <minify@mullie.eu>
8 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9 * @license MIT License
10 */
11 namespace FVM\MatthiasMullie\Minify\Exceptions;
12
13 /**
14 * IO Exception Class
15 *
16 * @package Minify\Exception
17 * @author Matthias Mullie <minify@mullie.eu>
18 */
19 class IOException extends BasicException
20 {
21 }
1 <?php
2 /**
3 * JavaScript minifier
4 *
5 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6 *
7 * @author Matthias Mullie <minify@mullie.eu>
8 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9 * @license MIT License
10 */
11 namespace FVM\MatthiasMullie\Minify;
12
13 /**
14 * JavaScript Minifier Class
15 *
16 * Please report bugs on https://github.com/matthiasmullie/minify/issues
17 *
18 * @package Minify
19 * @author Matthias Mullie <minify@mullie.eu>
20 * @author Tijs Verkoyen <minify@verkoyen.eu>
21 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
22 * @license MIT License
23 */
24 class JS extends Minify
25 {
26 /**
27 * Var-matching regex based on http://stackoverflow.com/a/9337047/802993.
28 *
29 * Note that regular expressions using that bit must have the PCRE_UTF8
30 * pattern modifier (/u) set.
31 *
32 * @var string
33 */
34 const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b';
35
36 /**
37 * Full list of JavaScript reserved words.
38 * Will be loaded from /data/js/keywords_reserved.txt.
39 *
40 * @see https://mathiasbynens.be/notes/reserved-keywords
41 *
42 * @var string[]
43 */
44 protected $keywordsReserved = array();
45
46 /**
47 * List of JavaScript reserved words that accept a <variable, value, ...>
48 * after them. Some end of lines are not the end of a statement, like with
49 * these keywords.
50 *
51 * E.g.: we shouldn't insert a ; after this else
52 * else
53 * console.log('this is quite fine')
54 *
55 * Will be loaded from /data/js/keywords_before.txt
56 *
57 * @var string[]
58 */
59 protected $keywordsBefore = array();
60
61 /**
62 * List of JavaScript reserved words that accept a <variable, value, ...>
63 * before them. Some end of lines are not the end of a statement, like when
64 * continued by one of these keywords on the newline.
65 *
66 * E.g.: we shouldn't insert a ; before this instanceof
67 * variable
68 * instanceof String
69 *
70 * Will be loaded from /data/js/keywords_after.txt
71 *
72 * @var string[]
73 */
74 protected $keywordsAfter = array();
75
76 /**
77 * List of all JavaScript operators.
78 *
79 * Will be loaded from /data/js/operators.txt
80 *
81 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
82 *
83 * @var string[]
84 */
85 protected $operators = array();
86
87 /**
88 * List of JavaScript operators that accept a <variable, value, ...> after
89 * them. Some end of lines are not the end of a statement, like with these
90 * operators.
91 *
92 * Note: Most operators are fine, we've only removed ++ and --.
93 * ++ & -- have to be joined with the value they're in-/decrementing.
94 *
95 * Will be loaded from /data/js/operators_before.txt
96 *
97 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
98 *
99 * @var string[]
100 */
101 protected $operatorsBefore = array();
102
103 /**
104 * List of JavaScript operators that accept a <variable, value, ...> before
105 * them. Some end of lines are not the end of a statement, like when
106 * continued by one of these operators on the newline.
107 *
108 * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~.
109 * There can't be a newline separating ! or ~ and whatever it is negating.
110 * ++ & -- have to be joined with the value they're in-/decrementing.
111 * ) & ] are "special" in that they have lots or usecases. () for example
112 * is used for function calls, for grouping, in if () and for (), ...
113 *
114 * Will be loaded from /data/js/operators_after.txt
115 *
116 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
117 *
118 * @var string[]
119 */
120 protected $operatorsAfter = array();
121
122 /**
123 * {@inheritdoc}
124 */
125 public function __construct()
126 {
127 call_user_func_array(array('parent', '__construct'), func_get_args());
128
129 $dataDir = __DIR__.'/../data/js/';
130 $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
131 $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options);
132 $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options);
133 $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options);
134 $this->operators = file($dataDir.'operators.txt', $options);
135 $this->operatorsBefore = file($dataDir.'operators_before.txt', $options);
136 $this->operatorsAfter = file($dataDir.'operators_after.txt', $options);
137 }
138
139 /**
140 * Minify the data.
141 * Perform JS optimizations.
142 *
143 * @param string[optional] $path Path to write the data to
144 *
145 * @return string The minified data
146 */
147 public function execute($path = null)
148 {
149 $content = '';
150
151 /*
152 * Let's first take out strings, comments and regular expressions.
153 * All of these can contain JS code-like characters, and we should make
154 * sure any further magic ignores anything inside of these.
155 *
156 * Consider this example, where we should not strip any whitespace:
157 * var str = "a test";
158 *
159 * Comments will be removed altogether, strings and regular expressions
160 * will be replaced by placeholder text, which we'll restore later.
161 */
162 $this->extractStrings('\'"`');
163 $this->stripComments();
164 $this->extractRegex();
165
166 // loop files
167 foreach ($this->data as $source => $js) {
168 // take out strings, comments & regex (for which we've registered
169 // the regexes just a few lines earlier)
170 $js = $this->replace($js);
171
172 $js = $this->propertyNotation($js);
173 $js = $this->shortenBools($js);
174 $js = $this->stripWhitespace($js);
175
176 // combine js: separating the scripts by a ;
177 $content .= $js.";";
178 }
179
180 // clean up leftover `;`s from the combination of multiple scripts
181 $content = ltrim($content, ';');
182 $content = (string) substr($content, 0, -1);
183
184 /*
185 * Earlier, we extracted strings & regular expressions and replaced them
186 * with placeholder text. This will restore them.
187 */
188 $content = $this->restoreExtractedData($content);
189
190 return $content;
191 }
192
193 /**
194 * Strip comments from source code.
195 */
196 protected function stripComments()
197 {
198 // PHP only supports $this inside anonymous functions since 5.4
199 $minifier = $this;
200 $callback = function ($match) use ($minifier) {
201 $count = count($minifier->extracted);
202 $placeholder = '/*'.$count.'*/';
203 $minifier->extracted[$placeholder] = $match[0];
204
205 return $placeholder;
206 };
207 // multi-line comments
208 $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
209 $this->registerPattern('/\/\*.*?\*\//s', '');
210
211 // single-line comments
212 $this->registerPattern('/\/\/.*$/m', '');
213 }
214
215 /**
216 * JS can have /-delimited regular expressions, like: /ab+c/.match(string).
217 *
218 * The content inside the regex can contain characters that may be confused
219 * for JS code: e.g. it could contain whitespace it needs to match & we
220 * don't want to strip whitespace in there.
221 *
222 * The regex can be pretty simple: we don't have to care about comments,
223 * (which also use slashes) because stripComments() will have stripped those
224 * already.
225 *
226 * This method will replace all string content with simple REGEX#
227 * placeholder text, so we've rid all regular expressions from characters
228 * that may be misinterpreted. Original regex content will be saved in
229 * $this->extracted and after doing all other minifying, we can restore the
230 * original content via restoreRegex()
231 */
232 protected function extractRegex()
233 {
234 // PHP only supports $this inside anonymous functions since 5.4
235 $minifier = $this;
236 $callback = function ($match) use ($minifier) {
237 $count = count($minifier->extracted);
238 $placeholder = '"'.$count.'"';
239 $minifier->extracted[$placeholder] = $match[0];
240
241 return $placeholder;
242 };
243
244 // match all chars except `/` and `\`
245 // `\` is allowed though, along with whatever char follows (which is the
246 // one being escaped)
247 // this should allow all chars, except for an unescaped `/` (= the one
248 // closing the regex)
249 // then also ignore bare `/` inside `[]`, where they don't need to be
250 // escaped: anything inside `[]` can be ignored safely
251 $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*';
252
253 // a regular expression can only be followed by a few operators or some
254 // of the RegExp methods (a `\` followed by a variable or value is
255 // likely part of a division, not a regex)
256 $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof');
257 $before = '([=:,;\+\-\*\/\}\(\{\[&\|!]|^|'.implode('|', $keywords).')\s*';
258 $propertiesAndMethods = array(
259 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2
260 'constructor',
261 'flags',
262 'global',
263 'ignoreCase',
264 'multiline',
265 'source',
266 'sticky',
267 'unicode',
268 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2
269 'compile(',
270 'exec(',
271 'test(',
272 'toSource(',
273 'toString(',
274 );
275 $delimiters = array_fill(0, count($propertiesAndMethods), '/');
276 $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters);
277 $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))';
278 $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
279
280 // regular expressions following a `)` are rather annoying to detect...
281 // quite often, `/` after `)` is a division operator & if it happens to
282 // be followed by another one (or a comment), it is likely to be
283 // confused for a regular expression
284 // however, it's perfectly possible for a regex to follow a `)`: after
285 // a single-line `if()`, `while()`, ... statement, for example
286 // since, when they occur like that, they're always the start of a
287 // statement, there's only a limited amount of ways they can be useful:
288 // by calling the regex methods directly
289 // if a regex following `)` is not followed by `.<property or method>`,
290 // it's quite likely not a regex
291 $before = '\)\s*';
292 $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))';
293 $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
294
295 // 1 more edge case: a regex can be followed by a lot more operators or
296 // keywords if there's a newline (ASI) in between, where the operator
297 // actually starts a new statement
298 // (https://github.com/matthiasmullie/minify/issues/56)
299 $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/');
300 $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/');
301 $after = '(?=\s*\n\s*('.implode('|', $operators).'))';
302 $this->registerPattern('/'.$pattern.$after.'/', $callback);
303 }
304
305 /**
306 * Strip whitespace.
307 *
308 * We won't strip *all* whitespace, but as much as possible. The thing that
309 * we'll preserve are newlines we're unsure about.
310 * JavaScript doesn't require statements to be terminated with a semicolon.
311 * It will automatically fix missing semicolons with ASI (automatic semi-
312 * colon insertion) at the end of line causing errors (without semicolon.)
313 *
314 * Because it's sometimes hard to tell if a newline is part of a statement
315 * that should be terminated or not, we'll just leave some of them alone.
316 *
317 * @param string $content The content to strip the whitespace for
318 *
319 * @return string
320 */
321 protected function stripWhitespace($content)
322 {
323 // uniform line endings, make them all line feed
324 $content = str_replace(array("\r\n", "\r"), "\n", $content);
325
326 // collapse all non-line feed whitespace into a single space
327 $content = preg_replace('/[^\S\n]+/', ' ', $content);
328
329 // strip leading & trailing whitespace
330 $content = str_replace(array(" \n", "\n "), "\n", $content);
331
332 // collapse consecutive line feeds into just 1
333 $content = preg_replace('/\n+/', "\n", $content);
334
335 $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/');
336 $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/');
337 $operators = $this->getOperatorsForRegex($this->operators, '/');
338 $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/');
339 $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/');
340
341 // strip whitespace that ends in (or next line begin with) an operator
342 // that allows statements to be broken up over multiple lines
343 unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']);
344 $content = preg_replace(
345 array(
346 '/('.implode('|', $operatorsBefore).')\s+/',
347 '/\s+('.implode('|', $operatorsAfter).')/',
348 ),
349 '\\1',
350 $content
351 );
352
353 // make sure + and - can't be mistaken for, or joined into ++ and --
354 $content = preg_replace(
355 array(
356 '/(?<![\+\-])\s*([\+\-])(?![\+\-])/',
357 '/(?<![\+\-])([\+\-])\s*(?![\+\-])/',
358 ),
359 '\\1',
360 $content
361 );
362
363 // collapse whitespace around reserved words into single space
364 $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content);
365 $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content);
366
367 /*
368 * We didn't strip whitespace after a couple of operators because they
369 * could be used in different contexts and we can't be sure it's ok to
370 * strip the newlines. However, we can safely strip any non-line feed
371 * whitespace that follows them.
372 */
373 $operatorsDiffBefore = array_diff($operators, $operatorsBefore);
374 $operatorsDiffAfter = array_diff($operators, $operatorsAfter);
375 $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content);
376 $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content);
377
378 /*
379 * Whitespace after `return` can be omitted in a few occasions
380 * (such as when followed by a string or regex)
381 * Same for whitespace in between `)` and `{`, or between `{` and some
382 * keywords.
383 */
384 $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content);
385 $content = preg_replace('/\)\s+\{/', '){', $content);
386 $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content);
387
388 /*
389 * Get rid of double semicolons, except where they can be used like:
390 * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))".
391 * I'll safeguard these double semicolons inside for-loops by
392 * temporarily replacing them with an invalid condition: they won't have
393 * a double semicolon and will be easy to spot to restore afterwards.
394 */
395 $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content);
396 $content = preg_replace('/;+/', ';', $content);
397 $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content);
398
399 /*
400 * Next, we'll be removing all semicolons where ASI kicks in.
401 * for-loops however, can have an empty body (ending in only a
402 * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);`
403 * Here, nothing happens during the loop; it's just used to keep
404 * increasing `i`. With that ; omitted, the next line would be expected
405 * to be the for-loop's body... Same goes for while loops.
406 * I'm going to double that semicolon (if any) so after the next line,
407 * which strips semicolons here & there, we're still left with this one.
408 */
409 $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content);
410 $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
411 /*
412 * Below will also keep `;` after a `do{}while();` along with `while();`
413 * While these could be stripped after do-while, detecting this
414 * distinction is cumbersome, so I'll play it safe and make sure `;`
415 * after any kind of `while` is kept.
416 */
417 $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
418
419 /*
420 * We also can't strip empty else-statements. Even though they're
421 * useless and probably shouldn't be in the code in the first place, we
422 * shouldn't be stripping the `;` that follows it as it breaks the code.
423 * We can just remove those useless else-statements completely.
424 *
425 * @see https://github.com/matthiasmullie/minify/issues/91
426 */
427 $content = preg_replace('/else;/s', '', $content);
428
429 /*
430 * We also don't really want to terminate statements followed by closing
431 * curly braces (which we've ignored completely up until now) or end-of-
432 * script: ASI will kick in here & we're all about minifying.
433 * Semicolons at beginning of the file don't make any sense either.
434 */
435 $content = preg_replace('/;(\}|$)/s', '\\1', $content);
436 $content = ltrim($content, ';');
437
438 // get rid of remaining whitespace af beginning/end
439 return trim($content);
440 }
441
442 /**
443 * We'll strip whitespace around certain operators with regular expressions.
444 * This will prepare the given array by escaping all characters.
445 *
446 * @param string[] $operators
447 * @param string $delimiter
448 *
449 * @return string[]
450 */
451 protected function getOperatorsForRegex(array $operators, $delimiter = '/')
452 {
453 // escape operators for use in regex
454 $delimiters = array_fill(0, count($operators), $delimiter);
455 $escaped = array_map('preg_quote', $operators, $delimiters);
456
457 $operators = array_combine($operators, $escaped);
458
459 // ignore + & - for now, they'll get special treatment
460 unset($operators['+'], $operators['-']);
461
462 // dot can not just immediately follow a number; it can be confused for
463 // decimal point, or calling a method on it, e.g. 42 .toString()
464 $operators['.'] = '(?<![0-9]\s)\.';
465
466 // don't confuse = with other assignment shortcuts (e.g. +=)
467 $chars = preg_quote('+-*\=<>%&|', $delimiter);
468 $operators['='] = '(?<!['.$chars.'])\=';
469
470 return $operators;
471 }
472
473 /**
474 * We'll strip whitespace around certain keywords with regular expressions.
475 * This will prepare the given array by escaping all characters.
476 *
477 * @param string[] $keywords
478 * @param string $delimiter
479 *
480 * @return string[]
481 */
482 protected function getKeywordsForRegex(array $keywords, $delimiter = '/')
483 {
484 // escape keywords for use in regex
485 $delimiter = array_fill(0, count($keywords), $delimiter);
486 $escaped = array_map('preg_quote', $keywords, $delimiter);
487
488 // add word boundaries
489 array_walk($keywords, function ($value) {
490 return '\b'.$value.'\b';
491 });
492
493 $keywords = array_combine($keywords, $escaped);
494
495 return $keywords;
496 }
497
498 /**
499 * Replaces all occurrences of array['key'] by array.key.
500 *
501 * @param string $content
502 *
503 * @return string
504 */
505 protected function propertyNotation($content)
506 {
507 // PHP only supports $this inside anonymous functions since 5.4
508 $minifier = $this;
509 $keywords = $this->keywordsReserved;
510 $callback = function ($match) use ($minifier, $keywords) {
511 $property = trim($minifier->extracted[$match[1]], '\'"');
512
513 /*
514 * Check if the property is a reserved keyword. In this context (as
515 * property of an object literal/array) it shouldn't matter, but IE8
516 * freaks out with "Expected identifier".
517 */
518 if (in_array($property, $keywords)) {
519 return $match[0];
520 }
521
522 /*
523 * See if the property is in a variable-like format (e.g.
524 * array['key-here'] can't be replaced by array.key-here since '-'
525 * is not a valid character there.
526 */
527 if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) {
528 return $match[0];
529 }
530
531 return '.'.$property;
532 };
533
534 /*
535 * Figure out if previous character is a variable name (of the array
536 * we want to use property notation on) - this is to make sure
537 * standalone ['value'] arrays aren't confused for keys-of-an-array.
538 * We can (and only have to) check the last character, because PHP's
539 * regex implementation doesn't allow unfixed-length look-behind
540 * assertions.
541 */
542 preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
543 $previousChar = $previousChar[1];
544
545 /*
546 * Make sure word preceding the ['value'] is not a keyword, e.g.
547 * return['x']. Because -again- PHP's regex implementation doesn't allow
548 * unfixed-length look-behind assertions, I'm just going to do a lot of
549 * separate look-behind assertions, one for each keyword.
550 */
551 $keywords = $this->getKeywordsForRegex($keywords);
552 $keywords = '(?<!'.implode(')(?<!', $keywords).')';
553
554 return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content);
555 }
556
557 /**
558 * Replaces true & false by !0 and !1.
559 *
560 * @param string $content
561 *
562 * @return string
563 */
564 protected function shortenBools($content)
565 {
566 /*
567 * 'true' or 'false' could be used as property names (which may be
568 * followed by whitespace) - we must not replace those!
569 * Since PHP doesn't allow variable-length (to account for the
570 * whitespace) lookbehind assertions, I need to capture the leading
571 * character and check if it's a `.`
572 */
573 $callback = function ($match) {
574 if (trim($match[1]) === '.') {
575 return $match[0];
576 }
577
578 return $match[1].($match[2] === 'true' ? '!0' : '!1');
579 };
580 $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content);
581
582 // for(;;) is exactly the same as while(true), but shorter :)
583 $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content);
584
585 // now make sure we didn't turn any do ... while(true) into do ... for(;;)
586 preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
587
588 // go backward to make sure positional offsets aren't altered when $content changes
589 $dos = array_reverse($dos);
590 foreach ($dos as $do) {
591 $offsetDo = $do[0][1];
592
593 // find all `while` (now `for`) following `do`: one of those must be
594 // associated with the `do` and be turned back into `while`
595 preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo);
596 foreach ($whiles as $while) {
597 $offsetWhile = $while[0][1];
598
599 $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo);
600 $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo);
601 if ($open === $close) {
602 // only restore `while` if amount of `{` and `}` are the same;
603 // otherwise, that `for` isn't associated with this `do`
604 $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)'));
605 break;
606 }
607 }
608 }
609
610 return $content;
611 }
612 }
1 <?php
2 /**
3 * Abstract minifier class
4 *
5 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6 *
7 * @author Matthias Mullie <minify@mullie.eu>
8 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9 * @license MIT License
10 */
11 namespace FVM\MatthiasMullie\Minify;
12
13 use FVM\MatthiasMullie\Minify\Exceptions\IOException;
14 use Psr\Cache\CacheItemInterface;
15
16 /**
17 * Abstract minifier class.
18 *
19 * Please report bugs on https://github.com/matthiasmullie/minify/issues
20 *
21 * @package Minify
22 * @author Matthias Mullie <minify@mullie.eu>
23 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
24 * @license MIT License
25 */
26 abstract class Minify
27 {
28 /**
29 * The data to be minified.
30 *
31 * @var string[]
32 */
33 protected $data = array();
34
35 /**
36 * Array of patterns to match.
37 *
38 * @var string[]
39 */
40 protected $patterns = array();
41
42 /**
43 * This array will hold content of strings and regular expressions that have
44 * been extracted from the JS source code, so we can reliably match "code",
45 * without having to worry about potential "code-like" characters inside.
46 *
47 * @var string[]
48 */
49 public $extracted = array();
50
51 /**
52 * Init the minify class - optionally, code may be passed along already.
53 */
54 public function __construct(/* $data = null, ... */)
55 {
56 // it's possible to add the source through the constructor as well ;)
57 if (func_num_args()) {
58 call_user_func_array(array($this, 'add'), func_get_args());
59 }
60 }
61
62 /**
63 * Add a file or straight-up code to be minified.
64 *
65 * @param string|string[] $data
66 *
67 * @return static
68 */
69 public function add($data /* $data = null, ... */)
70 {
71 // bogus "usage" of parameter $data: scrutinizer warns this variable is
72 // not used (we're using func_get_args instead to support overloading),
73 // but it still needs to be defined because it makes no sense to have
74 // this function without argument :)
75 $args = array($data) + func_get_args();
76
77 // this method can be overloaded
78 foreach ($args as $data) {
79 if (is_array($data)) {
80 call_user_func_array(array($this, 'add'), $data);
81 continue;
82 }
83
84 // redefine var
85 $data = (string) $data;
86
87 // load data
88 $value = $this->load($data);
89 $key = ($data != $value) ? $data : count($this->data);
90
91 // replace CR linefeeds etc.
92 // @see https://github.com/matthiasmullie/minify/pull/139
93 $value = str_replace(array("\r\n", "\r"), "\n", $value);
94
95 // store data
96 $this->data[$key] = $value;
97 }
98
99 return $this;
100 }
101
102 /**
103 * Add a file to be minified.
104 *
105 * @param string|string[] $data
106 *
107 * @return static
108 *
109 * @throws IOException
110 */
111 public function addFile($data /* $data = null, ... */)
112 {
113 // bogus "usage" of parameter $data: scrutinizer warns this variable is
114 // not used (we're using func_get_args instead to support overloading),
115 // but it still needs to be defined because it makes no sense to have
116 // this function without argument :)
117 $args = array($data) + func_get_args();
118
119 // this method can be overloaded
120 foreach ($args as $path) {
121 if (is_array($path)) {
122 call_user_func_array(array($this, 'addFile'), $path);
123 continue;
124 }
125
126 // redefine var
127 $path = (string) $path;
128
129 // check if we can read the file
130 if (!$this->canImportFile($path)) {
131 throw new IOException('The file "'.$path.'" could not be opened for reading. Check if PHP has enough permissions.');
132 }
133
134 $this->add($path);
135 }
136
137 return $this;
138 }
139
140 /**
141 * Minify the data & (optionally) saves it to a file.
142 *
143 * @param string[optional] $path Path to write the data to
144 *
145 * @return string The minified data
146 */
147 public function minify($path = null)
148 {
149 $content = $this->execute($path);
150
151 // save to path
152 if ($path !== null) {
153 $this->save($content, $path);
154 }
155
156 return $content;
157 }
158
159 /**
160 * Minify & gzip the data & (optionally) saves it to a file.
161 *
162 * @param string[optional] $path Path to write the data to
163 * @param int[optional] $level Compression level, from 0 to 9
164 *
165 * @return string The minified & gzipped data
166 */
167 public function gzip($path = null, $level = 9)
168 {
169 $content = $this->execute($path);
170 $content = gzencode($content, $level, FORCE_GZIP);
171
172 // save to path
173 if ($path !== null) {
174 $this->save($content, $path);
175 }
176
177 return $content;
178 }
179
180 /**
181 * Minify the data & write it to a CacheItemInterface object.
182 *
183 * @param CacheItemInterface $item Cache item to write the data to
184 *
185 * @return CacheItemInterface Cache item with the minifier data
186 */
187 public function cache(CacheItemInterface $item)
188 {
189 $content = $this->execute();
190 $item->set($content);
191
192 return $item;
193 }
194
195 /**
196 * Minify the data.
197 *
198 * @param string[optional] $path Path to write the data to
199 *
200 * @return string The minified data
201 */
202 abstract public function execute($path = null);
203
204 /**
205 * Load data.
206 *
207 * @param string $data Either a path to a file or the content itself
208 *
209 * @return string
210 */
211 protected function load($data)
212 {
213 // check if the data is a file
214 if ($this->canImportFile($data)) {
215 $data = file_get_contents($data);
216
217 // strip BOM, if any
218 if (substr($data, 0, 3) == "\xef\xbb\xbf") {
219 $data = substr($data, 3);
220 }
221 }
222
223 return $data;
224 }
225
226 /**
227 * Save to file.
228 *
229 * @param string $content The minified data
230 * @param string $path The path to save the minified data to
231 *
232 * @throws IOException
233 */
234 protected function save($content, $path)
235 {
236 $handler = $this->openFileForWriting($path);
237
238 $this->writeToFile($handler, $content);
239
240 @fclose($handler);
241 }
242
243 /**
244 * Register a pattern to execute against the source content.
245 *
246 * @param string $pattern PCRE pattern
247 * @param string|callable $replacement Replacement value for matched pattern
248 */
249 protected function registerPattern($pattern, $replacement = '')
250 {
251 // study the pattern, we'll execute it more than once
252 $pattern .= 'S';
253
254 $this->patterns[] = array($pattern, $replacement);
255 }
256
257 /**
258 * We can't "just" run some regular expressions against JavaScript: it's a
259 * complex language. E.g. having an occurrence of // xyz would be a comment,
260 * unless it's used within a string. Of you could have something that looks
261 * like a 'string', but inside a comment.
262 * The only way to accurately replace these pieces is to traverse the JS one
263 * character at a time and try to find whatever starts first.
264 *
265 * @param string $content The content to replace patterns in
266 *
267 * @return string The (manipulated) content
268 */
269 protected function replace($content)
270 {
271 $processed = '';
272 $positions = array_fill(0, count($this->patterns), -1);
273 $matches = array();
274
275 while ($content) {
276 // find first match for all patterns
277 foreach ($this->patterns as $i => $pattern) {
278 list($pattern, $replacement) = $pattern;
279
280 // we can safely ignore patterns for positions we've unset earlier,
281 // because we know these won't show up anymore
282 if (array_key_exists($i, $positions) == false) {
283 continue;
284 }
285
286 // no need to re-run matches that are still in the part of the
287 // content that hasn't been processed
288 if ($positions[$i] >= 0) {
289 continue;
290 }
291
292 $match = null;
293 if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) {
294 $matches[$i] = $match;
295
296 // we'll store the match position as well; that way, we
297 // don't have to redo all preg_matches after changing only
298 // the first (we'll still know where those others are)
299 $positions[$i] = $match[0][1];
300 } else {
301 // if the pattern couldn't be matched, there's no point in
302 // executing it again in later runs on this same content;
303 // ignore this one until we reach end of content
304 unset($matches[$i], $positions[$i]);
305 }
306 }
307
308 // no more matches to find: everything's been processed, break out
309 if (!$matches) {
310 $processed .= $content;
311 break;
312 }
313
314 // see which of the patterns actually found the first thing (we'll
315 // only want to execute that one, since we're unsure if what the
316 // other found was not inside what the first found)
317 $discardLength = min($positions);
318 $firstPattern = array_search($discardLength, $positions);
319 $match = $matches[$firstPattern][0][0];
320
321 // execute the pattern that matches earliest in the content string
322 list($pattern, $replacement) = $this->patterns[$firstPattern];
323 $replacement = $this->replacePattern($pattern, $replacement, $content);
324
325 // figure out which part of the string was unmatched; that's the
326 // part we'll execute the patterns on again next
327 $content = (string) substr($content, $discardLength);
328 $unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
329
330 // move the replaced part to $processed and prepare $content to
331 // again match batch of patterns against
332 $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
333 $content = $unmatched;
334
335 // first match has been replaced & that content is to be left alone,
336 // the next matches will start after this replacement, so we should
337 // fix their offsets
338 foreach ($positions as $i => $position) {
339 $positions[$i] -= $discardLength + strlen($match);
340 }
341 }
342
343 return $processed;
344 }
345
346 /**
347 * This is where a pattern is matched against $content and the matches
348 * are replaced by their respective value.
349 * This function will be called plenty of times, where $content will always
350 * move up 1 character.
351 *
352 * @param string $pattern Pattern to match
353 * @param string|callable $replacement Replacement value
354 * @param string $content Content to match pattern against
355 *
356 * @return string
357 */
358 protected function replacePattern($pattern, $replacement, $content)
359 {
360 if (is_callable($replacement)) {
361 return preg_replace_callback($pattern, $replacement, $content, 1, $count);
362 } else {
363 return preg_replace($pattern, $replacement, $content, 1, $count);
364 }
365 }
366
367 /**
368 * Strings are a pattern we need to match, in order to ignore potential
369 * code-like content inside them, but we just want all of the string
370 * content to remain untouched.
371 *
372 * This method will replace all string content with simple STRING#
373 * placeholder text, so we've rid all strings from characters that may be
374 * misinterpreted. Original string content will be saved in $this->extracted
375 * and after doing all other minifying, we can restore the original content
376 * via restoreStrings().
377 *
378 * @param string[optional] $chars
379 * @param string[optional] $placeholderPrefix
380 */
381 protected function extractStrings($chars = '\'"', $placeholderPrefix = '')
382 {
383 // PHP only supports $this inside anonymous functions since 5.4
384 $minifier = $this;
385 $callback = function ($match) use ($minifier, $placeholderPrefix) {
386 // check the second index here, because the first always contains a quote
387 if ($match[2] === '') {
388 /*
389 * Empty strings need no placeholder; they can't be confused for
390 * anything else anyway.
391 * But we still needed to match them, for the extraction routine
392 * to skip over this particular string.
393 */
394 return $match[0];
395 }
396
397 $count = count($minifier->extracted);
398 $placeholder = $match[1].$placeholderPrefix.$count.$match[1];
399 $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
400
401 return $placeholder;
402 };
403
404 /*
405 * The \\ messiness explained:
406 * * Don't count ' or " as end-of-string if it's escaped (has backslash
407 * in front of it)
408 * * Unless... that backslash itself is escaped (another leading slash),
409 * in which case it's no longer escaping the ' or "
410 * * So there can be either no backslash, or an even number
411 * * multiply all of that times 4, to account for the escaping that has
412 * to be done to pass the backslash into the PHP string without it being
413 * considered as escape-char (times 2) and to get it in the regex,
414 * escaped (times 2)
415 */
416 $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
417 }
418
419 /**
420 * This method will restore all extracted data (strings, regexes) that were
421 * replaced with placeholder text in extract*(). The original content was
422 * saved in $this->extracted.
423 *
424 * @param string $content
425 *
426 * @return string
427 */
428 protected function restoreExtractedData($content)
429 {
430 if (!$this->extracted) {
431 // nothing was extracted, nothing to restore
432 return $content;
433 }
434
435 $content = strtr($content, $this->extracted);
436
437 $this->extracted = array();
438
439 return $content;
440 }
441
442 /**
443 * Check if the path is a regular file and can be read.
444 *
445 * @param string $path
446 *
447 * @return bool
448 */
449 protected function canImportFile($path)
450 {
451 $parsed = parse_url($path);
452 if (
453 // file is elsewhere
454 isset($parsed['host']) ||
455 // file responds to queries (may change, or need to bypass cache)
456 isset($parsed['query'])
457 ) {
458 return false;
459 }
460
461 return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
462 }
463
464 /**
465 * Attempts to open file specified by $path for writing.
466 *
467 * @param string $path The path to the file
468 *
469 * @return resource Specifier for the target file
470 *
471 * @throws IOException
472 */
473 protected function openFileForWriting($path)
474 {
475 if (($handler = @fopen($path, 'w')) === false) {
476 throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
477 }
478
479 return $handler;
480 }
481
482 /**
483 * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
484 *
485 * @param resource $handler The resource to write to
486 * @param string $content The content to write
487 * @param string $path The path to the file (for exception printing only)
488 *
489 * @throws IOException
490 */
491 protected function writeToFile($handler, $content, $path = '')
492 {
493 if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
494 throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
495 }
496 }
497 }
1 <?php
2
3 namespace FVM\MatthiasMullie\PathConverter;
4
5 /**
6 * Convert paths relative from 1 file to another.
7 *
8 * E.g.
9 * ../../images/icon.jpg relative to /css/imports/icons.css
10 * becomes
11 * ../images/icon.jpg relative to /css/minified.css
12 *
13 * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
14 *
15 * @author Matthias Mullie <pathconverter@mullie.eu>
16 * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
17 * @license MIT License
18 */
19 class Converter implements ConverterInterface
20 {
21 /**
22 * @var string
23 */
24 protected $from;
25
26 /**
27 * @var string
28 */
29 protected $to;
30
31 /**
32 * @param string $from The original base path (directory, not file!)
33 * @param string $to The new base path (directory, not file!)
34 * @param string $root Root directory (defaults to `getcwd`)
35 */
36 public function __construct($from, $to, $root = '')
37 {
38 $shared = $this->shared($from, $to);
39 if ($shared === '') {
40 // when both paths have nothing in common, one of them is probably
41 // absolute while the other is relative
42 $root = $root ?: getcwd();
43 $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from);
44 $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to);
45
46 // or traveling the tree via `..`
47 // attempt to resolve path, or assume it's fine if it doesn't exist
48 $from = @realpath($from) ?: $from;
49 $to = @realpath($to) ?: $to;
50 }
51
52 $from = $this->dirname($from);
53 $to = $this->dirname($to);
54
55 $from = $this->normalize($from);
56 $to = $this->normalize($to);
57
58 $this->from = $from;
59 $this->to = $to;
60 }
61
62 /**
63 * Normalize path.
64 *
65 * @param string $path
66 *
67 * @return string
68 */
69 protected function normalize($path)
70 {
71 // deal with different operating systems' directory structure
72 $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
73
74 /*
75 * Example:
76 * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
77 * to
78 * /home/forkcms/frontend/core/layout/images/img.gif
79 */
80 do {
81 $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
82 } while ($count);
83
84 return $path;
85 }
86
87 /**
88 * Figure out the shared path of 2 locations.
89 *
90 * Example:
91 * /home/forkcms/frontend/core/layout/images/img.gif
92 * and
93 * /home/forkcms/frontend/cache/minified_css
94 * share
95 * /home/forkcms/frontend
96 *
97 * @param string $path1
98 * @param string $path2
99 *
100 * @return string
101 */
102 protected function shared($path1, $path2)
103 {
104 // $path could theoretically be empty (e.g. no path is given), in which
105 // case it shouldn't expand to array(''), which would compare to one's
106 // root /
107 $path1 = $path1 ? explode('/', $path1) : array();
108 $path2 = $path2 ? explode('/', $path2) : array();
109
110 $shared = array();
111
112 // compare paths & strip identical ancestors
113 foreach ($path1 as $i => $chunk) {
114 if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
115 $shared[] = $chunk;
116 } else {
117 break;
118 }
119 }
120
121 return implode('/', $shared);
122 }
123
124 /**
125 * Convert paths relative from 1 file to another.
126 *
127 * E.g.
128 * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
129 * should become:
130 * ../../core/layout/images/img.gif relative to
131 * /home/forkcms/frontend/cache/minified_css
132 *
133 * @param string $path The relative path that needs to be converted
134 *
135 * @return string The new relative path
136 */
137 public function convert($path)
138 {
139 // quit early if conversion makes no sense
140 if ($this->from === $this->to) {
141 return $path;
142 }
143
144 $path = $this->normalize($path);
145 // if we're not dealing with a relative path, just return absolute
146 if (strpos($path, '/') === 0) {
147 return $path;
148 }
149
150 // normalize paths
151 $path = $this->normalize($this->from.'/'.$path);
152
153 // strip shared ancestor paths
154 $shared = $this->shared($path, $this->to);
155 $path = mb_substr($path, mb_strlen($shared));
156 $to = mb_substr($this->to, mb_strlen($shared));
157
158 // add .. for every directory that needs to be traversed to new path
159 $to = str_repeat('../', count(array_filter(explode('/', $to))));
160
161 return $to.ltrim($path, '/');
162 }
163
164 /**
165 * Attempt to get the directory name from a path.
166 *
167 * @param string $path
168 *
169 * @return string
170 */
171 protected function dirname($path)
172 {
173 if (@is_file($path)) {
174 return dirname($path);
175 }
176
177 if (@is_dir($path)) {
178 return rtrim($path, '/');
179 }
180
181 // no known file/dir, start making assumptions
182
183 // ends in / = dir
184 if (mb_substr($path, -1) === '/') {
185 return rtrim($path, '/');
186 }
187
188 // has a dot in the name, likely a file
189 if (preg_match('/.*\..*$/', basename($path)) !== 0) {
190 return dirname($path);
191 }
192
193 // you're on your own here!
194 return $path;
195 }
196 }
1 <?php
2
3 namespace FVM\MatthiasMullie\PathConverter;
4
5 /**
6 * Convert file paths.
7 *
8 * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
9 *
10 * @author Matthias Mullie <pathconverter@mullie.eu>
11 * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
12 * @license MIT License
13 */
14 interface ConverterInterface
15 {
16 /**
17 * Convert file paths.
18 *
19 * @param string $path The path to be converted
20 *
21 * @return string The new path
22 */
23 public function convert($path);
24 }
1 <?php
2
3 namespace FVM\MatthiasMullie\PathConverter;
4
5 /**
6 * Don't convert paths.
7 *
8 * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
9 *
10 * @author Matthias Mullie <pathconverter@mullie.eu>
11 * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
12 * @license MIT License
13 */
14 class NoConverter implements ConverterInterface
15 {
16 /**
17 * {@inheritdoc}
18 */
19 public function convert($path)
20 {
21 return $path;
22 }
23 }
1 <?php
2 /**
3 *
4 * Website: https://wpraiser.com/
5 * Author: Raul Peixoto (https://www.upwork.com/fl/raulpeixoto)
6 * Licensed under GPLv2 (or later)
7 * Version 1.0
8 *
9 * Usage: fvm_raisermin_js($js);
10 *
11 */
12
13 # Exit if accessed directly
14 if (!defined('ABSPATH')){ exit(); }
15
16 # minify js, whitespace only
17 function fvm_raisermin_js($code){
18
19 # remove // comments
20 $code = preg_replace('/(^|\s)\/\/(.*)\n/m', '', $code);
21 $code = preg_replace('/(\{|\}|\[|\]|\(|\)|\;)\/\/(.*)\n/m', '$1', $code);
22
23 # remove /* ... */ comments
24 $code = preg_replace('/(^|\s)\/\*(.*)\*\//Us', '', $code);
25 $code = preg_replace('/(\;|\{)\/\*(.*)\*\//Us', '$1', $code);
26
27 # remove sourceMappingURL
28 $code = preg_replace('/(\/\/\s*[#]\s*sourceMappingURL\s*[=]\s*)([a-zA-Z0-9-_\.\/]+)(\.map)/ui', '', $code);
29
30 # uniform line endings, make them all line feed
31 $code = str_replace(array("\r\n", "\r"), "\n", $code);
32
33 # collapse all non-line feed whitespace into a single space
34 $code = preg_replace('/[^\S\n]+/', ' ', $code);
35
36 # strip leading & trailing whitespace
37 $code = str_replace(array(" \n", "\n "), "\n", $code);
38
39 # collapse consecutive line feeds into just 1
40 $code = preg_replace('/\n+/', "\n", $code);
41
42 # process horizontal space
43 $code = preg_replace('/([\[\]\(\)\{\}\;\<\>])(\h+)([\[\]\(\)\{\}\;\<\>])/ui', '$1 $3', $code);
44 $code = preg_replace('/([\)])(\h?)(\.)/ui', '$1$3', $code);
45 $code = preg_replace('/([\)\?])(\h?)(\.)/ui', '$1$3', $code);
46 $code = preg_replace('/(\,)(\h+)/ui', '$1 ', $code);
47 $code = preg_replace('/(\h+)(\,)/ui', ' $2', $code);
48 $code = preg_replace('/([if])(\h+)(\()/ui', '$1$3', $code);
49
50 # trim whitespace on beginning/end
51 return trim($code);
52 }
53
54
55 # remove UTF8 BOM
56 function fvm_min_remove_utf8_bom($text) {
57 $bom = pack('H*','EFBBBF');
58 while (preg_match("/^$bom/", $text)) {
59 $text = preg_replace("/^$bom/ui", '', $text);
60 }
61 return $text;
62 }
63
64
65
66
67 # minify html, don't touch certain tags
68 function fvm_raisermin_html($html) {
69
70 # clone
71 $content = $html;
72
73 # get all scripts
74 $skp = array();
75 preg_match_all('/(\<script(.*?)\<(\s*)\/script(\s*)\>|\<noscript(.*?)\<(\s*)\/noscript(\s*)\>|\<code(.*?)\<(\s*)\/code(\s*)\>|\<pre(.*?)\<(\s*)\/pre(\s*)\>)/uis', $html, $skp);
76
77 # replace all skippable patterns with comment
78 if(is_array($skp) && isset($skp[0]) && count($skp[0]) > 0) {
79 foreach ($skp[0] as $k=>$v) {
80 $content = str_replace($v, '<!-- SKIP '.$k.' -->', $content);
81 }
82 }
83
84 # remove line breaks, and colapse two or more white spaces into one
85 $content = preg_replace('/\s+/u', " ", $content);
86
87 # add linebreaks after html and head tags, for readability
88 $content = str_replace('<head>', PHP_EOL . '<head>' . PHP_EOL, $content);
89 $content = str_replace('</head>', PHP_EOL . '</head>' . PHP_EOL, $content);
90 $content = str_replace('<html', PHP_EOL . '<html', $content);
91 $content = str_replace('</html>', PHP_EOL . '</html>', $content);
92
93 # final readability adjustments
94 $content = str_replace('<meta ', PHP_EOL . '<meta ', $content);
95 $content = str_replace('<link ', PHP_EOL . '<link ', $content);
96 $content = str_replace('<style', PHP_EOL . '<style', $content);
97 $content = str_replace('<noscript', PHP_EOL . '<noscript>', $content);
98
99 # replace markers for scripts last
100 if(is_array($skp) && isset($skp[0]) && count($skp[0]) > 0) {
101 foreach ($skp[0] as $k=>$v) {
102 $content = str_replace('<!-- SKIP '.$k.' -->', PHP_EOL . $v . PHP_EOL, $content);
103 }
104 }
105
106 # no empty lines
107 $lines = explode(PHP_EOL, $content);
108 foreach($lines as $k=>$ln) { $ln = ltrim($ln); if(empty($ln)) { unset($lines[$k]); } else { $lines[$k] = $ln; } }
109 $content = implode(PHP_EOL, $lines);
110
111 # save as html, if not empty
112 if(!empty($content)) {
113 $html = $content;
114 }
115
116 # return
117 return $html;
118 }
1 <?php
2 /**
3 * Website: http://sourceforge.net/projects/simplehtmldom/
4 * Additional projects: http://sourceforge.net/projects/debugobject/
5 * Acknowledge: Jose Solorzano (https://sourceforge.net/projects/php-html/)
6 *
7 * Licensed under The MIT License
8 * See the LICENSE file in the project root for more information.
9 *
10 * Authors:
11 * S.C. Chen
12 * John Schlick
13 * Rus Carroll
14 * logmanoriginal
15 *
16 * Contributors:
17 * Yousuke Kumakura
18 * Vadim Voituk
19 * Antcs
20 *
21 * Version Rev. 1.9.1 (291) (edited for FVM)
22 */
23
24 # Exit if accessed directly
25 if (!defined('ABSPATH')){ exit(); }
26
27 # mod
28 defined('FVM_MAX_FILE_SIZE') || define('FVM_MAX_FILE_SIZE', 2000000); # Process HTML up to 2 Mb
29 defined('DEFAULT_TARGET_CHARSET') || define('DEFAULT_TARGET_CHARSET', 'UTF-8');
30 defined('DEFAULT_BR_TEXT') || define('DEFAULT_BR_TEXT', "\r\n");
31 defined('DEFAULT_SPAN_TEXT') || define('DEFAULT_SPAN_TEXT', ' ');
32
33 # other
34 define('HDOM_TYPE_ELEMENT', 1);
35 define('HDOM_TYPE_COMMENT', 2);
36 define('HDOM_TYPE_TEXT', 3);
37 define('HDOM_TYPE_ENDTAG', 4);
38 define('HDOM_TYPE_ROOT', 5);
39 define('HDOM_TYPE_UNKNOWN', 6);
40 define('HDOM_QUOTE_DOUBLE', 0);
41 define('HDOM_QUOTE_SINGLE', 1);
42 define('HDOM_QUOTE_NO', 3);
43 define('HDOM_INFO_BEGIN', 0);
44 define('HDOM_INFO_END', 1);
45 define('HDOM_INFO_QUOTE', 2);
46 define('HDOM_INFO_SPACE', 3);
47 define('HDOM_INFO_TEXT', 4);
48 define('HDOM_INFO_INNER', 5);
49 define('HDOM_INFO_OUTER', 6);
50 define('HDOM_INFO_ENDSPACE', 7);
51 define('HDOM_SMARTY_AS_TEXT', 1);
52
53 # functions
54 function file_get_html(
55 $url,
56 $use_include_path = false,
57 $context = null,
58 $offset = 0,
59 $maxLen = -1,
60 $lowercase = true,
61 $forceTagsClosed = true,
62 $target_charset = DEFAULT_TARGET_CHARSET,
63 $stripRN = true,
64 $defaultBRText = DEFAULT_BR_TEXT,
65 $defaultSpanText = DEFAULT_SPAN_TEXT)
66 {
67 if($maxLen <= 0) { $maxLen = FVM_MAX_FILE_SIZE; }
68
69 $dom = new simple_html_dom(
70 null,
71 $lowercase,
72 $forceTagsClosed,
73 $target_charset,
74 $stripRN,
75 $defaultBRText,
76 $defaultSpanText
77 );
78
79 /**
80 * For sourceforge users: uncomment the next line and comment the
81 * retrieve_url_contents line 2 lines down if it is not already done.
82 */
83 $contents = file_get_contents(
84 $url,
85 $use_include_path,
86 $context,
87 $offset,
88 $maxLen
89 );
90 // $contents = retrieve_url_contents($url);
91
92 if (empty($contents) || strlen($contents) > $maxLen) {
93 $dom->clear();
94 return false;
95 }
96
97 return $dom->load($contents, $lowercase, $stripRN);
98 }
99
100 function str_get_html(
101 $str,
102 $lowercase = true,
103 $forceTagsClosed = true,
104 $target_charset = DEFAULT_TARGET_CHARSET,
105 $stripRN = true,
106 $defaultBRText = DEFAULT_BR_TEXT,
107 $defaultSpanText = DEFAULT_SPAN_TEXT)
108 {
109 $dom = new simple_html_dom(
110 null,
111 $lowercase,
112 $forceTagsClosed,
113 $target_charset,
114 $stripRN,
115 $defaultBRText,
116 $defaultSpanText
117 );
118
119 if (empty($str) || strlen($str) > FVM_MAX_FILE_SIZE) {
120 $dom->clear();
121 return false;
122 }
123
124 return $dom->load($str, $lowercase, $stripRN);
125 }
126
127 function dump_html_tree($node, $show_attr = true, $deep = 0)
128 {
129 $node->dump($node);
130 }
131
132 class simple_html_dom_node
133 {
134 public $nodetype = HDOM_TYPE_TEXT;
135 public $tag = 'text';
136 public $attr = array();
137 public $children = array();
138 public $nodes = array();
139 public $parent = null;
140 public $_ = array();
141 public $tag_start = 0;
142 private $dom = null;
143
144 function __construct($dom)
145 {
146 $this->dom = $dom;
147 $dom->nodes[] = $this;
148 }
149
150 function __destruct()
151 {
152 $this->clear();
153 }
154
155 function __toString()
156 {
157 return $this->outertext();
158 }
159
160 function clear()
161 {
162 $this->dom = null;
163 $this->nodes = null;
164 $this->parent = null;
165 $this->children = null;
166 }
167
168 function dump($show_attr = true, $depth = 0)
169 {
170 echo str_repeat("\t", $depth) . $this->tag;
171
172 if ($show_attr && count($this->attr) > 0) {
173 echo '(';
174 foreach ($this->attr as $k => $v) {
175 echo "[$k]=>\"$v\", ";
176 }
177 echo ')';
178 }
179
180 echo "\n";
181
182 if ($this->nodes) {
183 foreach ($this->nodes as $node) {
184 $node->dump($show_attr, $depth + 1);
185 }
186 }
187 }
188
189 function dump_node($echo = true)
190 {
191 $string = $this->tag;
192
193 if (count($this->attr) > 0) {
194 $string .= '(';
195 foreach ($this->attr as $k => $v) {
196 $string .= "[$k]=>\"$v\", ";
197 }
198 $string .= ')';
199 }
200
201 if (count($this->_) > 0) {
202 $string .= ' $_ (';
203 foreach ($this->_ as $k => $v) {
204 if (is_array($v)) {
205 $string .= "[$k]=>(";
206 foreach ($v as $k2 => $v2) {
207 $string .= "[$k2]=>\"$v2\", ";
208 }
209 $string .= ')';
210 } else {
211 $string .= "[$k]=>\"$v\", ";
212 }
213 }
214 $string .= ')';
215 }
216
217 if (isset($this->text)) {
218 $string .= " text: ({$this->text})";
219 }
220
221 $string .= ' HDOM_INNER_INFO: ';
222
223 if (isset($node->_[HDOM_INFO_INNER])) {
224 $string .= "'" . $node->_[HDOM_INFO_INNER] . "'";
225 } else {
226 $string .= ' NULL ';
227 }
228
229 $string .= ' children: ' . count($this->children);
230 $string .= ' nodes: ' . count($this->nodes);
231 $string .= ' tag_start: ' . $this->tag_start;
232 $string .= "\n";
233
234 if ($echo) {
235 echo $string;
236 return;
237 } else {
238 return $string;
239 }
240 }
241
242 function parent($parent = null)
243 {
244 // I am SURE that this doesn't work properly.
245 // It fails to unset the current node from it's current parents nodes or
246 // children list first.
247 if ($parent !== null) {
248 $this->parent = $parent;
249 $this->parent->nodes[] = $this;
250 $this->parent->children[] = $this;
251 }
252
253 return $this->parent;
254 }
255
256 function has_child()
257 {
258 return !empty($this->children);
259 }
260
261 function children($idx = -1)
262 {
263 if ($idx === -1) {
264 return $this->children;
265 }
266
267 if (isset($this->children[$idx])) {
268 return $this->children[$idx];
269 }
270
271 return null;
272 }
273
274 function first_child()
275 {
276 if (count($this->children) > 0) {
277 return $this->children[0];
278 }
279 return null;
280 }
281
282 function last_child()
283 {
284 if (count($this->children) > 0) {
285 return end($this->children);
286 }
287 return null;
288 }
289
290 function next_sibling()
291 {
292 if ($this->parent === null) {
293 return null;
294 }
295
296 $idx = array_search($this, $this->parent->children, true);
297
298 if ($idx !== false && isset($this->parent->children[$idx + 1])) {
299 return $this->parent->children[$idx + 1];
300 }
301
302 return null;
303 }
304
305 function prev_sibling()
306 {
307 if ($this->parent === null) {
308 return null;
309 }
310
311 $idx = array_search($this, $this->parent->children, true);
312
313 if ($idx !== false && $idx > 0) {
314 return $this->parent->children[$idx - 1];
315 }
316
317 return null;
318 }
319
320 function find_ancestor_tag($tag)
321 {
322 global $debug_object;
323 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
324
325 if ($this->parent === null) {
326 return null;
327 }
328
329 $ancestor = $this->parent;
330
331 while (!is_null($ancestor)) {
332 if (is_object($debug_object)) {
333 $debug_object->debug_log(2, 'Current tag is: ' . $ancestor->tag);
334 }
335
336 if ($ancestor->tag === $tag) {
337 break;
338 }
339
340 $ancestor = $ancestor->parent;
341 }
342
343 return $ancestor;
344 }
345
346 function innertext()
347 {
348 if (isset($this->_[HDOM_INFO_INNER])) {
349 return $this->_[HDOM_INFO_INNER];
350 }
351
352 if (isset($this->_[HDOM_INFO_TEXT])) {
353 return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
354 }
355
356 $ret = '';
357
358 foreach ($this->nodes as $n) {
359 $ret .= $n->outertext();
360 }
361
362 return $ret;
363 }
364
365 function outertext()
366 {
367 global $debug_object;
368
369 if (is_object($debug_object)) {
370 $text = '';
371
372 if ($this->tag === 'text') {
373 if (!empty($this->text)) {
374 $text = ' with text: ' . $this->text;
375 }
376 }
377
378 $debug_object->debug_log(1, 'Innertext of tag: ' . $this->tag . $text);
379 }
380
381 if ($this->tag === 'root') {
382 return $this->innertext();
383 }
384
385 // todo: What is the use of this callback? Remove?
386 if ($this->dom && $this->dom->callback !== null) {
387 call_user_func_array($this->dom->callback, array($this));
388 }
389
390 if (isset($this->_[HDOM_INFO_OUTER])) {
391 return $this->_[HDOM_INFO_OUTER];
392 }
393
394 if (isset($this->_[HDOM_INFO_TEXT])) {
395 return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
396 }
397
398 $ret = '';
399
400 if ($this->dom && $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]) {
401 $ret = $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]->makeup();
402 }
403
404 if (isset($this->_[HDOM_INFO_INNER])) {
405 // todo: <br> should either never have HDOM_INFO_INNER or always
406 if ($this->tag !== 'br') {
407 $ret .= $this->_[HDOM_INFO_INNER];
408 }
409 } elseif ($this->nodes) {
410 foreach ($this->nodes as $n) {
411 $ret .= $this->convert_text($n->outertext());
412 }
413 }
414
415 if (isset($this->_[HDOM_INFO_END]) && $this->_[HDOM_INFO_END] != 0) {
416 $ret .= '</' . $this->tag . '>';
417 }
418
419 return $ret;
420 }
421
422 function text()
423 {
424 if (isset($this->_[HDOM_INFO_INNER])) {
425 return $this->_[HDOM_INFO_INNER];
426 }
427
428 switch ($this->nodetype) {
429 case HDOM_TYPE_TEXT: return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
430 case HDOM_TYPE_COMMENT: return '';
431 case HDOM_TYPE_UNKNOWN: return '';
432 }
433
434 if (strcasecmp($this->tag, 'script') === 0) { return ''; }
435 if (strcasecmp($this->tag, 'style') === 0) { return ''; }
436
437 $ret = '';
438
439 // In rare cases, (always node type 1 or HDOM_TYPE_ELEMENT - observed
440 // for some span tags, and some p tags) $this->nodes is set to NULL.
441 // NOTE: This indicates that there is a problem where it's set to NULL
442 // without a clear happening.
443 // WHY is this happening?
444 if (!is_null($this->nodes)) {
445 foreach ($this->nodes as $n) {
446 // Start paragraph after a blank line
447 if ($n->tag === 'p') {
448 $ret = trim($ret) . "\n\n";
449 }
450
451 $ret .= $this->convert_text($n->text());
452
453 // If this node is a span... add a space at the end of it so
454 // multiple spans don't run into each other. This is plaintext
455 // after all.
456 if ($n->tag === 'span') {
457 $ret .= $this->dom->default_span_text;
458 }
459 }
460 }
461 return $ret;
462 }
463
464 function xmltext()
465 {
466 $ret = $this->innertext();
467 $ret = str_ireplace('<![CDATA[', '', $ret);
468 $ret = str_replace(']]>', '', $ret);
469 return $ret;
470 }
471
472 function makeup()
473 {
474 // text, comment, unknown
475 if (isset($this->_[HDOM_INFO_TEXT])) {
476 return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
477 }
478
479 $ret = '<' . $this->tag;
480 $i = -1;
481
482 foreach ($this->attr as $key => $val) {
483 ++$i;
484
485 // skip removed attribute
486 if ($val === null || $val === false) { continue; }
487
488 $ret .= $this->_[HDOM_INFO_SPACE][$i][0];
489
490 //no value attr: nowrap, checked selected...
491 if ($val === true) {
492 $ret .= $key;
493 } else {
494 switch ($this->_[HDOM_INFO_QUOTE][$i])
495 {
496 case HDOM_QUOTE_DOUBLE: $quote = '"'; break;
497 case HDOM_QUOTE_SINGLE: $quote = '\''; break;
498 default: $quote = '';
499 }
500
501 $ret .= $key
502 . $this->_[HDOM_INFO_SPACE][$i][1]
503 . '='
504 . $this->_[HDOM_INFO_SPACE][$i][2]
505 . $quote
506 . $val
507 . $quote;
508 }
509 }
510
511 $ret = $this->dom->restore_noise($ret);
512 return $ret . $this->_[HDOM_INFO_ENDSPACE] . '>';
513 }
514
515 function find($selector, $idx = null, $lowercase = false)
516 {
517 $selectors = $this->parse_selector($selector);
518 if (($count = count($selectors)) === 0) { return array(); }
519 $found_keys = array();
520
521 // find each selector
522 for ($c = 0; $c < $count; ++$c) {
523 // The change on the below line was documented on the sourceforge
524 // code tracker id 2788009
525 // used to be: if (($levle=count($selectors[0]))===0) return array();
526 if (($levle = count($selectors[$c])) === 0) { return array(); }
527 if (!isset($this->_[HDOM_INFO_BEGIN])) { return array(); }
528
529 $head = array($this->_[HDOM_INFO_BEGIN] => 1);
530 $cmd = ' '; // Combinator
531
532 // handle descendant selectors, no recursive!
533 for ($l = 0; $l < $levle; ++$l) {
534 $ret = array();
535
536 foreach ($head as $k => $v) {
537 $n = ($k === -1) ? $this->dom->root : $this->dom->nodes[$k];
538 //PaperG - Pass this optional parameter on to the seek function.
539 $n->seek($selectors[$c][$l], $ret, $cmd, $lowercase);
540 }
541
542 $head = $ret;
543 $cmd = $selectors[$c][$l][4]; // Next Combinator
544 }
545
546 foreach ($head as $k => $v) {
547 if (!isset($found_keys[$k])) {
548 $found_keys[$k] = 1;
549 }
550 }
551 }
552
553 // sort keys
554 ksort($found_keys);
555
556 $found = array();
557 foreach ($found_keys as $k => $v) {
558 $found[] = $this->dom->nodes[$k];
559 }
560
561 // return nth-element or array
562 if (is_null($idx)) { return $found; }
563 elseif ($idx < 0) { $idx = count($found) + $idx; }
564 return (isset($found[$idx])) ? $found[$idx] : null;
565 }
566
567 protected function seek($selector, &$ret, $parent_cmd, $lowercase = false)
568 {
569 global $debug_object;
570 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
571
572 list($tag, $id, $class, $attributes, $cmb) = $selector;
573 $nodes = array();
574
575 if ($parent_cmd === ' ') { // Descendant Combinator
576 // Find parent closing tag if the current element doesn't have a closing
577 // tag (i.e. void element)
578 $end = (!empty($this->_[HDOM_INFO_END])) ? $this->_[HDOM_INFO_END] : 0;
579 if ($end == 0) {
580 $parent = $this->parent;
581 while (!isset($parent->_[HDOM_INFO_END]) && $parent !== null) {
582 $end -= 1;
583 $parent = $parent->parent;
584 }
585 $end += $parent->_[HDOM_INFO_END];
586 }
587
588 // Get list of target nodes
589 $nodes_start = $this->_[HDOM_INFO_BEGIN] + 1;
590 $nodes_count = $end - $nodes_start;
591 $nodes = array_slice($this->dom->nodes, $nodes_start, $nodes_count, true);
592 } elseif ($parent_cmd === '>') { // Child Combinator
593 $nodes = $this->children;
594 } elseif ($parent_cmd === '+'
595 && $this->parent
596 && in_array($this, $this->parent->children)) { // Next-Sibling Combinator
597 $index = array_search($this, $this->parent->children, true) + 1;
598 if ($index < count($this->parent->children))
599 $nodes[] = $this->parent->children[$index];
600 } elseif ($parent_cmd === '~'
601 && $this->parent
602 && in_array($this, $this->parent->children)) { // Subsequent Sibling Combinator
603 $index = array_search($this, $this->parent->children, true);
604 $nodes = array_slice($this->parent->children, $index);
605 }
606
607 // Go throgh each element starting at this element until the end tag
608 // Note: If this element is a void tag, any previous void element is
609 // skipped.
610 foreach($nodes as $node) {
611 $pass = true;
612
613 // Skip root nodes
614 if(!$node->parent) {
615 $pass = false;
616 }
617
618 // Handle 'text' selector
619 if($pass && $tag === 'text' && $node->tag === 'text') {
620 $ret[array_search($node, $this->dom->nodes, true)] = 1;
621 unset($node);
622 continue;
623 }
624
625 // Skip if node isn't a child node (i.e. text nodes)
626 if($pass && !in_array($node, $node->parent->children, true)) {
627 $pass = false;
628 }
629
630 // Skip if tag doesn't match
631 if ($pass && $tag !== '' && $tag !== $node->tag && $tag !== '*') {
632 $pass = false;
633 }
634
635 // Skip if ID doesn't exist
636 if ($pass && $id !== '' && !isset($node->attr['id'])) {
637 $pass = false;
638 }
639
640 // Check if ID matches
641 if ($pass && $id !== '' && isset($node->attr['id'])) {
642 // Note: Only consider the first ID (as browsers do)
643 $node_id = explode(' ', trim($node->attr['id']))[0];
644
645 if($id !== $node_id) { $pass = false; }
646 }
647
648 // Check if all class(es) exist
649 if ($pass && $class !== '' && is_array($class) && !empty($class)) {
650 if (isset($node->attr['class'])) {
651 $node_classes = explode(' ', $node->attr['class']);
652
653 if ($lowercase) {
654 $node_classes = array_map('strtolower', $node_classes);
655 }
656
657 foreach($class as $c) {
658 if(!in_array($c, $node_classes)) {
659 $pass = false;
660 break;
661 }
662 }
663 } else {
664 $pass = false;
665 }
666 }
667
668 // Check attributes
669 if ($pass
670 && $attributes !== ''
671 && is_array($attributes)
672 && !empty($attributes)) {
673 foreach($attributes as $a) {
674 list (
675 $att_name,
676 $att_expr,
677 $att_val,
678 $att_inv,
679 $att_case_sensitivity
680 ) = $a;
681
682 // Handle indexing attributes (i.e. "[2]")
683 /**
684 * Note: This is not supported by the CSS Standard but adds
685 * the ability to select items compatible to XPath (i.e.
686 * the 3rd element within it's parent).
687 *
688 * Note: This doesn't conflict with the CSS Standard which
689 * doesn't work on numeric attributes anyway.
690 */
691 if (is_numeric($att_name)
692 && $att_expr === ''
693 && $att_val === '') {
694 $count = 0;
695
696 // Find index of current element in parent
697 foreach ($node->parent->children as $c) {
698 if ($c->tag === $node->tag) ++$count;
699 if ($c === $node) break;
700 }
701
702 // If this is the correct node, continue with next
703 // attribute
704 if ($count === (int)$att_name) continue;
705 }
706
707 // Check attribute availability
708 if ($att_inv) { // Attribute should NOT be set
709 if (isset($node->attr[$att_name])) {
710 $pass = false;
711 break;
712 }
713 } else { // Attribute should be set
714 // todo: "plaintext" is not a valid CSS selector!
715 if ($att_name !== 'plaintext'
716 && !isset($node->attr[$att_name])) {
717 $pass = false;
718 break;
719 }
720 }
721
722 // Continue with next attribute if expression isn't defined
723 if ($att_expr === '') continue;
724
725 // If they have told us that this is a "plaintext"
726 // search then we want the plaintext of the node - right?
727 // todo "plaintext" is not a valid CSS selector!
728 if ($att_name === 'plaintext') {
729 $nodeKeyValue = $node->text();
730 } else {
731 $nodeKeyValue = $node->attr[$att_name];
732 }
733
734 if (is_object($debug_object)) {
735 $debug_object->debug_log(2,
736 'testing node: '
737 . $node->tag
738 . ' for attribute: '
739 . $att_name
740 . $att_expr
741 . $att_val
742 . ' where nodes value is: '
743 . $nodeKeyValue
744 );
745 }
746
747 // If lowercase is set, do a case insensitive test of
748 // the value of the selector.
749 if ($lowercase) {
750 $check = $this->match(
751 $att_expr,
752 strtolower($att_val),
753 strtolower($nodeKeyValue),
754 $att_case_sensitivity
755 );
756 } else {
757 $check = $this->match(
758 $att_expr,
759 $att_val,
760 $nodeKeyValue,
761 $att_case_sensitivity
762 );
763 }
764
765 if (is_object($debug_object)) {
766 $debug_object->debug_log(2,
767 'after match: '
768 . ($check ? 'true' : 'false')
769 );
770 }
771
772 if (!$check) {
773 $pass = false;
774 break;
775 }
776 }
777 }
778
779 // Found a match. Add to list and clear node
780 if ($pass) $ret[$node->_[HDOM_INFO_BEGIN]] = 1;
781 unset($node);
782 }
783 // It's passed by reference so this is actually what this function returns.
784 if (is_object($debug_object)) {
785 $debug_object->debug_log(1, 'EXIT - ret: ', $ret);
786 }
787 }
788
789 protected function match($exp, $pattern, $value, $case_sensitivity)
790 {
791 global $debug_object;
792 if (is_object($debug_object)) {$debug_object->debug_log_entry(1);}
793
794 if ($case_sensitivity === 'i') {
795 $pattern = strtolower($pattern);
796 $value = strtolower($value);
797 }
798
799 switch ($exp) {
800 case '=':
801 return ($value === $pattern);
802 case '!=':
803 return ($value !== $pattern);
804 case '^=':
805 return preg_match('/^' . preg_quote($pattern, '/') . '/', $value);
806 case '$=':
807 return preg_match('/' . preg_quote($pattern, '/') . '$/', $value);
808 case '*=':
809 return preg_match('/' . preg_quote($pattern, '/') . '/', $value);
810 case '|=':
811 /**
812 * [att|=val]
813 *
814 * Represents an element with the att attribute, its value
815 * either being exactly "val" or beginning with "val"
816 * immediately followed by "-" (U+002D).
817 */
818 return strpos($value, $pattern) === 0;
819 case '~=':
820 /**
821 * [att~=val]
822 *
823 * Represents an element with the att attribute whose value is a
824 * whitespace-separated list of words, one of which is exactly
825 * "val". If "val" contains whitespace, it will never represent
826 * anything (since the words are separated by spaces). Also if
827 * "val" is the empty string, it will never represent anything.
828 */
829 return in_array($pattern, explode(' ', trim($value)), true);
830 }
831 return false;
832 }
833
834 protected function parse_selector($selector_string)
835 {
836 global $debug_object;
837 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
838
839 /**
840 * Pattern of CSS selectors, modified from mootools (https://mootools.net/)
841 *
842 * Paperg: Add the colon to the attribute, so that it properly finds
843 * <tag attr:ibute="something" > like google does.
844 *
845 * Note: if you try to look at this attribute, you MUST use getAttribute
846 * since $dom->x:y will fail the php syntax check.
847 *
848 * Notice the \[ starting the attribute? and the @? following? This
849 * implies that an attribute can begin with an @ sign that is not
850 * captured. This implies that an html attribute specifier may start
851 * with an @ sign that is NOT captured by the expression. Farther study
852 * is required to determine of this should be documented or removed.
853 *
854 * Matches selectors in this order:
855 *
856 * [0] - full match
857 *
858 * [1] - tag name
859 * ([\w:\*-]*)
860 * Matches the tag name consisting of zero or more words, colons,
861 * asterisks and hyphens.
862 *
863 * [2] - id name
864 * (?:\#([\w-]+))
865 * Optionally matches a id name, consisting of an "#" followed by
866 * the id name (one or more words and hyphens).
867 *
868 * [3] - class names (including dots)
869 * (?:\.([\w\.-]+))?
870 * Optionally matches a list of classs, consisting of an "."
871 * followed by the class name (one or more words and hyphens)
872 * where multiple classes can be chained (i.e. ".foo.bar.baz")
873 *
874 * [4] - attributes
875 * ((?:\[@?(?:!?[\w:-]+)(?:(?:[!*^$|~]?=)[\"']?(?:.*?)[\"']?)?(?:\s*?(?:[iIsS])?)?\])+)?
876 * Optionally matches the attributes list
877 *
878 * [5] - separator
879 * ([\/, >+~]+)
880 * Matches the selector list separator
881 */
882 // phpcs:ignore Generic.Files.LineLength
883 $pattern = "/([\w:\*-]*)(?:\#([\w-]+))?(?:|\.([\w\.-]+))?((?:\[@?(?:!?[\w:-]+)(?:(?:[!*^$|~]?=)[\"']?(?:.*?)[\"']?)?(?:\s*?(?:[iIsS])?)?\])+)?([\/, >+~]+)/is";
884
885 preg_match_all(
886 $pattern,
887 trim($selector_string) . ' ', // Add final ' ' as pseudo separator
888 $matches,
889 PREG_SET_ORDER
890 );
891
892 if (is_object($debug_object)) {
893 $debug_object->debug_log(2, 'Matches Array: ', $matches);
894 }
895
896 $selectors = array();
897 $result = array();
898
899 foreach ($matches as $m) {
900 $m[0] = trim($m[0]);
901
902 // Skip NoOps
903 if ($m[0] === '' || $m[0] === '/' || $m[0] === '//') { continue; }
904
905 // Convert to lowercase
906 if ($this->dom->lowercase) {
907 $m[1] = strtolower($m[1]);
908 }
909
910 // Extract classes
911 if ($m[3] !== '') { $m[3] = explode('.', $m[3]); }
912
913 /* Extract attributes (pattern based on the pattern above!)
914
915 * [0] - full match
916 * [1] - attribute name
917 * [2] - attribute expression
918 * [3] - attribute value
919 * [4] - case sensitivity
920 *
921 * Note: Attributes can be negated with a "!" prefix to their name
922 */
923 if($m[4] !== '') {
924 preg_match_all(
925 "/\[@?(!?[\w:-]+)(?:([!*^$|~]?=)[\"']?(.*?)[\"']?)?(?:\s+?([iIsS])?)?\]/is",
926 trim($m[4]),
927 $attributes,
928 PREG_SET_ORDER
929 );
930
931 // Replace element by array
932 $m[4] = array();
933
934 foreach($attributes as $att) {
935 // Skip empty matches
936 if(trim($att[0]) === '') { continue; }
937
938 $inverted = (isset($att[1][0]) && $att[1][0] === '!');
939 $m[4][] = array(
940 $inverted ? substr($att[1], 1) : $att[1], // Name
941 (isset($att[2])) ? $att[2] : '', // Expression
942 (isset($att[3])) ? $att[3] : '', // Value
943 $inverted, // Inverted Flag
944 (isset($att[4])) ? strtolower($att[4]) : '', // Case-Sensitivity
945 );
946 }
947 }
948
949 // Sanitize Separator
950 if ($m[5] !== '' && trim($m[5]) === '') { // Descendant Separator
951 $m[5] = ' ';
952 } else { // Other Separator
953 $m[5] = trim($m[5]);
954 }
955
956 // Clear Separator if it's a Selector List
957 if ($is_list = ($m[5] === ',')) { $m[5] = ''; }
958
959 // Remove full match before adding to results
960 array_shift($m);
961 $result[] = $m;
962
963 if ($is_list) { // Selector List
964 $selectors[] = $result;
965 $result = array();
966 }
967 }
968
969 if (count($result) > 0) { $selectors[] = $result; }
970 return $selectors;
971 }
972
973 function __get($name)
974 {
975 if (isset($this->attr[$name])) {
976 return $this->convert_text($this->attr[$name]);
977 }
978 switch ($name) {
979 case 'outertext': return $this->outertext();
980 case 'innertext': return $this->innertext();
981 case 'plaintext': return $this->text();
982 case 'xmltext': return $this->xmltext();
983 default: return array_key_exists($name, $this->attr);
984 }
985 }
986
987 function __set($name, $value)
988 {
989 global $debug_object;
990 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
991
992 switch ($name) {
993 case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value;
994 case 'innertext':
995 if (isset($this->_[HDOM_INFO_TEXT])) {
996 return $this->_[HDOM_INFO_TEXT] = $value;
997 }
998 return $this->_[HDOM_INFO_INNER] = $value;
999 }
1000
1001 if (!isset($this->attr[$name])) {
1002 $this->_[HDOM_INFO_SPACE][] = array(' ', '', '');
1003 $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE;
1004 }
1005
1006 $this->attr[$name] = $value;
1007 }
1008
1009 function __isset($name)
1010 {
1011 switch ($name) {
1012 case 'outertext': return true;
1013 case 'innertext': return true;
1014 case 'plaintext': return true;
1015 }
1016 //no value attr: nowrap, checked selected...
1017 return (array_key_exists($name, $this->attr)) ? true : isset($this->attr[$name]);
1018 }
1019
1020 function __unset($name)
1021 {
1022 if (isset($this->attr[$name])) { unset($this->attr[$name]); }
1023 }
1024
1025 function convert_text($text)
1026 {
1027 global $debug_object;
1028 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
1029
1030 $converted_text = $text;
1031
1032 $sourceCharset = '';
1033 $targetCharset = '';
1034
1035 if ($this->dom) {
1036 $sourceCharset = strtoupper($this->dom->_charset);
1037 $targetCharset = strtoupper($this->dom->_target_charset);
1038 }
1039
1040 if (is_object($debug_object)) {
1041 $debug_object->debug_log(3,
1042 'source charset: '
1043 . $sourceCharset
1044 . ' target charaset: '
1045 . $targetCharset
1046 );
1047 }
1048
1049 if (!empty($sourceCharset)
1050 && !empty($targetCharset)
1051 && (strcasecmp($sourceCharset, $targetCharset) != 0)) {
1052 // Check if the reported encoding could have been incorrect and the text is actually already UTF-8
1053 if ((strcasecmp($targetCharset, 'UTF-8') == 0)
1054 && ($this->is_utf8($text))) {
1055 $converted_text = $text;
1056 } else {
1057 $converted_text = iconv($sourceCharset, $targetCharset, $text);
1058 }
1059 }
1060
1061 // Lets make sure that we don't have that silly BOM issue with any of the utf-8 text we output.
1062 if ($targetCharset === 'UTF-8') {
1063 if (substr($converted_text, 0, 3) === "\xef\xbb\xbf") {
1064 $converted_text = substr($converted_text, 3);
1065 }
1066
1067 if (substr($converted_text, -3) === "\xef\xbb\xbf") {
1068 $converted_text = substr($converted_text, 0, -3);
1069 }
1070 }
1071
1072 return $converted_text;
1073 }
1074
1075 static function is_utf8($str)
1076 {
1077 $c = 0; $b = 0;
1078 $bits = 0;
1079 $len = strlen($str);
1080 for($i = 0; $i < $len; $i++) {
1081 $c = ord($str[$i]);
1082 if($c > 128) {
1083 if(($c >= 254)) { return false; }
1084 elseif($c >= 252) { $bits = 6; }
1085 elseif($c >= 248) { $bits = 5; }
1086 elseif($c >= 240) { $bits = 4; }
1087 elseif($c >= 224) { $bits = 3; }
1088 elseif($c >= 192) { $bits = 2; }
1089 else { return false; }
1090 if(($i + $bits) > $len) { return false; }
1091 while($bits > 1) {
1092 $i++;
1093 $b = ord($str[$i]);
1094 if($b < 128 || $b > 191) { return false; }
1095 $bits--;
1096 }
1097 }
1098 }
1099 return true;
1100 }
1101
1102 function get_display_size()
1103 {
1104 global $debug_object;
1105
1106 $width = -1;
1107 $height = -1;
1108
1109 if ($this->tag !== 'img') {
1110 return false;
1111 }
1112
1113 // See if there is aheight or width attribute in the tag itself.
1114 if (isset($this->attr['width'])) {
1115 $width = $this->attr['width'];
1116 }
1117
1118 if (isset($this->attr['height'])) {
1119 $height = $this->attr['height'];
1120 }
1121
1122 // Now look for an inline style.
1123 if (isset($this->attr['style'])) {
1124 // Thanks to user gnarf from stackoverflow for this regular expression.
1125 $attributes = array();
1126
1127 preg_match_all(
1128 '/([\w-]+)\s*:\s*([^;]+)\s*;?/',
1129 $this->attr['style'],
1130 $matches,
1131 PREG_SET_ORDER
1132 );
1133
1134 foreach ($matches as $match) {
1135 $attributes[$match[1]] = $match[2];
1136 }
1137
1138 // If there is a width in the style attributes:
1139 if (isset($attributes['width']) && $width == -1) {
1140 // check that the last two characters are px (pixels)
1141 if (strtolower(substr($attributes['width'], -2)) === 'px') {
1142 $proposed_width = substr($attributes['width'], 0, -2);
1143 // Now make sure that it's an integer and not something stupid.
1144 if (filter_var($proposed_width, FILTER_VALIDATE_INT)) {
1145 $width = $proposed_width;
1146 }
1147 }
1148 }
1149
1150 // If there is a width in the style attributes:
1151 if (isset($attributes['height']) && $height == -1) {
1152 // check that the last two characters are px (pixels)
1153 if (strtolower(substr($attributes['height'], -2)) == 'px') {
1154 $proposed_height = substr($attributes['height'], 0, -2);
1155 // Now make sure that it's an integer and not something stupid.
1156 if (filter_var($proposed_height, FILTER_VALIDATE_INT)) {
1157 $height = $proposed_height;
1158 }
1159 }
1160 }
1161
1162 }
1163
1164 // Future enhancement:
1165 // Look in the tag to see if there is a class or id specified that has
1166 // a height or width attribute to it.
1167
1168 // Far future enhancement
1169 // Look at all the parent tags of this image to see if they specify a
1170 // class or id that has an img selector that specifies a height or width
1171 // Note that in this case, the class or id will have the img subselector
1172 // for it to apply to the image.
1173
1174 // ridiculously far future development
1175 // If the class or id is specified in a SEPARATE css file thats not on
1176 // the page, go get it and do what we were just doing for the ones on
1177 // the page.
1178
1179 $result = array(
1180 'height' => $height,
1181 'width' => $width
1182 );
1183
1184 return $result;
1185 }
1186
1187 function save($filepath = '')
1188 {
1189 $ret = $this->outertext();
1190
1191 if ($filepath !== '') {
1192 file_put_contents($filepath, $ret, LOCK_EX);
1193 }
1194
1195 return $ret;
1196 }
1197
1198 function addClass($class)
1199 {
1200 if (is_string($class)) {
1201 $class = explode(' ', $class);
1202 }
1203
1204 if (is_array($class)) {
1205 foreach($class as $c) {
1206 if (isset($this->class)) {
1207 if ($this->hasClass($c)) {
1208 continue;
1209 } else {
1210 $this->class .= ' ' . $c;
1211 }
1212 } else {
1213 $this->class = $c;
1214 }
1215 }
1216 } else {
1217 if (is_object($debug_object)) {
1218 $debug_object->debug_log(2, 'Invalid type: ', gettype($class));
1219 }
1220 }
1221 }
1222
1223 function hasClass($class)
1224 {
1225 if (is_string($class)) {
1226 if (isset($this->class)) {
1227 return in_array($class, explode(' ', $this->class), true);
1228 }
1229 } else {
1230 if (is_object($debug_object)) {
1231 $debug_object->debug_log(2, 'Invalid type: ', gettype($class));
1232 }
1233 }
1234
1235 return false;
1236 }
1237
1238 function removeClass($class = null)
1239 {
1240 if (!isset($this->class)) {
1241 return;
1242 }
1243
1244 if (is_null($class)) {
1245 $this->removeAttribute('class');
1246 return;
1247 }
1248
1249 if (is_string($class)) {
1250 $class = explode(' ', $class);
1251 }
1252
1253 if (is_array($class)) {
1254 $class = array_diff(explode(' ', $this->class), $class);
1255 if (empty($class)) {
1256 $this->removeAttribute('class');
1257 } else {
1258 $this->class = implode(' ', $class);
1259 }
1260 }
1261 }
1262
1263 function getAllAttributes()
1264 {
1265 return $this->attr;
1266 }
1267
1268 function getAttribute($name)
1269 {
1270 return $this->__get($name);
1271 }
1272
1273 function setAttribute($name, $value)
1274 {
1275 $this->__set($name, $value);
1276 }
1277
1278 function hasAttribute($name)
1279 {
1280 return $this->__isset($name);
1281 }
1282
1283 function removeAttribute($name)
1284 {
1285 $this->__set($name, null);
1286 }
1287
1288 function remove()
1289 {
1290 if ($this->parent) {
1291 $this->parent->removeChild($this);
1292 }
1293 }
1294
1295 function removeChild($node)
1296 {
1297 $nidx = array_search($node, $this->nodes, true);
1298 $cidx = array_search($node, $this->children, true);
1299 $didx = array_search($node, $this->dom->nodes, true);
1300
1301 if ($nidx !== false && $cidx !== false && $didx !== false) {
1302
1303 foreach($node->children as $child) {
1304 $node->removeChild($child);
1305 }
1306
1307 foreach($node->nodes as $entity) {
1308 $enidx = array_search($entity, $node->nodes, true);
1309 $edidx = array_search($entity, $node->dom->nodes, true);
1310
1311 if ($enidx !== false && $edidx !== false) {
1312 unset($node->nodes[$enidx]);
1313 unset($node->dom->nodes[$edidx]);
1314 }
1315 }
1316
1317 unset($this->nodes[$nidx]);
1318 unset($this->children[$cidx]);
1319 unset($this->dom->nodes[$didx]);
1320
1321 $node->clear();
1322
1323 }
1324 }
1325
1326 function getElementById($id)
1327 {
1328 return $this->find("#$id", 0);
1329 }
1330
1331 function getElementsById($id, $idx = null)
1332 {
1333 return $this->find("#$id", $idx);
1334 }
1335
1336 function getElementByTagName($name)
1337 {
1338 return $this->find($name, 0);
1339 }
1340
1341 function getElementsByTagName($name, $idx = null)
1342 {
1343 return $this->find($name, $idx);
1344 }
1345
1346 function parentNode()
1347 {
1348 return $this->parent();
1349 }
1350
1351 function childNodes($idx = -1)
1352 {
1353 return $this->children($idx);
1354 }
1355
1356 function firstChild()
1357 {
1358 return $this->first_child();
1359 }
1360
1361 function lastChild()
1362 {
1363 return $this->last_child();
1364 }
1365
1366 function nextSibling()
1367 {
1368 return $this->next_sibling();
1369 }
1370
1371 function previousSibling()
1372 {
1373 return $this->prev_sibling();
1374 }
1375
1376 function hasChildNodes()
1377 {
1378 return $this->has_child();
1379 }
1380
1381 function nodeName()
1382 {
1383 return $this->tag;
1384 }
1385
1386 function appendChild($node)
1387 {
1388 $node->parent($this);
1389 return $node;
1390 }
1391
1392 }
1393
1394 class simple_html_dom
1395 {
1396 public $root = null;
1397 public $nodes = array();
1398 public $callback = null;
1399 public $lowercase = false;
1400 public $original_size;
1401 public $size;
1402
1403 protected $pos;
1404 protected $doc;
1405 protected $char;
1406
1407 protected $cursor;
1408 protected $parent;
1409 protected $noise = array();
1410 protected $token_blank = " \t\r\n";
1411 protected $token_equal = ' =/>';
1412 protected $token_slash = " />\r\n\t";
1413 protected $token_attr = ' >';
1414
1415 public $_charset = '';
1416 public $_target_charset = '';
1417
1418 protected $default_br_text = '';
1419
1420 public $default_span_text = '';
1421
1422 protected $self_closing_tags = array(
1423 'area' => 1,
1424 'base' => 1,
1425 'br' => 1,
1426 'col' => 1,
1427 'embed' => 1,
1428 'hr' => 1,
1429 'img' => 1,
1430 'input' => 1,
1431 'link' => 1,
1432 'meta' => 1,
1433 'param' => 1,
1434 'source' => 1,
1435 'track' => 1,
1436 'wbr' => 1
1437 );
1438 protected $block_tags = array(
1439 'body' => 1,
1440 'div' => 1,
1441 'form' => 1,
1442 'root' => 1,
1443 'span' => 1,
1444 'table' => 1
1445 );
1446 protected $optional_closing_tags = array(
1447 // Not optional, see
1448 // https://www.w3.org/TR/html/textlevel-semantics.html#the-b-element
1449 'b' => array('b' => 1),
1450 'dd' => array('dd' => 1, 'dt' => 1),
1451 // Not optional, see
1452 // https://www.w3.org/TR/html/grouping-content.html#the-dl-element
1453 'dl' => array('dd' => 1, 'dt' => 1),
1454 'dt' => array('dd' => 1, 'dt' => 1),
1455 'li' => array('li' => 1),
1456 'optgroup' => array('optgroup' => 1, 'option' => 1),
1457 'option' => array('optgroup' => 1, 'option' => 1),
1458 'p' => array('p' => 1),
1459 'rp' => array('rp' => 1, 'rt' => 1),
1460 'rt' => array('rp' => 1, 'rt' => 1),
1461 'td' => array('td' => 1, 'th' => 1),
1462 'th' => array('td' => 1, 'th' => 1),
1463 'tr' => array('td' => 1, 'th' => 1, 'tr' => 1),
1464 );
1465
1466 function __construct(
1467 $str = null,
1468 $lowercase = true,
1469 $forceTagsClosed = true,
1470 $target_charset = DEFAULT_TARGET_CHARSET,
1471 $stripRN = true,
1472 $defaultBRText = DEFAULT_BR_TEXT,
1473 $defaultSpanText = DEFAULT_SPAN_TEXT,
1474 $options = 0)
1475 {
1476 if ($str) {
1477 if (preg_match('/^http:\/\//i', $str) || is_file($str)) {
1478 $this->load_file($str);
1479 } else {
1480 $this->load(
1481 $str,
1482 $lowercase,
1483 $stripRN,
1484 $defaultBRText,
1485 $defaultSpanText,
1486 $options
1487 );
1488 }
1489 }
1490 // Forcing tags to be closed implies that we don't trust the html, but
1491 // it can lead to parsing errors if we SHOULD trust the html.
1492 if (!$forceTagsClosed) {
1493 $this->optional_closing_array = array();
1494 }
1495
1496 $this->_target_charset = $target_charset;
1497 }
1498
1499 function __destruct()
1500 {
1501 $this->clear();
1502 }
1503
1504 function load(
1505 $str,
1506 $lowercase = true,
1507 $stripRN = true,
1508 $defaultBRText = DEFAULT_BR_TEXT,
1509 $defaultSpanText = DEFAULT_SPAN_TEXT,
1510 $options = 0)
1511 {
1512 global $debug_object;
1513
1514 // prepare
1515 $this->prepare($str, $lowercase, $defaultBRText, $defaultSpanText);
1516
1517 // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037
1518 // Script tags removal now preceeds style tag removal.
1519 // strip out <script> tags
1520 $this->remove_noise("'<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>'is");
1521 $this->remove_noise("'<\s*script\s*>(.*?)<\s*/\s*script\s*>'is");
1522
1523 // strip out the \r \n's if we are told to.
1524 if ($stripRN) {
1525 $this->doc = str_replace("\r", ' ', $this->doc);
1526 $this->doc = str_replace("\n", ' ', $this->doc);
1527
1528 // set the length of content since we have changed it.
1529 $this->size = strlen($this->doc);
1530 }
1531
1532 // strip out cdata
1533 $this->remove_noise("'<!\[CDATA\[(.*?)\]\]>'is", true);
1534 // strip out comments
1535 $this->remove_noise("'<!--(.*?)-->'is");
1536 // strip out <style> tags
1537 $this->remove_noise("'<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>'is");
1538 $this->remove_noise("'<\s*style\s*>(.*?)<\s*/\s*style\s*>'is");
1539 // strip out preformatted tags
1540 $this->remove_noise("'<\s*(?:code)[^>]*>(.*?)<\s*/\s*(?:code)\s*>'is");
1541 // strip out server side scripts
1542 $this->remove_noise("'(<\?)(.*?)(\?>)'s", true);
1543
1544 if($options & HDOM_SMARTY_AS_TEXT) { // Strip Smarty scripts
1545 $this->remove_noise("'(\{\w)(.*?)(\})'s", true);
1546 }
1547
1548 // parsing
1549 $this->parse();
1550 // end
1551 $this->root->_[HDOM_INFO_END] = $this->cursor;
1552 $this->parse_charset();
1553
1554 // make load function chainable
1555 return $this;
1556 }
1557
1558 function load_file()
1559 {
1560 $args = func_get_args();
1561
1562 if(($doc = call_user_func_array('file_get_contents', $args)) !== false) {
1563 $this->load($doc, true);
1564 } else {
1565 return false;
1566 }
1567 }
1568
1569 function set_callback($function_name)
1570 {
1571 $this->callback = $function_name;
1572 }
1573
1574 function remove_callback()
1575 {
1576 $this->callback = null;
1577 }
1578
1579 function save($filepath = '')
1580 {
1581 $ret = $this->root->innertext();
1582 if ($filepath !== '') { file_put_contents($filepath, $ret, LOCK_EX); }
1583 return $ret;
1584 }
1585
1586 function find($selector, $idx = null, $lowercase = false)
1587 {
1588 return $this->root->find($selector, $idx, $lowercase);
1589 }
1590
1591 function clear()
1592 {
1593 if (isset($this->nodes)) {
1594 foreach ($this->nodes as $n) {
1595 $n->clear();
1596 $n = null;
1597 }
1598 }
1599
1600 // This add next line is documented in the sourceforge repository.
1601 // 2977248 as a fix for ongoing memory leaks that occur even with the
1602 // use of clear.
1603 if (isset($this->children)) {
1604 foreach ($this->children as $n) {
1605 $n->clear();
1606 $n = null;
1607 }
1608 }
1609
1610 if (isset($this->parent)) {
1611 $this->parent->clear();
1612 unset($this->parent);
1613 }
1614
1615 if (isset($this->root)) {
1616 $this->root->clear();
1617 unset($this->root);
1618 }
1619
1620 unset($this->doc);
1621 unset($this->noise);
1622 }
1623
1624 function dump($show_attr = true)
1625 {
1626 $this->root->dump($show_attr);
1627 }
1628
1629 protected function prepare(
1630 $str, $lowercase = true,
1631 $defaultBRText = DEFAULT_BR_TEXT,
1632 $defaultSpanText = DEFAULT_SPAN_TEXT)
1633 {
1634 $this->clear();
1635
1636 $this->doc = trim($str);
1637 $this->size = strlen($this->doc);
1638 $this->original_size = $this->size; // original size of the html
1639 $this->pos = 0;
1640 $this->cursor = 1;
1641 $this->noise = array();
1642 $this->nodes = array();
1643 $this->lowercase = $lowercase;
1644 $this->default_br_text = $defaultBRText;
1645 $this->default_span_text = $defaultSpanText;
1646 $this->root = new simple_html_dom_node($this);
1647 $this->root->tag = 'root';
1648 $this->root->_[HDOM_INFO_BEGIN] = -1;
1649 $this->root->nodetype = HDOM_TYPE_ROOT;
1650 $this->parent = $this->root;
1651 if ($this->size > 0) { $this->char = $this->doc[0]; }
1652 }
1653
1654 protected function parse()
1655 {
1656 while (true) {
1657 // Read next tag if there is no text between current position and the
1658 // next opening tag.
1659 if (($s = $this->copy_until_char('<')) === '') {
1660 if($this->read_tag()) {
1661 continue;
1662 } else {
1663 return true;
1664 }
1665 }
1666
1667 // Add a text node for text between tags
1668 $node = new simple_html_dom_node($this);
1669 ++$this->cursor;
1670 $node->_[HDOM_INFO_TEXT] = $s;
1671 $this->link_nodes($node, false);
1672 }
1673 }
1674
1675 protected function parse_charset()
1676 {
1677 global $debug_object;
1678
1679 $charset = null;
1680
1681 if (function_exists('get_last_retrieve_url_contents_content_type')) {
1682 $contentTypeHeader = get_last_retrieve_url_contents_content_type();
1683 $success = preg_match('/charset=(.+)/', $contentTypeHeader, $matches);
1684 if ($success) {
1685 $charset = $matches[1];
1686 if (is_object($debug_object)) {
1687 $debug_object->debug_log(2,
1688 'header content-type found charset of: '
1689 . $charset
1690 );
1691 }
1692 }
1693 }
1694
1695 if (empty($charset)) {
1696 // https://www.w3.org/TR/html/document-metadata.html#statedef-http-equiv-content-type
1697 $el = $this->root->find('meta[http-equiv=Content-Type]', 0, true);
1698
1699 if (!empty($el)) {
1700 $fullvalue = $el->content;
1701 if (is_object($debug_object)) {
1702 $debug_object->debug_log(2,
1703 'meta content-type tag found'
1704 . $fullvalue
1705 );
1706 }
1707
1708 if (!empty($fullvalue)) {
1709 $success = preg_match(
1710 '/charset=(.+)/i',
1711 $fullvalue,
1712 $matches
1713 );
1714
1715 if ($success) {
1716 $charset = $matches[1];
1717 } else {
1718 // If there is a meta tag, and they don't specify the
1719 // character set, research says that it's typically
1720 // ISO-8859-1
1721 if (is_object($debug_object)) {
1722 $debug_object->debug_log(2,
1723 'meta content-type tag couldn\'t be parsed. using iso-8859 default.'
1724 );
1725 }
1726
1727 $charset = 'ISO-8859-1';
1728 }
1729 }
1730 }
1731 }
1732
1733 if (empty($charset)) {
1734 // https://www.w3.org/TR/html/document-metadata.html#character-encoding-declaration
1735 if ($meta = $this->root->find('meta[charset]', 0)) {
1736 $charset = $meta->charset;
1737 if (is_object($debug_object)) {
1738 $debug_object->debug_log(2, 'meta charset: ' . $charset);
1739 }
1740 }
1741 }
1742
1743 if (empty($charset)) {
1744 // Try to guess the charset based on the content
1745 // Requires Multibyte String (mbstring) support (optional)
1746 if (function_exists('mb_detect_encoding')) {
1747 /**
1748 * mb_detect_encoding() is not intended to distinguish between
1749 * charsets, especially single-byte charsets. Its primary
1750 * purpose is to detect which multibyte encoding is in use,
1751 * i.e. UTF-8, UTF-16, shift-JIS, etc.
1752 *
1753 * -- https://bugs.php.net/bug.php?id=38138
1754 *
1755 * Adding both CP1251/ISO-8859-5 and CP1252/ISO-8859-1 will
1756 * always result in CP1251/ISO-8859-5 and vice versa.
1757 *
1758 * Thus, only detect if it's either UTF-8 or CP1252/ISO-8859-1
1759 * to stay compatible.
1760 */
1761 $encoding = mb_detect_encoding(
1762 $this->doc,
1763 array( 'UTF-8', 'CP1252', 'ISO-8859-1' )
1764 );
1765
1766 if ($encoding === 'CP1252' || $encoding === 'ISO-8859-1') {
1767 // Due to a limitation of mb_detect_encoding
1768 // 'CP1251'/'ISO-8859-5' will be detected as
1769 // 'CP1252'/'ISO-8859-1'. This will cause iconv to fail, in
1770 // which case we can simply assume it is the other charset.
1771 if (!@iconv('CP1252', 'UTF-8', $this->doc)) {
1772 $encoding = 'CP1251';
1773 }
1774 }
1775
1776 if ($encoding !== false) {
1777 $charset = $encoding;
1778 if (is_object($debug_object)) {
1779 $debug_object->debug_log(2, 'mb_detect: ' . $charset);
1780 }
1781 }
1782 }
1783 }
1784
1785 if (empty($charset)) {
1786 // Assume it's UTF-8 as it is the most likely charset to be used
1787 $charset = 'UTF-8';
1788 if (is_object($debug_object)) {
1789 $debug_object->debug_log(2, 'No match found, assume ' . $charset);
1790 }
1791 }
1792
1793 // Since CP1252 is a superset, if we get one of it's subsets, we want
1794 // it instead.
1795 if ((strtolower($charset) == 'iso-8859-1')
1796 || (strtolower($charset) == 'latin1')
1797 || (strtolower($charset) == 'latin-1')) {
1798 $charset = 'CP1252';
1799 if (is_object($debug_object)) {
1800 $debug_object->debug_log(2,
1801 'replacing ' . $charset . ' with CP1252 as its a superset'
1802 );
1803 }
1804 }
1805
1806 if (is_object($debug_object)) {
1807 $debug_object->debug_log(1, 'EXIT - ' . $charset);
1808 }
1809
1810 return $this->_charset = $charset;
1811 }
1812
1813 protected function read_tag()
1814 {
1815 // Set end position if no further tags found
1816 if ($this->char !== '<') {
1817 $this->root->_[HDOM_INFO_END] = $this->cursor;
1818 return false;
1819 }
1820
1821 $begin_tag_pos = $this->pos;
1822 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1823
1824 // end tag
1825 if ($this->char === '/') {
1826 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1827
1828 // Skip whitespace in end tags (i.e. in "</ html>")
1829 $this->skip($this->token_blank);
1830 $tag = $this->copy_until_char('>');
1831
1832 // Skip attributes in end tags
1833 if (($pos = strpos($tag, ' ')) !== false) {
1834 $tag = substr($tag, 0, $pos);
1835 }
1836
1837 $parent_lower = strtolower($this->parent->tag);
1838 $tag_lower = strtolower($tag);
1839
1840 // The end tag is supposed to close the parent tag. Handle situations
1841 // when it doesn't
1842 if ($parent_lower !== $tag_lower) {
1843 // Parent tag does not have to be closed necessarily (optional closing tag)
1844 // Current tag is a block tag, so it may close an ancestor
1845 if (isset($this->optional_closing_tags[$parent_lower])
1846 && isset($this->block_tags[$tag_lower])) {
1847
1848 $this->parent->_[HDOM_INFO_END] = 0;
1849 $org_parent = $this->parent;
1850
1851 // Traverse ancestors to find a matching opening tag
1852 // Stop at root node
1853 while (($this->parent->parent)
1854 && strtolower($this->parent->tag) !== $tag_lower
1855 ){
1856 $this->parent = $this->parent->parent;
1857 }
1858
1859 // If we don't have a match add current tag as text node
1860 if (strtolower($this->parent->tag) !== $tag_lower) {
1861 $this->parent = $org_parent; // restore origonal parent
1862
1863 if ($this->parent->parent) {
1864 $this->parent = $this->parent->parent;
1865 }
1866
1867 $this->parent->_[HDOM_INFO_END] = $this->cursor;
1868 return $this->as_text_node($tag);
1869 }
1870 } elseif (($this->parent->parent)
1871 && isset($this->block_tags[$tag_lower])
1872 ) {
1873 // Grandparent exists and current tag is a block tag, so our
1874 // parent doesn't have an end tag
1875 $this->parent->_[HDOM_INFO_END] = 0; // No end tag
1876 $org_parent = $this->parent;
1877
1878 // Traverse ancestors to find a matching opening tag
1879 // Stop at root node
1880 while (($this->parent->parent)
1881 && strtolower($this->parent->tag) !== $tag_lower
1882 ) {
1883 $this->parent = $this->parent->parent;
1884 }
1885
1886 // If we don't have a match add current tag as text node
1887 if (strtolower($this->parent->tag) !== $tag_lower) {
1888 $this->parent = $org_parent; // restore origonal parent
1889 $this->parent->_[HDOM_INFO_END] = $this->cursor;
1890 return $this->as_text_node($tag);
1891 }
1892 } elseif (($this->parent->parent)
1893 && strtolower($this->parent->parent->tag) === $tag_lower
1894 ) { // Grandparent exists and current tag closes it
1895 $this->parent->_[HDOM_INFO_END] = 0;
1896 $this->parent = $this->parent->parent;
1897 } else { // Random tag, add as text node
1898 return $this->as_text_node($tag);
1899 }
1900 }
1901
1902 // Set end position of parent tag to current cursor position
1903 $this->parent->_[HDOM_INFO_END] = $this->cursor;
1904
1905 if ($this->parent->parent) {
1906 $this->parent = $this->parent->parent;
1907 }
1908
1909 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1910 return true;
1911 }
1912
1913 // start tag
1914 $node = new simple_html_dom_node($this);
1915 $node->_[HDOM_INFO_BEGIN] = $this->cursor;
1916 ++$this->cursor;
1917 $tag = $this->copy_until($this->token_slash); // Get tag name
1918 $node->tag_start = $begin_tag_pos;
1919
1920 // doctype, cdata & comments...
1921 // <!DOCTYPE html>
1922 // <![CDATA[ ... ]]>
1923 // <!-- Comment -->
1924 if (isset($tag[0]) && $tag[0] === '!') {
1925 $node->_[HDOM_INFO_TEXT] = '<' . $tag . $this->copy_until_char('>');
1926
1927 if (isset($tag[2]) && $tag[1] === '-' && $tag[2] === '-') { // Comment ("<!--")
1928 $node->nodetype = HDOM_TYPE_COMMENT;
1929 $node->tag = 'comment';
1930 } else { // Could be doctype or CDATA but we don't care
1931 $node->nodetype = HDOM_TYPE_UNKNOWN;
1932 $node->tag = 'unknown';
1933 }
1934
1935 if ($this->char === '>') { $node->_[HDOM_INFO_TEXT] .= '>'; }
1936
1937 $this->link_nodes($node, true);
1938 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1939 return true;
1940 }
1941
1942 // The start tag cannot contain another start tag, if so add as text
1943 // i.e. "<<html>"
1944 if ($pos = strpos($tag, '<') !== false) {
1945 $tag = '<' . substr($tag, 0, -1);
1946 $node->_[HDOM_INFO_TEXT] = $tag;
1947 $this->link_nodes($node, false);
1948 $this->char = $this->doc[--$this->pos]; // prev
1949 return true;
1950 }
1951
1952 // Handle invalid tag names (i.e. "<html#doc>")
1953 if (!preg_match('/^\w[\w:-]*$/', $tag)) {
1954 $node->_[HDOM_INFO_TEXT] = '<' . $tag . $this->copy_until('<>');
1955
1956 // Next char is the beginning of a new tag, don't touch it.
1957 if ($this->char === '<') {
1958 $this->link_nodes($node, false);
1959 return true;
1960 }
1961
1962 // Next char closes current tag, add and be done with it.
1963 if ($this->char === '>') { $node->_[HDOM_INFO_TEXT] .= '>'; }
1964 $this->link_nodes($node, false);
1965 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1966 return true;
1967 }
1968
1969 // begin tag, add new node
1970 $node->nodetype = HDOM_TYPE_ELEMENT;
1971 $tag_lower = strtolower($tag);
1972 $node->tag = ($this->lowercase) ? $tag_lower : $tag;
1973
1974 // handle optional closing tags
1975 if (isset($this->optional_closing_tags[$tag_lower])) {
1976 // Traverse ancestors to close all optional closing tags
1977 while (isset($this->optional_closing_tags[$tag_lower][strtolower($this->parent->tag)])) {
1978 $this->parent->_[HDOM_INFO_END] = 0;
1979 $this->parent = $this->parent->parent;
1980 }
1981 $node->parent = $this->parent;
1982 }
1983
1984 $guard = 0; // prevent infinity loop
1985
1986 // [0] Space between tag and first attribute
1987 $space = array($this->copy_skip($this->token_blank), '', '');
1988
1989 // attributes
1990 do {
1991 // Everything until the first equal sign should be the attribute name
1992 $name = $this->copy_until($this->token_equal);
1993
1994 if ($name === '' && $this->char !== null && $space[0] === '') {
1995 break;
1996 }
1997
1998 if ($guard === $this->pos) { // Escape infinite loop
1999 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2000 continue;
2001 }
2002
2003 $guard = $this->pos;
2004
2005 // handle endless '<'
2006 // Out of bounds before the tag ended
2007 if ($this->pos >= $this->size - 1 && $this->char !== '>') {
2008 $node->nodetype = HDOM_TYPE_TEXT;
2009 $node->_[HDOM_INFO_END] = 0;
2010 $node->_[HDOM_INFO_TEXT] = '<' . $tag . $space[0] . $name;
2011 $node->tag = 'text';
2012 $this->link_nodes($node, false);
2013 return true;
2014 }
2015
2016 // handle mismatch '<'
2017 // Attributes cannot start after opening tag
2018 if ($this->doc[$this->pos - 1] == '<') {
2019 $node->nodetype = HDOM_TYPE_TEXT;
2020 $node->tag = 'text';
2021 $node->attr = array();
2022 $node->_[HDOM_INFO_END] = 0;
2023 $node->_[HDOM_INFO_TEXT] = substr(
2024 $this->doc,
2025 $begin_tag_pos,
2026 $this->pos - $begin_tag_pos - 1
2027 );
2028 $this->pos -= 2;
2029 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2030 $this->link_nodes($node, false);
2031 return true;
2032 }
2033
2034 if ($name !== '/' && $name !== '') { // this is a attribute name
2035 // [1] Whitespace after attribute name
2036 $space[1] = $this->copy_skip($this->token_blank);
2037
2038 $name = $this->restore_noise($name); // might be a noisy name
2039
2040 if ($this->lowercase) { $name = strtolower($name); }
2041
2042 if ($this->char === '=') { // attribute with value
2043 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2044 $this->parse_attr($node, $name, $space); // get attribute value
2045 } else {
2046 //no value attr: nowrap, checked selected...
2047 $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO;
2048 $node->attr[$name] = true;
2049 if ($this->char != '>') { $this->char = $this->doc[--$this->pos]; } // prev
2050 }
2051
2052 $node->_[HDOM_INFO_SPACE][] = $space;
2053
2054 // prepare for next attribute
2055 $space = array(
2056 $this->copy_skip($this->token_blank),
2057 '',
2058 ''
2059 );
2060 } else { // no more attributes
2061 break;
2062 }
2063 } while ($this->char !== '>' && $this->char !== '/'); // go until the tag ended
2064
2065 $this->link_nodes($node, true);
2066 $node->_[HDOM_INFO_ENDSPACE] = $space[0];
2067
2068 // handle empty tags (i.e. "<div/>")
2069 if ($this->copy_until_char('>') === '/') {
2070 $node->_[HDOM_INFO_ENDSPACE] .= '/';
2071 $node->_[HDOM_INFO_END] = 0;
2072 } else {
2073 // reset parent
2074 if (!isset($this->self_closing_tags[strtolower($node->tag)])) {
2075 $this->parent = $node;
2076 }
2077 }
2078
2079 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2080
2081 // If it's a BR tag, we need to set it's text to the default text.
2082 // This way when we see it in plaintext, we can generate formatting that the user wants.
2083 // since a br tag never has sub nodes, this works well.
2084 if ($node->tag === 'br') {
2085 $node->_[HDOM_INFO_INNER] = $this->default_br_text;
2086 }
2087
2088 return true;
2089 }
2090
2091 protected function parse_attr($node, $name, &$space)
2092 {
2093 $is_duplicate = isset($node->attr[$name]);
2094
2095 if (!$is_duplicate) // Copy whitespace between "=" and value
2096 $space[2] = $this->copy_skip($this->token_blank);
2097
2098 switch ($this->char) {
2099 case '"':
2100 $quote_type = HDOM_QUOTE_DOUBLE;
2101 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2102 $value = $this->copy_until_char('"');
2103 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2104 break;
2105 case '\'':
2106 $quote_type = HDOM_QUOTE_SINGLE;
2107 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2108 $value = $this->copy_until_char('\'');
2109 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2110 break;
2111 default:
2112 $quote_type = HDOM_QUOTE_NO;
2113 $value = $this->copy_until($this->token_attr);
2114 }
2115
2116 $value = $this->restore_noise($value);
2117
2118 // PaperG: Attributes should not have \r or \n in them, that counts as
2119 // html whitespace.
2120 $value = str_replace("\r", '', $value);
2121 $value = str_replace("\n", '', $value);
2122
2123 // PaperG: If this is a "class" selector, lets get rid of the preceeding
2124 // and trailing space since some people leave it in the multi class case.
2125 if ($name === 'class') {
2126 $value = trim($value);
2127 }
2128
2129 if (!$is_duplicate) {
2130 $node->_[HDOM_INFO_QUOTE][] = $quote_type;
2131 $node->attr[$name] = $value;
2132 }
2133 }
2134
2135 protected function link_nodes(&$node, $is_child)
2136 {
2137 $node->parent = $this->parent;
2138 $this->parent->nodes[] = $node;
2139 if ($is_child) {
2140 $this->parent->children[] = $node;
2141 }
2142 }
2143
2144 protected function as_text_node($tag)
2145 {
2146 $node = new simple_html_dom_node($this);
2147 ++$this->cursor;
2148 $node->_[HDOM_INFO_TEXT] = '</' . $tag . '>';
2149 $this->link_nodes($node, false);
2150 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2151 return true;
2152 }
2153
2154 protected function skip($chars)
2155 {
2156 $this->pos += strspn($this->doc, $chars, $this->pos);
2157 $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2158 }
2159
2160 protected function copy_skip($chars)
2161 {
2162 $pos = $this->pos;
2163 $len = strspn($this->doc, $chars, $pos);
2164 $this->pos += $len;
2165 $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2166 if ($len === 0) { return ''; }
2167 return substr($this->doc, $pos, $len);
2168 }
2169
2170 protected function copy_until($chars)
2171 {
2172 $pos = $this->pos;
2173 $len = strcspn($this->doc, $chars, $pos);
2174 $this->pos += $len;
2175 $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2176 return substr($this->doc, $pos, $len);
2177 }
2178
2179 protected function copy_until_char($char)
2180 {
2181 if ($this->char === null) { return ''; }
2182
2183 if (($pos = strpos($this->doc, $char, $this->pos)) === false) {
2184 $ret = substr($this->doc, $this->pos, $this->size - $this->pos);
2185 $this->char = null;
2186 $this->pos = $this->size;
2187 return $ret;
2188 }
2189
2190 if ($pos === $this->pos) { return ''; }
2191
2192 $pos_old = $this->pos;
2193 $this->char = $this->doc[$pos];
2194 $this->pos = $pos;
2195 return substr($this->doc, $pos_old, $pos - $pos_old);
2196 }
2197
2198 protected function remove_noise($pattern, $remove_tag = false)
2199 {
2200 global $debug_object;
2201 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
2202
2203 $count = preg_match_all(
2204 $pattern,
2205 $this->doc,
2206 $matches,
2207 PREG_SET_ORDER | PREG_OFFSET_CAPTURE
2208 );
2209
2210 for ($i = $count - 1; $i > -1; --$i) {
2211 $key = '___noise___' . sprintf('% 5d', count($this->noise) + 1000);
2212
2213 if (is_object($debug_object)) {
2214 $debug_object->debug_log(2, 'key is: ' . $key);
2215 }
2216
2217 $idx = ($remove_tag) ? 0 : 1; // 0 = entire match, 1 = submatch
2218 $this->noise[$key] = $matches[$i][$idx][0];
2219 $this->doc = substr_replace($this->doc, $key, $matches[$i][$idx][1], strlen($matches[$i][$idx][0]));
2220 }
2221
2222 // reset the length of content
2223 $this->size = strlen($this->doc);
2224
2225 if ($this->size > 0) {
2226 $this->char = $this->doc[0];
2227 }
2228 }
2229
2230 function restore_noise($text)
2231 {
2232 global $debug_object;
2233 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
2234
2235 while (($pos = strpos($text, '___noise___')) !== false) {
2236 // Sometimes there is a broken piece of markup, and we don't GET the
2237 // pos+11 etc... token which indicates a problem outside of us...
2238
2239 // todo: "___noise___1000" (or any number with four or more digits)
2240 // in the DOM causes an infinite loop which could be utilized by
2241 // malicious software
2242 if (strlen($text) > $pos + 15) {
2243 $key = '___noise___'
2244 . $text[$pos + 11]
2245 . $text[$pos + 12]
2246 . $text[$pos + 13]
2247 . $text[$pos + 14]
2248 . $text[$pos + 15];
2249
2250 if (is_object($debug_object)) {
2251 $debug_object->debug_log(2, 'located key of: ' . $key);
2252 }
2253
2254 if (isset($this->noise[$key])) {
2255 $text = substr($text, 0, $pos)
2256 . $this->noise[$key]
2257 . substr($text, $pos + 16);
2258 } else {
2259 // do this to prevent an infinite loop.
2260 $text = substr($text, 0, $pos)
2261 . 'UNDEFINED NOISE FOR KEY: '
2262 . $key
2263 . substr($text, $pos + 16);
2264 }
2265 } else {
2266 // There is no valid key being given back to us... We must get
2267 // rid of the ___noise___ or we will have a problem.
2268 $text = substr($text, 0, $pos)
2269 . 'NO NUMERIC NOISE KEY'
2270 . substr($text, $pos + 11);
2271 }
2272 }
2273 return $text;
2274 }
2275
2276 function search_noise($text)
2277 {
2278 global $debug_object;
2279 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
2280
2281 foreach($this->noise as $noiseElement) {
2282 if (strpos($noiseElement, $text) !== false) {
2283 return $noiseElement;
2284 }
2285 }
2286 }
2287
2288 function __toString()
2289 {
2290 return $this->root->innertext();
2291 }
2292
2293 function __get($name)
2294 {
2295 switch ($name) {
2296 case 'outertext':
2297 return $this->root->innertext();
2298 case 'innertext':
2299 return $this->root->innertext();
2300 case 'plaintext':
2301 return $this->root->text();
2302 case 'charset':
2303 return $this->_charset;
2304 case 'target_charset':
2305 return $this->_target_charset;
2306 }
2307 }
2308
2309 function childNodes($idx = -1)
2310 {
2311 return $this->root->childNodes($idx);
2312 }
2313
2314 function firstChild()
2315 {
2316 return $this->root->first_child();
2317 }
2318
2319 function lastChild()
2320 {
2321 return $this->root->last_child();
2322 }
2323
2324 function createElement($name, $value = null)
2325 {
2326 return @str_get_html("<$name>$value</$name>")->firstChild();
2327 }
2328
2329 function createTextNode($value)
2330 {
2331 return @end(str_get_html($value)->nodes);
2332 }
2333
2334 function getElementById($id)
2335 {
2336 return $this->find("#$id", 0);
2337 }
2338
2339 function getElementsById($id, $idx = null)
2340 {
2341 return $this->find("#$id", $idx);
2342 }
2343
2344 function getElementByTagName($name)
2345 {
2346 return $this->find($name, 0);
2347 }
2348
2349 function getElementsByTagName($name, $idx = -1)
2350 {
2351 return $this->find($name, $idx);
2352 }
2353
2354 function loadFile()
2355 {
2356 $args = func_get_args();
2357 $this->load_file($args);
2358 }
2359 }
1 === Fast Velocity Minify ===
2 Contributors: Alignak
3 Tags: PHP Minify, Lighthouse, GTmetrix, Pingdom, Pagespeed, Merging, Minification, Optimization, Speed, Performance, FVM
4 Requires at least: 4.9
5 Requires PHP: 5.6
6 Stable tag: 3.2.2
7 Tested up to: 5.7.1
8 Text Domain: fast-velocity-minify
9 License: GPLv3 or later
10 License URI: http://www.gnu.org/licenses/gpl-3.0.html
11
12 Improve your speed score on GTmetrix, Pingdom Tools and Google PageSpeed Insights by adjusting your CSS and JS files (defer, async, minify, combine, etc), compressing HTML, simplifying fonts and a few more speed optimization options.
13
14
15 == Description ==
16 HTML, CSS & JS optimization plugin for developers and advanced users. Note you need to look into the HELP tab after installing the plugin and manually configure it for your site. Each site is different, so the default recommendations may or may not work for you and you will need to test and find out how to adjust your settings.
17
18 Minification is done on the frontend during the first uncached request. Once the first request is processed, any other pages that require the same set of CSS and JS files will be able to reuse the same generated static CSS or JS file.
19
20 If your cache is growing significantly, this could mean one of your CSS or JS files is dynamic and changes on every pageview. In that case, you would need to add the file to the ignore list, else the cache would grow indefinitely (because obviously the original files themselves are dynamic and when you minify, the plugin sees a different file).
21
22 Kindly read the HELP section after installing the plugin, about possible issues and how to solve them.
23
24 = Additional Optimization =
25
26 I can offer you additional `custom made` optimization on top of this plugin. If you would like to hire me, please visit my profile links for further information.
27
28
29 = WP-CLI Commands =
30 * Purge all caches: `wp fvm purge`
31 * Purge all caches on a network site: `wp --url=blog.example.com fvm purge`
32 * Purge all caches on the entire network (linux): `wp site list --field=url | xargs -n1 -I % wp --url=% fvm purge`
33
34 = How to customize the cache path ? =
35 You need a public directory to store and serve minified cache files. If you need to customize the path and url, you need to edit your `wp-config.php` and add both `define('FVM_DIR', '/path/to/example.com/your/public/directory');` and `define('FVM_URL', 'https://example.com/your/public/directory');` .
36
37
38 == Installation ==
39
40 1. Upload the plugin folder to the `/wp-content/plugins/` directory or upload the zip within WordPress
41 2. Activate the plugin through the `Plugins` menu in WordPress
42 3. Configure the options under: `Settings > Fast Velocity Minify` and that's it.
43
44
45 == Screenshots ==
46
47 1. The Settings page.
48
49
50 == Changelog ==
51
52 = 3.2.2 [2021.05.09] =
53 * added auto varnish cache purge for Cloudways
54 * fixed some JS files not being minified
55
56 = 3.2.1 [2021.05.07] =
57 * added support for custom cache location via wp-config.php constants
58 * changed the default cache directory to wp-content/cache
59
60 = 3.2.0 [2021.05.06] =
61 * fixed an issue where some files were not being minified
62 * better sourceMappingURL removal during minification
63
64 = 3.1.9 [2021.05.05] =
65 * fixed an issue with some base64 encoded fonts and icons becoming invalid
66 * changed @import CSS rules order according to specification (@import rules need to get processed first when minifying)
67
68 = 3.1.8 [2021.04.30] =
69 * fixed missing dynamic css/js urls
70 * fixed some relative static assets paths
71 * added auto disabling of FVM on Web Stories (AMP)
72
73 = 3.1.7 [2021.04.26] =
74 * more php notices fixes
75
76 = 3.1.6 [2021.04.25] =
77 * php notices fixes
78 * better http2 support
79
80 = 3.1.5 [2021.04.24] =
81 * added support for WP Cloudflare Super Page Cache plugin
82 * fixed support for LiteSpeed Cache purging
83 * changed the cache directory to the uploads directory (WP_Filesystem_Direct)
84 * deprecated CSS and JS merging as this is no longer recommended for HTTP/2 servers
85 * stop removing RSS feeds references on the header cleanup option
86 * changed some descriptions and updated the HELP section
87 * other bug fixes
88
89 = 3.1.4 [2021.01.11] =
90 * disable FVM update routines when a user runs wp-cli commands outside of the root directory
91 * database routine improvements for users with custom table prefixes
92
93 = 3.1.3 [2021.01.10] =
94 * Link preload headers improvement
95
96 = 3.1.2 [2021.01.09] =
97 * Fixed a PHP notice on wp-admin
98
99 = 3.1.1 [2021.01.09] =
100 * Added option to disable preload header
101 * Added support for the preload header importance attribute
102 * Better default settings for new installs
103 * Other bug fixes related to UTF-8 decoding and merging
104
105 = 3.1.0 [2021.01.06] =
106 * Added support for WP AMP by custom4web
107 * Fix for <code> and <pre> tags being minified
108 * Better HTML document detection for minification
109
110 = 3.0.9 [2021.01.04] =
111 * Added page caching purging support for Hummingbird and WP-Optimize from FVM
112
113 = 3.0.8 [2021.01.02] =
114 * Improved compatibility and better detection of dynamic CSS and JS files (files generated with PHP instead of being static)
115
116 = 3.0.7 [2021.01.02] =
117 * Fixed incorrect paths on subdirectory sites (inside merged CSS files)
118
119 = 3.0.6 [2021.01.01] =
120 * Adjusted the HELP tab settings
121 * Improved compatibility with CSS merging on WP Bakery
122
123 = 3.0.5 [2021.01.01] =
124 * Fixed the cache paths on Windows Servers
125 * Fixed incorrect file paths on subdirectory sites
126 * Fixed the CDN integration not replacing the domain name
127 * Fixed CSS font-display replacements
128
129 = 3.0.4 [2020.12.31] =
130 * Improved compatibility on CSS merging with optimole and similar services
131 * Fixed some PHP notices and other minor issues
132
133 = 3.0.3 [2020.12.29] =
134 * Prevent minification on XML content that do not trigger WordPress conditionals
135 * Added support for critical path positioning before the CSS files when Async mode is enabled
136 * Minor bugfixes
137
138 = 3.0.2 [2020.12.29] =
139 * Added option to preserve settings on uninstall
140 * Added option to inline all CSS (merging is still the recommended method)
141 * Added option to force HTTPS on the generated cache file urls
142 * Added an ignore list to the JS section (also imported from FVM 2 settings)
143 * Improved compatibility with FVM 2 (you still need to specify what JS paths you want to merge)
144 * Preserve the old FVM 2 settings on the database (will be removed on version 3.2)
145
146 = 3.0.1 [2020.12.27] =
147 * Added initial translation support under the "fast-velocity-minify" text domain.
148
149 = 3.0.0 [2020.12.26] =
150 * New version has been remade from scratch
151 * JS Optimization is disabled by default and requires manual configuration
152 * Third party scripts can now be delayed until user interaction, to improve the initial loading time
153
154 = 2.8.9 [2020.06.23] =
155 * new filter for wp hide compatibility
156
157 = 2.8.8 [2020.05.01] =
158 * bug fixes for woocommerce, which could result in 403 errors when adding to cart under certain cases
159
160 = 2.8.7 [2020.04.30] =
161 * fixed the sourceMappingURL removal regex introduced on 2.8.3 for js files and css files
162
163 = 2.8.6 [2020.04.30] =
164 * fixed an error notice on php
165
166 = 2.8.5 [2020.04.30] =
167 * bug fixes and some more minification default exclusions
168
169 = 2.8.4 [2020.04.24] =
170 * added frontend-builder-global-functions.js to the list of minification exclusions, but allowing merging (Divi Compatibility)
171
172 = 2.8.3 [2020.04.17] =
173 * Removed some options out of the autoload wp_option to avoid getting cached on the alloptions when using OPCache
174 * Removed the CDN purge option for WP Engine (not needed since FVM automatically does cache busting)
175 * Added support for Kinsta, Pagely, Pressidum, Savvii and Pantheon
176 * Better sourcemaps regex removal from minified css and js files
177
178 = 2.8.2 [2020.04.13] =
179 * Skip changing clip-path: url(#some-svg); to absolute urls during css minification
180 * Added a better cronjob duplicate cleanup task, when uninstalling the plugin
181
182 = 2.8.1 [2020.03.15] =
183 * added filter for the fvm_get_url function
184
185 = 2.8.0 [2020.03.10] =
186 * improved compatibility with Thrive Architect editor
187 * improved compatibility with Divi theme
188
189 = 2.7.9 [2020.02.18] =
190 * changed cache file names hash to longer names to avoid colisions on elementor plugin
191
192 = 2.7.8 [2020.02.06] =
193 * updated PHP Minify with full support for PHP 7.4
194 * added try, catch wrappers for merged javacript files with console log errors (instead of letting the browser stop execution on error)
195 * improved compatibility with windows servers
196 * improved compatibility for font paths with some themes
197
198 = 2.7.7 [2019.10.15] =
199 * added a capability check on the status page ajax request, which could show the cache file path when debug mode is enabled to subscribers
200
201 = 2.7.6 [2019.10.10] =
202 * bug fix release
203
204 = 2.7.5 [2019.10.09] =
205 * added support to "after" scripts added via wp_add_inline_script
206
207 = 2.7.4 [2019.08.18] =
208 * change to open JS/CSS files suspected of having PHP code via HTTP request, instead of reading the file directly from disk
209
210 = 2.7.3 [2019.07.29] =
211 * Beaver Builder compatibility fix
212
213 = 2.7.2 [2019.07.29] =
214 * fixed a PHP notice when WP_DEBUG mode is enabled on wordpress
215 * small improvements on google fonts merging
216
217 = 2.7.1 [2019.07.27] =
218 * fixed an AMP validation javascript error
219
220 = 2.7.0 [2019.07.23] =
221 * some score fixes when deferring to pagespeed is enabled
222
223 = 2.6.9 [2019.07.15] =
224 * custom cache path permissions fix (thanks to @fariazz)
225
226 = 2.6.8 [2019.07.06] =
227 * header preload fixes (thanks to @vandreev)
228
229 = 2.6.7 [2019.07.04] =
230 * added cache purging support for the swift cache plugin
231 * changed cache directory to the uploads directory for compatibility reasons
232 * better cache purging checks
233
234 = 2.6.6 [2019.06.20] =
235 * cache purging bug fixes
236 * php notice fixes
237
238 = 2.6.5 [2019.05.04] =
239 * fixed cache purging on Hyper Cache plugin
240 * removed support for WPFC (plugin author implemented a notice stating that FVM is incompatible with WPFC)
241 * improved the filtering engine for pagespeed insights on desktop
242
243 = 2.6.4 [2019.03.31] =
244 * fixed subdirectories permissions
245
246 = 2.6.3 [2019.03.30] =
247 * fixed another minor PHP notice
248
249 = 2.6.2 [2019.03.27] =
250 * fixed a PHP notice on urls with query strings that include arrays on keys or values
251
252 = 2.6.1 [2019.03.26] =
253 * fixed compatibility with the latest elementor plugin
254 * fixed adding duplicate cron jobs + existing duplicate cronjobs cleanup
255 * fixed duplicate "cache/cache" directory path
256 * changed the minimum PHP requirements to PHP 5.5
257
258 = 2.6.0 [2019.03.02] =
259 * fixed cache purging with the hypercache plugin
260 * fixed a bug with inline scripts and styles not showing up if there is no url for the enqueued handle
261 * changed the cache directory from the wp-content/uploads to wp-content/cache
262 * improved compatibility with page cache plugins and servers (purging FVM without purging the page cache should be fine now)
263 * added a daily cronjob, to delete public invalid cache files that are older than 3 months (your page cache should expire before this)
264
265 = 2.0.0 [2017.05.11] =
266 * version 2.x branch release
267
268 = 1.0 [2016.06.19] =
269 * Initial Release