Version Description
Download this release
Release Info
Developer | bpayton |
Plugin | Page Optimize |
Version | 0.4.2 |
Comparing to | |
See all releases |
Version 0.4.2
- LICENSE +339 -0
- concat-css.php +224 -0
- concat-js.php +297 -0
- cssmin/Colors.php +155 -0
- cssmin/Minifier.php +935 -0
- cssmin/Utils.php +149 -0
- cssmin/cssmin.php +8 -0
- dependency-path-mapping.php +135 -0
- page-optimize.php +269 -0
- readme.txt +48 -0
- service.php +331 -0
- settings.php +202 -0
- utils.php +44 -0
LICENSE
ADDED
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 2, June 1991
|
3 |
+
|
4 |
+
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
5 |
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
6 |
+
Everyone is permitted to copy and distribute verbatim copies
|
7 |
+
of this license document, but changing it is not allowed.
|
8 |
+
|
9 |
+
Preamble
|
10 |
+
|
11 |
+
The licenses for most software are designed to take away your
|
12 |
+
freedom to share and change it. By contrast, the GNU General Public
|
13 |
+
License is intended to guarantee your freedom to share and change free
|
14 |
+
software--to make sure the software is free for all its users. This
|
15 |
+
General Public License applies to most of the Free Software
|
16 |
+
Foundation's software and to any other program whose authors commit to
|
17 |
+
using it. (Some other Free Software Foundation software is covered by
|
18 |
+
the GNU Lesser General Public License instead.) You can apply it to
|
19 |
+
your programs, too.
|
20 |
+
|
21 |
+
When we speak of free software, we are referring to freedom, not
|
22 |
+
price. Our General Public Licenses are designed to make sure that you
|
23 |
+
have the freedom to distribute copies of free software (and charge for
|
24 |
+
this service if you wish), that you receive source code or can get it
|
25 |
+
if you want it, that you can change the software or use pieces of it
|
26 |
+
in new free programs; and that you know you can do these things.
|
27 |
+
|
28 |
+
To protect your rights, we need to make restrictions that forbid
|
29 |
+
anyone to deny you these rights or to ask you to surrender the rights.
|
30 |
+
These restrictions translate to certain responsibilities for you if you
|
31 |
+
distribute copies of the software, or if you modify it.
|
32 |
+
|
33 |
+
For example, if you distribute copies of such a program, whether
|
34 |
+
gratis or for a fee, you must give the recipients all the rights that
|
35 |
+
you have. You must make sure that they, too, receive or can get the
|
36 |
+
source code. And you must show them these terms so they know their
|
37 |
+
rights.
|
38 |
+
|
39 |
+
We protect your rights with two steps: (1) copyright the software, and
|
40 |
+
(2) offer you this license which gives you legal permission to copy,
|
41 |
+
distribute and/or modify the software.
|
42 |
+
|
43 |
+
Also, for each author's protection and ours, we want to make certain
|
44 |
+
that everyone understands that there is no warranty for this free
|
45 |
+
software. If the software is modified by someone else and passed on, we
|
46 |
+
want its recipients to know that what they have is not the original, so
|
47 |
+
that any problems introduced by others will not reflect on the original
|
48 |
+
authors' reputations.
|
49 |
+
|
50 |
+
Finally, any free program is threatened constantly by software
|
51 |
+
patents. We wish to avoid the danger that redistributors of a free
|
52 |
+
program will individually obtain patent licenses, in effect making the
|
53 |
+
program proprietary. To prevent this, we have made it clear that any
|
54 |
+
patent must be licensed for everyone's free use or not licensed at all.
|
55 |
+
|
56 |
+
The precise terms and conditions for copying, distribution and
|
57 |
+
modification follow.
|
58 |
+
|
59 |
+
GNU GENERAL PUBLIC LICENSE
|
60 |
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
61 |
+
|
62 |
+
0. This License applies to any program or other work which contains
|
63 |
+
a notice placed by the copyright holder saying it may be distributed
|
64 |
+
under the terms of this General Public License. The "Program", below,
|
65 |
+
refers to any such program or work, and a "work based on the Program"
|
66 |
+
means either the Program or any derivative work under copyright law:
|
67 |
+
that is to say, a work containing the Program or a portion of it,
|
68 |
+
either verbatim or with modifications and/or translated into another
|
69 |
+
language. (Hereinafter, translation is included without limitation in
|
70 |
+
the term "modification".) Each licensee is addressed as "you".
|
71 |
+
|
72 |
+
Activities other than copying, distribution and modification are not
|
73 |
+
covered by this License; they are outside its scope. The act of
|
74 |
+
running the Program is not restricted, and the output from the Program
|
75 |
+
is covered only if its contents constitute a work based on the
|
76 |
+
Program (independent of having been made by running the Program).
|
77 |
+
Whether that is true depends on what the Program does.
|
78 |
+
|
79 |
+
1. You may copy and distribute verbatim copies of the Program's
|
80 |
+
source code as you receive it, in any medium, provided that you
|
81 |
+
conspicuously and appropriately publish on each copy an appropriate
|
82 |
+
copyright notice and disclaimer of warranty; keep intact all the
|
83 |
+
notices that refer to this License and to the absence of any warranty;
|
84 |
+
and give any other recipients of the Program a copy of this License
|
85 |
+
along with the Program.
|
86 |
+
|
87 |
+
You may charge a fee for the physical act of transferring a copy, and
|
88 |
+
you may at your option offer warranty protection in exchange for a fee.
|
89 |
+
|
90 |
+
2. You may modify your copy or copies of the Program or any portion
|
91 |
+
of it, thus forming a work based on the Program, and copy and
|
92 |
+
distribute such modifications or work under the terms of Section 1
|
93 |
+
above, provided that you also meet all of these conditions:
|
94 |
+
|
95 |
+
a) You must cause the modified files to carry prominent notices
|
96 |
+
stating that you changed the files and the date of any change.
|
97 |
+
|
98 |
+
b) You must cause any work that you distribute or publish, that in
|
99 |
+
whole or in part contains or is derived from the Program or any
|
100 |
+
part thereof, to be licensed as a whole at no charge to all third
|
101 |
+
parties under the terms of this License.
|
102 |
+
|
103 |
+
c) If the modified program normally reads commands interactively
|
104 |
+
when run, you must cause it, when started running for such
|
105 |
+
interactive use in the most ordinary way, to print or display an
|
106 |
+
announcement including an appropriate copyright notice and a
|
107 |
+
notice that there is no warranty (or else, saying that you provide
|
108 |
+
a warranty) and that users may redistribute the program under
|
109 |
+
these conditions, and telling the user how to view a copy of this
|
110 |
+
License. (Exception: if the Program itself is interactive but
|
111 |
+
does not normally print such an announcement, your work based on
|
112 |
+
the Program is not required to print an announcement.)
|
113 |
+
|
114 |
+
These requirements apply to the modified work as a whole. If
|
115 |
+
identifiable sections of that work are not derived from the Program,
|
116 |
+
and can be reasonably considered independent and separate works in
|
117 |
+
themselves, then this License, and its terms, do not apply to those
|
118 |
+
sections when you distribute them as separate works. But when you
|
119 |
+
distribute the same sections as part of a whole which is a work based
|
120 |
+
on the Program, the distribution of the whole must be on the terms of
|
121 |
+
this License, whose permissions for other licensees extend to the
|
122 |
+
entire whole, and thus to each and every part regardless of who wrote it.
|
123 |
+
|
124 |
+
Thus, it is not the intent of this section to claim rights or contest
|
125 |
+
your rights to work written entirely by you; rather, the intent is to
|
126 |
+
exercise the right to control the distribution of derivative or
|
127 |
+
collective works based on the Program.
|
128 |
+
|
129 |
+
In addition, mere aggregation of another work not based on the Program
|
130 |
+
with the Program (or with a work based on the Program) on a volume of
|
131 |
+
a storage or distribution medium does not bring the other work under
|
132 |
+
the scope of this License.
|
133 |
+
|
134 |
+
3. You may copy and distribute the Program (or a work based on it,
|
135 |
+
under Section 2) in object code or executable form under the terms of
|
136 |
+
Sections 1 and 2 above provided that you also do one of the following:
|
137 |
+
|
138 |
+
a) Accompany it with the complete corresponding machine-readable
|
139 |
+
source code, which must be distributed under the terms of Sections
|
140 |
+
1 and 2 above on a medium customarily used for software interchange; or,
|
141 |
+
|
142 |
+
b) Accompany it with a written offer, valid for at least three
|
143 |
+
years, to give any third party, for a charge no more than your
|
144 |
+
cost of physically performing source distribution, a complete
|
145 |
+
machine-readable copy of the corresponding source code, to be
|
146 |
+
distributed under the terms of Sections 1 and 2 above on a medium
|
147 |
+
customarily used for software interchange; or,
|
148 |
+
|
149 |
+
c) Accompany it with the information you received as to the offer
|
150 |
+
to distribute corresponding source code. (This alternative is
|
151 |
+
allowed only for noncommercial distribution and only if you
|
152 |
+
received the program in object code or executable form with such
|
153 |
+
an offer, in accord with Subsection b above.)
|
154 |
+
|
155 |
+
The source code for a work means the preferred form of the work for
|
156 |
+
making modifications to it. For an executable work, complete source
|
157 |
+
code means all the source code for all modules it contains, plus any
|
158 |
+
associated interface definition files, plus the scripts used to
|
159 |
+
control compilation and installation of the executable. However, as a
|
160 |
+
special exception, the source code distributed need not include
|
161 |
+
anything that is normally distributed (in either source or binary
|
162 |
+
form) with the major components (compiler, kernel, and so on) of the
|
163 |
+
operating system on which the executable runs, unless that component
|
164 |
+
itself accompanies the executable.
|
165 |
+
|
166 |
+
If distribution of executable or object code is made by offering
|
167 |
+
access to copy from a designated place, then offering equivalent
|
168 |
+
access to copy the source code from the same place counts as
|
169 |
+
distribution of the source code, even though third parties are not
|
170 |
+
compelled to copy the source along with the object code.
|
171 |
+
|
172 |
+
4. You may not copy, modify, sublicense, or distribute the Program
|
173 |
+
except as expressly provided under this License. Any attempt
|
174 |
+
otherwise to copy, modify, sublicense or distribute the Program is
|
175 |
+
void, and will automatically terminate your rights under this License.
|
176 |
+
However, parties who have received copies, or rights, from you under
|
177 |
+
this License will not have their licenses terminated so long as such
|
178 |
+
parties remain in full compliance.
|
179 |
+
|
180 |
+
5. You are not required to accept this License, since you have not
|
181 |
+
signed it. However, nothing else grants you permission to modify or
|
182 |
+
distribute the Program or its derivative works. These actions are
|
183 |
+
prohibited by law if you do not accept this License. Therefore, by
|
184 |
+
modifying or distributing the Program (or any work based on the
|
185 |
+
Program), you indicate your acceptance of this License to do so, and
|
186 |
+
all its terms and conditions for copying, distributing or modifying
|
187 |
+
the Program or works based on it.
|
188 |
+
|
189 |
+
6. Each time you redistribute the Program (or any work based on the
|
190 |
+
Program), the recipient automatically receives a license from the
|
191 |
+
original licensor to copy, distribute or modify the Program subject to
|
192 |
+
these terms and conditions. You may not impose any further
|
193 |
+
restrictions on the recipients' exercise of the rights granted herein.
|
194 |
+
You are not responsible for enforcing compliance by third parties to
|
195 |
+
this License.
|
196 |
+
|
197 |
+
7. If, as a consequence of a court judgment or allegation of patent
|
198 |
+
infringement or for any other reason (not limited to patent issues),
|
199 |
+
conditions are imposed on you (whether by court order, agreement or
|
200 |
+
otherwise) that contradict the conditions of this License, they do not
|
201 |
+
excuse you from the conditions of this License. If you cannot
|
202 |
+
distribute so as to satisfy simultaneously your obligations under this
|
203 |
+
License and any other pertinent obligations, then as a consequence you
|
204 |
+
may not distribute the Program at all. For example, if a patent
|
205 |
+
license would not permit royalty-free redistribution of the Program by
|
206 |
+
all those who receive copies directly or indirectly through you, then
|
207 |
+
the only way you could satisfy both it and this License would be to
|
208 |
+
refrain entirely from distribution of the Program.
|
209 |
+
|
210 |
+
If any portion of this section is held invalid or unenforceable under
|
211 |
+
any particular circumstance, the balance of the section is intended to
|
212 |
+
apply and the section as a whole is intended to apply in other
|
213 |
+
circumstances.
|
214 |
+
|
215 |
+
It is not the purpose of this section to induce you to infringe any
|
216 |
+
patents or other property right claims or to contest validity of any
|
217 |
+
such claims; this section has the sole purpose of protecting the
|
218 |
+
integrity of the free software distribution system, which is
|
219 |
+
implemented by public license practices. Many people have made
|
220 |
+
generous contributions to the wide range of software distributed
|
221 |
+
through that system in reliance on consistent application of that
|
222 |
+
system; it is up to the author/donor to decide if he or she is willing
|
223 |
+
to distribute software through any other system and a licensee cannot
|
224 |
+
impose that choice.
|
225 |
+
|
226 |
+
This section is intended to make thoroughly clear what is believed to
|
227 |
+
be a consequence of the rest of this License.
|
228 |
+
|
229 |
+
8. If the distribution and/or use of the Program is restricted in
|
230 |
+
certain countries either by patents or by copyrighted interfaces, the
|
231 |
+
original copyright holder who places the Program under this License
|
232 |
+
may add an explicit geographical distribution limitation excluding
|
233 |
+
those countries, so that distribution is permitted only in or among
|
234 |
+
countries not thus excluded. In such case, this License incorporates
|
235 |
+
the limitation as if written in the body of this License.
|
236 |
+
|
237 |
+
9. The Free Software Foundation may publish revised and/or new versions
|
238 |
+
of the General Public License from time to time. Such new versions will
|
239 |
+
be similar in spirit to the present version, but may differ in detail to
|
240 |
+
address new problems or concerns.
|
241 |
+
|
242 |
+
Each version is given a distinguishing version number. If the Program
|
243 |
+
specifies a version number of this License which applies to it and "any
|
244 |
+
later version", you have the option of following the terms and conditions
|
245 |
+
either of that version or of any later version published by the Free
|
246 |
+
Software Foundation. If the Program does not specify a version number of
|
247 |
+
this License, you may choose any version ever published by the Free Software
|
248 |
+
Foundation.
|
249 |
+
|
250 |
+
10. If you wish to incorporate parts of the Program into other free
|
251 |
+
programs whose distribution conditions are different, write to the author
|
252 |
+
to ask for permission. For software which is copyrighted by the Free
|
253 |
+
Software Foundation, write to the Free Software Foundation; we sometimes
|
254 |
+
make exceptions for this. Our decision will be guided by the two goals
|
255 |
+
of preserving the free status of all derivatives of our free software and
|
256 |
+
of promoting the sharing and reuse of software generally.
|
257 |
+
|
258 |
+
NO WARRANTY
|
259 |
+
|
260 |
+
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
261 |
+
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
262 |
+
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
263 |
+
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
264 |
+
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
265 |
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
266 |
+
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
267 |
+
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
268 |
+
REPAIR OR CORRECTION.
|
269 |
+
|
270 |
+
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
271 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
272 |
+
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
273 |
+
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
274 |
+
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
275 |
+
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
276 |
+
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
277 |
+
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
278 |
+
POSSIBILITY OF SUCH DAMAGES.
|
279 |
+
|
280 |
+
END OF TERMS AND CONDITIONS
|
281 |
+
|
282 |
+
How to Apply These Terms to Your New Programs
|
283 |
+
|
284 |
+
If you develop a new program, and you want it to be of the greatest
|
285 |
+
possible use to the public, the best way to achieve this is to make it
|
286 |
+
free software which everyone can redistribute and change under these terms.
|
287 |
+
|
288 |
+
To do so, attach the following notices to the program. It is safest
|
289 |
+
to attach them to the start of each source file to most effectively
|
290 |
+
convey the exclusion of warranty; and each file should have at least
|
291 |
+
the "copyright" line and a pointer to where the full notice is found.
|
292 |
+
|
293 |
+
{description}
|
294 |
+
Copyright (C) {year} {fullname}
|
295 |
+
|
296 |
+
This program is free software; you can redistribute it and/or modify
|
297 |
+
it under the terms of the GNU General Public License as published by
|
298 |
+
the Free Software Foundation; either version 2 of the License, or
|
299 |
+
(at your option) any later version.
|
300 |
+
|
301 |
+
This program is distributed in the hope that it will be useful,
|
302 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
303 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
304 |
+
GNU General Public License for more details.
|
305 |
+
|
306 |
+
You should have received a copy of the GNU General Public License along
|
307 |
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
308 |
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
309 |
+
|
310 |
+
Also add information on how to contact you by electronic and paper mail.
|
311 |
+
|
312 |
+
If the program is interactive, make it output a short notice like this
|
313 |
+
when it starts in an interactive mode:
|
314 |
+
|
315 |
+
Gnomovision version 69, Copyright (C) year name of author
|
316 |
+
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
317 |
+
This is free software, and you are welcome to redistribute it
|
318 |
+
under certain conditions; type `show c' for details.
|
319 |
+
|
320 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
321 |
+
parts of the General Public License. Of course, the commands you use may
|
322 |
+
be called something other than `show w' and `show c'; they could even be
|
323 |
+
mouse-clicks or menu items--whatever suits your program.
|
324 |
+
|
325 |
+
You should also get your employer (if you work as a programmer) or your
|
326 |
+
school, if any, to sign a "copyright disclaimer" for the program, if
|
327 |
+
necessary. Here is a sample; alter the names:
|
328 |
+
|
329 |
+
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
330 |
+
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
331 |
+
|
332 |
+
{signature of Ty Coon}, 1 April 1989
|
333 |
+
Ty Coon, President of Vice
|
334 |
+
|
335 |
+
This General Public License does not permit incorporating your program into
|
336 |
+
proprietary programs. If your program is a subroutine library, you may
|
337 |
+
consider it more useful to permit linking proprietary applications with the
|
338 |
+
library. If this is what you want to do, use the GNU Lesser General
|
339 |
+
Public License instead of this License.
|
concat-css.php
ADDED
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
require_once __DIR__ . '/dependency-path-mapping.php';
|
4 |
+
require_once __DIR__ . '/utils.php';
|
5 |
+
|
6 |
+
if ( ! defined( 'ALLOW_GZIP_COMPRESSION' ) ) {
|
7 |
+
define( 'ALLOW_GZIP_COMPRESSION', true );
|
8 |
+
}
|
9 |
+
|
10 |
+
class Page_Optimize_CSS_Concat extends WP_Styles {
|
11 |
+
private $dependency_path_mapping;
|
12 |
+
private $old_styles;
|
13 |
+
|
14 |
+
public $allow_gzip_compression;
|
15 |
+
|
16 |
+
function __construct( $styles ) {
|
17 |
+
if ( empty( $styles ) || ! ( $styles instanceof WP_Styles ) ) {
|
18 |
+
$this->old_styles = new WP_Styles();
|
19 |
+
} else {
|
20 |
+
$this->old_styles = $styles;
|
21 |
+
}
|
22 |
+
|
23 |
+
// Unset all the object properties except our private copy of the styles object.
|
24 |
+
// We have to unset everything so that the overload methods talk to $this->old_styles->whatever
|
25 |
+
// instead of $this->whatever.
|
26 |
+
foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
|
27 |
+
if ( 'old_styles' === $key ) {
|
28 |
+
continue;
|
29 |
+
}
|
30 |
+
unset( $this->$key );
|
31 |
+
}
|
32 |
+
|
33 |
+
$this->dependency_path_mapping = new Page_Optimize_Dependency_Path_Mapping(
|
34 |
+
apply_filters( 'page_optimize_site_url', $this->base_url )
|
35 |
+
);
|
36 |
+
}
|
37 |
+
|
38 |
+
function do_items( $handles = false, $group = false ) {
|
39 |
+
$handles = false === $handles ? $this->queue : (array) $handles;
|
40 |
+
$stylesheets = array();
|
41 |
+
$siteurl = apply_filters( 'page_optimize_site_url', $this->base_url );
|
42 |
+
|
43 |
+
$this->all_deps( $handles );
|
44 |
+
|
45 |
+
$stylesheet_group_index = 0;
|
46 |
+
// Merge CSS into a single file
|
47 |
+
$concat_group = 'concat';
|
48 |
+
// Concat group on top (first array element gets processed earlier)
|
49 |
+
$stylesheets[ $concat_group ] = array();
|
50 |
+
|
51 |
+
foreach ( $this->to_do as $key => $handle ) {
|
52 |
+
$obj = $this->registered[ $handle ];
|
53 |
+
$obj->src = apply_filters( 'style_loader_src', $obj->src, $obj->handle );
|
54 |
+
|
55 |
+
// Core is kind of broken and returns "true" for src of "colors" handle
|
56 |
+
// http://core.trac.wordpress.org/attachment/ticket/16827/colors-hacked-fixed.diff
|
57 |
+
// http://core.trac.wordpress.org/ticket/20729
|
58 |
+
$css_url = $obj->src;
|
59 |
+
if ( 'colors' == $obj->handle && true === $css_url ) {
|
60 |
+
$css_url = wp_style_loader_src( $css_url, $obj->handle );
|
61 |
+
}
|
62 |
+
|
63 |
+
$css_url_parsed = parse_url( $obj->src );
|
64 |
+
$extra = $obj->extra;
|
65 |
+
|
66 |
+
// Don't concat by default
|
67 |
+
$do_concat = false;
|
68 |
+
|
69 |
+
// Only try to concat static css files
|
70 |
+
if ( false !== strpos( $css_url_parsed['path'], '.css' ) ) {
|
71 |
+
$do_concat = true;
|
72 |
+
} else {
|
73 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
74 |
+
echo sprintf( "\n<!-- No Concat CSS %s => Maybe Not Static File %s -->\n", esc_html( $handle ), esc_html( $obj->src ) );
|
75 |
+
}
|
76 |
+
}
|
77 |
+
|
78 |
+
// Don't try to concat styles which are loaded conditionally (like IE stuff)
|
79 |
+
if ( isset( $extra['conditional'] ) ) {
|
80 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
81 |
+
echo sprintf( "\n<!-- No Concat CSS %s => Has Conditional -->\n", esc_html( $handle ) );
|
82 |
+
}
|
83 |
+
$do_concat = false;
|
84 |
+
}
|
85 |
+
|
86 |
+
// Don't concat rtl stuff for now until concat supports it correctly
|
87 |
+
if ( $do_concat && 'rtl' === $this->text_direction && ! empty( $extra['rtl'] ) ) {
|
88 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
89 |
+
echo sprintf( "\n<!-- No Concat CSS %s => Is RTL -->\n", esc_html( $handle ) );
|
90 |
+
}
|
91 |
+
$do_concat = false;
|
92 |
+
}
|
93 |
+
|
94 |
+
// Don't try to concat externally hosted scripts
|
95 |
+
$is_internal_uri = $this->dependency_path_mapping->is_internal_uri( $css_url );
|
96 |
+
if ( $do_concat && ! $is_internal_uri ) {
|
97 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
98 |
+
echo sprintf( "\n<!-- No Concat CSS %s => External URL: %s -->\n", esc_html( $handle ), esc_url( $css_url ) );
|
99 |
+
}
|
100 |
+
$do_concat = false;
|
101 |
+
}
|
102 |
+
|
103 |
+
if ( $do_concat ) {
|
104 |
+
// Resolve paths and concat styles that exist in the filesystem
|
105 |
+
$css_realpath = $this->dependency_path_mapping->dependency_src_to_fs_path( $css_url );
|
106 |
+
if ( false === $css_realpath ) {
|
107 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
108 |
+
echo sprintf( "\n<!-- No Concat CSS %s => Invalid Path %s -->\n", esc_html( $handle ), esc_html( $css_realpath ) );
|
109 |
+
}
|
110 |
+
$do_concat = false;
|
111 |
+
}
|
112 |
+
}
|
113 |
+
|
114 |
+
// Skip concating CSS from exclusion list
|
115 |
+
$exclude_list = page_optimize_css_exclude_list();
|
116 |
+
foreach ( $exclude_list as $exclude ) {
|
117 |
+
if ( $do_concat && $handle === $exclude ) {
|
118 |
+
$do_concat = false;
|
119 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
120 |
+
echo sprintf( "\n<!-- No Concat CSS %s => Excluded option -->\n", esc_html( $handle ) );
|
121 |
+
}
|
122 |
+
}
|
123 |
+
}
|
124 |
+
|
125 |
+
// Allow plugins to disable concatenation of certain stylesheets.
|
126 |
+
if ( $do_concat && ! apply_filters( 'css_do_concat', $do_concat, $handle ) ) {
|
127 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
128 |
+
echo sprintf( "\n<!-- No Concat CSS %s => Filtered `false` -->\n", esc_html( $handle ) );
|
129 |
+
}
|
130 |
+
}
|
131 |
+
$do_concat = apply_filters( 'css_do_concat', $do_concat, $handle );
|
132 |
+
|
133 |
+
if ( true === $do_concat ) {
|
134 |
+
$media = $obj->args;
|
135 |
+
if ( empty( $media ) ) {
|
136 |
+
$media = 'all';
|
137 |
+
}
|
138 |
+
|
139 |
+
$stylesheets[ $concat_group ][ $media ][ $handle ] = $css_url_parsed['path'];
|
140 |
+
$this->done[] = $handle;
|
141 |
+
} else {
|
142 |
+
$stylesheet_group_index ++;
|
143 |
+
$stylesheets[ $stylesheet_group_index ]['noconcat'][] = $handle;
|
144 |
+
$stylesheet_group_index ++;
|
145 |
+
}
|
146 |
+
unset( $this->to_do[ $key ] );
|
147 |
+
}
|
148 |
+
|
149 |
+
foreach ( $stylesheets as $idx => $stylesheets_group ) {
|
150 |
+
foreach ( $stylesheets_group as $media => $css ) {
|
151 |
+
if ( 'noconcat' == $media ) {
|
152 |
+
foreach ( $css as $handle ) {
|
153 |
+
if ( $this->do_item( $handle, $group ) ) {
|
154 |
+
$this->done[] = $handle;
|
155 |
+
}
|
156 |
+
}
|
157 |
+
continue;
|
158 |
+
} elseif ( count( $css ) > 1 ) {
|
159 |
+
$fs_paths = array();
|
160 |
+
foreach ( $css as $css_uri_path ) {
|
161 |
+
$fs_paths[] = $this->dependency_path_mapping->uri_path_to_fs_path( $css_uri_path );
|
162 |
+
}
|
163 |
+
|
164 |
+
$mtime = max( array_map( 'filemtime', $fs_paths ) );
|
165 |
+
if ( page_optimize_use_concat_base_dir() ) {
|
166 |
+
$path_str = implode( ',', array_map( 'page_optimize_remove_concat_base_prefix', $fs_paths ) );
|
167 |
+
} else {
|
168 |
+
$path_str = implode( ',', $css );
|
169 |
+
}
|
170 |
+
$path_str = "$path_str?m=$mtime";
|
171 |
+
|
172 |
+
if ( $this->allow_gzip_compression ) {
|
173 |
+
$path_64 = base64_encode( gzcompress( $path_str ) );
|
174 |
+
if ( strlen( $path_str ) > ( strlen( $path_64 ) + 1 ) ) {
|
175 |
+
$path_str = '-' . $path_64;
|
176 |
+
}
|
177 |
+
}
|
178 |
+
|
179 |
+
$href = $siteurl . "/_static/??" . $path_str;
|
180 |
+
} else {
|
181 |
+
$href = Page_Optimize_Utils::cache_bust_mtime( current( $css ), $siteurl );
|
182 |
+
}
|
183 |
+
|
184 |
+
$handles = array_keys( $css );
|
185 |
+
$css_id = "$media-css-" . md5( $href );
|
186 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
187 |
+
echo apply_filters( 'page_optimize_style_loader_tag', "<link data-handles='" . esc_attr( implode( ',', $handles ) ) . "' rel='stylesheet' id='$css_id' href='$href' type='text/css' media='$media' />\n", $handles, $href, $media );
|
188 |
+
} else {
|
189 |
+
echo apply_filters( 'page_optimize_style_loader_tag', "<link rel='stylesheet' id='$css_id' href='$href' type='text/css' media='$media' />\n", $handles, $href, $media );
|
190 |
+
}
|
191 |
+
array_map( array( $this, 'print_inline_style' ), array_keys( $css ) );
|
192 |
+
}
|
193 |
+
}
|
194 |
+
|
195 |
+
return $this->done;
|
196 |
+
}
|
197 |
+
|
198 |
+
function __isset( $key ) {
|
199 |
+
return isset( $this->old_styles->$key );
|
200 |
+
}
|
201 |
+
|
202 |
+
function __unset( $key ) {
|
203 |
+
unset( $this->old_styles->$key );
|
204 |
+
}
|
205 |
+
|
206 |
+
function &__get( $key ) {
|
207 |
+
return $this->old_styles->$key;
|
208 |
+
}
|
209 |
+
|
210 |
+
function __set( $key, $value ) {
|
211 |
+
$this->old_styles->$key = $value;
|
212 |
+
}
|
213 |
+
}
|
214 |
+
|
215 |
+
function page_optimize_css_concat_init() {
|
216 |
+
global $wp_styles;
|
217 |
+
|
218 |
+
$wp_styles = new Page_Optimize_CSS_Concat( $wp_styles );
|
219 |
+
$wp_styles->allow_gzip_compression = ALLOW_GZIP_COMPRESSION;
|
220 |
+
}
|
221 |
+
|
222 |
+
if ( page_optimize_should_concat_css() ) {
|
223 |
+
add_action( 'init', 'page_optimize_css_concat_init' );
|
224 |
+
}
|
concat-js.php
ADDED
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
require_once __DIR__ . '/dependency-path-mapping.php';
|
4 |
+
require_once __DIR__ . '/utils.php';
|
5 |
+
|
6 |
+
if ( ! defined( 'ALLOW_GZIP_COMPRESSION' ) ) {
|
7 |
+
define( 'ALLOW_GZIP_COMPRESSION', true );
|
8 |
+
}
|
9 |
+
|
10 |
+
class Page_Optimize_JS_Concat extends WP_Scripts {
|
11 |
+
private $dependency_path_mapping;
|
12 |
+
private $old_scripts;
|
13 |
+
|
14 |
+
public $allow_gzip_compression;
|
15 |
+
|
16 |
+
function __construct( $scripts ) {
|
17 |
+
if ( empty( $scripts ) || ! ( $scripts instanceof WP_Scripts ) ) {
|
18 |
+
$this->old_scripts = new WP_Scripts();
|
19 |
+
} else {
|
20 |
+
$this->old_scripts = $scripts;
|
21 |
+
}
|
22 |
+
|
23 |
+
// Unset all the object properties except our private copy of the scripts object.
|
24 |
+
// We have to unset everything so that the overload methods talk to $this->old_scripts->whatever
|
25 |
+
// instead of $this->whatever.
|
26 |
+
foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
|
27 |
+
if ( 'old_scripts' === $key ) {
|
28 |
+
continue;
|
29 |
+
}
|
30 |
+
unset( $this->$key );
|
31 |
+
}
|
32 |
+
|
33 |
+
$this->dependency_path_mapping = new Page_Optimize_Dependency_Path_Mapping(
|
34 |
+
apply_filters( 'page_optimize_site_url', $this->base_url )
|
35 |
+
);
|
36 |
+
}
|
37 |
+
|
38 |
+
protected function has_inline_content( $handle ) {
|
39 |
+
$before_output = $this->get_data( $handle, 'before' );
|
40 |
+
if ( ! empty( $before_output ) ) {
|
41 |
+
return true;
|
42 |
+
}
|
43 |
+
|
44 |
+
$after_output = $this->get_data( $handle, 'after' );
|
45 |
+
if ( ! empty( $after_output ) ) {
|
46 |
+
return true;
|
47 |
+
}
|
48 |
+
|
49 |
+
// JavaScript translations
|
50 |
+
$has_translations = ! empty( $this->registered[ $handle ]->textdomain );
|
51 |
+
if ( $has_translations ) {
|
52 |
+
return true;
|
53 |
+
}
|
54 |
+
|
55 |
+
return false;
|
56 |
+
}
|
57 |
+
|
58 |
+
function do_items( $handles = false, $group = false ) {
|
59 |
+
$handles = false === $handles ? $this->queue : (array) $handles;
|
60 |
+
$javascripts = array();
|
61 |
+
$siteurl = apply_filters( 'page_optimize_site_url', $this->base_url );
|
62 |
+
$this->all_deps( $handles );
|
63 |
+
$level = 0;
|
64 |
+
|
65 |
+
$using_strict = false;
|
66 |
+
foreach ( $this->to_do as $key => $handle ) {
|
67 |
+
$script_is_strict = false;
|
68 |
+
if ( in_array( $handle, $this->done ) || ! isset( $this->registered[ $handle ] ) ) {
|
69 |
+
continue;
|
70 |
+
}
|
71 |
+
|
72 |
+
if ( 0 === $group && $this->groups[ $handle ] > 0 ) {
|
73 |
+
$this->in_footer[] = $handle;
|
74 |
+
unset( $this->to_do[ $key ] );
|
75 |
+
continue;
|
76 |
+
}
|
77 |
+
|
78 |
+
if ( ! $this->registered[ $handle ]->src ) { // Defines a group.
|
79 |
+
// if there are localized items, echo them
|
80 |
+
$this->print_extra_script( $handle );
|
81 |
+
$this->done[] = $handle;
|
82 |
+
continue;
|
83 |
+
}
|
84 |
+
|
85 |
+
if ( false === $group && in_array( $handle, $this->in_footer, true ) ) {
|
86 |
+
$this->in_footer = array_diff( $this->in_footer, (array) $handle );
|
87 |
+
}
|
88 |
+
|
89 |
+
$obj = $this->registered[ $handle ];
|
90 |
+
$js_url = $obj->src;
|
91 |
+
$js_url_parsed = parse_url( $js_url );
|
92 |
+
|
93 |
+
// Don't concat by default
|
94 |
+
$do_concat = false;
|
95 |
+
|
96 |
+
// Only try to concat static js files
|
97 |
+
if ( false !== strpos( $js_url_parsed['path'], '.js' ) ) {
|
98 |
+
$do_concat = page_optimize_should_concat_js();
|
99 |
+
} else {
|
100 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
101 |
+
echo sprintf( "\n<!-- No Concat JS %s => Maybe Not Static File %s -->\n", esc_html( $handle ), esc_html( $obj->src ) );
|
102 |
+
}
|
103 |
+
}
|
104 |
+
|
105 |
+
// Don't try to concat externally hosted scripts
|
106 |
+
$is_internal_uri = $this->dependency_path_mapping->is_internal_uri( $js_url );
|
107 |
+
if ( $do_concat && ! $is_internal_uri ) {
|
108 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
109 |
+
echo sprintf( "\n<!-- No Concat JS %s => External URL: %s -->\n", esc_html( $handle ), esc_url( $js_url ) );
|
110 |
+
}
|
111 |
+
$do_concat = false;
|
112 |
+
}
|
113 |
+
|
114 |
+
if ( $do_concat ) {
|
115 |
+
// Resolve paths and concat scripts that exist in the filesystem
|
116 |
+
$js_realpath = $this->dependency_path_mapping->dependency_src_to_fs_path( $js_url );
|
117 |
+
if ( false === $js_realpath ) {
|
118 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
119 |
+
echo sprintf( "\n<!-- No Concat JS %s => Invalid Path %s -->\n", esc_html( $handle ), esc_html( $js_realpath ) );
|
120 |
+
}
|
121 |
+
$do_concat = false;
|
122 |
+
}
|
123 |
+
}
|
124 |
+
|
125 |
+
if ( $do_concat && $this->has_inline_content( $handle ) ) {
|
126 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
127 |
+
echo sprintf( "\n<!-- No Concat JS %s => Has Inline Content -->\n", esc_html( $handle ) );
|
128 |
+
}
|
129 |
+
$do_concat = false;
|
130 |
+
}
|
131 |
+
|
132 |
+
// Skip core scripts that use Strict Mode
|
133 |
+
if ( $do_concat && ( 'react' === $handle || 'react-dom' === $handle ) ) {
|
134 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
135 |
+
echo sprintf( "\n<!-- No Concat JS %s => Has Strict Mode (Core) -->\n", esc_html( $handle ) );
|
136 |
+
}
|
137 |
+
$do_concat = false;
|
138 |
+
$script_is_strict = true;
|
139 |
+
} else if ( $do_concat && preg_match_all( '/^[\',"]use strict[\',"];/Uims', file_get_contents( $js_realpath ), $matches ) ) {
|
140 |
+
// Skip third-party scripts that use Strict Mode
|
141 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
142 |
+
echo sprintf( "\n<!-- No Concat JS %s => Has Strict Mode (Third-Party) -->\n", esc_html( $handle ) );
|
143 |
+
}
|
144 |
+
$do_concat = false;
|
145 |
+
$script_is_strict = true;
|
146 |
+
} else {
|
147 |
+
$script_is_strict = false;
|
148 |
+
}
|
149 |
+
|
150 |
+
// Skip concating scripts from exclusion list
|
151 |
+
$exclude_list = page_optimize_js_exclude_list();
|
152 |
+
foreach ( $exclude_list as $exclude ) {
|
153 |
+
if ( $do_concat && $handle === $exclude ) {
|
154 |
+
$do_concat = false;
|
155 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
156 |
+
echo sprintf( "\n<!-- No Concat JS %s => Excluded option -->\n", esc_html( $handle ) );
|
157 |
+
}
|
158 |
+
}
|
159 |
+
}
|
160 |
+
|
161 |
+
// Allow plugins to disable concatenation of certain scripts.
|
162 |
+
if ( $do_concat && ! apply_filters( 'js_do_concat', $do_concat, $handle ) ) {
|
163 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
164 |
+
echo sprintf( "\n<!-- No Concat JS %s => Filtered `false` -->\n", esc_html( $handle ) );
|
165 |
+
}
|
166 |
+
}
|
167 |
+
$do_concat = apply_filters( 'js_do_concat', $do_concat, $handle );
|
168 |
+
|
169 |
+
if ( true === $do_concat ) {
|
170 |
+
if ( ! isset( $javascripts[ $level ] ) ) {
|
171 |
+
$javascripts[ $level ]['type'] = 'concat';
|
172 |
+
}
|
173 |
+
|
174 |
+
$javascripts[ $level ]['paths'][] = $js_url_parsed['path'];
|
175 |
+
$javascripts[ $level ]['handles'][] = $handle;
|
176 |
+
|
177 |
+
} else {
|
178 |
+
$level ++;
|
179 |
+
$javascripts[ $level ]['type'] = 'do_item';
|
180 |
+
$javascripts[ $level ]['handle'] = $handle;
|
181 |
+
$level ++;
|
182 |
+
}
|
183 |
+
unset( $this->to_do[ $key ] );
|
184 |
+
|
185 |
+
if ( $using_strict !== $script_is_strict ) {
|
186 |
+
if ( $script_is_strict ) {
|
187 |
+
$using_strict = true;
|
188 |
+
$strict_count = 0;
|
189 |
+
} else {
|
190 |
+
$using_strict = false;
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
if ( $script_is_strict ) {
|
195 |
+
$strict_count ++;
|
196 |
+
}
|
197 |
+
}
|
198 |
+
|
199 |
+
if ( empty( $javascripts ) ) {
|
200 |
+
return $this->done;
|
201 |
+
}
|
202 |
+
|
203 |
+
foreach ( $javascripts as $js_array ) {
|
204 |
+
if ( 'do_item' == $js_array['type'] ) {
|
205 |
+
if ( $this->do_item( $js_array['handle'], $group ) ) {
|
206 |
+
$this->done[] = $js_array['handle'];
|
207 |
+
}
|
208 |
+
} else if ( 'concat' == $js_array['type'] ) {
|
209 |
+
array_map( array( $this, 'print_extra_script' ), $js_array['handles'] );
|
210 |
+
|
211 |
+
if ( isset( $js_array['paths'] ) && count( $js_array['paths'] ) > 1 ) {
|
212 |
+
$fs_paths = array();
|
213 |
+
foreach ( $js_array['paths'] as $js_url ) {
|
214 |
+
$fs_paths[] = $this->dependency_path_mapping->uri_path_to_fs_path( $js_url );
|
215 |
+
}
|
216 |
+
|
217 |
+
$mtime = max( array_map( 'filemtime', $fs_paths ) );
|
218 |
+
if ( page_optimize_use_concat_base_dir() ) {
|
219 |
+
$path_str = implode( ',', array_map( 'page_optimize_remove_concat_base_prefix', $fs_paths ) );
|
220 |
+
} else {
|
221 |
+
$path_str = implode( ',', $js_array['paths'] );
|
222 |
+
}
|
223 |
+
$path_str = "$path_str?m=$mtime";
|
224 |
+
|
225 |
+
if ( $this->allow_gzip_compression ) {
|
226 |
+
$path_64 = base64_encode( gzcompress( $path_str ) );
|
227 |
+
if ( strlen( $path_str ) > ( strlen( $path_64 ) + 1 ) ) {
|
228 |
+
$path_str = '-' . $path_64;
|
229 |
+
}
|
230 |
+
}
|
231 |
+
|
232 |
+
$href = $siteurl . "/_static/??" . $path_str;
|
233 |
+
} elseif ( isset( $js_array['paths'] ) && is_array( $js_array['paths'] ) ) {
|
234 |
+
$href = Page_Optimize_Utils::cache_bust_mtime( $js_array['paths'][0], $siteurl );
|
235 |
+
}
|
236 |
+
|
237 |
+
$this->done = array_merge( $this->done, $js_array['handles'] );
|
238 |
+
|
239 |
+
// Print before/after scripts from wp_inline_scripts() and concatenated script tag
|
240 |
+
if ( isset( $js_array['extras']['before'] ) ) {
|
241 |
+
foreach ( $js_array['extras']['before'] as $inline_before ) {
|
242 |
+
echo $inline_before;
|
243 |
+
}
|
244 |
+
}
|
245 |
+
|
246 |
+
if ( isset( $href ) ) {
|
247 |
+
$handles = implode( ',', $js_array['handles'] );
|
248 |
+
|
249 |
+
$load_mode = page_optimize_load_mode_js();
|
250 |
+
|
251 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
252 |
+
echo "<script data-handles='" . esc_attr( $handles ) . "' $load_mode type='text/javascript' src='$href'></script>\n";
|
253 |
+
} else {
|
254 |
+
echo "<script type='text/javascript' $load_mode src='$href'></script>\n";
|
255 |
+
}
|
256 |
+
}
|
257 |
+
|
258 |
+
if ( isset( $js_array['extras']['after'] ) ) {
|
259 |
+
foreach ( $js_array['extras']['after'] as $inline_after ) {
|
260 |
+
echo $inline_after;
|
261 |
+
}
|
262 |
+
}
|
263 |
+
}
|
264 |
+
}
|
265 |
+
|
266 |
+
do_action( 'js_concat_did_items', $javascripts );
|
267 |
+
|
268 |
+
return $this->done;
|
269 |
+
}
|
270 |
+
|
271 |
+
function __isset( $key ) {
|
272 |
+
return isset( $this->old_scripts->$key );
|
273 |
+
}
|
274 |
+
|
275 |
+
function __unset( $key ) {
|
276 |
+
unset( $this->old_scripts->$key );
|
277 |
+
}
|
278 |
+
|
279 |
+
function &__get( $key ) {
|
280 |
+
return $this->old_scripts->$key;
|
281 |
+
}
|
282 |
+
|
283 |
+
function __set( $key, $value ) {
|
284 |
+
$this->old_scripts->$key = $value;
|
285 |
+
}
|
286 |
+
}
|
287 |
+
|
288 |
+
function page_optimize_js_concat_init() {
|
289 |
+
global $wp_scripts;
|
290 |
+
|
291 |
+
$wp_scripts = new Page_Optimize_JS_Concat( $wp_scripts );
|
292 |
+
$wp_scripts->allow_gzip_compression = ALLOW_GZIP_COMPRESSION;
|
293 |
+
}
|
294 |
+
|
295 |
+
if ( ! is_admin() && ( page_optimize_should_concat_js() || page_optimize_load_mode_js() ) ) {
|
296 |
+
add_action( 'init', 'page_optimize_js_concat_init' );
|
297 |
+
}
|
cssmin/Colors.php
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace tubalmartin\CssMin;
|
4 |
+
|
5 |
+
class Colors
|
6 |
+
{
|
7 |
+
public static function getHexToNamedMap()
|
8 |
+
{
|
9 |
+
// Hex colors longer than named counterpart
|
10 |
+
return array(
|
11 |
+
'#f0ffff' => 'azure',
|
12 |
+
'#f5f5dc' => 'beige',
|
13 |
+
'#ffe4c4' => 'bisque',
|
14 |
+
'#a52a2a' => 'brown',
|
15 |
+
'#ff7f50' => 'coral',
|
16 |
+
'#ffd700' => 'gold',
|
17 |
+
'#808080' => 'gray',
|
18 |
+
'#008000' => 'green',
|
19 |
+
'#4b0082' => 'indigo',
|
20 |
+
'#fffff0' => 'ivory',
|
21 |
+
'#f0e68c' => 'khaki',
|
22 |
+
'#faf0e6' => 'linen',
|
23 |
+
'#800000' => 'maroon',
|
24 |
+
'#000080' => 'navy',
|
25 |
+
'#fdf5e6' => 'oldlace',
|
26 |
+
'#808000' => 'olive',
|
27 |
+
'#ffa500' => 'orange',
|
28 |
+
'#da70d6' => 'orchid',
|
29 |
+
'#cd853f' => 'peru',
|
30 |
+
'#ffc0cb' => 'pink',
|
31 |
+
'#dda0dd' => 'plum',
|
32 |
+
'#800080' => 'purple',
|
33 |
+
'#f00' => 'red',
|
34 |
+
'#fa8072' => 'salmon',
|
35 |
+
'#a0522d' => 'sienna',
|
36 |
+
'#c0c0c0' => 'silver',
|
37 |
+
'#fffafa' => 'snow',
|
38 |
+
'#d2b48c' => 'tan',
|
39 |
+
'#008080' => 'teal',
|
40 |
+
'#ff6347' => 'tomato',
|
41 |
+
'#ee82ee' => 'violet',
|
42 |
+
'#f5deb3' => 'wheat'
|
43 |
+
);
|
44 |
+
}
|
45 |
+
|
46 |
+
public static function getNamedToHexMap()
|
47 |
+
{
|
48 |
+
// Named colors longer than hex counterpart
|
49 |
+
return array(
|
50 |
+
'aliceblue' => '#f0f8ff',
|
51 |
+
'antiquewhite' => '#faebd7',
|
52 |
+
'aquamarine' => '#7fffd4',
|
53 |
+
'black' => '#000',
|
54 |
+
'blanchedalmond' => '#ffebcd',
|
55 |
+
'blueviolet' => '#8a2be2',
|
56 |
+
'burlywood' => '#deb887',
|
57 |
+
'cadetblue' => '#5f9ea0',
|
58 |
+
'chartreuse' => '#7fff00',
|
59 |
+
'chocolate' => '#d2691e',
|
60 |
+
'cornflowerblue' => '#6495ed',
|
61 |
+
'cornsilk' => '#fff8dc',
|
62 |
+
'darkblue' => '#00008b',
|
63 |
+
'darkcyan' => '#008b8b',
|
64 |
+
'darkgoldenrod' => '#b8860b',
|
65 |
+
'darkgray' => '#a9a9a9',
|
66 |
+
'darkgreen' => '#006400',
|
67 |
+
'darkgrey' => '#a9a9a9',
|
68 |
+
'darkkhaki' => '#bdb76b',
|
69 |
+
'darkmagenta' => '#8b008b',
|
70 |
+
'darkolivegreen' => '#556b2f',
|
71 |
+
'darkorange' => '#ff8c00',
|
72 |
+
'darkorchid' => '#9932cc',
|
73 |
+
'darksalmon' => '#e9967a',
|
74 |
+
'darkseagreen' => '#8fbc8f',
|
75 |
+
'darkslateblue' => '#483d8b',
|
76 |
+
'darkslategray' => '#2f4f4f',
|
77 |
+
'darkslategrey' => '#2f4f4f',
|
78 |
+
'darkturquoise' => '#00ced1',
|
79 |
+
'darkviolet' => '#9400d3',
|
80 |
+
'deeppink' => '#ff1493',
|
81 |
+
'deepskyblue' => '#00bfff',
|
82 |
+
'dodgerblue' => '#1e90ff',
|
83 |
+
'firebrick' => '#b22222',
|
84 |
+
'floralwhite' => '#fffaf0',
|
85 |
+
'forestgreen' => '#228b22',
|
86 |
+
'fuchsia' => '#f0f',
|
87 |
+
'gainsboro' => '#dcdcdc',
|
88 |
+
'ghostwhite' => '#f8f8ff',
|
89 |
+
'goldenrod' => '#daa520',
|
90 |
+
'greenyellow' => '#adff2f',
|
91 |
+
'honeydew' => '#f0fff0',
|
92 |
+
'indianred' => '#cd5c5c',
|
93 |
+
'lavender' => '#e6e6fa',
|
94 |
+
'lavenderblush' => '#fff0f5',
|
95 |
+
'lawngreen' => '#7cfc00',
|
96 |
+
'lemonchiffon' => '#fffacd',
|
97 |
+
'lightblue' => '#add8e6',
|
98 |
+
'lightcoral' => '#f08080',
|
99 |
+
'lightcyan' => '#e0ffff',
|
100 |
+
'lightgoldenrodyellow' => '#fafad2',
|
101 |
+
'lightgray' => '#d3d3d3',
|
102 |
+
'lightgreen' => '#90ee90',
|
103 |
+
'lightgrey' => '#d3d3d3',
|
104 |
+
'lightpink' => '#ffb6c1',
|
105 |
+
'lightsalmon' => '#ffa07a',
|
106 |
+
'lightseagreen' => '#20b2aa',
|
107 |
+
'lightskyblue' => '#87cefa',
|
108 |
+
'lightslategray' => '#778899',
|
109 |
+
'lightslategrey' => '#778899',
|
110 |
+
'lightsteelblue' => '#b0c4de',
|
111 |
+
'lightyellow' => '#ffffe0',
|
112 |
+
'limegreen' => '#32cd32',
|
113 |
+
'mediumaquamarine' => '#66cdaa',
|
114 |
+
'mediumblue' => '#0000cd',
|
115 |
+
'mediumorchid' => '#ba55d3',
|
116 |
+
'mediumpurple' => '#9370db',
|
117 |
+
'mediumseagreen' => '#3cb371',
|
118 |
+
'mediumslateblue' => '#7b68ee',
|
119 |
+
'mediumspringgreen' => '#00fa9a',
|
120 |
+
'mediumturquoise' => '#48d1cc',
|
121 |
+
'mediumvioletred' => '#c71585',
|
122 |
+
'midnightblue' => '#191970',
|
123 |
+
'mintcream' => '#f5fffa',
|
124 |
+
'mistyrose' => '#ffe4e1',
|
125 |
+
'moccasin' => '#ffe4b5',
|
126 |
+
'navajowhite' => '#ffdead',
|
127 |
+
'olivedrab' => '#6b8e23',
|
128 |
+
'orangered' => '#ff4500',
|
129 |
+
'palegoldenrod' => '#eee8aa',
|
130 |
+
'palegreen' => '#98fb98',
|
131 |
+
'paleturquoise' => '#afeeee',
|
132 |
+
'palevioletred' => '#db7093',
|
133 |
+
'papayawhip' => '#ffefd5',
|
134 |
+
'peachpuff' => '#ffdab9',
|
135 |
+
'powderblue' => '#b0e0e6',
|
136 |
+
'rebeccapurple' => '#663399',
|
137 |
+
'rosybrown' => '#bc8f8f',
|
138 |
+
'royalblue' => '#4169e1',
|
139 |
+
'saddlebrown' => '#8b4513',
|
140 |
+
'sandybrown' => '#f4a460',
|
141 |
+
'seagreen' => '#2e8b57',
|
142 |
+
'seashell' => '#fff5ee',
|
143 |
+
'slateblue' => '#6a5acd',
|
144 |
+
'slategray' => '#708090',
|
145 |
+
'slategrey' => '#708090',
|
146 |
+
'springgreen' => '#00ff7f',
|
147 |
+
'steelblue' => '#4682b4',
|
148 |
+
'turquoise' => '#40e0d0',
|
149 |
+
'white' => '#fff',
|
150 |
+
'whitesmoke' => '#f5f5f5',
|
151 |
+
'yellow' => '#ff0',
|
152 |
+
'yellowgreen' => '#9acd32'
|
153 |
+
);
|
154 |
+
}
|
155 |
+
}
|
cssmin/Minifier.php
ADDED
@@ -0,0 +1,935 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*!
|
4 |
+
* CssMin
|
5 |
+
* Author: Tubal Martin - http://tubalmartin.me/
|
6 |
+
* Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
|
7 |
+
*
|
8 |
+
* This is a PHP port of the CSS minification tool distributed with YUICompressor,
|
9 |
+
* itself a port of the cssmin utility by Isaac Schlueter - http://foohack.com/
|
10 |
+
* Permission is hereby granted to use the PHP version under the same
|
11 |
+
* conditions as the YUICompressor.
|
12 |
+
*/
|
13 |
+
|
14 |
+
/*!
|
15 |
+
* YUI Compressor
|
16 |
+
* http://developer.yahoo.com/yui/compressor/
|
17 |
+
* Author: Julien Lecomte - http://www.julienlecomte.net/
|
18 |
+
* Copyright (c) 2013 Yahoo! Inc. All rights reserved.
|
19 |
+
* The copyrights embodied in the content of this file are licensed
|
20 |
+
* by Yahoo! Inc. under the BSD (revised) open source license.
|
21 |
+
*/
|
22 |
+
|
23 |
+
namespace tubalmartin\CssMin;
|
24 |
+
|
25 |
+
class Minifier
|
26 |
+
{
|
27 |
+
const QUERY_FRACTION = '_CSSMIN_QF_';
|
28 |
+
const COMMENT_TOKEN = '_CSSMIN_CMT_%d_';
|
29 |
+
const COMMENT_TOKEN_START = '_CSSMIN_CMT_';
|
30 |
+
const RULE_BODY_TOKEN = '_CSSMIN_RBT_%d_';
|
31 |
+
const PRESERVED_TOKEN = '_CSSMIN_PTK_%d_';
|
32 |
+
const UNQUOTED_FONT_TOKEN = '_CSSMIN_UFT_%d_';
|
33 |
+
|
34 |
+
// Token lists
|
35 |
+
private $comments = array();
|
36 |
+
private $ruleBodies = array();
|
37 |
+
private $preservedTokens = array();
|
38 |
+
private $unquotedFontTokens = array();
|
39 |
+
|
40 |
+
// Output options
|
41 |
+
private $keepImportantComments = true;
|
42 |
+
private $keepSourceMapComment = false;
|
43 |
+
private $linebreakPosition = 0;
|
44 |
+
|
45 |
+
// PHP ini limits
|
46 |
+
private $raisePhpLimits;
|
47 |
+
private $memoryLimit;
|
48 |
+
private $maxExecutionTime = 60; // 1 min
|
49 |
+
private $pcreBacktrackLimit;
|
50 |
+
private $pcreRecursionLimit;
|
51 |
+
|
52 |
+
// Color maps
|
53 |
+
private $hexToNamedColorsMap;
|
54 |
+
private $namedToHexColorsMap;
|
55 |
+
|
56 |
+
// Regexes
|
57 |
+
private $numRegex;
|
58 |
+
private $charsetRegex = '/@charset [^;]+;/Si';
|
59 |
+
private $importRegex = '/@import [^;]+;/Si';
|
60 |
+
private $namespaceRegex = '/@namespace [^;]+;/Si';
|
61 |
+
private $namedToHexColorsRegex;
|
62 |
+
private $shortenOneZeroesRegex;
|
63 |
+
private $shortenTwoZeroesRegex;
|
64 |
+
private $shortenThreeZeroesRegex;
|
65 |
+
private $shortenFourZeroesRegex;
|
66 |
+
private $unitsGroupRegex = '(?:ch|cm|em|ex|gd|in|mm|px|pt|pc|q|rem|vh|vmax|vmin|vw|%)';
|
67 |
+
private $unquotedFontsRegex = '/(font-family:|font:)([^\'"]+?)[^};]*/Si';
|
68 |
+
|
69 |
+
/**
|
70 |
+
* @param bool|int $raisePhpLimits If true, PHP settings will be raised if needed
|
71 |
+
*/
|
72 |
+
public function __construct($raisePhpLimits = true)
|
73 |
+
{
|
74 |
+
$this->raisePhpLimits = (bool) $raisePhpLimits;
|
75 |
+
$this->memoryLimit = 128 * 1048576; // 128MB in bytes
|
76 |
+
$this->pcreBacktrackLimit = 1000 * 1000;
|
77 |
+
$this->pcreRecursionLimit = 500 * 1000;
|
78 |
+
$this->hexToNamedColorsMap = Colors::getHexToNamedMap();
|
79 |
+
$this->namedToHexColorsMap = Colors::getNamedToHexMap();
|
80 |
+
$this->namedToHexColorsRegex = sprintf(
|
81 |
+
'/([:,( ])(%s)( |,|\)|;|$)/Si',
|
82 |
+
implode('|', array_keys($this->namedToHexColorsMap))
|
83 |
+
);
|
84 |
+
$this->numRegex = sprintf('-?\d*\.?\d+%s?', $this->unitsGroupRegex);
|
85 |
+
$this->setShortenZeroValuesRegexes();
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Parses & minifies the given input CSS string
|
90 |
+
* @param string $css
|
91 |
+
* @return string
|
92 |
+
*/
|
93 |
+
public function run($css = '')
|
94 |
+
{
|
95 |
+
if (empty($css) || !is_string($css)) {
|
96 |
+
return '';
|
97 |
+
}
|
98 |
+
|
99 |
+
$this->resetRunProperties();
|
100 |
+
|
101 |
+
if ($this->raisePhpLimits) {
|
102 |
+
$this->doRaisePhpLimits();
|
103 |
+
}
|
104 |
+
|
105 |
+
return $this->minify($css);
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Sets whether to keep or remove sourcemap special comment.
|
110 |
+
* Sourcemap comments are removed by default.
|
111 |
+
* @param bool $keepSourceMapComment
|
112 |
+
*/
|
113 |
+
public function keepSourceMapComment($keepSourceMapComment = true)
|
114 |
+
{
|
115 |
+
$this->keepSourceMapComment = (bool) $keepSourceMapComment;
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Sets whether to keep or remove important comments.
|
120 |
+
* Important comments outside of a declaration block are kept by default.
|
121 |
+
* @param bool $removeImportantComments
|
122 |
+
*/
|
123 |
+
public function removeImportantComments($removeImportantComments = true)
|
124 |
+
{
|
125 |
+
$this->keepImportantComments = !(bool) $removeImportantComments;
|
126 |
+
}
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Sets the approximate column after which long lines will be splitted in the output
|
130 |
+
* with a linebreak.
|
131 |
+
* @param int $position
|
132 |
+
*/
|
133 |
+
public function setLineBreakPosition($position)
|
134 |
+
{
|
135 |
+
$this->linebreakPosition = (int) $position;
|
136 |
+
}
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Sets the memory limit for this script
|
140 |
+
* @param int|string $limit
|
141 |
+
*/
|
142 |
+
public function setMemoryLimit($limit)
|
143 |
+
{
|
144 |
+
$this->memoryLimit = Utils::normalizeInt($limit);
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Sets the maximum execution time for this script
|
149 |
+
* @param int|string $seconds
|
150 |
+
*/
|
151 |
+
public function setMaxExecutionTime($seconds)
|
152 |
+
{
|
153 |
+
$this->maxExecutionTime = (int) $seconds;
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Sets the PCRE backtrack limit for this script
|
158 |
+
* @param int $limit
|
159 |
+
*/
|
160 |
+
public function setPcreBacktrackLimit($limit)
|
161 |
+
{
|
162 |
+
$this->pcreBacktrackLimit = (int) $limit;
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Sets the PCRE recursion limit for this script
|
167 |
+
* @param int $limit
|
168 |
+
*/
|
169 |
+
public function setPcreRecursionLimit($limit)
|
170 |
+
{
|
171 |
+
$this->pcreRecursionLimit = (int) $limit;
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* Builds regular expressions needed for shortening zero values
|
176 |
+
*/
|
177 |
+
private function setShortenZeroValuesRegexes()
|
178 |
+
{
|
179 |
+
$zeroRegex = '0'. $this->unitsGroupRegex;
|
180 |
+
$numOrPosRegex = '('. $this->numRegex .'|top|left|bottom|right|center) ';
|
181 |
+
$oneZeroSafeProperties = array(
|
182 |
+
'(?:line-)?height',
|
183 |
+
'(?:(?:min|max)-)?width',
|
184 |
+
'top',
|
185 |
+
'left',
|
186 |
+
'background-position',
|
187 |
+
'bottom',
|
188 |
+
'right',
|
189 |
+
'border(?:-(?:top|left|bottom|right))?(?:-width)?',
|
190 |
+
'border-(?:(?:top|bottom)-(?:left|right)-)?radius',
|
191 |
+
'column-(?:gap|width)',
|
192 |
+
'margin(?:-(?:top|left|bottom|right))?',
|
193 |
+
'outline-width',
|
194 |
+
'padding(?:-(?:top|left|bottom|right))?'
|
195 |
+
);
|
196 |
+
|
197 |
+
// First zero regex
|
198 |
+
$regex = '/(^|;)('. implode('|', $oneZeroSafeProperties) .'):%s/Si';
|
199 |
+
$this->shortenOneZeroesRegex = sprintf($regex, $zeroRegex);
|
200 |
+
|
201 |
+
// Multiple zeroes regexes
|
202 |
+
$regex = '/(^|;)(margin|padding|border-(?:width|radius)|background-position):%s/Si';
|
203 |
+
$this->shortenTwoZeroesRegex = sprintf($regex, $numOrPosRegex . $zeroRegex);
|
204 |
+
$this->shortenThreeZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $zeroRegex);
|
205 |
+
$this->shortenFourZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $numOrPosRegex . $zeroRegex);
|
206 |
+
}
|
207 |
+
|
208 |
+
/**
|
209 |
+
* Resets properties whose value may change between runs
|
210 |
+
*/
|
211 |
+
private function resetRunProperties()
|
212 |
+
{
|
213 |
+
$this->comments = array();
|
214 |
+
$this->ruleBodies = array();
|
215 |
+
$this->preservedTokens = array();
|
216 |
+
}
|
217 |
+
|
218 |
+
/**
|
219 |
+
* Tries to configure PHP to use at least the suggested minimum settings
|
220 |
+
* @return void
|
221 |
+
*/
|
222 |
+
private function doRaisePhpLimits()
|
223 |
+
{
|
224 |
+
$phpLimits = array(
|
225 |
+
'memory_limit' => $this->memoryLimit,
|
226 |
+
'max_execution_time' => $this->maxExecutionTime,
|
227 |
+
'pcre.backtrack_limit' => $this->pcreBacktrackLimit,
|
228 |
+
'pcre.recursion_limit' => $this->pcreRecursionLimit
|
229 |
+
);
|
230 |
+
|
231 |
+
// If current settings are higher respect them.
|
232 |
+
foreach ($phpLimits as $name => $suggested) {
|
233 |
+
$current = Utils::normalizeInt(ini_get($name));
|
234 |
+
|
235 |
+
if ($current >= $suggested) {
|
236 |
+
continue;
|
237 |
+
}
|
238 |
+
|
239 |
+
// memoryLimit exception: allow -1 for "no memory limit".
|
240 |
+
if ($name === 'memory_limit' && $current === -1) {
|
241 |
+
continue;
|
242 |
+
}
|
243 |
+
|
244 |
+
// maxExecutionTime exception: allow 0 for "no memory limit".
|
245 |
+
if ($name === 'max_execution_time' && $current === 0) {
|
246 |
+
continue;
|
247 |
+
}
|
248 |
+
|
249 |
+
ini_set($name, $suggested);
|
250 |
+
}
|
251 |
+
}
|
252 |
+
|
253 |
+
/**
|
254 |
+
* Registers a preserved token
|
255 |
+
* @param string $token
|
256 |
+
* @return string The token ID string
|
257 |
+
*/
|
258 |
+
private function registerPreservedToken($token)
|
259 |
+
{
|
260 |
+
$tokenId = sprintf(self::PRESERVED_TOKEN, count($this->preservedTokens));
|
261 |
+
$this->preservedTokens[$tokenId] = $token;
|
262 |
+
return $tokenId;
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Registers a candidate comment token
|
267 |
+
* @param string $comment
|
268 |
+
* @return string The comment token ID string
|
269 |
+
*/
|
270 |
+
private function registerCommentToken($comment)
|
271 |
+
{
|
272 |
+
$tokenId = sprintf(self::COMMENT_TOKEN, count($this->comments));
|
273 |
+
$this->comments[$tokenId] = $comment;
|
274 |
+
return $tokenId;
|
275 |
+
}
|
276 |
+
|
277 |
+
/**
|
278 |
+
* Registers a rule body token
|
279 |
+
* @param string $body the minified rule body
|
280 |
+
* @return string The rule body token ID string
|
281 |
+
*/
|
282 |
+
private function registerRuleBodyToken($body)
|
283 |
+
{
|
284 |
+
if (empty($body)) {
|
285 |
+
return '';
|
286 |
+
}
|
287 |
+
|
288 |
+
$tokenId = sprintf(self::RULE_BODY_TOKEN, count($this->ruleBodies));
|
289 |
+
$this->ruleBodies[$tokenId] = $body;
|
290 |
+
return $tokenId;
|
291 |
+
}
|
292 |
+
|
293 |
+
private function registerUnquotedFontToken($body)
|
294 |
+
{
|
295 |
+
if (empty($body)) {
|
296 |
+
return '';
|
297 |
+
}
|
298 |
+
|
299 |
+
$tokenId = sprintf(self::UNQUOTED_FONT_TOKEN, count($this->unquotedFontTokens));
|
300 |
+
$this->unquotedFontTokens[$tokenId] = $body;
|
301 |
+
return $tokenId;
|
302 |
+
}
|
303 |
+
|
304 |
+
/**
|
305 |
+
* Parses & minifies the given input CSS string
|
306 |
+
* @param string $css
|
307 |
+
* @return string
|
308 |
+
*/
|
309 |
+
private function minify($css)
|
310 |
+
{
|
311 |
+
// Process data urls
|
312 |
+
$css = $this->processDataUrls($css);
|
313 |
+
|
314 |
+
// Process comments
|
315 |
+
$css = preg_replace_callback(
|
316 |
+
'/(?<!\\\\)\/\*(.*?)\*(?<!\\\\)\//Ss',
|
317 |
+
array($this, 'processCommentsCallback'),
|
318 |
+
$css
|
319 |
+
);
|
320 |
+
|
321 |
+
// IE7: Process Microsoft matrix filters (whitespaces between Matrix parameters). Can contain strings inside.
|
322 |
+
$css = preg_replace_callback(
|
323 |
+
'/filter:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^)]+)\)/Ss',
|
324 |
+
array($this, 'processOldIeSpecificMatrixDefinitionCallback'),
|
325 |
+
$css
|
326 |
+
);
|
327 |
+
|
328 |
+
// Process quoted unquotable attribute selectors to unquote them. Covers most common cases.
|
329 |
+
// Likelyhood of a quoted attribute selector being a substring in a string: Very very low.
|
330 |
+
$css = preg_replace(
|
331 |
+
'/\[\s*([a-z][a-z-]+)\s*([\*\|\^\$~]?=)\s*[\'"](-?[a-z_][a-z0-9-_]+)[\'"]\s*\]/Ssi',
|
332 |
+
'[$1$2$3]',
|
333 |
+
$css
|
334 |
+
);
|
335 |
+
|
336 |
+
// Process strings so their content doesn't get accidentally minified
|
337 |
+
$css = preg_replace_callback(
|
338 |
+
'/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S",
|
339 |
+
array($this, 'processStringsCallback'),
|
340 |
+
$css
|
341 |
+
);
|
342 |
+
|
343 |
+
// Normalize all whitespace strings to single spaces. Easier to work with that way.
|
344 |
+
$css = preg_replace('/\s+/S', ' ', $css);
|
345 |
+
|
346 |
+
// Process import At-rules with unquoted URLs so URI reserved characters such as a semicolon may be used safely.
|
347 |
+
$css = preg_replace_callback(
|
348 |
+
'/@import url\(([^\'"]+?)\)( |;)/Si',
|
349 |
+
array($this, 'processImportUnquotedUrlAtRulesCallback'),
|
350 |
+
$css
|
351 |
+
);
|
352 |
+
|
353 |
+
// Process comments
|
354 |
+
$css = $this->processComments($css);
|
355 |
+
|
356 |
+
// Process rule bodies
|
357 |
+
$css = $this->processRuleBodies($css);
|
358 |
+
|
359 |
+
// Process at-rules and selectors
|
360 |
+
$css = $this->processAtRulesAndSelectors($css);
|
361 |
+
|
362 |
+
// Restore preserved rule bodies before splitting
|
363 |
+
$css = strtr($css, $this->ruleBodies);
|
364 |
+
|
365 |
+
// Split long lines in output if required
|
366 |
+
$css = $this->processLongLineSplitting($css);
|
367 |
+
|
368 |
+
// Restore preserved comments and strings
|
369 |
+
$css = strtr($css, $this->preservedTokens);
|
370 |
+
|
371 |
+
return trim($css);
|
372 |
+
}
|
373 |
+
|
374 |
+
/**
|
375 |
+
* Searches & replaces all data urls with tokens before we start compressing,
|
376 |
+
* to avoid performance issues running some of the subsequent regexes against large string chunks.
|
377 |
+
* @param string $css
|
378 |
+
* @return string
|
379 |
+
*/
|
380 |
+
private function processDataUrls($css)
|
381 |
+
{
|
382 |
+
$ret = '';
|
383 |
+
$searchOffset = $substrOffset = 0;
|
384 |
+
|
385 |
+
// Since we need to account for non-base64 data urls, we need to handle
|
386 |
+
// ' and ) being part of the data string.
|
387 |
+
while (preg_match('/url\(\s*(["\']?)data:/Si', $css, $m, PREG_OFFSET_CAPTURE, $searchOffset)) {
|
388 |
+
$matchStartIndex = $m[0][1];
|
389 |
+
$dataStartIndex = $matchStartIndex + 4; // url( length
|
390 |
+
$searchOffset = $matchStartIndex + strlen($m[0][0]);
|
391 |
+
$terminator = $m[1][0]; // ', " or empty (not quoted)
|
392 |
+
$terminatorRegex = '/(?<!\\\\)'. (strlen($terminator) === 0 ? '' : $terminator.'\s*') .'(\))/S';
|
393 |
+
|
394 |
+
$ret .= substr($css, $substrOffset, $matchStartIndex - $substrOffset);
|
395 |
+
|
396 |
+
// Terminator found
|
397 |
+
if (preg_match($terminatorRegex, $css, $matches, PREG_OFFSET_CAPTURE, $searchOffset)) {
|
398 |
+
$matchEndIndex = $matches[1][1];
|
399 |
+
$searchOffset = $matchEndIndex + 1;
|
400 |
+
$token = substr($css, $dataStartIndex, $matchEndIndex - $dataStartIndex);
|
401 |
+
|
402 |
+
// Remove all spaces only for base64 encoded URLs.
|
403 |
+
if (stripos($token, 'base64,') !== false) {
|
404 |
+
$token = preg_replace('/\s+/S', '', $token);
|
405 |
+
}
|
406 |
+
|
407 |
+
$ret .= 'url('. $this->registerPreservedToken(trim($token)) .')';
|
408 |
+
// No end terminator found, re-add the whole match. Should we throw/warn here?
|
409 |
+
} else {
|
410 |
+
$ret .= substr($css, $matchStartIndex, $searchOffset - $matchStartIndex);
|
411 |
+
}
|
412 |
+
|
413 |
+
$substrOffset = $searchOffset;
|
414 |
+
}
|
415 |
+
|
416 |
+
$ret .= substr($css, $substrOffset);
|
417 |
+
|
418 |
+
return $ret;
|
419 |
+
}
|
420 |
+
|
421 |
+
/**
|
422 |
+
* Registers all comments found as candidates to be preserved.
|
423 |
+
* @param array $matches
|
424 |
+
* @return string
|
425 |
+
*/
|
426 |
+
private function processCommentsCallback($matches)
|
427 |
+
{
|
428 |
+
return '/*'. $this->registerCommentToken($matches[1]) .'*/';
|
429 |
+
}
|
430 |
+
|
431 |
+
/**
|
432 |
+
* Preserves old IE Matrix string definition
|
433 |
+
* @param array $matches
|
434 |
+
* @return string
|
435 |
+
*/
|
436 |
+
private function processOldIeSpecificMatrixDefinitionCallback($matches)
|
437 |
+
{
|
438 |
+
return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $this->registerPreservedToken($matches[1]) .')';
|
439 |
+
}
|
440 |
+
|
441 |
+
/**
|
442 |
+
* Preserves strings found
|
443 |
+
* @param array $matches
|
444 |
+
* @return string
|
445 |
+
*/
|
446 |
+
private function processStringsCallback($matches)
|
447 |
+
{
|
448 |
+
$match = $matches[0];
|
449 |
+
$quote = substr($match, 0, 1);
|
450 |
+
$match = substr($match, 1, -1);
|
451 |
+
|
452 |
+
// maybe the string contains a comment-like substring?
|
453 |
+
// one, maybe more? put'em back then
|
454 |
+
if (strpos($match, self::COMMENT_TOKEN_START) !== false) {
|
455 |
+
$match = strtr($match, $this->comments);
|
456 |
+
}
|
457 |
+
|
458 |
+
// minify alpha opacity in filter strings
|
459 |
+
$match = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $match);
|
460 |
+
|
461 |
+
return $quote . $this->registerPreservedToken($match) . $quote;
|
462 |
+
}
|
463 |
+
|
464 |
+
/**
|
465 |
+
* Searches & replaces all import at-rule unquoted urls with tokens so URI reserved characters such as a semicolon
|
466 |
+
* may be used safely in a URL.
|
467 |
+
* @param array $matches
|
468 |
+
* @return string
|
469 |
+
*/
|
470 |
+
private function processImportUnquotedUrlAtRulesCallback($matches)
|
471 |
+
{
|
472 |
+
return '@import url('. $this->registerPreservedToken($matches[1]) .')'. $matches[2];
|
473 |
+
}
|
474 |
+
|
475 |
+
/**
|
476 |
+
* Preserves or removes comments found.
|
477 |
+
* @param string $css
|
478 |
+
* @return string
|
479 |
+
*/
|
480 |
+
private function processComments($css)
|
481 |
+
{
|
482 |
+
foreach ($this->comments as $commentId => $comment) {
|
483 |
+
$commentIdString = '/*'. $commentId .'*/';
|
484 |
+
|
485 |
+
// ! in the first position of the comment means preserve
|
486 |
+
// so push to the preserved tokens keeping the !
|
487 |
+
if ($this->keepImportantComments && strpos($comment, '!') === 0) {
|
488 |
+
$preservedTokenId = $this->registerPreservedToken($comment);
|
489 |
+
// Put new lines before and after /*! important comments
|
490 |
+
$css = str_replace($commentIdString, "\n/*$preservedTokenId*/\n", $css);
|
491 |
+
continue;
|
492 |
+
}
|
493 |
+
|
494 |
+
// # sourceMappingURL= in the first position of the comment means sourcemap
|
495 |
+
// so push to the preserved tokens if {$this->keepSourceMapComment} is truthy.
|
496 |
+
if ($this->keepSourceMapComment && strpos($comment, '# sourceMappingURL=') === 0) {
|
497 |
+
$preservedTokenId = $this->registerPreservedToken($comment);
|
498 |
+
// Add new line before the sourcemap comment
|
499 |
+
$css = str_replace($commentIdString, "\n/*$preservedTokenId*/", $css);
|
500 |
+
continue;
|
501 |
+
}
|
502 |
+
|
503 |
+
// Keep empty comments after child selectors (IE7 hack)
|
504 |
+
// e.g. html >/**/ body
|
505 |
+
if (strlen($comment) === 0 && strpos($css, '>/*'.$commentId) !== false) {
|
506 |
+
$css = str_replace($commentId, $this->registerPreservedToken(''), $css);
|
507 |
+
continue;
|
508 |
+
}
|
509 |
+
|
510 |
+
// in all other cases kill the comment
|
511 |
+
$css = str_replace($commentIdString, '', $css);
|
512 |
+
}
|
513 |
+
|
514 |
+
// Normalize whitespace again
|
515 |
+
$css = preg_replace('/ +/S', ' ', $css);
|
516 |
+
|
517 |
+
return $css;
|
518 |
+
}
|
519 |
+
|
520 |
+
/**
|
521 |
+
* Finds, minifies & preserves all rule bodies.
|
522 |
+
* @param string $css the whole stylesheet.
|
523 |
+
* @return string
|
524 |
+
*/
|
525 |
+
private function processRuleBodies($css)
|
526 |
+
{
|
527 |
+
$ret = '';
|
528 |
+
$searchOffset = $substrOffset = 0;
|
529 |
+
|
530 |
+
while (($blockStartPos = strpos($css, '{', $searchOffset)) !== false) {
|
531 |
+
$blockEndPos = strpos($css, '}', $blockStartPos);
|
532 |
+
// When ending curly brace is missing, let's
|
533 |
+
// behave like there was one at the end of the block...
|
534 |
+
if ( false === $blockEndPos ) {
|
535 |
+
$blockEndPos = strlen($css) - 1;
|
536 |
+
}
|
537 |
+
$nextBlockStartPos = strpos($css, '{', $blockStartPos + 1);
|
538 |
+
$ret .= substr($css, $substrOffset, $blockStartPos - $substrOffset);
|
539 |
+
|
540 |
+
if ($nextBlockStartPos !== false && $nextBlockStartPos < $blockEndPos) {
|
541 |
+
$ret .= substr($css, $blockStartPos, $nextBlockStartPos - $blockStartPos);
|
542 |
+
$searchOffset = $nextBlockStartPos;
|
543 |
+
} else {
|
544 |
+
$ruleBody = substr($css, $blockStartPos + 1, $blockEndPos - $blockStartPos - 1);
|
545 |
+
$ruleBodyToken = $this->registerRuleBodyToken($this->processRuleBody($ruleBody));
|
546 |
+
$ret .= '{'. $ruleBodyToken .'}';
|
547 |
+
$searchOffset = $blockEndPos + 1;
|
548 |
+
}
|
549 |
+
|
550 |
+
$substrOffset = $searchOffset;
|
551 |
+
}
|
552 |
+
|
553 |
+
$ret .= substr($css, $substrOffset);
|
554 |
+
|
555 |
+
return $ret;
|
556 |
+
}
|
557 |
+
|
558 |
+
/**
|
559 |
+
* Compresses non-group rule bodies.
|
560 |
+
* @param string $body The rule body without curly braces
|
561 |
+
* @return string
|
562 |
+
*/
|
563 |
+
private function processRuleBody($body)
|
564 |
+
{
|
565 |
+
$body = trim($body);
|
566 |
+
|
567 |
+
// Remove spaces before the things that should not have spaces before them.
|
568 |
+
$body = preg_replace('/ ([:=,)*\/;\n])/S', '$1', $body);
|
569 |
+
|
570 |
+
// Remove the spaces after the things that should not have spaces after them.
|
571 |
+
$body = preg_replace('/([:=,(*\/!;\n]) /S', '$1', $body);
|
572 |
+
|
573 |
+
// Replace multiple semi-colons in a row by a single one
|
574 |
+
$body = preg_replace('/;;+/S', ';', $body);
|
575 |
+
|
576 |
+
// Remove semicolon before closing brace except when:
|
577 |
+
// - The last property is prefixed with a `*` (lte IE7 hack) to avoid issues on Symbian S60 3.x browsers.
|
578 |
+
if (!preg_match('/\*[a-z0-9-]+:[^;]+;$/Si', $body)) {
|
579 |
+
$body = rtrim($body, ';');
|
580 |
+
}
|
581 |
+
|
582 |
+
// Remove important comments inside a rule body (because they make no sense here).
|
583 |
+
if (strpos($body, '/*') !== false) {
|
584 |
+
$body = preg_replace('/\n?\/\*[A-Z0-9_]+\*\/\n?/S', '', $body);
|
585 |
+
}
|
586 |
+
|
587 |
+
// Empty rule body? Exit :)
|
588 |
+
if (empty($body)) {
|
589 |
+
return '';
|
590 |
+
}
|
591 |
+
|
592 |
+
// Shorten font-weight values
|
593 |
+
$body = preg_replace(
|
594 |
+
array('/(font-weight:)bold\b/Si', '/(font-weight:)normal\b/Si'),
|
595 |
+
array('${1}700', '${1}400'),
|
596 |
+
$body
|
597 |
+
);
|
598 |
+
|
599 |
+
// Shorten background property
|
600 |
+
$body = preg_replace('/(background:)(?:none|transparent)( !|;|$)/Si', '${1}0 0$2', $body);
|
601 |
+
|
602 |
+
// Shorten opacity IE filter
|
603 |
+
$body = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $body);
|
604 |
+
|
605 |
+
// Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
|
606 |
+
// Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
|
607 |
+
// This makes it more likely that it'll get further compressed in the next step.
|
608 |
+
$body = preg_replace_callback(
|
609 |
+
'/(rgb|hsl)\(([0-9,.% -]+)\)(.|$)/Si',
|
610 |
+
array($this, 'shortenHslAndRgbToHexCallback'),
|
611 |
+
$body
|
612 |
+
);
|
613 |
+
|
614 |
+
// Shorten colors from #AABBCC to #ABC or shorter color name:
|
615 |
+
// - Look for hex colors which don't have a "=" in front of them (to avoid MSIE filters)
|
616 |
+
$body = preg_replace_callback(
|
617 |
+
'/(?<!=)#([0-9a-f]{3,6})( |,|\)|;|$)/Si',
|
618 |
+
array($this, 'shortenHexColorsCallback'),
|
619 |
+
$body
|
620 |
+
);
|
621 |
+
|
622 |
+
// Tokenize unquoted font names in order to hide them from
|
623 |
+
// color name replacements.
|
624 |
+
$body = preg_replace_callback(
|
625 |
+
$this->unquotedFontsRegex,
|
626 |
+
array($this, 'preserveUnquotedFontTokens'),
|
627 |
+
$body
|
628 |
+
);
|
629 |
+
|
630 |
+
// Shorten long named colors with a shorter HEX counterpart: white -> #fff.
|
631 |
+
// Run at least 2 times to cover most cases
|
632 |
+
$body = preg_replace_callback(
|
633 |
+
array($this->namedToHexColorsRegex, $this->namedToHexColorsRegex),
|
634 |
+
array($this, 'shortenNamedColorsCallback'),
|
635 |
+
$body
|
636 |
+
);
|
637 |
+
|
638 |
+
// Restore unquoted font tokens now after colors have been changed.
|
639 |
+
$body = $this->restoreUnquotedFontTokens($body);
|
640 |
+
|
641 |
+
// Replace positive sign from numbers before the leading space is removed.
|
642 |
+
// +1.2em to 1.2em, +.8px to .8px, +2% to 2%
|
643 |
+
$body = preg_replace('/([ :,(])\+(\.?\d+)/S', '$1$2', $body);
|
644 |
+
|
645 |
+
// shorten ms to s
|
646 |
+
$body = preg_replace_callback('/([ :,(])(-?)(\d{3,})ms/Si', function ($matches) {
|
647 |
+
return $matches[1] . $matches[2] . ((int) $matches[3] / 1000) .'s';
|
648 |
+
}, $body);
|
649 |
+
|
650 |
+
// Remove leading zeros from integer and float numbers.
|
651 |
+
// 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
|
652 |
+
$body = preg_replace('/([ :,(])(-?)0+([1-9]?\.?\d+)/S', '$1$2$3', $body);
|
653 |
+
|
654 |
+
// Remove trailing zeros from float numbers.
|
655 |
+
// -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
|
656 |
+
$body = preg_replace('/([ :,(])(-?\d?\.\d+?)0+([^\d])/S', '$1$2$3', $body);
|
657 |
+
|
658 |
+
// Remove trailing .0 -> -9.0 to -9
|
659 |
+
$body = preg_replace('/([ :,(])(-?\d+)\.0([^\d])/S', '$1$2$3', $body);
|
660 |
+
|
661 |
+
// Replace 0 length numbers with 0
|
662 |
+
$body = preg_replace('/([ :,(])-?\.?0+([^\d])/S', '${1}0$2', $body);
|
663 |
+
|
664 |
+
// Shorten zero values for safe properties only
|
665 |
+
$body = preg_replace(
|
666 |
+
array(
|
667 |
+
$this->shortenOneZeroesRegex,
|
668 |
+
$this->shortenTwoZeroesRegex,
|
669 |
+
$this->shortenThreeZeroesRegex,
|
670 |
+
$this->shortenFourZeroesRegex
|
671 |
+
),
|
672 |
+
array(
|
673 |
+
'$1$2:0',
|
674 |
+
'$1$2:$3 0',
|
675 |
+
'$1$2:$3 $4 0',
|
676 |
+
'$1$2:$3 $4 $5 0'
|
677 |
+
),
|
678 |
+
$body
|
679 |
+
);
|
680 |
+
|
681 |
+
// Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property.
|
682 |
+
$body = preg_replace('/(background-position):0(?: 0){2,3}( !|;|$)/Si', '$1:0 0$2', $body);
|
683 |
+
|
684 |
+
// Shorten suitable shorthand properties with repeated values
|
685 |
+
$body = preg_replace(
|
686 |
+
array(
|
687 |
+
'/(margin|padding|border-(?:width|radius)):('.$this->numRegex.')(?: \2)+( !|;|$)/Si',
|
688 |
+
'/(border-(?:style|color)):([#a-z0-9]+)(?: \2)+( !|;|$)/Si'
|
689 |
+
),
|
690 |
+
'$1:$2$3',
|
691 |
+
$body
|
692 |
+
);
|
693 |
+
$body = preg_replace(
|
694 |
+
array(
|
695 |
+
'/(margin|padding|border-(?:width|radius)):'.
|
696 |
+
'('.$this->numRegex.') ('.$this->numRegex.') \2 \3( !|;|$)/Si',
|
697 |
+
'/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) \2 \3( !|;|$)/Si'
|
698 |
+
),
|
699 |
+
'$1:$2 $3$4',
|
700 |
+
$body
|
701 |
+
);
|
702 |
+
$body = preg_replace(
|
703 |
+
array(
|
704 |
+
'/(margin|padding|border-(?:width|radius)):'.
|
705 |
+
'('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') \3( !|;|$)/Si',
|
706 |
+
'/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) ([#a-z0-9]+) \3( !|;|$)/Si'
|
707 |
+
),
|
708 |
+
'$1:$2 $3 $4$5',
|
709 |
+
$body
|
710 |
+
);
|
711 |
+
|
712 |
+
// Lowercase some common functions that can be values
|
713 |
+
$body = preg_replace_callback(
|
714 |
+
'/(?:attr|blur|brightness|circle|contrast|cubic-bezier|drop-shadow|ellipse|from|grayscale|'.
|
715 |
+
'hsla?|hue-rotate|inset|invert|local|minmax|opacity|perspective|polygon|rgba?|rect|repeat|saturate|sepia|'.
|
716 |
+
'steps|to|url|var|-webkit-gradient|'.
|
717 |
+
'(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|(?:repeating-)?(?:linear|radial)-gradient))\(/Si',
|
718 |
+
array($this, 'strtolowerCallback'),
|
719 |
+
$body
|
720 |
+
);
|
721 |
+
|
722 |
+
// Lowercase all uppercase properties
|
723 |
+
$body = preg_replace_callback('/(?:^|;)[A-Z-]+:/S', array($this, 'strtolowerCallback'), $body);
|
724 |
+
|
725 |
+
return $body;
|
726 |
+
}
|
727 |
+
|
728 |
+
private function preserveUnquotedFontTokens($matches)
|
729 |
+
{
|
730 |
+
return $this->registerUnquotedFontToken($matches[0]);
|
731 |
+
}
|
732 |
+
|
733 |
+
private function restoreUnquotedFontTokens($body)
|
734 |
+
{
|
735 |
+
return strtr($body, $this->unquotedFontTokens);
|
736 |
+
}
|
737 |
+
|
738 |
+
/**
|
739 |
+
* Compresses At-rules and selectors.
|
740 |
+
* @param string $css the whole stylesheet with rule bodies tokenized.
|
741 |
+
* @return string
|
742 |
+
*/
|
743 |
+
private function processAtRulesAndSelectors($css)
|
744 |
+
{
|
745 |
+
$charset = '';
|
746 |
+
$imports = '';
|
747 |
+
$namespaces = '';
|
748 |
+
|
749 |
+
// Remove spaces before the things that should not have spaces before them.
|
750 |
+
$css = preg_replace('/ ([@{};>+)\]~=,\/\n])/S', '$1', $css);
|
751 |
+
|
752 |
+
// Remove the spaces after the things that should not have spaces after them.
|
753 |
+
$css = preg_replace('/([{}:;>+(\[~=,\/\n]) /S', '$1', $css);
|
754 |
+
|
755 |
+
// Shorten shortable double colon (CSS3) pseudo-elements to single colon (CSS2)
|
756 |
+
$css = preg_replace('/::(before|after|first-(?:line|letter))(\{|,)/Si', ':$1$2', $css);
|
757 |
+
|
758 |
+
// Retain space for special IE6 cases
|
759 |
+
$css = preg_replace_callback('/:first-(line|letter)(\{|,)/Si', function ($matches) {
|
760 |
+
return ':first-'. strtolower($matches[1]) .' '. $matches[2];
|
761 |
+
}, $css);
|
762 |
+
|
763 |
+
// Find a fraction that may used in some @media queries such as: (min-aspect-ratio: 1/1)
|
764 |
+
// Add token to add the "/" back in later
|
765 |
+
$css = preg_replace('/\(([a-z-]+):([0-9]+)\/([0-9]+)\)/Si', '($1:$2'. self::QUERY_FRACTION .'$3)', $css);
|
766 |
+
|
767 |
+
// Remove empty rule blocks up to 2 levels deep.
|
768 |
+
$css = preg_replace(array_fill(0, 2, '/(\{)[^{};\/\n]+\{\}/S'), '$1', $css);
|
769 |
+
$css = preg_replace('/[^{};\/\n]+\{\}/S', '', $css);
|
770 |
+
|
771 |
+
// Two important comments next to each other? Remove extra newline.
|
772 |
+
if ($this->keepImportantComments) {
|
773 |
+
$css = str_replace("\n\n", "\n", $css);
|
774 |
+
}
|
775 |
+
|
776 |
+
// Restore fraction
|
777 |
+
$css = str_replace(self::QUERY_FRACTION, '/', $css);
|
778 |
+
|
779 |
+
// Lowercase some popular @directives
|
780 |
+
$css = preg_replace_callback(
|
781 |
+
'/(?<!\\\\)@(?:charset|document|font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|'.
|
782 |
+
'namespace|page|supports|viewport)/Si',
|
783 |
+
array($this, 'strtolowerCallback'),
|
784 |
+
$css
|
785 |
+
);
|
786 |
+
|
787 |
+
// Lowercase some popular media types
|
788 |
+
$css = preg_replace_callback(
|
789 |
+
'/[ ,](?:all|aural|braille|handheld|print|projection|screen|tty|tv|embossed|speech)[ ,;{]/Si',
|
790 |
+
array($this, 'strtolowerCallback'),
|
791 |
+
$css
|
792 |
+
);
|
793 |
+
|
794 |
+
// Lowercase some common pseudo-classes & pseudo-elements
|
795 |
+
$css = preg_replace_callback(
|
796 |
+
'/(?<!\\\\):(?:active|after|before|checked|default|disabled|empty|enabled|first-(?:child|of-type)|'.
|
797 |
+
'focus(?:-within)?|hover|indeterminate|in-range|invalid|lang\(|last-(?:child|of-type)|left|link|not\(|'.
|
798 |
+
'nth-(?:child|of-type)\(|nth-last-(?:child|of-type)\(|only-(?:child|of-type)|optional|out-of-range|'.
|
799 |
+
'read-(?:only|write)|required|right|root|:selection|target|valid|visited)/Si',
|
800 |
+
array($this, 'strtolowerCallback'),
|
801 |
+
$css
|
802 |
+
);
|
803 |
+
|
804 |
+
// @charset handling
|
805 |
+
if (preg_match($this->charsetRegex, $css, $matches)) {
|
806 |
+
// Keep the first @charset at-rule found
|
807 |
+
$charset = $matches[0];
|
808 |
+
// Delete all @charset at-rules
|
809 |
+
$css = preg_replace($this->charsetRegex, '', $css);
|
810 |
+
}
|
811 |
+
|
812 |
+
// @import handling
|
813 |
+
$css = preg_replace_callback($this->importRegex, function ($matches) use (&$imports) {
|
814 |
+
// Keep all @import at-rules found for later
|
815 |
+
$imports .= $matches[0];
|
816 |
+
// Delete all @import at-rules
|
817 |
+
return '';
|
818 |
+
}, $css);
|
819 |
+
|
820 |
+
// @namespace handling
|
821 |
+
$css = preg_replace_callback($this->namespaceRegex, function ($matches) use (&$namespaces) {
|
822 |
+
// Keep all @namespace at-rules found for later
|
823 |
+
$namespaces .= $matches[0];
|
824 |
+
// Delete all @namespace at-rules
|
825 |
+
return '';
|
826 |
+
}, $css);
|
827 |
+
|
828 |
+
// Order critical at-rules:
|
829 |
+
// 1. @charset first
|
830 |
+
// 2. @imports below @charset
|
831 |
+
// 3. @namespaces below @imports
|
832 |
+
$css = $charset . $imports . $namespaces . $css;
|
833 |
+
|
834 |
+
return $css;
|
835 |
+
}
|
836 |
+
|
837 |
+
/**
|
838 |
+
* Splits long lines after a specific column.
|
839 |
+
*
|
840 |
+
* Some source control tools don't like it when files containing lines longer
|
841 |
+
* than, say 8000 characters, are checked in. The linebreak option is used in
|
842 |
+
* that case to split long lines after a specific column.
|
843 |
+
*
|
844 |
+
* @param string $css the whole stylesheet.
|
845 |
+
* @return string
|
846 |
+
*/
|
847 |
+
private function processLongLineSplitting($css)
|
848 |
+
{
|
849 |
+
if ($this->linebreakPosition > 0) {
|
850 |
+
$l = strlen($css);
|
851 |
+
$offset = $this->linebreakPosition;
|
852 |
+
while (preg_match('/(?<!\\\\)\}(?!\n)/S', $css, $matches, PREG_OFFSET_CAPTURE, $offset)) {
|
853 |
+
$matchIndex = $matches[0][1];
|
854 |
+
$css = substr_replace($css, "\n", $matchIndex + 1, 0);
|
855 |
+
$offset = $matchIndex + 2 + $this->linebreakPosition;
|
856 |
+
$l += 1;
|
857 |
+
if ($offset > $l) {
|
858 |
+
break;
|
859 |
+
}
|
860 |
+
}
|
861 |
+
}
|
862 |
+
|
863 |
+
return $css;
|
864 |
+
}
|
865 |
+
|
866 |
+
/**
|
867 |
+
* Converts hsl() & rgb() colors to HEX format.
|
868 |
+
* @param $matches
|
869 |
+
* @return string
|
870 |
+
*/
|
871 |
+
private function shortenHslAndRgbToHexCallback($matches)
|
872 |
+
{
|
873 |
+
$type = $matches[1];
|
874 |
+
$values = explode(',', $matches[2]);
|
875 |
+
$terminator = $matches[3];
|
876 |
+
|
877 |
+
if ($type === 'hsl') {
|
878 |
+
$values = Utils::hslToRgb($values);
|
879 |
+
}
|
880 |
+
|
881 |
+
$hexColors = Utils::rgbToHex($values);
|
882 |
+
|
883 |
+
// Restore space after rgb() or hsl() function in some cases such as:
|
884 |
+
// background-image: linear-gradient(to bottom, rgb(210,180,140) 10%, rgb(255,0,0) 90%);
|
885 |
+
if (!empty($terminator) && !preg_match('/[ ,);]/S', $terminator)) {
|
886 |
+
$terminator = ' '. $terminator;
|
887 |
+
}
|
888 |
+
|
889 |
+
return '#'. implode('', $hexColors) . $terminator;
|
890 |
+
}
|
891 |
+
|
892 |
+
/**
|
893 |
+
* Compresses HEX color values of the form #AABBCC to #ABC or short color name.
|
894 |
+
* @param $matches
|
895 |
+
* @return string
|
896 |
+
*/
|
897 |
+
private function shortenHexColorsCallback($matches)
|
898 |
+
{
|
899 |
+
$hex = $matches[1];
|
900 |
+
|
901 |
+
// Shorten suitable 6 chars HEX colors
|
902 |
+
if (strlen($hex) === 6 && preg_match('/^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/Si', $hex, $m)) {
|
903 |
+
$hex = $m[1] . $m[2] . $m[3];
|
904 |
+
}
|
905 |
+
|
906 |
+
// Lowercase
|
907 |
+
$hex = '#'. strtolower($hex);
|
908 |
+
|
909 |
+
// Replace Hex colors with shorter color names
|
910 |
+
$color = array_key_exists($hex, $this->hexToNamedColorsMap) ? $this->hexToNamedColorsMap[$hex] : $hex;
|
911 |
+
|
912 |
+
return $color . $matches[2];
|
913 |
+
}
|
914 |
+
|
915 |
+
/**
|
916 |
+
* Shortens all named colors with a shorter HEX counterpart for a set of safe properties
|
917 |
+
* e.g. white -> #fff
|
918 |
+
* @param array $matches
|
919 |
+
* @return string
|
920 |
+
*/
|
921 |
+
private function shortenNamedColorsCallback($matches)
|
922 |
+
{
|
923 |
+
return $matches[1] . $this->namedToHexColorsMap[strtolower($matches[2])] . $matches[3];
|
924 |
+
}
|
925 |
+
|
926 |
+
/**
|
927 |
+
* Makes a string lowercase
|
928 |
+
* @param array $matches
|
929 |
+
* @return string
|
930 |
+
*/
|
931 |
+
private function strtolowerCallback($matches)
|
932 |
+
{
|
933 |
+
return strtolower($matches[0]);
|
934 |
+
}
|
935 |
+
}
|
cssmin/Utils.php
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace tubalmartin\CssMin;
|
4 |
+
|
5 |
+
class Utils
|
6 |
+
{
|
7 |
+
/**
|
8 |
+
* Clamps a number between a minimum and a maximum value.
|
9 |
+
* @param int|float $n the number to clamp
|
10 |
+
* @param int|float $min the lower end number allowed
|
11 |
+
* @param int|float $max the higher end number allowed
|
12 |
+
* @return int|float
|
13 |
+
*/
|
14 |
+
public static function clampNumber($n, $min, $max)
|
15 |
+
{
|
16 |
+
return min(max($n, $min), $max);
|
17 |
+
}
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Clamps a RGB color number outside the sRGB color space
|
21 |
+
* @param int|float $n the number to clamp
|
22 |
+
* @return int|float
|
23 |
+
*/
|
24 |
+
public static function clampNumberSrgb($n)
|
25 |
+
{
|
26 |
+
return self::clampNumber($n, 0, 255);
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Converts a HSL color into a RGB color
|
31 |
+
* @param array $hslValues
|
32 |
+
* @return array
|
33 |
+
*/
|
34 |
+
public static function hslToRgb($hslValues)
|
35 |
+
{
|
36 |
+
$h = floatval($hslValues[0]);
|
37 |
+
$s = floatval(str_replace('%', '', $hslValues[1]));
|
38 |
+
$l = floatval(str_replace('%', '', $hslValues[2]));
|
39 |
+
|
40 |
+
// Wrap and clamp, then fraction!
|
41 |
+
$h = ((($h % 360) + 360) % 360) / 360;
|
42 |
+
$s = self::clampNumber($s, 0, 100) / 100;
|
43 |
+
$l = self::clampNumber($l, 0, 100) / 100;
|
44 |
+
|
45 |
+
if ($s == 0) {
|
46 |
+
$r = $g = $b = self::roundNumber(255 * $l);
|
47 |
+
} else {
|
48 |
+
$v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
|
49 |
+
$v1 = (2 * $l) - $v2;
|
50 |
+
$r = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h + (1/3)));
|
51 |
+
$g = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h));
|
52 |
+
$b = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h - (1/3)));
|
53 |
+
}
|
54 |
+
|
55 |
+
return array($r, $g, $b);
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Tests and selects the correct formula for each RGB color channel
|
60 |
+
* @param $v1
|
61 |
+
* @param $v2
|
62 |
+
* @param $vh
|
63 |
+
* @return mixed
|
64 |
+
*/
|
65 |
+
public static function hueToRgb($v1, $v2, $vh)
|
66 |
+
{
|
67 |
+
$vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
|
68 |
+
|
69 |
+
if ($vh * 6 < 1) {
|
70 |
+
return $v1 + ($v2 - $v1) * 6 * $vh;
|
71 |
+
}
|
72 |
+
|
73 |
+
if ($vh * 2 < 1) {
|
74 |
+
return $v2;
|
75 |
+
}
|
76 |
+
|
77 |
+
if ($vh * 3 < 2) {
|
78 |
+
return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6;
|
79 |
+
}
|
80 |
+
|
81 |
+
return $v1;
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Convert strings like "64M" or "30" to int values
|
86 |
+
* @param mixed $size
|
87 |
+
* @return int
|
88 |
+
*/
|
89 |
+
public static function normalizeInt($size)
|
90 |
+
{
|
91 |
+
if (is_string($size)) {
|
92 |
+
$letter = substr($size, -1);
|
93 |
+
$size = intval($size);
|
94 |
+
switch ($letter) {
|
95 |
+
case 'M':
|
96 |
+
case 'm':
|
97 |
+
return (int) $size * 1048576;
|
98 |
+
case 'K':
|
99 |
+
case 'k':
|
100 |
+
return (int) $size * 1024;
|
101 |
+
case 'G':
|
102 |
+
case 'g':
|
103 |
+
return (int) $size * 1073741824;
|
104 |
+
}
|
105 |
+
}
|
106 |
+
return (int) $size;
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5
|
111 |
+
* @param $rgbPercentage
|
112 |
+
* @return int
|
113 |
+
*/
|
114 |
+
public static function rgbPercentageToRgbInteger($rgbPercentage)
|
115 |
+
{
|
116 |
+
if (strpos($rgbPercentage, '%') !== false) {
|
117 |
+
$rgbPercentage = self::roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55);
|
118 |
+
}
|
119 |
+
|
120 |
+
return intval($rgbPercentage, 10);
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Converts a RGB color into a HEX color
|
125 |
+
* @param array $rgbColors
|
126 |
+
* @return array
|
127 |
+
*/
|
128 |
+
public static function rgbToHex($rgbColors)
|
129 |
+
{
|
130 |
+
$hexColors = array();
|
131 |
+
|
132 |
+
// Values outside the sRGB color space should be clipped (0-255)
|
133 |
+
for ($i = 0, $l = count($rgbColors); $i < $l; $i++) {
|
134 |
+
$hexColors[$i] = sprintf("%02x", self::clampNumberSrgb(self::rgbPercentageToRgbInteger($rgbColors[$i])));
|
135 |
+
}
|
136 |
+
|
137 |
+
return $hexColors;
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
* Rounds a number to its closest integer
|
142 |
+
* @param $n
|
143 |
+
* @return int
|
144 |
+
*/
|
145 |
+
public static function roundNumber($n)
|
146 |
+
{
|
147 |
+
return intval(round(floatval($n)), 10);
|
148 |
+
}
|
149 |
+
}
|
cssmin/cssmin.php
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// Simple loader file
|
4 |
+
// Library downloaded directly from https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
|
5 |
+
|
6 |
+
require( __DIR__ . '/Colors.php' );
|
7 |
+
require( __DIR__ . '/Utils.php' );
|
8 |
+
require( __DIR__ . '/Minifier.php' );
|
dependency-path-mapping.php
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* This is a class to map script and style URLs to local filesystem paths.
|
5 |
+
* This is necessary when we are deciding what we can concatenate and when
|
6 |
+
* actually building the concatenation.
|
7 |
+
*/
|
8 |
+
class Page_Optimize_Dependency_Path_Mapping {
|
9 |
+
// Save entire site URL so we can check whether other URLs are based on it (internal URLs)
|
10 |
+
public $site_url;
|
11 |
+
|
12 |
+
// Save URI path and dir for mapping URIs to filesystem paths
|
13 |
+
public $site_uri_path = null;
|
14 |
+
public $site_dir = null;
|
15 |
+
public $content_uri_path = null;
|
16 |
+
public $content_dir = null;
|
17 |
+
public $plugin_uri_path = null;
|
18 |
+
public $plugin_dir = null;
|
19 |
+
|
20 |
+
function __construct(
|
21 |
+
// Expose URLs and DIRs for unit test
|
22 |
+
$site_url = null, // default site URL is determined dynamically
|
23 |
+
$site_dir = PAGE_OPTIMIZE_ABSPATH,
|
24 |
+
$content_url = WP_CONTENT_URL,
|
25 |
+
$content_dir = WP_CONTENT_DIR,
|
26 |
+
$plugin_url = WP_PLUGIN_URL,
|
27 |
+
$plugin_dir = WP_PLUGIN_DIR
|
28 |
+
) {
|
29 |
+
if ( null === $site_url ) {
|
30 |
+
$site_url = is_multisite() ? get_site_url( get_current_blog_id() ) : get_site_url();
|
31 |
+
}
|
32 |
+
$this->site_url = trailingslashit( $site_url );
|
33 |
+
$this->site_uri_path = parse_url( $site_url, PHP_URL_PATH );
|
34 |
+
$this->site_dir = trailingslashit( $site_dir );
|
35 |
+
|
36 |
+
// Only resolve content URLs if they are under the site URL
|
37 |
+
if ( $this->is_internal_uri( $content_url ) ) {
|
38 |
+
$this->content_uri_path = parse_url( trailingslashit( $content_url ), PHP_URL_PATH );
|
39 |
+
$this->content_dir = trailingslashit( $content_dir );
|
40 |
+
}
|
41 |
+
|
42 |
+
// Only resolve plugin URLs if they are under the site URL
|
43 |
+
if ( $this->is_internal_uri( $plugin_url ) ) {
|
44 |
+
$this->plugin_uri_path = parse_url( trailingslashit( $plugin_url ), PHP_URL_PATH );
|
45 |
+
$this->plugin_dir = trailingslashit( $plugin_dir );
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Given the full URL of a script/style dependency, return its local filesystem path.
|
51 |
+
*/
|
52 |
+
function dependency_src_to_fs_path( $src ) {
|
53 |
+
if ( ! $this->is_internal_uri( $src ) ) {
|
54 |
+
// If a URI is not internal, we can have no confidence
|
55 |
+
// we are resolving to the correct file.
|
56 |
+
return false;
|
57 |
+
}
|
58 |
+
|
59 |
+
$src_parts = parse_url( $src );
|
60 |
+
if ( false === $src_parts ) {
|
61 |
+
return false;
|
62 |
+
}
|
63 |
+
|
64 |
+
if ( empty( $src_parts['path'] ) ) {
|
65 |
+
// We can't find anything to resolve
|
66 |
+
return false;
|
67 |
+
}
|
68 |
+
$path = $src_parts['path'];
|
69 |
+
|
70 |
+
if ( empty( $src_parts['host'] ) ) {
|
71 |
+
// With no host, this is a path relative to the WordPress root
|
72 |
+
$fs_path = "{$this->site_dir}{$path}";
|
73 |
+
return file_exists( $fs_path ) ? $fs_path : false;
|
74 |
+
}
|
75 |
+
|
76 |
+
return $this->uri_path_to_fs_path( $path );
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Given a URI path of a script/style resource, return its local filesystem path.
|
81 |
+
*/
|
82 |
+
function uri_path_to_fs_path( $uri_path ) {
|
83 |
+
if ( 1 === preg_match( '#(?:^|/)\.\.?(?:/|$)#', $uri_path ) ) {
|
84 |
+
// Reject relative paths
|
85 |
+
return false;
|
86 |
+
}
|
87 |
+
|
88 |
+
// The plugin URI path may be contained within the content URI path, so we check it before the content URI.
|
89 |
+
// And both the plugin and content URI paths must be contained within the site URI path,
|
90 |
+
// so we check them before checking the site URI.
|
91 |
+
if ( isset( $this->plugin_uri_path ) && static::is_descendant_uri( $this->plugin_uri_path, $uri_path ) ) {
|
92 |
+
$file_path = $this->plugin_dir . substr( $uri_path, strlen( $this->plugin_uri_path ) );
|
93 |
+
} else if ( isset( $this->content_uri_path ) && static::is_descendant_uri( $this->content_uri_path, $uri_path ) ) {
|
94 |
+
$file_path = $this->content_dir . substr( $uri_path, strlen( $this->content_uri_path ) );
|
95 |
+
} else if ( static::is_descendant_uri( $this->site_uri_path, $uri_path ) ) {
|
96 |
+
$file_path = $this->site_dir . substr( $uri_path, strlen( $this->site_uri_path ) );
|
97 |
+
}
|
98 |
+
|
99 |
+
if ( isset( $file_path ) && file_exists( $file_path ) ) {
|
100 |
+
return $file_path;
|
101 |
+
} else {
|
102 |
+
return false;
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Determine whether a URI is internal, contained by this site.
|
108 |
+
*
|
109 |
+
* This method helps ensure we only resolve to local FS paths.
|
110 |
+
*/
|
111 |
+
function is_internal_uri( $uri ) {
|
112 |
+
if ( page_optimize_starts_with( '/', $uri ) && ! page_optimize_starts_with( '//', $uri ) ) {
|
113 |
+
// Absolute paths are internal because they are based on the site dir (typically ABSPATH),
|
114 |
+
// and this looks like an absolute path.
|
115 |
+
return true;
|
116 |
+
}
|
117 |
+
|
118 |
+
// To be internal, a URL must have the same scheme, host, and port as the site URL
|
119 |
+
// and start with the same path as the site URL.
|
120 |
+
return static::is_descendant_uri( $this->site_url, $uri );
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Check whether a path is descended from the given directory path.
|
125 |
+
*
|
126 |
+
* Does not handle relative paths.
|
127 |
+
*/
|
128 |
+
static function is_descendant_uri( $dir_path, $candidate ) {
|
129 |
+
// Ensure a trailing slash to avoid false matches like
|
130 |
+
// "/wp-content/resource" being judged a descendant of "/wp".
|
131 |
+
$dir_path = trailingslashit( $dir_path );
|
132 |
+
|
133 |
+
return page_optimize_starts_with( $dir_path, $candidate );
|
134 |
+
}
|
135 |
+
}
|
page-optimize.php
ADDED
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
Plugin Name: Page Optimize
|
4 |
+
Plugin URI: https://wordpress.org/plugins/page-optimize/
|
5 |
+
Description: Optimizes JS and CSS for faster page load and render in the browser.
|
6 |
+
Author: Automattic
|
7 |
+
Version: 0.4.2
|
8 |
+
Author URI: http://automattic.com/
|
9 |
+
*/
|
10 |
+
|
11 |
+
// Default cache directory
|
12 |
+
if ( ! defined( 'PAGE_OPTIMIZE_CACHE_DIR' ) ) {
|
13 |
+
define( 'PAGE_OPTIMIZE_CACHE_DIR', WP_CONTENT_DIR . '/cache/page_optimize' );
|
14 |
+
}
|
15 |
+
|
16 |
+
if ( ! defined( 'PAGE_OPTIMIZE_ABSPATH' ) ) {
|
17 |
+
define( 'PAGE_OPTIMIZE_ABSPATH', ABSPATH );
|
18 |
+
}
|
19 |
+
|
20 |
+
define( 'PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB', 'page_optimize_cron_cache_cleanup' );
|
21 |
+
|
22 |
+
// TODO: Copy tests from nginx-http-concat and/or write them
|
23 |
+
|
24 |
+
// TODO: Make concat URL dir configurable
|
25 |
+
if ( isset( $_SERVER['REQUEST_URI'] ) && '/_static/' === substr( $_SERVER['REQUEST_URI'], 0, 9 ) ) {
|
26 |
+
require_once __DIR__ . '/service.php';
|
27 |
+
page_optimize_service_request();
|
28 |
+
exit;
|
29 |
+
}
|
30 |
+
|
31 |
+
function page_optimize_cache_cleanup( $file_age = DAY_IN_SECONDS ) {
|
32 |
+
if ( ! is_dir( PAGE_OPTIMIZE_CACHE_DIR ) ) {
|
33 |
+
return;
|
34 |
+
}
|
35 |
+
|
36 |
+
// Grab all files in the cache directory
|
37 |
+
$cache_files = glob( PAGE_OPTIMIZE_CACHE_DIR . '/page-optimize-cache-*' );
|
38 |
+
|
39 |
+
// Cleanup all files older than 24 hours
|
40 |
+
foreach ( $cache_files as $cache_file ) {
|
41 |
+
if ( ! is_file( $cache_file ) ) {
|
42 |
+
continue;
|
43 |
+
}
|
44 |
+
|
45 |
+
if ( ( time() - $file_age ) > filemtime( $cache_file ) ) {
|
46 |
+
unlink( $cache_file );
|
47 |
+
}
|
48 |
+
}
|
49 |
+
}
|
50 |
+
add_action( PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB, 'page_optimize_cache_cleanup' );
|
51 |
+
|
52 |
+
// Unschedule cache cleanup, and purge cache directory
|
53 |
+
function page_optimize_deactivate() {
|
54 |
+
page_optimize_cache_cleanup( 0 /* max file age */ );
|
55 |
+
|
56 |
+
wp_clear_scheduled_hook( PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB );
|
57 |
+
}
|
58 |
+
register_deactivation_hook( __FILE__, 'page_optimize_deactivate' );
|
59 |
+
|
60 |
+
function page_optimize_uninstall() {
|
61 |
+
// Run cleanup on uninstall. You can uninstall an active plugin w/o deactivation.
|
62 |
+
page_optimize_deactivate();
|
63 |
+
|
64 |
+
// JS
|
65 |
+
delete_option( 'page_optimize-js' );
|
66 |
+
delete_option( 'page_optimize-load-mode' );
|
67 |
+
delete_option( 'page_optimize-js-exclude' );
|
68 |
+
// CSS
|
69 |
+
delete_option( 'page_optimize-css' );
|
70 |
+
delete_option( 'page_optimize-css-exclude' );
|
71 |
+
|
72 |
+
}
|
73 |
+
register_uninstall_hook( __FILE__, 'page_optimize_uninstall' );
|
74 |
+
|
75 |
+
function page_optimize_get_text_domain() {
|
76 |
+
return 'page-optimize';
|
77 |
+
}
|
78 |
+
|
79 |
+
function page_optimize_should_concat_js() {
|
80 |
+
// Support query param for easy testing
|
81 |
+
if ( isset( $_GET['concat-js'] ) ) {
|
82 |
+
return $_GET['concat-js'] !== '0';
|
83 |
+
}
|
84 |
+
|
85 |
+
return !! get_option( 'page_optimize-js', page_optimize_js_default() );
|
86 |
+
}
|
87 |
+
|
88 |
+
// TODO: Support JS load mode regardless of whether concat is enabled
|
89 |
+
function page_optimize_load_mode_js() {
|
90 |
+
// Support query param for easy testing
|
91 |
+
if ( ! empty( $_GET['load-mode-js'] ) ) {
|
92 |
+
$load_mode = page_optimize_sanitize_js_load_mode( $_GET['load-mode-js'] );
|
93 |
+
} else {
|
94 |
+
$load_mode = page_optimize_sanitize_js_load_mode( get_option( 'page_optimize-load-mode', page_optimize_js_load_mode_default() ) );
|
95 |
+
}
|
96 |
+
|
97 |
+
return $load_mode;
|
98 |
+
}
|
99 |
+
|
100 |
+
function page_optimize_should_concat_css() {
|
101 |
+
// Support query param for easy testing
|
102 |
+
if ( isset( $_GET['concat-css'] ) ) {
|
103 |
+
return $_GET['concat-css'] !== '0';
|
104 |
+
}
|
105 |
+
|
106 |
+
return !! get_option( 'page_optimize-css', page_optimize_css_default() );
|
107 |
+
}
|
108 |
+
|
109 |
+
function page_optimize_js_default() {
|
110 |
+
return true;
|
111 |
+
}
|
112 |
+
|
113 |
+
function page_optimize_css_default() {
|
114 |
+
return true;
|
115 |
+
}
|
116 |
+
|
117 |
+
function page_optimize_js_load_mode_default() {
|
118 |
+
return '';
|
119 |
+
}
|
120 |
+
|
121 |
+
function page_optimize_js_exclude_list() {
|
122 |
+
$exclude_list = get_option( 'page_optimize-js-exclude' );
|
123 |
+
if ( false === $exclude_list ) {
|
124 |
+
// Use the default since the option is not set
|
125 |
+
return page_optimize_js_exclude_list_default();
|
126 |
+
}
|
127 |
+
if ( '' === $exclude_list ) {
|
128 |
+
return [];
|
129 |
+
}
|
130 |
+
|
131 |
+
return explode( ',', $exclude_list );
|
132 |
+
}
|
133 |
+
|
134 |
+
function page_optimize_js_exclude_list_default() {
|
135 |
+
// WordPress core stuff, a lot of other plugins depend on it.
|
136 |
+
return [ 'jquery', 'jquery-core', 'underscore', 'backbone' ];
|
137 |
+
}
|
138 |
+
|
139 |
+
function page_optimize_css_exclude_list() {
|
140 |
+
$exclude_list = get_option( 'page_optimize-css-exclude' );
|
141 |
+
if ( false === $exclude_list ) {
|
142 |
+
// Use the default since the option is not set
|
143 |
+
return page_optimize_css_exclude_list_default();
|
144 |
+
}
|
145 |
+
if ( '' === $exclude_list ) {
|
146 |
+
return [];
|
147 |
+
}
|
148 |
+
|
149 |
+
return explode( ',', $exclude_list );
|
150 |
+
}
|
151 |
+
|
152 |
+
function page_optimize_css_exclude_list_default() {
|
153 |
+
// WordPress core stuff
|
154 |
+
return [ 'admin-bar', 'dashicons' ];
|
155 |
+
}
|
156 |
+
|
157 |
+
function page_optimize_sanitize_js_load_mode( $value ) {
|
158 |
+
switch ( $value ) {
|
159 |
+
case 'async':
|
160 |
+
case 'defer':
|
161 |
+
break;
|
162 |
+
default:
|
163 |
+
$value = '';
|
164 |
+
break;
|
165 |
+
}
|
166 |
+
|
167 |
+
return $value;
|
168 |
+
}
|
169 |
+
|
170 |
+
function page_optimize_sanitize_exclude_field( $value ) {
|
171 |
+
if ( empty( $value ) ) {
|
172 |
+
return '';
|
173 |
+
}
|
174 |
+
|
175 |
+
$excluded_strings = explode( ',', sanitize_text_field( $value ) );
|
176 |
+
$sanitized_values = [];
|
177 |
+
foreach ( $excluded_strings as $excluded_string ) {
|
178 |
+
if ( ! empty( $excluded_string ) ) {
|
179 |
+
$sanitized_values[] = trim( $excluded_string );
|
180 |
+
}
|
181 |
+
}
|
182 |
+
|
183 |
+
return implode( ',', $sanitized_values );
|
184 |
+
}
|
185 |
+
|
186 |
+
/**
|
187 |
+
* Determines whether a string starts with another string.
|
188 |
+
*/
|
189 |
+
function page_optimize_starts_with( $prefix, $str ) {
|
190 |
+
$prefix_length = strlen( $prefix );
|
191 |
+
if ( strlen( $str ) < $prefix_length ) {
|
192 |
+
return false;
|
193 |
+
}
|
194 |
+
|
195 |
+
return substr( $str, 0, $prefix_length ) === $prefix;
|
196 |
+
}
|
197 |
+
|
198 |
+
/**
|
199 |
+
* Answers whether the plugin should provide concat resource URIs
|
200 |
+
* that are relative to a common ancestor directory. Assuming a common ancestor
|
201 |
+
* allows us to skip resolving resource URIs to filesystem paths later on.
|
202 |
+
*/
|
203 |
+
function page_optimize_use_concat_base_dir() {
|
204 |
+
return defined( 'PAGE_OPTIMIZE_CONCAT_BASE_DIR' ) && file_exists( PAGE_OPTIMIZE_CONCAT_BASE_DIR );
|
205 |
+
}
|
206 |
+
|
207 |
+
/**
|
208 |
+
* Get a filesystem path relative to a configured base path for resources
|
209 |
+
* that will be concatenated. Assuming a common ancestor allows us to skip
|
210 |
+
* resolving resource URIs to filesystem paths later on.
|
211 |
+
*/
|
212 |
+
function page_optimize_remove_concat_base_prefix( $original_fs_path ) {
|
213 |
+
// Always check longer path first
|
214 |
+
if ( strlen( PAGE_OPTIMIZE_ABSPATH ) > strlen( PAGE_OPTIMIZE_CONCAT_BASE_DIR ) ) {
|
215 |
+
$longer_path = PAGE_OPTIMIZE_ABSPATH;
|
216 |
+
$shorter_path = PAGE_OPTIMIZE_CONCAT_BASE_DIR;
|
217 |
+
} else {
|
218 |
+
$longer_path = PAGE_OPTIMIZE_CONCAT_BASE_DIR;
|
219 |
+
$shorter_path = PAGE_OPTIMIZE_ABSPATH;
|
220 |
+
}
|
221 |
+
|
222 |
+
$prefix_abspath = trailingslashit( $longer_path );
|
223 |
+
if ( page_optimize_starts_with( $prefix_abspath, $original_fs_path ) ) {
|
224 |
+
return substr( $original_fs_path, strlen( $prefix_abspath ) );
|
225 |
+
}
|
226 |
+
|
227 |
+
$prefix_basedir = trailingslashit( $shorter_path );
|
228 |
+
if ( page_optimize_starts_with( $prefix_basedir, $original_fs_path ) ) {
|
229 |
+
return substr( $original_fs_path, strlen( $prefix_basedir ) );
|
230 |
+
}
|
231 |
+
|
232 |
+
// If we end up here, this is a resource we shouldn't have tried to concat in the first place
|
233 |
+
return '/page-optimize-resource-outside-base-path/' . basename( $original_fs_path );
|
234 |
+
}
|
235 |
+
|
236 |
+
// Cases when we don't want to concat
|
237 |
+
function page_optimize_bail() {
|
238 |
+
// Bail if we're in customizer
|
239 |
+
global $wp_customize;
|
240 |
+
if ( isset( $wp_customize ) ) {
|
241 |
+
return true;
|
242 |
+
}
|
243 |
+
|
244 |
+
// Bail if Divi theme is active, and we're in the Divi Front End Builder
|
245 |
+
if ( ! empty( $_GET['et_fb'] ) && 'Divi' === wp_get_theme()->get_template() ) {
|
246 |
+
return true;
|
247 |
+
}
|
248 |
+
|
249 |
+
return false;
|
250 |
+
}
|
251 |
+
|
252 |
+
function page_optimize_init() {
|
253 |
+
if ( page_optimize_bail() ) {
|
254 |
+
return;
|
255 |
+
}
|
256 |
+
|
257 |
+
// Schedule cache cleanup on init
|
258 |
+
if( ! wp_next_scheduled( PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB ) ) {
|
259 |
+
wp_schedule_event( time(), 'daily', PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB );
|
260 |
+
}
|
261 |
+
|
262 |
+
require_once __DIR__ . '/settings.php';
|
263 |
+
require_once __DIR__ . '/concat-css.php';
|
264 |
+
require_once __DIR__ . '/concat-js.php';
|
265 |
+
|
266 |
+
// Disable Jetpack photon-cdn for static JS/CSS
|
267 |
+
add_filter( 'jetpack_force_disable_site_accelerator', '__return_true' );
|
268 |
+
}
|
269 |
+
add_action( 'plugins_loaded', 'page_optimize_init' );
|
readme.txt
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== page-optimize ===
|
2 |
+
Contributors: aidvu, bpayton
|
3 |
+
Tags: performance
|
4 |
+
Requires at least: 5.3
|
5 |
+
Tested up to: 5.3
|
6 |
+
Requires PHP: 7.2
|
7 |
+
Stable tag: 0.4.2
|
8 |
+
License: GPLv2 or later
|
9 |
+
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
10 |
+
|
11 |
+
Optimize pages for faster load and render in the browser.
|
12 |
+
|
13 |
+
== Description ==
|
14 |
+
|
15 |
+
This plugin supports a few features that may improve the performance of page loading and rendering in the browser:
|
16 |
+
|
17 |
+
* Concatenate CSS
|
18 |
+
* Concatenate JavaScript
|
19 |
+
* Execution timing of non-critical scripts
|
20 |
+
|
21 |
+
Notes:
|
22 |
+
* Changing script execution timing can be risky and will not work well for all sites.
|
23 |
+
|
24 |
+
== Installation ==
|
25 |
+
|
26 |
+
This plugin uses sensible defaults so it can operate without configuration, but there are a number of constants you may use for a custom configuration.
|
27 |
+
|
28 |
+
= PAGE_OPTIMIZE_CACHE_DIR =
|
29 |
+
|
30 |
+
Page Optimize caches concatenated scripts and styles by default, and this constant controls where the cache files are stored. The default directory is `cache/page_optimize` under your site's `wp-content` folder.
|
31 |
+
|
32 |
+
To change the cache location, set this constant to the absolute filesystem path of that location.
|
33 |
+
|
34 |
+
To disable caching, set this constant to `false`. Please note that disabling Page Optimize caching may negatively impact performance unless you are caching elsewhere.
|
35 |
+
|
36 |
+
== Testing ==
|
37 |
+
|
38 |
+
To test features without enabling them for the entire site, you may append query params to a WordPress post or page URL. For example, to test enabling JavaScript concatenation for `https://example.com/blog/`, you can use the URL `https://example.com/blog/?concat-js=1`.
|
39 |
+
|
40 |
+
Supported query params:
|
41 |
+
|
42 |
+
* `concat-css` controls CSS concatenation. Values: `1` for ON and `0` for OFF.
|
43 |
+
* `concat-js` controls JavaScript concatenation. Values: `1` for ON and `0` for OFF.
|
44 |
+
* `load-mode-js` controls how non-critical JavaScript are loaded. Values: 'defer' for [deferred](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer), 'async' for [async loading](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async), any other value indicates the feature should be disabled.
|
45 |
+
|
46 |
+
== Changelog ==
|
47 |
+
|
48 |
+
Initial release. No changes yet. :)
|
service.php
ADDED
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
$page_optimize_types = array(
|
4 |
+
'css' => 'text/css',
|
5 |
+
'js' => 'application/javascript'
|
6 |
+
);
|
7 |
+
|
8 |
+
// TODO: Provide a settings button to clear the cache
|
9 |
+
// TODO: Should we provide a default at all? Is this a reasonable default?
|
10 |
+
|
11 |
+
function page_optimize_service_request() {
|
12 |
+
$use_cache = defined( 'PAGE_OPTIMIZE_CACHE_DIR' ) && ! empty( PAGE_OPTIMIZE_CACHE_DIR );
|
13 |
+
if ( $use_cache && ! is_dir( PAGE_OPTIMIZE_CACHE_DIR ) && ! mkdir( PAGE_OPTIMIZE_CACHE_DIR, 0775, true ) ) {
|
14 |
+
$use_cache = false;
|
15 |
+
error_log( sprintf(
|
16 |
+
/* translators: a filesystem path to a directory */
|
17 |
+
__( "Disabling page-optimize cache. Unable to create cache directory '%s'.", page_optimize_get_text_domain() ),
|
18 |
+
PAGE_OPTIMIZE_CACHE_DIR
|
19 |
+
) );
|
20 |
+
}
|
21 |
+
if ( $use_cache && ( ! is_dir( PAGE_OPTIMIZE_CACHE_DIR ) || ! is_writable( PAGE_OPTIMIZE_CACHE_DIR ) || ! is_executable( PAGE_OPTIMIZE_CACHE_DIR ) ) ) {
|
22 |
+
$use_cache = false;
|
23 |
+
error_log( sprintf(
|
24 |
+
/* translators: a filesystem path to a directory */
|
25 |
+
__( "Disabling page-optimize cache. Unable to write to cache directory '%s'.", page_optimize_get_text_domain() ),
|
26 |
+
PAGE_OPTIMIZE_CACHE_DIR
|
27 |
+
) );
|
28 |
+
}
|
29 |
+
|
30 |
+
if ( $use_cache ) {
|
31 |
+
$request_uri_hash = md5( $_SERVER['REQUEST_URI'] );
|
32 |
+
$cache_file = PAGE_OPTIMIZE_CACHE_DIR . "/page-optimize-cache-$request_uri_hash";
|
33 |
+
$cache_file_meta = PAGE_OPTIMIZE_CACHE_DIR . "/page-optimize-cache-meta-$request_uri_hash";
|
34 |
+
|
35 |
+
if ( file_exists( $cache_file ) ) {
|
36 |
+
if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
|
37 |
+
if ( strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) < filemtime( $cache_file ) ) {
|
38 |
+
header( 'HTTP/1.1 304 Not Modified' );
|
39 |
+
exit;
|
40 |
+
}
|
41 |
+
}
|
42 |
+
|
43 |
+
if ( file_exists( $cache_file_meta ) ) {
|
44 |
+
$meta = json_decode( file_get_contents( $cache_file_meta ) );
|
45 |
+
if ( null !== $meta && isset( $meta->headers ) ) {
|
46 |
+
foreach ( $meta->headers as $header ) {
|
47 |
+
header( $header );
|
48 |
+
}
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
$etag = '"' . md5( file_get_contents( $cache_file ) ) . '"';
|
53 |
+
|
54 |
+
ob_start( 'ob_gzhandler' );
|
55 |
+
header( 'X-Page-Optimize: cached' );
|
56 |
+
header( 'Cache-Control: max-age=' . 31536000 );
|
57 |
+
header( 'ETag: ' . $etag );
|
58 |
+
|
59 |
+
echo file_get_contents( $cache_file ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We need to trust this unfortunately.
|
60 |
+
$output = ob_get_clean();
|
61 |
+
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We need to trust this unfortunately.
|
62 |
+
die();
|
63 |
+
}
|
64 |
+
}
|
65 |
+
|
66 |
+
$output = page_optimize_build_output();
|
67 |
+
$content = $output['content'];
|
68 |
+
$headers = $output['headers'];
|
69 |
+
|
70 |
+
foreach( $headers as $header ) {
|
71 |
+
header( $header );
|
72 |
+
}
|
73 |
+
header( 'X-Page-Optimize: uncached' );
|
74 |
+
header( 'Cache-Control: max-age=' . 31536000 );
|
75 |
+
header( 'ETag: "' . md5( $content ) . '"' );
|
76 |
+
|
77 |
+
echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We need to trust this unfortunately.
|
78 |
+
|
79 |
+
if ( $use_cache ) {
|
80 |
+
file_put_contents( $cache_file, $content );
|
81 |
+
file_put_contents( $cache_file_meta, json_encode( array( 'headers' => $headers ) ) );
|
82 |
+
}
|
83 |
+
|
84 |
+
die();
|
85 |
+
}
|
86 |
+
|
87 |
+
function page_optimize_build_output() {
|
88 |
+
global $page_optimize_types;
|
89 |
+
ob_start( 'ob_gzhandler' );
|
90 |
+
|
91 |
+
require_once __DIR__ . '/cssmin/cssmin.php';
|
92 |
+
|
93 |
+
/* Config */
|
94 |
+
$concat_max_files = 150;
|
95 |
+
$concat_unique = true;
|
96 |
+
|
97 |
+
/* Main() */
|
98 |
+
if ( ! in_array( $_SERVER['REQUEST_METHOD'], array( 'GET', 'HEAD' ) ) ) {
|
99 |
+
page_optimize_status_exit( 400 );
|
100 |
+
}
|
101 |
+
|
102 |
+
// /_static/??/foo/bar.css,/foo1/bar/baz.css?m=293847g
|
103 |
+
// or
|
104 |
+
// /_static/??-eJzTT8vP109KLNJLLi7W0QdyDEE8IK4CiVjn2hpZGluYmKcDABRMDPM=
|
105 |
+
$args = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY );
|
106 |
+
if ( ! $args || false === strpos( $args, '?' ) ) {
|
107 |
+
page_optimize_status_exit( 400 );
|
108 |
+
}
|
109 |
+
|
110 |
+
$args = substr( $args, strpos( $args, '?' ) + 1 );
|
111 |
+
|
112 |
+
// /foo/bar.css,/foo1/bar/baz.css?m=293847g
|
113 |
+
// or
|
114 |
+
// -eJzTT8vP109KLNJLLi7W0QdyDEE8IK4CiVjn2hpZGluYmKcDABRMDPM=
|
115 |
+
if ( '-' == $args[0] ) {
|
116 |
+
$args = @gzuncompress( base64_decode( substr( $args, 1 ) ) );
|
117 |
+
|
118 |
+
// Invalid data, abort!
|
119 |
+
if ( false === $args ) {
|
120 |
+
page_optimize_status_exit( 400 );
|
121 |
+
}
|
122 |
+
}
|
123 |
+
|
124 |
+
// /foo/bar.css,/foo1/bar/baz.css?m=293847g
|
125 |
+
$version_string_pos = strpos( $args, '?' );
|
126 |
+
if ( false !== $version_string_pos ) {
|
127 |
+
$args = substr( $args, 0, $version_string_pos );
|
128 |
+
}
|
129 |
+
|
130 |
+
// /foo/bar.css,/foo1/bar/baz.css
|
131 |
+
$args = explode( ',', $args );
|
132 |
+
if ( ! $args ) {
|
133 |
+
page_optimize_status_exit( 400 );
|
134 |
+
}
|
135 |
+
|
136 |
+
// array( '/foo/bar.css', '/foo1/bar/baz.css' )
|
137 |
+
if ( 0 == count( $args ) || count( $args ) > $concat_max_files ) {
|
138 |
+
page_optimize_status_exit( 400 );
|
139 |
+
}
|
140 |
+
|
141 |
+
// If we're in a subdirectory context, use that as the root.
|
142 |
+
// We can't assume that the root serves the same content as the subdir.
|
143 |
+
$subdir_path_prefix = '';
|
144 |
+
$request_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
|
145 |
+
$_static_index = strpos( $request_path, '/_static/' );
|
146 |
+
if ( $_static_index > 0 ) {
|
147 |
+
$subdir_path_prefix = substr( $request_path, 0, $_static_index );
|
148 |
+
}
|
149 |
+
unset( $request_path, $_static_index );
|
150 |
+
|
151 |
+
$last_modified = 0;
|
152 |
+
$pre_output = '';
|
153 |
+
$output = '';
|
154 |
+
|
155 |
+
$css_minify = new tubalmartin\CssMin\Minifier;
|
156 |
+
|
157 |
+
foreach ( $args as $uri ) {
|
158 |
+
$fullpath = page_optimize_get_path( $uri );
|
159 |
+
|
160 |
+
if ( ! file_exists( $fullpath ) ) {
|
161 |
+
page_optimize_status_exit( 404 );
|
162 |
+
}
|
163 |
+
|
164 |
+
$mime_type = page_optimize_get_mime_type( $fullpath );
|
165 |
+
if ( ! in_array( $mime_type, $page_optimize_types ) ) {
|
166 |
+
page_optimize_status_exit( 400 );
|
167 |
+
}
|
168 |
+
|
169 |
+
if ( $concat_unique ) {
|
170 |
+
if ( ! isset( $last_mime_type ) ) {
|
171 |
+
$last_mime_type = $mime_type;
|
172 |
+
}
|
173 |
+
|
174 |
+
if ( $last_mime_type != $mime_type ) {
|
175 |
+
page_optimize_status_exit( 400 );
|
176 |
+
}
|
177 |
+
}
|
178 |
+
|
179 |
+
$stat = stat( $fullpath );
|
180 |
+
if ( false === $stat ) {
|
181 |
+
page_optimize_status_exit( 500 );
|
182 |
+
}
|
183 |
+
|
184 |
+
if ( $stat['mtime'] > $last_modified ) {
|
185 |
+
$last_modified = $stat['mtime'];
|
186 |
+
}
|
187 |
+
|
188 |
+
$buf = file_get_contents( $fullpath );
|
189 |
+
if ( false === $buf ) {
|
190 |
+
page_optimize_status_exit( 500 );
|
191 |
+
}
|
192 |
+
|
193 |
+
if ( 'text/css' == $mime_type ) {
|
194 |
+
$dirpath = $subdir_path_prefix . dirname( $uri );
|
195 |
+
|
196 |
+
// url(relative/path/to/file) -> url(/absolute/and/not/relative/path/to/file)
|
197 |
+
$buf = page_optimize_relative_path_replace( $buf, $dirpath );
|
198 |
+
|
199 |
+
// AlphaImageLoader(...src='relative/path/to/file'...) -> AlphaImageLoader(...src='/absolute/path/to/file'...)
|
200 |
+
$buf = preg_replace(
|
201 |
+
'/(Microsoft.AlphaImageLoader\s*\([^\)]*src=(?:\'|")?)([^\/\'"\s\)](?:(?<!http:|https:).)*)\)/isU',
|
202 |
+
'$1' . ( $dirpath == '/' ? '/' : $dirpath . '/' ) . '$2)',
|
203 |
+
$buf
|
204 |
+
);
|
205 |
+
|
206 |
+
// The @charset rules must be on top of the output
|
207 |
+
if ( 0 === strpos( $buf, '@charset' ) ) {
|
208 |
+
preg_replace_callback(
|
209 |
+
'/(?P<charset_rule>@charset\s+[\'"][^\'"]+[\'"];)/i',
|
210 |
+
function ( $match ) {
|
211 |
+
global $pre_output;
|
212 |
+
|
213 |
+
if ( 0 === strpos( $pre_output, '@charset' ) ) {
|
214 |
+
return '';
|
215 |
+
}
|
216 |
+
|
217 |
+
$pre_output = $match[0] . "\n" . $pre_output;
|
218 |
+
|
219 |
+
return '';
|
220 |
+
},
|
221 |
+
$buf
|
222 |
+
);
|
223 |
+
}
|
224 |
+
|
225 |
+
// Move the @import rules on top of the concatenated output.
|
226 |
+
// Only @charset rule are allowed before them.
|
227 |
+
if ( false !== strpos( $buf, '@import' ) ) {
|
228 |
+
$buf = preg_replace_callback(
|
229 |
+
'/(?P<pre_path>@import\s+(?:url\s*\()?[\'"\s]*)(?P<path>[^\'"\s](?:https?:\/\/.+\/?)?.+?)(?P<post_path>[\'"\s\)]*;)/i',
|
230 |
+
function ( $match ) use ( $dirpath ) {
|
231 |
+
global $pre_output;
|
232 |
+
|
233 |
+
if ( 0 !== strpos( $match['path'], 'http' ) && '/' != $match['path'][0] ) {
|
234 |
+
$pre_output .= $match['pre_path'] . ( $dirpath == '/' ? '/' : $dirpath . '/' ) .
|
235 |
+
$match['path'] . $match['post_path'] . "\n";
|
236 |
+
} else {
|
237 |
+
$pre_output .= $match[0] . "\n";
|
238 |
+
}
|
239 |
+
|
240 |
+
return '';
|
241 |
+
},
|
242 |
+
$buf
|
243 |
+
);
|
244 |
+
}
|
245 |
+
|
246 |
+
$buf = $css_minify->run( $buf );
|
247 |
+
}
|
248 |
+
|
249 |
+
if ( $page_optimize_types['js'] === $mime_type ) {
|
250 |
+
$output .= "$buf;\n";
|
251 |
+
} else {
|
252 |
+
$output .= "$buf";
|
253 |
+
}
|
254 |
+
}
|
255 |
+
|
256 |
+
$headers = array(
|
257 |
+
'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $last_modified ) . ' GMT',
|
258 |
+
'Content-Length: ' . ( strlen( $pre_output ) + strlen( $output ) ),
|
259 |
+
"Content-Type: $mime_type",
|
260 |
+
);
|
261 |
+
|
262 |
+
echo $pre_output . $output;
|
263 |
+
|
264 |
+
return array(
|
265 |
+
'headers' => $headers,
|
266 |
+
'content' => ob_get_clean(),
|
267 |
+
);
|
268 |
+
}
|
269 |
+
|
270 |
+
function page_optimize_status_exit( $status ) {
|
271 |
+
http_response_code( $status );
|
272 |
+
exit;
|
273 |
+
}
|
274 |
+
|
275 |
+
function page_optimize_get_mime_type( $file ) {
|
276 |
+
global $page_optimize_types;
|
277 |
+
|
278 |
+
$lastdot_pos = strrpos( $file, '.' );
|
279 |
+
if ( false === $lastdot_pos ) {
|
280 |
+
return false;
|
281 |
+
}
|
282 |
+
|
283 |
+
$ext = substr( $file, $lastdot_pos + 1 );
|
284 |
+
|
285 |
+
return isset( $page_optimize_types[ $ext ] ) ? $page_optimize_types[ $ext ] : false;
|
286 |
+
}
|
287 |
+
|
288 |
+
function page_optimize_relative_path_replace( $buf, $dirpath ) {
|
289 |
+
// url(relative/path/to/file) -> url(/absolute/and/not/relative/path/to/file)
|
290 |
+
$buf = preg_replace(
|
291 |
+
'/(:?\s*url\s*\()\s*(?:\'|")?\s*([^\/\'"\s\)](?:(?<!data:|http:|https:|[\(\'"]#|%23).)*)[\'"\s]*\)/isU',
|
292 |
+
'$1' . ( $dirpath == '/' ? '/' : $dirpath . '/' ) . '$2)',
|
293 |
+
$buf
|
294 |
+
);
|
295 |
+
|
296 |
+
return $buf;
|
297 |
+
}
|
298 |
+
|
299 |
+
function page_optimize_get_path( $uri ) {
|
300 |
+
static $dependency_path_mapping;
|
301 |
+
|
302 |
+
if ( ! strlen( $uri ) ) {
|
303 |
+
page_optimize_status_exit( 400 );
|
304 |
+
}
|
305 |
+
|
306 |
+
if ( false !== strpos( $uri, '..' ) || false !== strpos( $uri, "\0" ) ) {
|
307 |
+
page_optimize_status_exit( 400 );
|
308 |
+
}
|
309 |
+
|
310 |
+
if ( defined( 'PAGE_OPTIMIZE_CONCAT_BASE_DIR' ) ) {
|
311 |
+
if ( file_exists( PAGE_OPTIMIZE_CONCAT_BASE_DIR . "/$uri" ) ) {
|
312 |
+
$path = realpath( PAGE_OPTIMIZE_CONCAT_BASE_DIR . "/$uri" );
|
313 |
+
}
|
314 |
+
|
315 |
+
if ( empty( $path ) && file_exists( PAGE_OPTIMIZE_ABSPATH . "/$uri" ) ) {
|
316 |
+
$path = realpath( PAGE_OPTIMIZE_ABSPATH . "/$uri" );
|
317 |
+
}
|
318 |
+
} else {
|
319 |
+
if ( empty( $dependency_path_mapping ) ) {
|
320 |
+
require_once __DIR__ . '/dependency-path-mapping.php';
|
321 |
+
$dependency_path_mapping = new Page_Optimize_Dependency_Path_Mapping();
|
322 |
+
}
|
323 |
+
$path = $dependency_path_mapping->uri_path_to_fs_path( $uri );
|
324 |
+
}
|
325 |
+
|
326 |
+
if ( false === $path ) {
|
327 |
+
page_optimize_status_exit( 404 );
|
328 |
+
}
|
329 |
+
|
330 |
+
return $path;
|
331 |
+
}
|
settings.php
ADDED
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
function page_optimize_settings_page() {
|
4 |
+
?>
|
5 |
+
<div class="wrap">
|
6 |
+
<h1><?php _e( 'Performance Settings', page_optimize_get_text_domain() ); ?></h1>
|
7 |
+
<form method="post" action="options.php">
|
8 |
+
<?php
|
9 |
+
settings_fields( 'performance' );
|
10 |
+
do_settings_sections( 'page-optimize' );
|
11 |
+
submit_button();
|
12 |
+
?>
|
13 |
+
</form>
|
14 |
+
</div>
|
15 |
+
<?php
|
16 |
+
}
|
17 |
+
|
18 |
+
function page_optimize_settings_section() {
|
19 |
+
_e(
|
20 |
+
'Concatenating JavaScript and CSS for faster site loading and lower number of requests. Scripts are grouped by the original placement.',
|
21 |
+
page_optimize_get_text_domain()
|
22 |
+
);
|
23 |
+
echo '<br />';
|
24 |
+
_e(
|
25 |
+
'This plugin disables Jetpack "Speed up static file load times".',
|
26 |
+
page_optimize_get_text_domain()
|
27 |
+
);
|
28 |
+
}
|
29 |
+
|
30 |
+
function page_optimize_settings_field_js( $args ) {
|
31 |
+
?>
|
32 |
+
<fieldset>
|
33 |
+
<label>
|
34 |
+
<input type="checkbox" id="page_optimize-js" name="page_optimize-js" value="1" <?php checked( get_option( 'page_optimize-js' ) ); ?>>
|
35 |
+
<?php _e( 'Concatenate scripts', page_optimize_get_text_domain() ); ?>
|
36 |
+
</label>
|
37 |
+
<br>
|
38 |
+
<label for="page_optimize-js-exclude">
|
39 |
+
<?php _e( 'Comma separated list of strings to exclude from JS concatenating:', page_optimize_get_text_domain() ); ?>
|
40 |
+
</label>
|
41 |
+
<br>
|
42 |
+
<input type="text"
|
43 |
+
id="page_optimize-js-exclude"
|
44 |
+
name="page_optimize-js-exclude"
|
45 |
+
value="<?php echo esc_html( get_option( 'page_optimize-js-exclude' ) ); ?>"
|
46 |
+
class="regular-text ltr">
|
47 |
+
</fieldset>
|
48 |
+
<?php
|
49 |
+
}
|
50 |
+
|
51 |
+
function page_optimize_settings_field_js_load_mode( $args ) {
|
52 |
+
?>
|
53 |
+
<fieldset>
|
54 |
+
<label>
|
55 |
+
<input type="radio" name="page_optimize-load-mode" value="" <?php checked( '', get_option( 'page_optimize-load-mode' ), true ); ?>>
|
56 |
+
<?php _e( 'None', page_optimize_get_text_domain() ); ?>
|
57 |
+
</label>
|
58 |
+
<label>
|
59 |
+
<input type="radio" name="page_optimize-load-mode" value="async" <?php checked( 'async', get_option( 'page_optimize-load-mode' ), true ); ?>>
|
60 |
+
<?php _e( 'Async', page_optimize_get_text_domain() ); ?>
|
61 |
+
</label>
|
62 |
+
<label>
|
63 |
+
<input type="radio" name="page_optimize-load-mode" value="defer" <?php checked( 'defer', get_option( 'page_optimize-load-mode' ), true ); ?>>
|
64 |
+
<?php _e( 'Defer', page_optimize_get_text_domain() ); ?>
|
65 |
+
</label>
|
66 |
+
|
67 |
+
<p class="description">
|
68 |
+
<?php _e( 'You can choose the execution mode of the concatenated JavaScript. This option might break your site, so use carefully.', page_optimize_get_text_domain() ); ?>
|
69 |
+
</p>
|
70 |
+
</fieldset>
|
71 |
+
<?php
|
72 |
+
}
|
73 |
+
|
74 |
+
function page_optimize_settings_field_css( $args ) {
|
75 |
+
?>
|
76 |
+
<fieldset>
|
77 |
+
<label>
|
78 |
+
<input type="checkbox" id="page_optimize-css" name="page_optimize-css" value="1" <?php checked( get_option( 'page_optimize-css' ) ); ?>>
|
79 |
+
<?php _e( 'Concatenate and minify styles', page_optimize_get_text_domain() ); ?>
|
80 |
+
</label>
|
81 |
+
<br>
|
82 |
+
<label for="page_optimize-css-exclude">
|
83 |
+
<?php _e( 'Comma separated list of strings to exclude from CSS concatenating:', page_optimize_get_text_domain() ); ?>
|
84 |
+
</label>
|
85 |
+
<br>
|
86 |
+
<input type="text"
|
87 |
+
id="page_optimize-css-exclude"
|
88 |
+
name="page_optimize-css-exclude"
|
89 |
+
value="<?php echo esc_html( get_option( 'page_optimize-css-exclude' ) ); ?>"
|
90 |
+
class="regular-text ltr">
|
91 |
+
</fieldset>
|
92 |
+
<?php
|
93 |
+
}
|
94 |
+
|
95 |
+
function page_optimize_settings_add_menu() {
|
96 |
+
add_options_page(
|
97 |
+
__( 'Performance Settings', page_optimize_get_text_domain() ),
|
98 |
+
__( 'Performance', page_optimize_get_text_domain() ),
|
99 |
+
'manage_options',
|
100 |
+
'page-optimize',
|
101 |
+
'page_optimize_settings_page'
|
102 |
+
);
|
103 |
+
}
|
104 |
+
|
105 |
+
add_action( 'admin_menu', 'page_optimize_settings_add_menu' );
|
106 |
+
|
107 |
+
function page_optimize_settings_init() {
|
108 |
+
register_setting(
|
109 |
+
'performance',
|
110 |
+
'page_optimize-js',
|
111 |
+
array(
|
112 |
+
'description' => __( 'JavaScript concatenation', page_optimize_get_text_domain() ),
|
113 |
+
'type' => 'boolean',
|
114 |
+
'default' => page_optimize_js_default(),
|
115 |
+
)
|
116 |
+
);
|
117 |
+
register_setting(
|
118 |
+
'performance',
|
119 |
+
'page_optimize-load-mode',
|
120 |
+
array(
|
121 |
+
'description' => __( 'Non-critical script execution mode', page_optimize_get_text_domain() ),
|
122 |
+
'type' => 'string',
|
123 |
+
'default' => page_optimize_js_load_mode_default(),
|
124 |
+
'sanitize_callback' => 'page_optimize_sanitize_js_load_mode',
|
125 |
+
)
|
126 |
+
);
|
127 |
+
register_setting(
|
128 |
+
'performance',
|
129 |
+
'page_optimize-js-exclude',
|
130 |
+
array(
|
131 |
+
'description' => __( 'Comma separated list of strings to exclude from JS concatenating', page_optimize_get_text_domain() ),
|
132 |
+
'type' => 'string',
|
133 |
+
'default' => implode( ',', page_optimize_js_exclude_list_default() ),
|
134 |
+
'sanitize_callback' => 'page_optimize_sanitize_exclude_field',
|
135 |
+
)
|
136 |
+
);
|
137 |
+
register_setting(
|
138 |
+
'performance',
|
139 |
+
'page_optimize-css',
|
140 |
+
array(
|
141 |
+
'description' => __( 'CSS concatenation', page_optimize_get_text_domain() ),
|
142 |
+
'type' => 'boolean',
|
143 |
+
'default' => page_optimize_css_default(),
|
144 |
+
)
|
145 |
+
);
|
146 |
+
register_setting(
|
147 |
+
'performance',
|
148 |
+
'page_optimize-css-exclude',
|
149 |
+
array(
|
150 |
+
'description' => __( 'Comma separated list of strings to exclude from CSS concating', page_optimize_get_text_domain() ),
|
151 |
+
'type' => 'string',
|
152 |
+
'default' => implode( ',', page_optimize_css_exclude_list_default() ),
|
153 |
+
'sanitize_callback' => 'page_optimize_sanitize_exclude_field',
|
154 |
+
)
|
155 |
+
);
|
156 |
+
|
157 |
+
add_settings_section(
|
158 |
+
'page_optimize_settings_section',
|
159 |
+
__( 'Page Optimization', page_optimize_get_text_domain() ),
|
160 |
+
'page_optimize_settings_section',
|
161 |
+
'page-optimize'
|
162 |
+
);
|
163 |
+
add_settings_field(
|
164 |
+
'page_optimize_js',
|
165 |
+
__( 'JavaScript', page_optimize_get_text_domain() ),
|
166 |
+
'page_optimize_settings_field_js',
|
167 |
+
'page-optimize',
|
168 |
+
'page_optimize_settings_section'
|
169 |
+
);
|
170 |
+
add_settings_field(
|
171 |
+
'page_optimize_js_load_mode',
|
172 |
+
__( 'Non-critical script execution mode', page_optimize_get_text_domain() ),
|
173 |
+
'page_optimize_settings_field_js_load_mode',
|
174 |
+
'page-optimize',
|
175 |
+
'page_optimize_settings_section'
|
176 |
+
);
|
177 |
+
add_settings_field(
|
178 |
+
'page_optimize_css',
|
179 |
+
__( 'CSS', page_optimize_get_text_domain() ),
|
180 |
+
'page_optimize_settings_field_css',
|
181 |
+
'page-optimize',
|
182 |
+
'page_optimize_settings_section'
|
183 |
+
);
|
184 |
+
}
|
185 |
+
|
186 |
+
add_action( 'admin_init', 'page_optimize_settings_init' );
|
187 |
+
|
188 |
+
function page_optimize_add_plugin_settings_link( $plugin_action_links, $plugin_file = null ) {
|
189 |
+
if ( ! ( 'page-optimize/page-optimize.php' === $plugin_file && current_user_can( 'manage_options' ) ) ) {
|
190 |
+
return $plugin_action_links;
|
191 |
+
}
|
192 |
+
|
193 |
+
$settings_link = sprintf(
|
194 |
+
'<a href="options-general.php?page=page-optimize">%s</a>',
|
195 |
+
__( 'Settings', page_optimize_get_text_domain() )
|
196 |
+
);
|
197 |
+
array_unshift( $plugin_action_links, $settings_link );
|
198 |
+
|
199 |
+
return $plugin_action_links;
|
200 |
+
}
|
201 |
+
|
202 |
+
add_filter( 'plugin_action_links', 'page_optimize_add_plugin_settings_link', 10, 2 );
|
utils.php
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Page_Optimize_Utils {
|
4 |
+
public static function cache_bust_mtime( $path, $siteurl ) {
|
5 |
+
static $dependency_path_mapping;
|
6 |
+
|
7 |
+
$url = $siteurl . $path;
|
8 |
+
|
9 |
+
if ( strpos( $url, '?m=' ) ) {
|
10 |
+
return $url;
|
11 |
+
}
|
12 |
+
|
13 |
+
$parts = parse_url( $url );
|
14 |
+
if ( ! isset( $parts['path'] ) || empty( $parts['path'] ) ) {
|
15 |
+
return $url;
|
16 |
+
}
|
17 |
+
|
18 |
+
if ( empty( $dependency_path_mapping ) ) {
|
19 |
+
$dependency_path_mapping = new Page_Optimize_Dependency_Path_Mapping();
|
20 |
+
}
|
21 |
+
|
22 |
+
$file = $dependency_path_mapping->dependency_src_to_fs_path( $url );
|
23 |
+
|
24 |
+
$mtime = false;
|
25 |
+
if ( file_exists( $file ) ) {
|
26 |
+
$mtime = filemtime( $file );
|
27 |
+
}
|
28 |
+
|
29 |
+
if ( ! $mtime ) {
|
30 |
+
return $url;
|
31 |
+
}
|
32 |
+
|
33 |
+
if ( false === strpos( $url, '?' ) ) {
|
34 |
+
$q = '';
|
35 |
+
} else {
|
36 |
+
list( $url, $q ) = explode( '?', $url, 2 );
|
37 |
+
if ( strlen( $q ) ) {
|
38 |
+
$q = '&' . $q;
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
return "$url?m={$mtime}{$q}";
|
43 |
+
}
|
44 |
+
}
|